Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiDataGrid] Implement draggable column headers #8015

Open
wants to merge 29 commits into
base: main
Choose a base branch
from

Conversation

mgadewoll
Copy link
Contributor

@mgadewoll mgadewoll commented Sep 11, 2024

Summary

closes #7136

This PR implements reordering EuiDataGrid columns via draggable column headers.
This implementation is opt-in keeping parity with current usages.

Todo

  • rebase/update after completed EuiDataGrid Emotion conversion
  • update VRT images
  • add docs update to EUI+ docs (DataGrid content is not yet available, EUI+ is still in need to be fully migrated/released)

Changes

  • adds prop canDragAndDropColumns on columnVisibility prop of EuiDataGrid to toggle draggable column headers - default value is false
  • implements EuiDragDropContext and EuiDroppable in EuiGridHeaderRow to enable a drop zone for all non-control columns
  • implements EuiDraggable in EuiDataGridHeaderCell to enable draggable column headers
    • requires reparenting via portal for dragged items to ensure correct positioning of the position: fixed element inside a transform context
  • updates the customDragHandle prop value on EuiDraggable to be boolean | 'custom'
  • adds cypress tests for the dragging behavior

Accessibility

The Draggable implementation comes with great accessibility out of the box (adding hints and announcements where needed) BUT for the use case of datagrid headers it was not completely fitting and needed a small adjustment.

The expected behavior is, that focussing a column header, we want the columnheader role to be read with all its expected labels and attached descriptions.
EuiDraggable currently always added a role on the drag container (button or group) which will be read instead of the content (effectively losing us semantic information). To solve this we optionally remove the added role on the drag container, which results in the content being read on focus as wanted.

This was tested and optimized on Win11 for JAWS and NVDA.

Screen reader output

// JAWS
// draggable, non-interactive

1. focus of a header cell titled "Name"

Name Press the Enter key to view this column’s actions column header
Press space bar to start a drag. When dragging you can use the arrow keys to move the item around and escape to cancel. Some screen readers may require you to be in focus mode or to use your pass through key

2. press Enter to open the actions menu popover

Tabular Content / EuiDataGrid - Playground ⋅ Storybook - Google Chrome dialog
EuiDataGrid; Page 1 of 1.

3. press Escape to close the actions menu popover

Name Press the Enter key to view this column’s actions column header
Press space bar to start a drag. When dragging you can use the arrow keys to move the item around and escape to cancel. Some screen readers may require you to be in focus mode or to use your pass through key

4. press Space to drag
5. press ArrowRight reorder

You have lifted an item in position 2
You have moved the item from position 2 to position 3

6. press Space to confirm the drag and reorder

You have dropped the item. You have moved the item from position 2 to position 3
// JAWS
// draggable, interactive

1. focus of a header cell titled "Name"

Name, column header
Press the Enter key to interact with this cell’s contents. Press space bar to start a drag. When dragging you can use the arrow keys to move the item around and escape to cancel. Some screen readers may require you to be in focus mode or to use your pass through key

2. press Enter to enter the cell (focussing the first interactive child element)

Additional information Button
To activate press Enter.
tooltip content

3. Press Tab to focus the next child element

Name. Click to view column header actions. Button
To activate press Enter.

4. press Escape to leave the cell

Name, column header
Exited cell content. Press the Enter key to interact with this cell’s contents.

4. press Space to drag
5. press ArrowRight reorder

You have lifted an item in position 1
You have moved the item from position 1 to position 2

6. press Space to confirm the drag and reorder

You have dropped the item. You have moved the item from position 1 to position 2
Press the Enter key to interact with this cell’s contents.
// NVDA
// draggable, non-interactive

1. focus of a header cell titled "Name"

Name Press the Enter key to view this column's actions  column header  Press space bar to start a drag. When dragging you can use the arrow keys to move the item around and escape to cancel. Some screen readers may require you to be in focus mode or to use your pass through key  row 1  column 2

2. press Enter to open the actions menu popover

You are in a dialog. 
Press Escape, or tap/click outside the dialog to close.To navigate through the   list of column actions, press the Tab or Up and Down arrow keys.

3. press Escape to close the actions menu popover

EuiDataGrid; Page 1 of 1.  table
Name Press the Enter key to view this column's actions  column header  Press space bar to start a drag. When dragging you can use the arrow keys to move the item around and escape to cancel. Some screen readers may require you to be in focus mode or to use your pass through key

4. press Space to drag
5. press ArrowRight reorder

space
You have lifted an item in position 2 
You have moved the item from position 2 to position 3 

6. press Space to confirm the drag and reorder

space
You have dropped the item. You have moved the item from position 2 to position 3
// NVDA
// draggable, interactive

1. focus of a header cell titled "Name"

Name,  column header  Press the Enter key to interact with this cell's contents. Press space bar to start a drag. When dragging you can use the arrow keys to move the item around and escape to cancel. Some screen readers may require you to be in focus mode or to use your pass through key  row 1  column 1

2. press Enter to enter the cell (focussing the first interactive child element)

Additional information  button  
tooltip content

3. Press Tab to focus the next child element

Name. Click to view column header actions.  button  

4. press Escape to leave the cell

Name,  column header  Exited cell content. Press the Enter key to interact with this cell's contents.

4. press Space to drag
5. press ArrowRight reorder

space
You have lifted an item in position 1 
You have moved the item from position 1 to position 2 

6. press Space to confirm the drag and reorder

space
You have dropped the item. You have moved the item from position 1 to position 2

Screenshots

mouse usage

Screen.Recording.2024-09-10.at.18.01.57.mov

keyboard usage

Screen.Recording.2024-09-10.at.18.05.00.mov

resizing

Screen.Recording.2024-09-10.at.18.06.38.mov

QA

Storybook: https://eui.elastic.co/pr_8015/storybook/?path=/story/tabular-content-euidatagrid--playground&args=columnDragDrop:!true

EUI docs: https://eui.elastic.co/pr_8015/index.html#/tabular-content/data-grid-schema-columns#draggable-columns

  • verify columnDragDrop prop works to en/disable draggable columns
  • verify drag behavior works with mouse
    • draggable columns indicate drag behavior on hover
    • clicking draggable columns sets a focus state
    • draggable columns can be dragged and reorder of columns works as expected
    • opening the actions menu popover works and that clicking on another draggable column closes it
  • verify that the drag behavior works with keyboard
    • ArrowLeft/Right keys focus draggable header cells
    • Space key starts dragging a cell
      • ArrowLeft/Right move a dragged cell to another position
      • Space drops the dragged cell in the current position
      • Escape key aborts the drag
    • Enter key on interactive header cells enters the cell content
      • Escape in an interactive cell exits the cell and focuses it
  • verify that resizing a draggable cell works as expected

General checklist

  • Browser QA
    • Checked in both light and dark modes
    • Checked in mobile
    • Checked in Chrome, Safari, Edge, and Firefox
    • Checked for accessibility including keyboard-only and screenreader modes
  • Docs site QA
  • Code quality checklist
  • Release checklist
    • A changelog entry exists and is marked appropriately.
    • If applicable, added the breaking change issue label (and filled out the breaking change checklist)
  • Designer checklist
    • If applicable, file an issue to update EUI's Figma library with any corresponding UI changes. (This is an internal repo, if you are external to Elastic, ask a maintainer to submit this request)

@@ -110,6 +115,8 @@ export const EuiDraggable: FunctionComponent<EuiDraggableProps> = ({
role={
hasInteractiveChildren
? 'group'
: customDragHandle === 'custom'
Copy link
Contributor Author

@mgadewoll mgadewoll Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here is to provide means to unset the role for specific use cases. Using customDragHandle=true for that would have impact on current usages, hence I'm suggesting to add a third custom option as this should not be a default.

The reasoning here is this:
EuiDraggable adds a drag wrapper around its content, which has role="button|group" - this works for most cases where the drag element should be the interactive/focused element. The impact of this is, that the content of this element is not read as standalone semantic elements. E.g. role="button" removes additional semantic content and just reads visible text content.
For datagrid column headers we rather want the columnheader to still be the element to be read via screen readers as it holds all semantic information. Instead we want the draggable accessible information added to the column header instead of the wrapper being read only.
To ensure the columnheader role element is read, the draggable wrapper needs to have no role that removes the content semantics. Hence the decision to provide means to unset it for this case.

@mgadewoll mgadewoll force-pushed the datagrid/7136-draggable-column-headers branch from 9e23fdd to 2ccfb4d Compare September 13, 2024 11:51
Copy link
Contributor Author

@mgadewoll mgadewoll Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All VRT images updates are related to this change which means the "Account" title now sits flush within the header cell padding instead of 2px offset due to the gap (which applied because the action elements are only visually hidden but available in the DOM)

Screenshot 2024-09-13 at 14 06 30

@mgadewoll mgadewoll changed the title [DRAFT] [EuiDataGrid] Implement draggable column headers [EuiDataGrid] Implement draggable column headers Sep 13, 2024
@mgadewoll mgadewoll marked this pull request as ready for review September 13, 2024 16:05
@mgadewoll mgadewoll requested a review from a team as a code owner September 13, 2024 16:05
@mgadewoll mgadewoll marked this pull request as draft September 13, 2024 16:33
@mgadewoll mgadewoll marked this pull request as ready for review September 16, 2024 08:47
// Draggable prevents FocusTrap onOutsideClick to be called.
// We manually close the popover for draggable cells and
// update the focus index onBlur to ensure execution order
// as closePopover() focuses its own cells first on close.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to run for today but I'm planning on coming back to this file and doing a closer review tomorrow (also delaying because I'm guessing this might change significantly if we need to support #8015 (comment)).

Just curious, do we know exactly why the draggable library interferes with our existing click events?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interactive children work as expected with the draggable header columns (example story).

The general problem is that clicking the draggable column will click the draggable wrapper (and does not receive focus), not the column header. And somewhere the focus is caught, because the focus listener on the header cell that handles the focus context does not trigger.
This starts happening as soon as the provided.draggableProps are added. I did not find the root cause in code so far. 🤔

@mgadewoll mgadewoll force-pushed the datagrid/7136-draggable-column-headers branch from 141c294 to deceb15 Compare September 18, 2024 09:02
- draggable cells prevent onOutsideClick to be triggered, we need to manually update focus to ensure expected behavior

- moves columnResizer element to ensure drag and resize actions stay separate
- add columnDragDrop control to custom ehader story for testing with interactive headers
- ensures the columnheader element is read instead of the wrapping draggable container; this requires the draggable wrapper to not have a role as the default roles button/group remove any semantics from their content when focused which results in the content not fully being read
- this reparenting approach is required due to transform context of datagrid which interfers with the positioning of dragged items
- use unique ids

- remove position style override as it's not needed for the reparented/portalled approach
- the changes are only related to the conditionally added gap on header cells
- prevents error about not finishing drop animation as there were duplicate elements being dragged
- prevents duplicate SR output in entering the cell
…e elements

- ensure we use the rowing tabindex and don't add additional unwanted tab stops
- includes column header with interactive cell content
- moving dragged item into the body scope to prevent issues due to stacked context. this requires applying styles to the dragged item manually as it's not part of the datagrid style scope

- added some smaller cleanups to ensure correct styles
@mgadewoll mgadewoll force-pushed the datagrid/7136-draggable-column-headers branch from b428995 to 45ec4e5 Compare September 24, 2024 13:20
@elasticmachine
Copy link
Collaborator

💚 Build Succeeded

History

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[EuiDataGrid] Implement draggable columns with table headers
5 participants