@react-aria/collections 3.0.0-nightly.5042 → 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/dist/BaseCollection.main.js +104 -0
- package/dist/BaseCollection.main.js.map +1 -1
- package/dist/BaseCollection.mjs +104 -0
- package/dist/BaseCollection.module.js +104 -0
- package/dist/BaseCollection.module.js.map +1 -1
- package/dist/CollectionBuilder.main.js +9 -3
- package/dist/CollectionBuilder.main.js.map +1 -1
- package/dist/CollectionBuilder.mjs +9 -3
- package/dist/CollectionBuilder.module.js +9 -3
- package/dist/CollectionBuilder.module.js.map +1 -1
- package/dist/Document.main.js +146 -107
- package/dist/Document.main.js.map +1 -1
- package/dist/Document.mjs +146 -107
- package/dist/Document.module.js +146 -107
- package/dist/Document.module.js.map +1 -1
- package/dist/Hidden.main.js +4 -11
- package/dist/Hidden.main.js.map +1 -1
- package/dist/Hidden.mjs +4 -11
- package/dist/Hidden.module.js +4 -11
- package/dist/Hidden.module.js.map +1 -1
- package/dist/types.d.ts +12 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/useCachedChildren.main.js +0 -2
- package/dist/useCachedChildren.main.js.map +1 -1
- package/dist/useCachedChildren.mjs +0 -2
- package/dist/useCachedChildren.module.js +0 -2
- package/dist/useCachedChildren.module.js.map +1 -1
- package/package.json +9 -8
- package/src/BaseCollection.ts +145 -10
- package/src/CollectionBuilder.tsx +20 -6
- package/src/Document.ts +210 -136
- package/src/Hidden.tsx +4 -12
- package/src/useCachedChildren.ts +3 -3
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
|
-
|
|
105
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
254
|
-
|
|
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.
|
|
259
|
-
node.nextKey =
|
|
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.
|
|
262
|
-
node.lastChildKey = this.
|
|
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?:
|
|
266
|
-
let node = 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
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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
|
|
316
|
-
private mutatedNodes: Set<ElementNode<T>> = new Set();
|
|
389
|
+
private nextCollection: C | null = null;
|
|
317
390
|
private subscriptions: Set<() => void> = new Set();
|
|
318
|
-
private
|
|
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.
|
|
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.
|
|
352
|
-
this.
|
|
353
|
-
this.collectionMutated = true;
|
|
410
|
+
if (!this.nextCollection) {
|
|
411
|
+
this.nextCollection = this.collection.clone();
|
|
354
412
|
}
|
|
355
413
|
|
|
356
|
-
return this.
|
|
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
|
-
|
|
364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
397
|
-
|
|
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
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
422
|
-
|
|
480
|
+
element.isMutated = false;
|
|
481
|
+
}
|
|
423
482
|
}
|
|
424
483
|
|
|
425
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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. */
|
package/src/useCachedChildren.ts
CHANGED
|
@@ -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
|
-
|
|
48
|
+
|
|
49
49
|
if (key == null) {
|
|
50
50
|
throw new Error('Could not determine key for item');
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
if (idScope) {
|
|
54
54
|
key = idScope + ':' + key;
|
|
55
55
|
}
|