@react-aria/collections 3.0.0-nightly.5038 → 3.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Document.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import {BaseCollection, CollectionNode, Mutable} from './BaseCollection';
14
- import {ForwardedRef, ReactElement} from 'react';
14
+ import {CSSProperties, ForwardedRef, ReactElement, ReactNode} from 'react';
15
15
  import {Node} from '@react-types/shared';
16
16
 
17
17
  // This Collection implementation is perhaps a little unusual. It works by rendering the React tree into a
@@ -38,13 +38,14 @@ export class BaseNode<T> {
38
38
  private _previousSibling: ElementNode<T> | null = null;
39
39
  private _nextSibling: ElementNode<T> | null = null;
40
40
  private _parentNode: BaseNode<T> | null = null;
41
+ private _minInvalidChildIndex: ElementNode<T> | null = null;
41
42
  ownerDocument: Document<T, any>;
42
43
 
43
44
  constructor(ownerDocument: Document<T, any>) {
44
45
  this.ownerDocument = ownerDocument;
45
46
  }
46
47
 
47
- *[Symbol.iterator]() {
48
+ *[Symbol.iterator](): Iterator<ElementNode<T>> {
48
49
  let node = this.firstChild;
49
50
  while (node) {
50
51
  yield node;
@@ -52,57 +53,71 @@ export class BaseNode<T> {
52
53
  }
53
54
  }
54
55
 
55
- get firstChild() {
56
+ get firstChild(): ElementNode<T> | null {
56
57
  return this._firstChild;
57
58
  }
58
59
 
59
- set firstChild(firstChild) {
60
+ set firstChild(firstChild: ElementNode<T> | null) {
60
61
  this._firstChild = firstChild;
61
62
  this.ownerDocument.markDirty(this);
62
63
  }
63
64
 
64
- get lastChild() {
65
+ get lastChild(): ElementNode<T> | null {
65
66
  return this._lastChild;
66
67
  }
67
68
 
68
- set lastChild(lastChild) {
69
+ set lastChild(lastChild: ElementNode<T> | null) {
69
70
  this._lastChild = lastChild;
70
71
  this.ownerDocument.markDirty(this);
71
72
  }
72
73
 
73
- get previousSibling() {
74
+ get previousSibling(): ElementNode<T> | null {
74
75
  return this._previousSibling;
75
76
  }
76
77
 
77
- set previousSibling(previousSibling) {
78
+ set previousSibling(previousSibling: ElementNode<T> | null) {
78
79
  this._previousSibling = previousSibling;
79
80
  this.ownerDocument.markDirty(this);
80
81
  }
81
82
 
82
- get nextSibling() {
83
+ get nextSibling(): ElementNode<T> | null {
83
84
  return this._nextSibling;
84
85
  }
85
86
 
86
- set nextSibling(nextSibling) {
87
+ set nextSibling(nextSibling: ElementNode<T> | null) {
87
88
  this._nextSibling = nextSibling;
88
89
  this.ownerDocument.markDirty(this);
89
90
  }
90
91
 
91
- get parentNode() {
92
+ get parentNode(): BaseNode<T> | null {
92
93
  return this._parentNode;
93
94
  }
94
95
 
95
- set parentNode(parentNode) {
96
+ set parentNode(parentNode: BaseNode<T> | null) {
96
97
  this._parentNode = parentNode;
97
98
  this.ownerDocument.markDirty(this);
98
99
  }
99
100
 
100
- get isConnected() {
101
+ get isConnected(): boolean {
101
102
  return this.parentNode?.isConnected || false;
102
103
  }
103
104
 
104
- appendChild(child: ElementNode<T>) {
105
- this.ownerDocument.startTransaction();
105
+ private invalidateChildIndices(child: ElementNode<T>): void {
106
+ if (this._minInvalidChildIndex == null || child.index < this._minInvalidChildIndex.index) {
107
+ this._minInvalidChildIndex = child;
108
+ }
109
+ }
110
+
111
+ updateChildIndices(): void {
112
+ let node = this._minInvalidChildIndex;
113
+ while (node) {
114
+ node.index = node.previousSibling ? node.previousSibling.index + 1 : 0;
115
+ node = node.nextSibling;
116
+ }
117
+ this._minInvalidChildIndex = null;
118
+ }
119
+
120
+ appendChild(child: ElementNode<T>): void {
106
121
  if (child.parentNode) {
107
122
  child.parentNode.removeChild(child);
108
123
  }
@@ -125,22 +140,14 @@ export class BaseNode<T> {
125
140
  this.lastChild = child;
126
141
 
127
142
  this.ownerDocument.markDirty(this);
128
- if (child.hasSetProps) {
129
- // Only add the node to the collection if we already received props for it.
130
- // Otherwise wait until then so we have the correct id for the node.
131
- this.ownerDocument.addNode(child);
132
- }
133
-
134
- this.ownerDocument.endTransaction();
135
143
  this.ownerDocument.queueUpdate();
136
144
  }
137
145
 
138
- insertBefore(newNode: ElementNode<T>, referenceNode: ElementNode<T>) {
146
+ insertBefore(newNode: ElementNode<T>, referenceNode: ElementNode<T>): void {
139
147
  if (referenceNode == null) {
140
148
  return this.appendChild(newNode);
141
149
  }
142
150
 
143
- this.ownerDocument.startTransaction();
144
151
  if (newNode.parentNode) {
145
152
  newNode.parentNode.removeChild(newNode);
146
153
  }
@@ -158,33 +165,17 @@ export class BaseNode<T> {
158
165
  referenceNode.previousSibling = newNode;
159
166
  newNode.parentNode = referenceNode.parentNode;
160
167
 
161
- let node: ElementNode<T> | null = referenceNode;
162
- while (node) {
163
- node.index++;
164
- node = node.nextSibling;
165
- }
166
-
167
- if (newNode.hasSetProps) {
168
- this.ownerDocument.addNode(newNode);
169
- }
170
-
171
- this.ownerDocument.endTransaction();
168
+ this.invalidateChildIndices(referenceNode);
172
169
  this.ownerDocument.queueUpdate();
173
170
  }
174
171
 
175
- removeChild(child: ElementNode<T>) {
172
+ removeChild(child: ElementNode<T>): void {
176
173
  if (child.parentNode !== this || !this.ownerDocument.isMounted) {
177
174
  return;
178
175
  }
179
-
180
- this.ownerDocument.startTransaction();
181
- let node = child.nextSibling;
182
- while (node) {
183
- node.index--;
184
- node = node.nextSibling;
185
- }
186
-
176
+
187
177
  if (child.nextSibling) {
178
+ this.invalidateChildIndices(child.nextSibling);
188
179
  child.nextSibling.previousSibling = child.previousSibling;
189
180
  }
190
181
 
@@ -205,13 +196,44 @@ export class BaseNode<T> {
205
196
  child.previousSibling = null;
206
197
  child.index = 0;
207
198
 
208
- this.ownerDocument.removeNode(child);
209
- this.ownerDocument.endTransaction();
199
+ this.ownerDocument.markDirty(child);
210
200
  this.ownerDocument.queueUpdate();
211
201
  }
212
202
 
213
- addEventListener() {}
214
- removeEventListener() {}
203
+ addEventListener(): void {}
204
+ removeEventListener(): void {}
205
+
206
+ get previousVisibleSibling(): ElementNode<T> | null {
207
+ let node = this.previousSibling;
208
+ while (node && node.isHidden) {
209
+ node = node.previousSibling;
210
+ }
211
+ return node;
212
+ }
213
+
214
+ get nextVisibleSibling(): ElementNode<T> | null {
215
+ let node = this.nextSibling;
216
+ while (node && node.isHidden) {
217
+ node = node.nextSibling;
218
+ }
219
+ return node;
220
+ }
221
+
222
+ get firstVisibleChild(): ElementNode<T> | null {
223
+ let node = this.firstChild;
224
+ while (node && node.isHidden) {
225
+ node = node.nextSibling;
226
+ }
227
+ return node;
228
+ }
229
+
230
+ get lastVisibleChild(): ElementNode<T> | null {
231
+ let node = this.lastChild;
232
+ while (node && node.isHidden) {
233
+ node = node.previousSibling;
234
+ }
235
+ return node;
236
+ }
215
237
  }
216
238
 
217
239
  /**
@@ -221,23 +243,21 @@ export class BaseNode<T> {
221
243
  export class ElementNode<T> extends BaseNode<T> {
222
244
  nodeType = 8; // COMMENT_NODE (we'd use ELEMENT_NODE but React DevTools will fail to get its dimensions)
223
245
  node: CollectionNode<T>;
246
+ isMutated = true;
224
247
  private _index: number = 0;
225
248
  hasSetProps = false;
249
+ isHidden = false;
226
250
 
227
251
  constructor(type: string, ownerDocument: Document<T, any>) {
228
252
  super(ownerDocument);
229
253
  this.node = new CollectionNode(type, `react-aria-${++ownerDocument.nodeId}`);
230
- // Start a transaction so that no updates are emitted from the collection
231
- // until the props for this node are set. We don't know the real id for the
232
- // node until then, so we need to avoid emitting collections in an inconsistent state.
233
- this.ownerDocument.startTransaction();
234
254
  }
235
255
 
236
- get index() {
256
+ get index(): number {
237
257
  return this._index;
238
258
  }
239
259
 
240
- set index(index) {
260
+ set index(index: number) {
241
261
  this._index = index;
242
262
  this.ownerDocument.markDirty(this);
243
263
  }
@@ -250,20 +270,45 @@ export class ElementNode<T> extends BaseNode<T> {
250
270
  return 0;
251
271
  }
252
272
 
253
- updateNode() {
254
- let node = this.ownerDocument.getMutableNode(this);
273
+ /**
274
+ * Lazily gets a mutable instance of a Node. If the node has already
275
+ * been cloned during this update cycle, it just returns the existing one.
276
+ */
277
+ private getMutableNode(): Mutable<CollectionNode<T>> {
278
+ if (!this.isMutated) {
279
+ this.node = this.node.clone();
280
+ this.isMutated = true;
281
+ }
282
+
283
+ this.ownerDocument.markDirty(this);
284
+ return this.node;
285
+ }
286
+
287
+ updateNode(): void {
288
+ let nextSibling = this.nextVisibleSibling;
289
+ let node = this.getMutableNode();
255
290
  node.index = this.index;
256
291
  node.level = this.level;
257
292
  node.parentKey = this.parentNode instanceof ElementNode ? this.parentNode.node.key : null;
258
- node.prevKey = this.previousSibling?.node.key ?? null;
259
- node.nextKey = this.nextSibling?.node.key ?? null;
293
+ node.prevKey = this.previousVisibleSibling?.node.key ?? null;
294
+ node.nextKey = nextSibling?.node.key ?? null;
260
295
  node.hasChildNodes = !!this.firstChild;
261
- node.firstChildKey = this.firstChild?.node.key ?? null;
262
- node.lastChildKey = this.lastChild?.node.key ?? null;
296
+ node.firstChildKey = this.firstVisibleChild?.node.key ?? null;
297
+ node.lastChildKey = this.lastVisibleChild?.node.key ?? null;
298
+
299
+ // Update the colIndex of sibling nodes if this node has a colSpan.
300
+ if ((node.colSpan != null || node.colIndex != null) && nextSibling) {
301
+ // This queues the next sibling for update, which means this happens recursively.
302
+ let nextColIndex = (node.colIndex ?? node.index) + (node.colSpan ?? 1);
303
+ if (nextColIndex !== nextSibling.node.colIndex) {
304
+ let siblingNode = nextSibling.getMutableNode();
305
+ siblingNode.colIndex = nextColIndex;
306
+ }
307
+ }
263
308
  }
264
309
 
265
- setProps<E extends Element>(obj: any, ref: ForwardedRef<E>, rendered?: any, render?: (node: Node<T>) => ReactElement) {
266
- let node = this.ownerDocument.getMutableNode(this);
310
+ setProps<E extends Element>(obj: {[key: string]: any}, ref: ForwardedRef<E>, rendered?: ReactNode, render?: (node: Node<T>) => ReactElement): void {
311
+ let node = this.getMutableNode();
267
312
  let {value, textValue, id, ...props} = obj;
268
313
  props.ref = ref;
269
314
  node.props = props;
@@ -278,25 +323,54 @@ export class ElementNode<T> extends BaseNode<T> {
278
323
  node.key = id;
279
324
  }
280
325
 
281
- // If this is the first time props have been set, end the transaction started in the constructor
282
- // so this node can be emitted.
283
- if (!this.hasSetProps) {
284
- this.ownerDocument.addNode(this);
285
- this.ownerDocument.endTransaction();
286
- this.hasSetProps = true;
326
+ if (props.colSpan != null) {
327
+ node.colSpan = props.colSpan;
287
328
  }
288
329
 
330
+ this.hasSetProps = true;
289
331
  this.ownerDocument.queueUpdate();
290
332
  }
291
333
 
292
- get style() {
293
- return {};
334
+ get style(): CSSProperties {
335
+ // React sets display: none to hide elements during Suspense.
336
+ // We'll handle this by setting the element to hidden and invalidating
337
+ // its siblings/parent. Hidden elements remain in the Document, but
338
+ // are removed from the Collection.
339
+ let element = this;
340
+ return {
341
+ get display() {
342
+ return element.isHidden ? 'none' : '';
343
+ },
344
+ set display(value) {
345
+ let isHidden = value === 'none';
346
+ if (element.isHidden !== isHidden) {
347
+ // Mark parent node dirty if this element is currently the first or last visible child.
348
+ if (element.parentNode?.firstVisibleChild === element || element.parentNode?.lastVisibleChild === element) {
349
+ element.ownerDocument.markDirty(element.parentNode);
350
+ }
351
+
352
+ // Mark sibling visible elements dirty.
353
+ let prev = element.previousVisibleSibling;
354
+ let next = element.nextVisibleSibling;
355
+ if (prev) {
356
+ element.ownerDocument.markDirty(prev);
357
+ }
358
+ if (next) {
359
+ element.ownerDocument.markDirty(next);
360
+ }
361
+
362
+ // Mark self dirty.
363
+ element.isHidden = isHidden;
364
+ element.ownerDocument.markDirty(element);
365
+ }
366
+ }
367
+ };
294
368
  }
295
369
 
296
- hasAttribute() {}
297
- setAttribute() {}
298
- setAttributeNS() {}
299
- removeAttribute() {}
370
+ hasAttribute(): void {}
371
+ setAttribute(): void {}
372
+ setAttributeNS(): void {}
373
+ removeAttribute(): void {}
300
374
  }
301
375
 
302
376
  /**
@@ -312,137 +386,137 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
312
386
  nodesByProps = new WeakMap<object, ElementNode<T>>();
313
387
  isMounted = true;
314
388
  private collection: C;
315
- private collectionMutated: boolean;
316
- private mutatedNodes: Set<ElementNode<T>> = new Set();
389
+ private nextCollection: C | null = null;
317
390
  private subscriptions: Set<() => void> = new Set();
318
- private transactionCount = 0;
391
+ private queuedRender = false;
392
+ private inSubscription = false;
319
393
 
320
394
  constructor(collection: C) {
321
395
  // @ts-ignore
322
396
  super(null);
323
397
  this.collection = collection;
324
- this.collectionMutated = true;
398
+ this.nextCollection = collection;
325
399
  }
326
400
 
327
- get isConnected() {
401
+ get isConnected(): boolean {
328
402
  return this.isMounted;
329
403
  }
330
404
 
331
- createElement(type: string) {
405
+ createElement(type: string): ElementNode<T> {
332
406
  return new ElementNode(type, this);
333
407
  }
334
408
 
335
- /**
336
- * Lazily gets a mutable instance of a Node. If the node has already
337
- * been cloned during this update cycle, it just returns the existing one.
338
- */
339
- getMutableNode(element: ElementNode<T>): Mutable<CollectionNode<T>> {
340
- let node = element.node;
341
- if (!this.mutatedNodes.has(element)) {
342
- node = element.node.clone();
343
- this.mutatedNodes.add(element);
344
- element.node = node;
345
- }
346
- this.markDirty(element);
347
- return node;
348
- }
349
-
350
409
  private getMutableCollection() {
351
- if (!this.isSSR && !this.collectionMutated) {
352
- this.collection = this.collection.clone();
353
- this.collectionMutated = true;
410
+ if (!this.nextCollection) {
411
+ this.nextCollection = this.collection.clone();
354
412
  }
355
413
 
356
- return this.collection;
414
+ return this.nextCollection;
357
415
  }
358
416
 
359
- markDirty(node: BaseNode<T>) {
417
+ markDirty(node: BaseNode<T>): void {
360
418
  this.dirtyNodes.add(node);
361
419
  }
362
420
 
363
- startTransaction() {
364
- this.transactionCount++;
365
- }
366
-
367
- endTransaction() {
368
- this.transactionCount--;
369
- }
421
+ private addNode(element: ElementNode<T>): void {
422
+ if (element.isHidden) {
423
+ return;
424
+ }
370
425
 
371
- addNode(element: ElementNode<T>) {
372
426
  let collection = this.getMutableCollection();
373
427
  if (!collection.getItem(element.node.key)) {
374
- collection.addNode(element.node);
375
-
376
428
  for (let child of element) {
377
429
  this.addNode(child);
378
430
  }
379
431
  }
380
432
 
381
- this.markDirty(element);
433
+ collection.addNode(element.node);
382
434
  }
383
435
 
384
- removeNode(node: ElementNode<T>) {
436
+ private removeNode(node: ElementNode<T>): void {
385
437
  for (let child of node) {
386
438
  this.removeNode(child);
387
439
  }
388
440
 
389
441
  let collection = this.getMutableCollection();
390
442
  collection.removeNode(node.node.key);
391
- this.markDirty(node);
392
443
  }
393
444
 
394
445
  /** Finalizes the collection update, updating all nodes and freezing the collection. */
395
446
  getCollection(): C {
396
- if (this.transactionCount > 0) {
397
- return this.collection;
447
+ // If in a subscription update, return a clone of the existing collection.
448
+ // This ensures React will queue a render. React will call getCollection again
449
+ // during render, at which point all the updates will be complete and we can return
450
+ // the new collection.
451
+ if (this.inSubscription) {
452
+ return this.collection.clone();
398
453
  }
399
454
 
455
+ // Reset queuedRender to false when getCollection is called during render.
456
+ this.queuedRender = false;
457
+
400
458
  this.updateCollection();
401
459
  return this.collection;
402
460
  }
403
461
 
404
- updateCollection() {
462
+ updateCollection(): void {
463
+ // First, remove disconnected nodes and update the indices of dirty element children.
405
464
  for (let element of this.dirtyNodes) {
406
- if (element instanceof ElementNode && element.isConnected) {
407
- element.updateNode();
465
+ if (element instanceof ElementNode && (!element.isConnected || element.isHidden)) {
466
+ this.removeNode(element);
467
+ } else {
468
+ element.updateChildIndices();
408
469
  }
409
470
  }
410
471
 
411
- this.dirtyNodes.clear();
412
-
413
- if (this.mutatedNodes.size || this.collectionMutated) {
414
- let collection = this.getMutableCollection();
415
- for (let element of this.mutatedNodes) {
416
- if (element.isConnected) {
417
- collection.addNode(element.node);
472
+ // Next, update dirty collection nodes.
473
+ for (let element of this.dirtyNodes) {
474
+ if (element instanceof ElementNode) {
475
+ if (element.isConnected && !element.isHidden) {
476
+ element.updateNode();
477
+ this.addNode(element);
418
478
  }
419
- }
420
479
 
421
- collection.commit(this.firstChild?.node.key ?? null, this.lastChild?.node.key ?? null, this.isSSR);
422
- this.mutatedNodes.clear();
480
+ element.isMutated = false;
481
+ }
423
482
  }
424
483
 
425
- this.collectionMutated = false;
484
+ this.dirtyNodes.clear();
485
+
486
+ // Finally, update the collection.
487
+ if (this.nextCollection) {
488
+ this.nextCollection.commit(this.firstVisibleChild?.node.key ?? null, this.lastVisibleChild?.node.key ?? null, this.isSSR);
489
+ if (!this.isSSR) {
490
+ this.collection = this.nextCollection;
491
+ this.nextCollection = null;
492
+ }
493
+ }
426
494
  }
427
495
 
428
- queueUpdate() {
429
- // Don't emit any updates if there is a transaction in progress.
430
- // queueUpdate should be called again after the transaction.
431
- if (this.dirtyNodes.size === 0 || this.transactionCount > 0) {
496
+ queueUpdate(): void {
497
+ if (this.dirtyNodes.size === 0 || this.queuedRender) {
432
498
  return;
433
499
  }
434
-
500
+
501
+ // Only trigger subscriptions once during an update, when the first item changes.
502
+ // React's useSyncExternalStore will call getCollection immediately, to check whether the snapshot changed.
503
+ // If so, React will queue a render to happen after the current commit to our fake DOM finishes.
504
+ // We track whether getCollection is called in a subscription, and once it is called during render,
505
+ // we reset queuedRender back to false.
506
+ this.queuedRender = true;
507
+ this.inSubscription = true;
435
508
  for (let fn of this.subscriptions) {
436
509
  fn();
437
510
  }
511
+ this.inSubscription = false;
438
512
  }
439
513
 
440
514
  subscribe(fn: () => void) {
441
515
  this.subscriptions.add(fn);
442
- return () => this.subscriptions.delete(fn);
516
+ return (): boolean => this.subscriptions.delete(fn);
443
517
  }
444
518
 
445
- resetAfterSSR() {
519
+ resetAfterSSR(): void {
446
520
  if (this.isSSR) {
447
521
  this.isSSR = false;
448
522
  this.firstChild = null;
package/src/Hidden.tsx CHANGED
@@ -10,10 +10,8 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {createPortal} from 'react-dom';
14
13
  import {forwardRefType} from '@react-types/shared';
15
14
  import React, {createContext, forwardRef, ReactElement, ReactNode, useContext} from 'react';
16
- import {useIsSSR} from '@react-aria/ssr';
17
15
 
18
16
  // React doesn't understand the <template> element, which doesn't have children like a normal element.
19
17
  // It will throw an error during hydration when it expects the firstChild to contain content rendered
@@ -37,12 +35,8 @@ if (typeof HTMLTemplateElement !== 'undefined') {
37
35
 
38
36
  export const HiddenContext = createContext<boolean>(false);
39
37
 
40
- // Portal to nowhere
41
- const hiddenFragment = typeof DocumentFragment !== 'undefined' ? new DocumentFragment() : null;
42
-
43
- export function Hidden(props: {children: ReactNode}) {
38
+ export function Hidden(props: {children: ReactNode}): ReactNode {
44
39
  let isHidden = useContext(HiddenContext);
45
- let isSSR = useIsSSR();
46
40
  if (isHidden) {
47
41
  // Don't hide again if we are already hidden.
48
42
  return <>{props.children}</>;
@@ -54,12 +48,10 @@ export function Hidden(props: {children: ReactNode}) {
54
48
  </HiddenContext.Provider>
55
49
  );
56
50
 
57
- // In SSR, portals are not supported by React. Instead, render into a <template>
51
+ // In SSR, portals are not supported by React. Instead, always render into a <template>
58
52
  // element, which the browser will never display to the user. In addition, the
59
- // content is not part of the DOM tree, so it won't affect ids or other accessibility attributes.
60
- return isSSR
61
- ? <template data-react-aria-hidden>{children}</template>
62
- : createPortal(children, hiddenFragment!);
53
+ // content is not part of the accessible DOM tree, so it won't affect ids or other accessibility attributes.
54
+ return <template data-react-aria-hidden>{children}</template>;
63
55
  }
64
56
 
65
57
  /** Creates a component that forwards its ref and returns null if it is in a hidden subtree. */
@@ -19,7 +19,7 @@ export interface CachedChildrenOptions<T> {
19
19
  /** The contents of the collection. */
20
20
  children?: ReactNode | ((item: T) => ReactNode),
21
21
  /** Values that should invalidate the item cache when using dynamic collections. */
22
- dependencies?: any[],
22
+ dependencies?: ReadonlyArray<any>,
23
23
  /** A scope to prepend to all child item ids to ensure they are unique. */
24
24
  idScope?: Key,
25
25
  /** Whether to add `id` and `value` props to all child items. */
@@ -45,11 +45,11 @@ export function useCachedChildren<T extends object>(props: CachedChildrenOptions
45
45
  rendered = children(item);
46
46
  // @ts-ignore
47
47
  let key = rendered.props.id ?? item.key ?? item.id;
48
- // eslint-disable-next-line max-depth
48
+
49
49
  if (key == null) {
50
50
  throw new Error('Could not determine key for item');
51
51
  }
52
- // eslint-disable-next-line max-depth
52
+
53
53
  if (idScope) {
54
54
  key = idScope + ':' + key;
55
55
  }