@react-aria/collections 3.0.0-nightly.5042 → 3.0.0-rc.1

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,72 @@ 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 || !this._minInvalidChildIndex.isConnected || child.index < this._minInvalidChildIndex.index) {
107
+ this._minInvalidChildIndex = child;
108
+ this.ownerDocument.markDirty(this);
109
+ }
110
+ }
111
+
112
+ updateChildIndices(): void {
113
+ let node = this._minInvalidChildIndex;
114
+ while (node) {
115
+ node.index = node.previousSibling ? node.previousSibling.index + 1 : 0;
116
+ node = node.nextSibling;
117
+ }
118
+ this._minInvalidChildIndex = null;
119
+ }
120
+
121
+ appendChild(child: ElementNode<T>): void {
106
122
  if (child.parentNode) {
107
123
  child.parentNode.removeChild(child);
108
124
  }
@@ -125,30 +141,27 @@ export class BaseNode<T> {
125
141
  this.lastChild = child;
126
142
 
127
143
  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);
144
+ if (this.isConnected) {
145
+ this.ownerDocument.queueUpdate();
132
146
  }
133
-
134
- this.ownerDocument.endTransaction();
135
- this.ownerDocument.queueUpdate();
136
147
  }
137
148
 
138
- insertBefore(newNode: ElementNode<T>, referenceNode: ElementNode<T>) {
149
+ insertBefore(newNode: ElementNode<T>, referenceNode: ElementNode<T>): void {
139
150
  if (referenceNode == null) {
140
151
  return this.appendChild(newNode);
141
152
  }
142
153
 
143
- this.ownerDocument.startTransaction();
144
154
  if (newNode.parentNode) {
145
155
  newNode.parentNode.removeChild(newNode);
146
156
  }
147
157
 
148
158
  newNode.nextSibling = referenceNode;
149
159
  newNode.previousSibling = referenceNode.previousSibling;
150
- newNode.index = referenceNode.index;
151
-
160
+ // Ensure that the newNode's index is less than that of the reference node so that
161
+ // invalidateChildIndices will properly use the newNode as the _minInvalidChildIndex, thus making sure
162
+ // we will properly update the indexes of all sibiling nodes after the newNode. The value here doesn't matter
163
+ // since updateChildIndices should calculate the proper indexes.
164
+ newNode.index = referenceNode.index - 1;
152
165
  if (this.firstChild === referenceNode) {
153
166
  this.firstChild = newNode;
154
167
  } else if (referenceNode.previousSibling) {
@@ -158,33 +171,23 @@ export class BaseNode<T> {
158
171
  referenceNode.previousSibling = newNode;
159
172
  newNode.parentNode = referenceNode.parentNode;
160
173
 
161
- let node: ElementNode<T> | null = referenceNode;
162
- while (node) {
163
- node.index++;
164
- node = node.nextSibling;
174
+ this.invalidateChildIndices(newNode);
175
+ if (this.isConnected) {
176
+ this.ownerDocument.queueUpdate();
165
177
  }
166
-
167
- if (newNode.hasSetProps) {
168
- this.ownerDocument.addNode(newNode);
169
- }
170
-
171
- this.ownerDocument.endTransaction();
172
- this.ownerDocument.queueUpdate();
173
178
  }
174
179
 
175
- removeChild(child: ElementNode<T>) {
180
+ removeChild(child: ElementNode<T>): void {
176
181
  if (child.parentNode !== this || !this.ownerDocument.isMounted) {
177
182
  return;
178
183
  }
179
184
 
180
- this.ownerDocument.startTransaction();
181
- let node = child.nextSibling;
182
- while (node) {
183
- node.index--;
184
- node = node.nextSibling;
185
+ if (this._minInvalidChildIndex === child) {
186
+ this._minInvalidChildIndex = null;
185
187
  }
186
188
 
187
189
  if (child.nextSibling) {
190
+ this.invalidateChildIndices(child.nextSibling);
188
191
  child.nextSibling.previousSibling = child.previousSibling;
189
192
  }
190
193
 
@@ -205,13 +208,46 @@ export class BaseNode<T> {
205
208
  child.previousSibling = null;
206
209
  child.index = 0;
207
210
 
208
- this.ownerDocument.removeNode(child);
209
- this.ownerDocument.endTransaction();
210
- this.ownerDocument.queueUpdate();
211
+ this.ownerDocument.markDirty(child);
212
+ if (this.isConnected) {
213
+ this.ownerDocument.queueUpdate();
214
+ }
215
+ }
216
+
217
+ addEventListener(): void {}
218
+ removeEventListener(): void {}
219
+
220
+ get previousVisibleSibling(): ElementNode<T> | null {
221
+ let node = this.previousSibling;
222
+ while (node && node.isHidden) {
223
+ node = node.previousSibling;
224
+ }
225
+ return node;
226
+ }
227
+
228
+ get nextVisibleSibling(): ElementNode<T> | null {
229
+ let node = this.nextSibling;
230
+ while (node && node.isHidden) {
231
+ node = node.nextSibling;
232
+ }
233
+ return node;
211
234
  }
212
235
 
213
- addEventListener() {}
214
- removeEventListener() {}
236
+ get firstVisibleChild(): ElementNode<T> | null {
237
+ let node = this.firstChild;
238
+ while (node && node.isHidden) {
239
+ node = node.nextSibling;
240
+ }
241
+ return node;
242
+ }
243
+
244
+ get lastVisibleChild(): ElementNode<T> | null {
245
+ let node = this.lastChild;
246
+ while (node && node.isHidden) {
247
+ node = node.previousSibling;
248
+ }
249
+ return node;
250
+ }
215
251
  }
216
252
 
217
253
  /**
@@ -221,23 +257,21 @@ export class BaseNode<T> {
221
257
  export class ElementNode<T> extends BaseNode<T> {
222
258
  nodeType = 8; // COMMENT_NODE (we'd use ELEMENT_NODE but React DevTools will fail to get its dimensions)
223
259
  node: CollectionNode<T>;
260
+ isMutated = true;
224
261
  private _index: number = 0;
225
262
  hasSetProps = false;
263
+ isHidden = false;
226
264
 
227
265
  constructor(type: string, ownerDocument: Document<T, any>) {
228
266
  super(ownerDocument);
229
267
  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
268
  }
235
269
 
236
- get index() {
270
+ get index(): number {
237
271
  return this._index;
238
272
  }
239
273
 
240
- set index(index) {
274
+ set index(index: number) {
241
275
  this._index = index;
242
276
  this.ownerDocument.markDirty(this);
243
277
  }
@@ -250,20 +284,45 @@ export class ElementNode<T> extends BaseNode<T> {
250
284
  return 0;
251
285
  }
252
286
 
253
- updateNode() {
254
- let node = this.ownerDocument.getMutableNode(this);
287
+ /**
288
+ * Lazily gets a mutable instance of a Node. If the node has already
289
+ * been cloned during this update cycle, it just returns the existing one.
290
+ */
291
+ private getMutableNode(): Mutable<CollectionNode<T>> {
292
+ if (!this.isMutated) {
293
+ this.node = this.node.clone();
294
+ this.isMutated = true;
295
+ }
296
+
297
+ this.ownerDocument.markDirty(this);
298
+ return this.node;
299
+ }
300
+
301
+ updateNode(): void {
302
+ let nextSibling = this.nextVisibleSibling;
303
+ let node = this.getMutableNode();
255
304
  node.index = this.index;
256
305
  node.level = this.level;
257
306
  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;
307
+ node.prevKey = this.previousVisibleSibling?.node.key ?? null;
308
+ node.nextKey = nextSibling?.node.key ?? null;
260
309
  node.hasChildNodes = !!this.firstChild;
261
- node.firstChildKey = this.firstChild?.node.key ?? null;
262
- node.lastChildKey = this.lastChild?.node.key ?? null;
310
+ node.firstChildKey = this.firstVisibleChild?.node.key ?? null;
311
+ node.lastChildKey = this.lastVisibleChild?.node.key ?? null;
312
+
313
+ // Update the colIndex of sibling nodes if this node has a colSpan.
314
+ if ((node.colSpan != null || node.colIndex != null) && nextSibling) {
315
+ // This queues the next sibling for update, which means this happens recursively.
316
+ let nextColIndex = (node.colIndex ?? node.index) + (node.colSpan ?? 1);
317
+ if (nextColIndex !== nextSibling.node.colIndex) {
318
+ let siblingNode = nextSibling.getMutableNode();
319
+ siblingNode.colIndex = nextColIndex;
320
+ }
321
+ }
263
322
  }
264
323
 
265
- setProps<E extends Element>(obj: any, ref: ForwardedRef<E>, rendered?: any, render?: (node: Node<T>) => ReactElement) {
266
- let node = this.ownerDocument.getMutableNode(this);
324
+ setProps<E extends Element>(obj: {[key: string]: any}, ref: ForwardedRef<E>, rendered?: ReactNode, render?: (node: Node<T>) => ReactElement): void {
325
+ let node = this.getMutableNode();
267
326
  let {value, textValue, id, ...props} = obj;
268
327
  props.ref = ref;
269
328
  node.props = props;
@@ -278,25 +337,56 @@ export class ElementNode<T> extends BaseNode<T> {
278
337
  node.key = id;
279
338
  }
280
339
 
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;
340
+ if (props.colSpan != null) {
341
+ node.colSpan = props.colSpan;
287
342
  }
288
343
 
289
- this.ownerDocument.queueUpdate();
344
+ this.hasSetProps = true;
345
+ if (this.isConnected) {
346
+ this.ownerDocument.queueUpdate();
347
+ }
290
348
  }
291
349
 
292
- get style() {
293
- return {};
350
+ get style(): CSSProperties {
351
+ // React sets display: none to hide elements during Suspense.
352
+ // We'll handle this by setting the element to hidden and invalidating
353
+ // its siblings/parent. Hidden elements remain in the Document, but
354
+ // are removed from the Collection.
355
+ let element = this;
356
+ return {
357
+ get display() {
358
+ return element.isHidden ? 'none' : '';
359
+ },
360
+ set display(value) {
361
+ let isHidden = value === 'none';
362
+ if (element.isHidden !== isHidden) {
363
+ // Mark parent node dirty if this element is currently the first or last visible child.
364
+ if (element.parentNode?.firstVisibleChild === element || element.parentNode?.lastVisibleChild === element) {
365
+ element.ownerDocument.markDirty(element.parentNode);
366
+ }
367
+
368
+ // Mark sibling visible elements dirty.
369
+ let prev = element.previousVisibleSibling;
370
+ let next = element.nextVisibleSibling;
371
+ if (prev) {
372
+ element.ownerDocument.markDirty(prev);
373
+ }
374
+ if (next) {
375
+ element.ownerDocument.markDirty(next);
376
+ }
377
+
378
+ // Mark self dirty.
379
+ element.isHidden = isHidden;
380
+ element.ownerDocument.markDirty(element);
381
+ }
382
+ }
383
+ };
294
384
  }
295
385
 
296
- hasAttribute() {}
297
- setAttribute() {}
298
- setAttributeNS() {}
299
- removeAttribute() {}
386
+ hasAttribute(): void {}
387
+ setAttribute(): void {}
388
+ setAttributeNS(): void {}
389
+ removeAttribute(): void {}
300
390
  }
301
391
 
302
392
  /**
@@ -312,137 +402,137 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
312
402
  nodesByProps = new WeakMap<object, ElementNode<T>>();
313
403
  isMounted = true;
314
404
  private collection: C;
315
- private collectionMutated: boolean;
316
- private mutatedNodes: Set<ElementNode<T>> = new Set();
405
+ private nextCollection: C | null = null;
317
406
  private subscriptions: Set<() => void> = new Set();
318
- private transactionCount = 0;
407
+ private queuedRender = false;
408
+ private inSubscription = false;
319
409
 
320
410
  constructor(collection: C) {
321
411
  // @ts-ignore
322
412
  super(null);
323
413
  this.collection = collection;
324
- this.collectionMutated = true;
414
+ this.nextCollection = collection;
325
415
  }
326
416
 
327
- get isConnected() {
417
+ get isConnected(): boolean {
328
418
  return this.isMounted;
329
419
  }
330
420
 
331
- createElement(type: string) {
421
+ createElement(type: string): ElementNode<T> {
332
422
  return new ElementNode(type, this);
333
423
  }
334
424
 
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
425
  private getMutableCollection() {
351
- if (!this.isSSR && !this.collectionMutated) {
352
- this.collection = this.collection.clone();
353
- this.collectionMutated = true;
426
+ if (!this.nextCollection) {
427
+ this.nextCollection = this.collection.clone();
354
428
  }
355
429
 
356
- return this.collection;
430
+ return this.nextCollection;
357
431
  }
358
432
 
359
- markDirty(node: BaseNode<T>) {
433
+ markDirty(node: BaseNode<T>): void {
360
434
  this.dirtyNodes.add(node);
361
435
  }
362
436
 
363
- startTransaction() {
364
- this.transactionCount++;
365
- }
366
-
367
- endTransaction() {
368
- this.transactionCount--;
369
- }
437
+ private addNode(element: ElementNode<T>): void {
438
+ if (element.isHidden) {
439
+ return;
440
+ }
370
441
 
371
- addNode(element: ElementNode<T>) {
372
442
  let collection = this.getMutableCollection();
373
443
  if (!collection.getItem(element.node.key)) {
374
- collection.addNode(element.node);
375
-
376
444
  for (let child of element) {
377
445
  this.addNode(child);
378
446
  }
379
447
  }
380
448
 
381
- this.markDirty(element);
449
+ collection.addNode(element.node);
382
450
  }
383
451
 
384
- removeNode(node: ElementNode<T>) {
452
+ private removeNode(node: ElementNode<T>): void {
385
453
  for (let child of node) {
386
454
  this.removeNode(child);
387
455
  }
388
456
 
389
457
  let collection = this.getMutableCollection();
390
458
  collection.removeNode(node.node.key);
391
- this.markDirty(node);
392
459
  }
393
460
 
394
461
  /** Finalizes the collection update, updating all nodes and freezing the collection. */
395
462
  getCollection(): C {
396
- if (this.transactionCount > 0) {
397
- return this.collection;
463
+ // If in a subscription update, return a clone of the existing collection.
464
+ // This ensures React will queue a render. React will call getCollection again
465
+ // during render, at which point all the updates will be complete and we can return
466
+ // the new collection.
467
+ if (this.inSubscription) {
468
+ return this.collection.clone();
398
469
  }
399
470
 
471
+ // Reset queuedRender to false when getCollection is called during render.
472
+ this.queuedRender = false;
473
+
400
474
  this.updateCollection();
401
475
  return this.collection;
402
476
  }
403
477
 
404
- updateCollection() {
478
+ updateCollection(): void {
479
+ // First, remove disconnected nodes and update the indices of dirty element children.
405
480
  for (let element of this.dirtyNodes) {
406
- if (element instanceof ElementNode && element.isConnected) {
407
- element.updateNode();
481
+ if (element instanceof ElementNode && (!element.isConnected || element.isHidden)) {
482
+ this.removeNode(element);
483
+ } else {
484
+ element.updateChildIndices();
408
485
  }
409
486
  }
410
487
 
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);
488
+ // Next, update dirty collection nodes.
489
+ for (let element of this.dirtyNodes) {
490
+ if (element instanceof ElementNode) {
491
+ if (element.isConnected && !element.isHidden) {
492
+ element.updateNode();
493
+ this.addNode(element);
418
494
  }
419
- }
420
495
 
421
- collection.commit(this.firstChild?.node.key ?? null, this.lastChild?.node.key ?? null, this.isSSR);
422
- this.mutatedNodes.clear();
496
+ element.isMutated = false;
497
+ }
423
498
  }
424
499
 
425
- this.collectionMutated = false;
500
+ this.dirtyNodes.clear();
501
+
502
+ // Finally, update the collection.
503
+ if (this.nextCollection) {
504
+ this.nextCollection.commit(this.firstVisibleChild?.node.key ?? null, this.lastVisibleChild?.node.key ?? null, this.isSSR);
505
+ if (!this.isSSR) {
506
+ this.collection = this.nextCollection;
507
+ this.nextCollection = null;
508
+ }
509
+ }
426
510
  }
427
511
 
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) {
512
+ queueUpdate(): void {
513
+ if (this.dirtyNodes.size === 0 || this.queuedRender) {
432
514
  return;
433
515
  }
434
516
 
517
+ // Only trigger subscriptions once during an update, when the first item changes.
518
+ // React's useSyncExternalStore will call getCollection immediately, to check whether the snapshot changed.
519
+ // If so, React will queue a render to happen after the current commit to our fake DOM finishes.
520
+ // We track whether getCollection is called in a subscription, and once it is called during render,
521
+ // we reset queuedRender back to false.
522
+ this.queuedRender = true;
523
+ this.inSubscription = true;
435
524
  for (let fn of this.subscriptions) {
436
525
  fn();
437
526
  }
527
+ this.inSubscription = false;
438
528
  }
439
529
 
440
530
  subscribe(fn: () => void) {
441
531
  this.subscriptions.add(fn);
442
- return () => this.subscriptions.delete(fn);
532
+ return (): boolean => this.subscriptions.delete(fn);
443
533
  }
444
534
 
445
- resetAfterSSR() {
535
+ resetAfterSSR(): void {
446
536
  if (this.isSSR) {
447
537
  this.isSSR = false;
448
538
  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
- import React, {createContext, forwardRef, ReactElement, ReactNode, useContext} from 'react';
16
- import {useIsSSR} from '@react-aria/ssr';
14
+ import React, {createContext, forwardRef, JSX, ReactElement, ReactNode, useContext} from 'react';
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}): JSX.Element {
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
  }