-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix: focus newly added table row on drop #9873
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
base: main
Are you sure you want to change the base?
Changes from 6 commits
78ffcc1
c31f48e
e2b2e95
d752135
9b90f43
00b3a2e
f537611
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2045,6 +2045,131 @@ describe('Tree', () => { | |||||||||||||
| expect(getItems).toHaveBeenCalledTimes(1); | ||||||||||||||
| expect(getItems).toHaveBeenCalledWith(new Set(['projects', 'reports'])); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| it('should select the parent and all its children when dropped', async () => { | ||||||||||||||
| let {getAllByRole} = render(<TreeWithDragAndDrop selectionMode="multiple" />); | ||||||||||||||
| let trees = getAllByRole('treegrid'); | ||||||||||||||
|
|
||||||||||||||
| let firstTreeTester = testUtilUser.createTester('Tree', {root: trees[0]}); | ||||||||||||||
| let secondTreeTester = testUtilUser.createTester('Tree', {root: trees[1]}); | ||||||||||||||
| expect(firstTreeTester.rows).toHaveLength(2); | ||||||||||||||
| // has the empty state row | ||||||||||||||
| expect(secondTreeTester.rows).toHaveLength(1); | ||||||||||||||
| await user.tab(); | ||||||||||||||
| // selects and drops first row onto second tree | ||||||||||||||
| await user.keyboard('{ArrowRight}'); | ||||||||||||||
| await user.keyboard('{ArrowRight}'); | ||||||||||||||
| await user.keyboard('{ArrowRight}'); | ||||||||||||||
| await user.keyboard('{Enter}'); | ||||||||||||||
| act(() => jest.runAllTimers()); | ||||||||||||||
| await user.tab(); | ||||||||||||||
| expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on'); | ||||||||||||||
| fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'}); | ||||||||||||||
| fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'}); | ||||||||||||||
| // run onInsert promise | ||||||||||||||
| await act(async () => {}); | ||||||||||||||
| act(() => jest.runAllTimers()); | ||||||||||||||
| expect(secondTreeTester.rows).toHaveLength(1); | ||||||||||||||
| // expands tree row children | ||||||||||||||
| await user.keyboard('{ArrowRight}'); | ||||||||||||||
| await user.keyboard('{ArrowDown}'); | ||||||||||||||
| await user.keyboard('{ArrowDown}'); | ||||||||||||||
| await user.keyboard('{ArrowRight}'); | ||||||||||||||
| expect(secondTreeTester.selectedRows).toHaveLength(9); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| it('should focus the parent row when dropped on if it isnt expanded', async () => { | ||||||||||||||
| let {getAllByRole} = render(<TreeWithDragAndDrop />); | ||||||||||||||
| let trees = getAllByRole('treegrid'); | ||||||||||||||
|
|
||||||||||||||
| let firstTreeTester = testUtilUser.createTester('Tree', {root: trees[0]}); | ||||||||||||||
| let secondTreeTester = testUtilUser.createTester('Tree', {root: trees[1]}); | ||||||||||||||
| expect(firstTreeTester.rows).toHaveLength(2); | ||||||||||||||
| // has the empty state row | ||||||||||||||
| expect(secondTreeTester.rows).toHaveLength(1); | ||||||||||||||
| await user.tab(); | ||||||||||||||
| // selects and drops first row onto second tree | ||||||||||||||
| await user.keyboard('{ArrowRight}'); | ||||||||||||||
| await user.keyboard('{ArrowRight}'); | ||||||||||||||
| await user.keyboard('{Enter}'); | ||||||||||||||
| act(() => jest.runAllTimers()); | ||||||||||||||
| await user.tab(); | ||||||||||||||
| expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on'); | ||||||||||||||
| fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'}); | ||||||||||||||
| fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'}); | ||||||||||||||
| // run onInsert promise | ||||||||||||||
| await act(async () => {}); | ||||||||||||||
| act(() => jest.runAllTimers()); | ||||||||||||||
| expect(secondTreeTester.rows).toHaveLength(1); | ||||||||||||||
| await user.keyboard('{ArrowRight}'); | ||||||||||||||
| expect(secondTreeTester.rows).toHaveLength(6); | ||||||||||||||
| // tab back to the first tree and drop a new row onto one of the 2nd tree's child rows as it is expanded | ||||||||||||||
| await user.tab({shift: true}); | ||||||||||||||
| expect(document.activeElement).toBe(firstTreeTester.rows[0]); | ||||||||||||||
| await user.keyboard('{ArrowRight}'); | ||||||||||||||
| await user.keyboard('{Enter}'); | ||||||||||||||
| act(() => jest.runAllTimers()); | ||||||||||||||
| await user.tab(); | ||||||||||||||
| for (let i = 0; i < 7; i++) { | ||||||||||||||
| await user.keyboard('{ArrowDown}'); | ||||||||||||||
| } | ||||||||||||||
| expect(document.activeElement).toHaveAttribute('aria-label', 'Drop on Project 2'); | ||||||||||||||
| await user.keyboard('{Enter}'); | ||||||||||||||
| await act(async () => {}); | ||||||||||||||
|
||||||||||||||
| await user.keyboard('{Enter}'); | |
| await act(async () => {}); | |
| await act(async () => { | |
| fireEvent.keyDown(document.activeElement as Element, {key: 'Enter'}); | |
| fireEvent.keyUp(document.activeElement as Element, {key: 'Enter'}); | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks! I confirmed that using this makes this test properly fail when reverting the useDroppableCollection changes.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -253,10 +253,18 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state: | |
| state.selectionManager.isSelectionEqual(prevSelectedKeys) | ||
| ) { | ||
| let newKeys = new Set<Key>(); | ||
| for (let item of state.collection) { | ||
| if (item.type === 'item' && !prevCollection.getItem(item.key)) { | ||
| let key = state.collection.getFirstKey(); | ||
| while (key != null) { | ||
| let item = state.collection.getItem(key); | ||
| if (item?.type === 'item' && !prevCollection.getItem(item.key)) { | ||
|
Comment on lines
-256
to
+259
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Root issue was that using the collection iterator for TableCollection returns just the column header and the body, not the actual rows. Walking the items should be more accurate I believe |
||
| newKeys.add(item.key); | ||
| } | ||
|
|
||
| if (item?.hasChildNodes && state.collection.getItem(item.lastChildKey!)?.type === 'item') { | ||
| key = item.firstChildKey!; | ||
| } else { | ||
| key = state.collection.getKeyAfter(key); | ||
| } | ||
| } | ||
|
|
||
| state.selectionManager.setSelectedKeys(newKeys); | ||
|
|
@@ -268,10 +276,11 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state: | |
| let first: Key | null | undefined = newKeys.keys().next().value; | ||
| if (first != null) { | ||
| let item = state.collection.getItem(first); | ||
|
|
||
| let dropTarget = droppingState.current.target; | ||
| let isParentRowExpanded = state.collection['expandedKeys'] ? state.collection['expandedKeys'].has(item?.parentKey) : false; | ||
| // If this is a cell, focus the parent row. | ||
| // eslint-disable-next-line max-depth | ||
| if (item?.type === 'cell') { | ||
| if (item && (item?.type === 'cell' || (dropTarget.type === 'item' && dropTarget.dropPosition === 'on' && !isParentRowExpanded))) { | ||
| first = item.parentKey; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two tests actually always pass without the changes in
useDroppableCollection, not quite sure why it differs from the observed browser behavior. Also interesting to note is that having Strict Mode on in the storybook locally also will cause the issue to not appear...Still digging as to why this would be the case