Skip to content

Conversation

majornista
Copy link
Collaborator

@majornista majornista commented Jan 4, 2023

Closes #3884, relates to #3039

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

See examples with fix here:

  1. TableView: CRUD example (with possible fix)
  2. TableView: Inline delete buttons (with possible fix)
  3. ActionBar: default (with possible fix)
  4. ListView: Restore focus after item removal
  5. ListBox: Restore focus after deleting selected items

🧢 Your Project:

Adobe/Accessibility

Michael Jordan added 2 commits January 4, 2023 11:57
When one or more rows are removed, restore focus to the appropriate cell within the next or previous remaining row.

TODO: Address race condition within CRUD example, using mouse, where pressing Delete button within the overlay dialog removes the row and closes the dialog losing focus to the document.body before useTable can restore focus to the appropriate key within the next or previous row. This causes the focus to be restored to the Select All checkbox.
@majornista majornista changed the title fix(#3884): useTable: gracefully handle focus when a focused row is removed from the collection fix(#3884): useGrid: gracefully handle focus when a focused row is removed from the collection Jan 5, 2023
onPrimaryAction={() => {
// Add delay so that dialog closes and restores focus
// to the TableView before removing the row.
setTimeout(() => list.remove(dialog.item.id), 60);
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we're going to want this timeout
Where does focus go without the setTimeout? I think it goes to the first focusable in the scope? or does table move it to the first row?
If table does it, seems like we could consult the cached information we have an move it somewhere better
If the focus scope does it, then maybe the table can become the first focusable thing and when it gets focus, it then forwards it to the appropriate row?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Without the timeout, focus moves to the first focusable in scope, the Select All checkbox.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

selectionManager.setFocusedKey executes within the useGrid and targets the appropriate key, but useRestoreFocus executes after, when the dialog closes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think I figured this out. The problem is that the FocusScope for Dialogs and Modals always contain focus using useOverlayFocusContain, so that when the user presses the Delete button to execute the primary action that removes the item, while the useEffect within useGrid does reset focus to the appropriate focusedKey, the FocusScope within the Overlay always steals focus back, even if the Overlay is in the process of closing. I think we need to account for the state of the Overlay when calling useOverlayFocusContain, so that an Overlay that is closing doesn't contain focus.

Copy link
Member

@snowystinger snowystinger Jan 10, 2023

Choose a reason for hiding this comment

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

I think we were doing that at one point, when transitioning out. But I can't find it, maybe it was in some work we did but never merged.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@snowystinger Is it this one? #3704

Copy link
Collaborator Author

@majornista majornista Jan 10, 2023

Choose a reason for hiding this comment

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

I can confirm that #3704 resolves the issue.
Unfortunately, I have to take that back. With keyboard it was always working, but with mouse interaction, the FocusScope within the overlay is still stealing focus back to the Dialog that is closing.

Copy link
Member

Choose a reason for hiding this comment

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

No, it was elsewhere, the idea was that when a Popover began transitioning out, then contains would be set to false. I think it was this #3725

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, it was elsewhere, the idea was that when a Popover began transitioning out, then contains would be set to false. I think it was this #3725

I tried incorporating the small changes in #3725, and it did not solve the problem.

@rspbot

This comment was marked as outdated.

@adobe adobe deleted a comment from rspbot Jan 12, 2023
@rspbot

This comment was marked as outdated.

@rspbot

This comment was marked as outdated.

@rspbot

This comment was marked as outdated.

@rspbot

This comment was marked as outdated.

@rspbot

This comment was marked as outdated.

@rspbot
Copy link

rspbot commented Feb 1, 2023

@majornista majornista requested a review from LFDanLu February 1, 2023 04:02
@rspbot
Copy link

rspbot commented Feb 1, 2023

let index = Math.min(
(
diff > 1 ?
Math.max(parentNode.index - diff + 1, 0) :
Copy link
Member

Choose a reason for hiding this comment

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

So if you deleted a row and at the same time added 2+ more the index would change but otherwise it stays the same? What was the reason for this?

Copy link
Collaborator Author

@majornista majornista Feb 2, 2023

Choose a reason for hiding this comment

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

Was added to try to address the concerns @LFDanLu raised with the ListBox example: #3885 (comment).

When you remove multiple items, and depending on where focus is in relation to the items selected for removal, one perceives the focus jumping ahead significantly from where it was. This code intends to mitigate that perception. Is there a better approach to this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So if you deleted a row and at the same time added 2+ more the index would change but otherwise it stays the same? What was the reason for this?

Also, in this use case the diff would be less than 1, because the new row count would be greater than the cached row count, const diff = cachedRows.length - rows.length;, so we would just use Math.min(parentNode.index, rows.length - 1).

Copy link
Member

Choose a reason for hiding this comment

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

Ok that makes sense. One weird case I see is if you select two items from the bottom up so focus is on the first one, focus moves to the previous item rather than the next one. For example, select Snake and Kangaroo, delete, and focus ends on Aardvark rather than Danni (which is now at the corresponding index as Snake). If you select the opposite direction so focus is on Snake, then focus moves to Danni instead (rather than Devon which is at the corresponding index).

Copy link
Member

Choose a reason for hiding this comment

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

I kinda like that behavior since it is restoring focus to the closest item in the direction the user's focus was closest/trending to if that makes sense (albeit that isn't what the code is explicitly doing). There is another odd case that I noted here, so I think it would be good for the team to test this vs the previous implementation for opinions/edge cases and maybe we can make a call on if we are fine with this or if we want to refine some more

Copy link
Member

Choose a reason for hiding this comment

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

Mac Finder:
Select two, bottom first. Delete. Loses selection entirely.
Select two, top first. Delete. Selects item that replaced first selected index, not the focused one.
Select one. Delete. Selects the item that followed it.
Select two, top first, gap of n items between. Selects item that followed last focused+selected item.
Select two, bottom first, gap of n items between. Selects item that followed last focused+selected item.

Could improve on this by not losing selection in the first case and sending it to the next available item (checking more than one down in otherwords). The rest seems fine to use as the template for how to move?

Copy link
Member

@devongovett devongovett left a comment

Choose a reason for hiding this comment

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

Approving for code. We can test and decide on final behavior for the edge cases.

@rspbot
Copy link

rspbot commented Feb 3, 2023

@rspbot
Copy link

rspbot commented Feb 4, 2023

@rspbot
Copy link

rspbot commented Feb 6, 2023

@rspbot
Copy link

rspbot commented Feb 6, 2023

## API Changes

unknown top level export { type: 'identifier', name: 'Column' }
unknown top level export { type: 'identifier', name: 'Column' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }
unknown type { type: 'link' }

@LFDanLu LFDanLu merged commit 27f8fa1 into main Feb 6, 2023
@LFDanLu LFDanLu deleted the tableview-restore-focus-on-remove-row branch February 6, 2023 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accessibility enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

useGrid: gracefully handle focus when a focused row is removed from the collection
5 participants