ckeditor5
ckeditor5 copied to clipboard
Discovery: research keyboard handling for grid components
Provide a description of the task
Research best practices on how to handle grid navigation using the keyboard.
We have multiple grid-like UI components:
- Colors in the font color dropdown.
- List styles (in a dropdown).
- Table dimension grid in insert table button dropdown.
- Special characters.
To make it clear: this issue is just discovery. It's meant just to figure out best way to implement it and see why we have it implemented the way we did. We also can make some PoC to see how much effort will it require to fix it.
Once we have figured how this should be solved, we can move on to each separate issue.
There's a curious thing in how we handle grids as some of them have not predefined columns.
For example, list styles have responsive grid, based only on CSS. However font colors do have predefined columns count.
As far as the implementation goes we might consider doing it as a helper rather than making this feature a UI component class.
An example of such composition-based approach is click outside handler: https://github.com/ckeditor/ckeditor5/blob/30286f77b39526fce2856b03b9be0ba4cc91d1c7/packages/ckeditor5-ui/src/bindings/clickoutsidehandler.js#L27
For the record, it looks like the table insertion grid should handle keyboard differently because when reaching the right boundary of the grid, the right arrow key should not move focus back to the first column (which would be expected in the color grid).
Yet another approach could be using a FocusCycler as it's already partly used in grid-based features. We'd have to enhance it for grids so e.g. the next/previous methods would 'know' that at some point a row of elements ends and we have to go to the beginning/end of the row instead of to the next/previous element. And of course we'd have to add vertical navigation as well for handling arrow up/down events.
We'd have to discuss if there should be one FocusCycler for everything or two FocusCyclers at the same time - one for main navigation and the second one for grid only.
BTW: https://github.com/ckeditor/ckeditor5/issues/5772
Navigation (tab vs arrow keys):
There should be two different navigation handled (they shouldn’t be mixed together):
- Between sections in the dropdown (tab)
- Between items of the grid (arrow keys)
In case we decided using 2 focusCyclers, they will have to operate on two different levels - we cannot mix the levels in one focusCycler, e.g. cycling through sections of the view with tab key should be in a different focusCycler than moving between grid items with arrow keys.
The approaches discussed regarding focusCycler:
- Extend focusCycler to support 2D navigation
- Has to be backward compatible: navigation through a vector and through grid elements
- No 3D navigation: for sections 1D navigation (with tab) and for grid elements 2D navigation (arrow keys)
- This extension would require one additional parameter: number of columns - the focusCycler should automatically switch to 2D mode
- This extension would require 2 additional methods (focusUp and focusDown as focusPrevious and focusNext already exist)
- Actually focusUp and focusDown are not good names, as focusPrevious and focusNext could actually mean right and left as well as up and down in case of 1D vertical dropdown
- Create a class inheriting from FocusCycler (e.g. GridFocusCycler) that adds additional constructor parameter and methods for navigation
3 possible solutions (for this ticket):
Actually we came to the conclusion that there are 3 ways of handling this issue:
- Create a helper and make no changes in current grids
- Create a helper and change current grids so they work the same
- Seems a good option to have GridView class and a helper inside (and also we could use this helper for inserting tables) - this would be still flexible solution with not so many changes regarding using this API
- But there is no such class as GridView - and it could be a good moment to create one (but it would take some time)
- Create new component for grids and no helper
We agreed that the best option would be 2 (helper + GridView class).
Notes about the implementation of the GridView class:
- We have to create a GridView class, very simple, can work as a factory
- Constructor would get a child constructor (e.g. ButtonView) or there could be a function that could create these children
- GridView would know how to calculate navigation as there would be information about number of columns
- GridView would also have a collection of children (can be linear)
- GridView would also have this new behaviour (helper function that attaches listeners for arrow keys and handles navigation)
- This way, Colors, ListProperties and SpecialCharacters could use this class and pass a custom callback function for creating children
- This way probably a lot of code could be removed from current features because now it would be handled in GridView class
- GridView would add keyboard navigation and maybe also some basic generic CSS for displaying elements as a grid; it would also get a number of columns to pass it to the helper and to use it in inline styles
- To be checked: number of columns does not change when the browser width changes
- There should be also a guide for UI regarding how to create grids
Summary:
- This solution (GridView class + helper) is a preferred one but requires rather a lot of refactoring
- To be on the safe side we should start with option 1 (creating a behaviour - a helper function) and add it to current features, also change the navigation in focusCyclers so it is consistent for all grids (tab vs arrow keys)
- But we should create a follow-up tickets for option 2 (creating a GridView class):
- Refactoring of grids so they use the same parent class
- FocusCycler and a helper together to be used in GridView class
- A separate ticket for inserting tables that could also use this solution
Decided:
- At first we focus on the helper that is added to all four grids
- Make the navigation consistent (tab vs arrow keys)
- Finish the poc so it fully works (tests not needed).
A follow-up issue for keyboard navigation in color picker in table properties: #12193
Edit: Extracted to #12224 - let's keep this epic focused on keyboard only.
We focused exclusively on keyboard handling so far - but we need to remember that semantical markup is core requirement for this component.
I started a research on markup that we should produce for accessible grid components. The benefit of semantically marked grid is that it will convey information that this is a 2 dimension widget (has rows and columns).
Other editors
- Google Docs - it implements grid/gridcell roles on top of a
<table>
element - so they went an extra mile ensuring that metadata is conveyed.
Others simply don’t implement it well. They simple are using options.
- CKEditor 4 - grid is conveyed as a linear data set. Also there’s no up / down arrow handling.
- Other js WYSIWYG editor - Also linear data set (but it handles up/down arrows well).
Available options
- Just use a
<table>
with agrid
role andgridcell
role on focusable elements.- 👎 We need to explicitly know number of items per row.
- 👎 More markup hassle.
- ❔ Rendering table with a fixed layout could be actually faster than any other approach?
- Use shallow, linear markup for items in grid and use
div[role=row]
with[aria-owns]
to mark relations which row contains which item.- Demo https://codepen.io/mlewand/pen/xxWjwaM
- 👍 It's possible to implement such solution for dynamical number of columns (as long as column number is observable).
- 👎 We'll have to observe item count and change the number ow
div[role=row]
at runtime. - 👎 Uses more complex WAI-ARIA markup that is not known to everyone.
- 👎 Requires us to generate id for each item and pass this item to
[aria-owns]
attribute.
Rejected options
- Using
aria-rowcount
ongrid
element andaria-rowindex
ongridcell
.- I made a test at https://codepen.io/mlewand/pen/ZExxqwK
- Rowless is not read properly on NVDA in chrome 😞
- Regular with
role=row
wrappers is read properly.
- I made a test at https://codepen.io/mlewand/pen/ZExxqwK