Skip to content

Commit a034793

Browse files
committed
chore(Collections): use stable ids based on parentKey
1 parent 2affbf5 commit a034793

3 files changed

Lines changed: 33 additions & 9 deletions

File tree

packages/react-aria/src/collections/CollectionBuilder.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ function useSSRCollectionNode<T extends Element>(CollectionNodeClass: Collection
162162
}
163163

164164
// @ts-ignore
165-
return <CollectionNodeClass.type ref={itemRef}>{children}</CollectionNodeClass.type>;
165+
return <CollectionNodeClass.type data-key={props.id} ref={itemRef}>{children}</CollectionNodeClass.type>;
166166
}
167167

168168
// eslint-disable-next-line @typescript-eslint/no-unused-vars

packages/react-aria/src/collections/Document.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ export class ElementNode<T> extends BaseNode<T> {
260260
node: CollectionNode<T> | null;
261261
isMutated = true;
262262
private _index: number = 0;
263+
private _attributesByNameMap = new Map<string, string>();
263264
isHidden = false;
264265

265266
constructor(type: string, ownerDocument: Document<T, any>) {
@@ -333,7 +334,7 @@ export class ElementNode<T> extends BaseNode<T> {
333334
let node;
334335
let {value, textValue, id, ...props} = obj;
335336
if (this.node == null) {
336-
node = new CollectionNodeClass(id ?? `react-aria-${++this.ownerDocument.nodeId}`);
337+
node = new CollectionNodeClass(id ?? makeId(this));
337338
this.node = node;
338339
} else {
339340
node = this.getMutableNode();
@@ -398,9 +399,20 @@ export class ElementNode<T> extends BaseNode<T> {
398399
}
399400

400401
hasAttribute(): void {}
401-
setAttribute(): void {}
402+
403+
setAttribute(qualifiedName: string, value: string): void {
404+
this._attributesByNameMap.set(qualifiedName, '' + value);
405+
}
406+
407+
getAttribute(qualifiedName: string): string | null {
408+
return this._attributesByNameMap.get(qualifiedName) ?? null;
409+
}
410+
402411
setAttributeNS(): void {}
403-
removeAttribute(): void {}
412+
413+
removeAttribute(qualifiedName: string): void {
414+
this._attributesByNameMap.delete(qualifiedName);
415+
}
404416
}
405417

406418
/**
@@ -567,3 +579,17 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
567579
}
568580
}
569581
}
582+
583+
function makeId(node: ElementNode<unknown>) {
584+
const identifierPrefix = 'react-aria';
585+
if (node.parentNode instanceof ElementNode) {
586+
// We only use this for id generation on the client
587+
const parentKey = node.parentNode.getAttribute('data-key');
588+
if (parentKey != null) {
589+
// If parentNode specifies a key, generate a stable id based on parentKey
590+
// so that useDroppableCollection can keep track of each child even if it is recreated
591+
return identifierPrefix + '-' + parentKey + '-' + node.index.toString(32);
592+
}
593+
}
594+
return identifierPrefix + '-' + (++node.ownerDocument.nodeId).toString(32);
595+
}

packages/react-aria/src/dnd/useDroppableCollection.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,6 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
245245
draggingKeys
246246
} = droppingState.current;
247247

248-
let prevFocusedItem = prevFocusedKey != null ? (state.collection.getItem(prevFocusedKey) ?? prevCollection.getItem(prevFocusedKey)) : null;
249-
250248
// If an insert occurs during a drop, we want to immediately select these items to give
251249
// feedback to the user that a drop occurred. Only do this if the selection didn't change
252250
// since the drop started so we don't override if the user or application did something.
@@ -298,15 +296,15 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state:
298296
}
299297
}
300298
} else if (
301-
prevFocusedItem != null &&
299+
prevFocusedKey != null &&
302300
state.selectionManager.focusedKey === prevFocusedKey &&
303301
isInternal &&
304302
target.type === 'item' &&
305303
target.dropPosition !== 'on' &&
306-
draggingKeys.has(prevFocusedItem.parentKey)
304+
draggingKeys.has(state.collection.getItem(prevFocusedKey)?.parentKey)
307305
) {
308306
// Focus row instead of cell when reordering.
309-
state.selectionManager.setFocusedKey(prevFocusedItem.parentKey ?? null);
307+
state.selectionManager.setFocusedKey(state.collection.getItem(prevFocusedKey)?.parentKey ?? null);
310308
setInteractionModality('keyboard');
311309
} else if (
312310
state.selectionManager.focusedKey === prevFocusedKey &&

0 commit comments

Comments
 (0)