fix(Table): Fix focus behavior after moving items to a different level via keyboard DnD#9975
fix(Table): Fix focus behavior after moving items to a different level via keyboard DnD#9975chirokas wants to merge 2 commits intoadobe:mainfrom
Conversation
snowystinger
left a comment
There was a problem hiding this comment.
We should probably stabilise cell ids (keys) based on the row if possible.
I checked, all the behaviour works if you supply an id to the Cell for the drag and drop button, because then it is stable. So it's only for the case where we generate the id instead
@LFDanLu it looks like you created this line, I vaguely recall some discussion around this, was there a reason we didn't use the parent key in this?
|
@snowystinger In React, the child |
|
Ah, that's a good point, hadn't gone that far yet. Hmmm... will keep thinking |
|
@chirokas We can resolve the key earlier in the lifecycle. I remember having explored using the is attribute to forward the key at |
This comment was marked as outdated.
This comment was marked as outdated.
|
@chirokas Browser support doesn't matter, since we are rendering into a fake document. React supports the is attribute since v16, which coincides with the compatibility range of react aria. It won't bail out in Webkit environments if that's what you are worried about - that's all left up to userland. |
This comment was marked as resolved.
This comment was marked as resolved.
|
@chirokas The comment below the line you linked refers to "is" having been passed to createElement by the time this is reached. This is done via the second argument of the createElement API (see MDN), which was working last time I explored. Once we extract the key there, we can store it on the element node ourselves, ideally in a I will see whether I can pull up a small code sample from the exploration once im back at the keys. Edit: Added pseudo diff of
+ nodeId: Key;
- constructor(ownerDocument: Document<T, any>) {
- this.ownerDocument = ownerDocument;
- }
+ constructor(key: Key, ownerDocument: Document<T, any>) {
+ this.ownerDocument = ownerDocument;
+ this.nodeId = key;
+ }
- nodeId: number;
+ nodeCount: number;
- constructor(collection: C) {
- // @ts-ignore
- super(null);
- this.collection = collection;
- this.nextCollection = collection;
- }
+ constructor(collection: C) {
+ // @ts-ignore
+ super('react-aria', null);
+ this.collection = collection;
+ this.nextCollection = collection;
+ }
- createElement(type: string): ElementNode<T> {
- return new ElementNode(type, this);
- }
+ createElement(type: string, opts: ElementCreationOptions): ElementNode<T> {
+ let key = opts.is != null ? JSON.parse(opts.is) : `react-aria-${++this.nodeCount}`;
+ return new ElementNode(type, key, this);
+ }
- constructor(type: string, ownerDocument: Document<T, any>) {
- super(ownerDocument);
- this.node = null;
- }
+ constructor(type: string, key: Key, ownerDocument: Document<T, any>) {
+ super(key, ownerDocument);
+ this.node = null;
+ }
- new CollectionNodeClass(id ?? `react-aria-${++this.ownerDocument.nodeId}`)
+ new CollectionNodeClass(id ?? `${this.parentNode.nodeId}-${this.index}`) |
…l via keyboard DnD
a034793 to
a478cc5
Compare
|
@nwidynski The approach I took is slightly different let me know what you think |
|
@chirokas What was the reason for only doing this client-side? I'm kind of concerned about the change in key strategy after the document has been built during hydration. From the top of my head, I think this could become a problem anytime a ref is re-mounted within the same document, e.g. inside an Activity. |
a478cc5 to
bba3730
Compare
|
LGTM, thanks for the update 🚀 |
snowystinger
left a comment
There was a problem hiding this comment.
I think this is a pretty good solution. Thanks for the help @nwidynski !
I have a feeling @devongovett will have some thoughts as well, specifically around performance. The nodes are meant to be as light as possible and now we're adding a map of attributes for each one. It may make more sense to just make it one known attribute for now so it's less of a memory footprint. If we need to add more down the line, we can expand on it.
| } | ||
|
|
||
| function makeId(node: ElementNode<unknown>, identifierPrefix = 'react-aria') { | ||
| if (node.parentNode instanceof ElementNode) { |
There was a problem hiding this comment.
| if (node.parentNode instanceof ElementNode) { | |
| if (node.parentNode !== node.ownerDocument) { |
Regarding performance, I think this is faster than traversing the prototype chain. Will need type assertions though.
Closes
Video.Project.mp4
When a row moves to a different level, it is removed and recreated during the
TableCollectionrebuild (due to the Fiber child reconciliation algorithm). The previously focused cell is recreated with a new key that no longer matchesprevFocusedKey, resulting in incorrect focus behavior.✅ Pull Request Checklist:
📝 Test Instructions:
🧢 Your Project: