@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/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 +159 -112
- package/dist/Document.main.js.map +1 -1
- package/dist/Document.mjs +159 -112
- package/dist/Document.module.js +159 -112
- 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 +226 -136
- package/src/Hidden.tsx +5 -13
- 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,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
|
-
|
|
105
|
-
this.
|
|
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 (
|
|
129
|
-
|
|
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
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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.
|
|
181
|
-
|
|
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.
|
|
209
|
-
this.
|
|
210
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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.
|
|
259
|
-
node.nextKey =
|
|
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.
|
|
262
|
-
node.lastChildKey = this.
|
|
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?:
|
|
266
|
-
let node = 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
|
-
|
|
282
|
-
|
|
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.
|
|
344
|
+
this.hasSetProps = true;
|
|
345
|
+
if (this.isConnected) {
|
|
346
|
+
this.ownerDocument.queueUpdate();
|
|
347
|
+
}
|
|
290
348
|
}
|
|
291
349
|
|
|
292
|
-
get style() {
|
|
293
|
-
|
|
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
|
|
316
|
-
private mutatedNodes: Set<ElementNode<T>> = new Set();
|
|
405
|
+
private nextCollection: C | null = null;
|
|
317
406
|
private subscriptions: Set<() => void> = new Set();
|
|
318
|
-
private
|
|
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.
|
|
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.
|
|
352
|
-
this.
|
|
353
|
-
this.collectionMutated = true;
|
|
426
|
+
if (!this.nextCollection) {
|
|
427
|
+
this.nextCollection = this.collection.clone();
|
|
354
428
|
}
|
|
355
429
|
|
|
356
|
-
return this.
|
|
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
|
-
|
|
364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
397
|
-
|
|
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
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
422
|
-
|
|
496
|
+
element.isMutated = false;
|
|
497
|
+
}
|
|
423
498
|
}
|
|
424
499
|
|
|
425
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|