@react-aria/collections 3.0.0-rc.3 → 3.0.0-rc.5
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 +112 -101
- package/dist/BaseCollection.main.js.map +1 -1
- package/dist/BaseCollection.mjs +108 -102
- package/dist/BaseCollection.module.js +108 -102
- package/dist/BaseCollection.module.js.map +1 -1
- package/dist/CollectionBuilder.main.js +20 -10
- package/dist/CollectionBuilder.main.js.map +1 -1
- package/dist/CollectionBuilder.mjs +21 -11
- package/dist/CollectionBuilder.module.js +21 -11
- package/dist/CollectionBuilder.module.js.map +1 -1
- package/dist/Document.main.js +21 -15
- package/dist/Document.main.js.map +1 -1
- package/dist/Document.mjs +21 -15
- package/dist/Document.module.js +21 -15
- package/dist/Document.module.js.map +1 -1
- package/dist/Hidden.main.js.map +1 -1
- package/dist/Hidden.module.js.map +1 -1
- package/dist/import.mjs +2 -2
- package/dist/main.js +5 -0
- package/dist/main.js.map +1 -1
- package/dist/module.js +2 -2
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +32 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/BaseCollection.ts +132 -124
- package/src/CollectionBuilder.tsx +32 -14
- package/src/Document.ts +34 -14
- package/src/Hidden.tsx +2 -2
- package/src/index.ts +1 -1
package/src/BaseCollection.ts
CHANGED
|
@@ -17,8 +17,11 @@ export type Mutable<T> = {
|
|
|
17
17
|
-readonly[P in keyof T]: T[P]
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
type FilterFn<T> = (textValue: string, node: Node<T>) => boolean;
|
|
21
|
+
|
|
20
22
|
/** An immutable object representing a Node in a Collection. */
|
|
21
23
|
export class CollectionNode<T> implements Node<T> {
|
|
24
|
+
static readonly type: string;
|
|
22
25
|
readonly type: string;
|
|
23
26
|
readonly key: Key;
|
|
24
27
|
readonly value: T | null = null;
|
|
@@ -38,8 +41,8 @@ export class CollectionNode<T> implements Node<T> {
|
|
|
38
41
|
readonly colSpan: number | null = null;
|
|
39
42
|
readonly colIndex: number | null = null;
|
|
40
43
|
|
|
41
|
-
constructor(
|
|
42
|
-
this.type = type;
|
|
44
|
+
constructor(key: Key) {
|
|
45
|
+
this.type = (this.constructor as typeof CollectionNode).type;
|
|
43
46
|
this.key = key;
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -47,8 +50,8 @@ export class CollectionNode<T> implements Node<T> {
|
|
|
47
50
|
throw new Error('childNodes is not supported');
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
clone():
|
|
51
|
-
let node: Mutable<
|
|
53
|
+
clone(): this {
|
|
54
|
+
let node: Mutable<this> = new (this.constructor as any)(this.key);
|
|
52
55
|
node.value = this.value;
|
|
53
56
|
node.level = this.level;
|
|
54
57
|
node.hasChildNodes = this.hasChildNodes;
|
|
@@ -67,6 +70,63 @@ export class CollectionNode<T> implements Node<T> {
|
|
|
67
70
|
node.colIndex = this.colIndex;
|
|
68
71
|
return node;
|
|
69
72
|
}
|
|
73
|
+
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
75
|
+
filter(collection: BaseCollection<T>, newCollection: BaseCollection<T>, filterFn: FilterFn<T>): CollectionNode<T> | null {
|
|
76
|
+
let clone = this.clone();
|
|
77
|
+
newCollection.addDescendants(clone, collection);
|
|
78
|
+
return clone;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class FilterableNode<T> extends CollectionNode<T> {
|
|
83
|
+
filter(collection: BaseCollection<T>, newCollection: BaseCollection<T>, filterFn: FilterFn<T>): CollectionNode<T> | null {
|
|
84
|
+
let [firstKey, lastKey] = filterChildren(collection, newCollection, this.firstChildKey, filterFn);
|
|
85
|
+
let newNode: Mutable<CollectionNode<T>> = this.clone();
|
|
86
|
+
newNode.firstChildKey = firstKey;
|
|
87
|
+
newNode.lastChildKey = lastKey;
|
|
88
|
+
return newNode;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class HeaderNode extends CollectionNode<unknown> {
|
|
93
|
+
static readonly type = 'header';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export class LoaderNode extends CollectionNode<unknown> {
|
|
97
|
+
static readonly type = 'loader';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class ItemNode<T> extends FilterableNode<T> {
|
|
101
|
+
static readonly type = 'item';
|
|
102
|
+
|
|
103
|
+
filter(collection: BaseCollection<T>, newCollection: BaseCollection<T>, filterFn: FilterFn<T>): ItemNode<T> | null {
|
|
104
|
+
if (filterFn(this.textValue, this)) {
|
|
105
|
+
let clone = this.clone();
|
|
106
|
+
newCollection.addDescendants(clone, collection);
|
|
107
|
+
return clone;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class SectionNode<T> extends FilterableNode<T> {
|
|
115
|
+
static readonly type = 'section';
|
|
116
|
+
|
|
117
|
+
filter(collection: BaseCollection<T>, newCollection: BaseCollection<T>, filterFn: FilterFn<T>): SectionNode<T> | null {
|
|
118
|
+
let filteredSection = super.filter(collection, newCollection, filterFn);
|
|
119
|
+
if (filteredSection) {
|
|
120
|
+
if (filteredSection.lastChildKey !== null) {
|
|
121
|
+
let lastChild = collection.getItem(filteredSection.lastChildKey);
|
|
122
|
+
if (lastChild && lastChild.type !== 'header') {
|
|
123
|
+
return filteredSection;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
70
130
|
}
|
|
71
131
|
|
|
72
132
|
/**
|
|
@@ -79,9 +139,11 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
|
|
|
79
139
|
private firstKey: Key | null = null;
|
|
80
140
|
private lastKey: Key | null = null;
|
|
81
141
|
private frozen = false;
|
|
142
|
+
private itemCount: number = 0;
|
|
143
|
+
isComplete = true;
|
|
82
144
|
|
|
83
145
|
get size(): number {
|
|
84
|
-
return this.
|
|
146
|
+
return this.itemCount;
|
|
85
147
|
}
|
|
86
148
|
|
|
87
149
|
getKeys(): IterableIterator<Key> {
|
|
@@ -184,6 +246,7 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
|
|
|
184
246
|
collection.keyMap = new Map(this.keyMap);
|
|
185
247
|
collection.firstKey = this.firstKey;
|
|
186
248
|
collection.lastKey = this.lastKey;
|
|
249
|
+
collection.itemCount = this.itemCount;
|
|
187
250
|
return collection;
|
|
188
251
|
}
|
|
189
252
|
|
|
@@ -192,14 +255,32 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
|
|
|
192
255
|
throw new Error('Cannot add a node to a frozen collection');
|
|
193
256
|
}
|
|
194
257
|
|
|
258
|
+
if (node.type === 'item' && this.keyMap.get(node.key) == null) {
|
|
259
|
+
this.itemCount++;
|
|
260
|
+
}
|
|
261
|
+
|
|
195
262
|
this.keyMap.set(node.key, node);
|
|
196
263
|
}
|
|
197
264
|
|
|
265
|
+
// Deeply add a node and its children to the collection from another collection, primarily used when filtering a collection
|
|
266
|
+
addDescendants(node: CollectionNode<T>, oldCollection: BaseCollection<T>): void {
|
|
267
|
+
this.addNode(node);
|
|
268
|
+
let children = oldCollection.getChildren(node.key);
|
|
269
|
+
for (let child of children) {
|
|
270
|
+
this.addDescendants(child as CollectionNode<T>, oldCollection);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
198
274
|
removeNode(key: Key): void {
|
|
199
275
|
if (this.frozen) {
|
|
200
276
|
throw new Error('Cannot remove a node to a frozen collection');
|
|
201
277
|
}
|
|
202
278
|
|
|
279
|
+
let node = this.keyMap.get(key);
|
|
280
|
+
if (node != null && node.type === 'item') {
|
|
281
|
+
this.itemCount--;
|
|
282
|
+
}
|
|
283
|
+
|
|
203
284
|
this.keyMap.delete(key);
|
|
204
285
|
}
|
|
205
286
|
|
|
@@ -213,134 +294,61 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
|
|
|
213
294
|
this.frozen = !isSSR;
|
|
214
295
|
}
|
|
215
296
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
// for updating the next/prevKey for every non-filtered node.
|
|
224
|
-
let lastNode: Mutable<CollectionNode<T>> | null = null;
|
|
225
|
-
|
|
226
|
-
for (let node of this) {
|
|
227
|
-
if (node.type === 'section' && node.hasChildNodes) {
|
|
228
|
-
let clonedSection: Mutable<CollectionNode<T>> = (node as CollectionNode<T>).clone();
|
|
229
|
-
let lastChildInSection: Mutable<CollectionNode<T>> | null = null;
|
|
230
|
-
for (let child of this.getChildren(node.key)) {
|
|
231
|
-
if (shouldKeepNode(child, filterFn, this, newCollection)) {
|
|
232
|
-
let clonedChild: Mutable<CollectionNode<T>> = (child as CollectionNode<T>).clone();
|
|
233
|
-
// eslint-disable-next-line max-depth
|
|
234
|
-
if (lastChildInSection == null) {
|
|
235
|
-
clonedSection.firstChildKey = clonedChild.key;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// eslint-disable-next-line max-depth
|
|
239
|
-
if (newCollection.firstKey == null) {
|
|
240
|
-
newCollection.firstKey = clonedSection.key;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// eslint-disable-next-line max-depth
|
|
244
|
-
if (lastChildInSection && lastChildInSection.parentKey === clonedChild.parentKey) {
|
|
245
|
-
lastChildInSection.nextKey = clonedChild.key;
|
|
246
|
-
clonedChild.prevKey = lastChildInSection.key;
|
|
247
|
-
} else {
|
|
248
|
-
clonedChild.prevKey = null;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
clonedChild.nextKey = null;
|
|
252
|
-
newCollection.addNode(clonedChild);
|
|
253
|
-
lastChildInSection = clonedChild;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
297
|
+
filter(filterFn: FilterFn<T>): this {
|
|
298
|
+
let newCollection = new (this.constructor as any)();
|
|
299
|
+
let [firstKey, lastKey] = filterChildren(this, newCollection, this.firstKey, filterFn);
|
|
300
|
+
newCollection?.commit(firstKey, lastKey);
|
|
301
|
+
return newCollection;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
256
304
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
newCollection.removeNode(lastChildInSection.key);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
} else if (node.type === 'separator') {
|
|
281
|
-
// will need to check if previous section key exists, if it does then we add the separator to the collection.
|
|
282
|
-
// After the full collection is created we'll need to remove it it is the last node in the section (aka no following section after the separator)
|
|
283
|
-
let clonedSeparator: Mutable<CollectionNode<T>> = (node as CollectionNode<T>).clone();
|
|
284
|
-
clonedSeparator.nextKey = null;
|
|
285
|
-
if (lastNode?.type === 'section') {
|
|
286
|
-
lastNode.nextKey = clonedSeparator.key;
|
|
287
|
-
clonedSeparator.prevKey = lastNode.key;
|
|
288
|
-
lastNode = clonedSeparator;
|
|
289
|
-
newCollection.addNode(clonedSeparator);
|
|
290
|
-
}
|
|
291
|
-
} else {
|
|
292
|
-
// At this point, the node is either a subdialogtrigger node or a standard row/item
|
|
293
|
-
let clonedNode: Mutable<CollectionNode<T>> = (node as CollectionNode<T>).clone();
|
|
294
|
-
if (shouldKeepNode(clonedNode, filterFn, this, newCollection)) {
|
|
295
|
-
if (newCollection.firstKey == null) {
|
|
296
|
-
newCollection.firstKey = clonedNode.key;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (lastNode != null && (lastNode.type !== 'section' && lastNode.type !== 'separator') && lastNode.parentKey === clonedNode.parentKey) {
|
|
300
|
-
lastNode.nextKey = clonedNode.key;
|
|
301
|
-
clonedNode.prevKey = lastNode.key;
|
|
302
|
-
} else {
|
|
303
|
-
clonedNode.prevKey = null;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
clonedNode.nextKey = null;
|
|
307
|
-
newCollection.addNode(clonedNode);
|
|
308
|
-
lastNode = clonedNode;
|
|
309
|
-
}
|
|
305
|
+
function filterChildren<T>(collection: BaseCollection<T>, newCollection: BaseCollection<T>, firstChildKey: Key | null, filterFn: FilterFn<T>): [Key | null, Key | null] {
|
|
306
|
+
// loop over the siblings for firstChildKey
|
|
307
|
+
// create new nodes based on calling node.filter for each child
|
|
308
|
+
// if it returns null then don't include it, otherwise update its prev/next keys
|
|
309
|
+
// add them to the newCollection
|
|
310
|
+
if (firstChildKey == null) {
|
|
311
|
+
return [null, null];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let firstNode: Node<T> | null = null;
|
|
315
|
+
let lastNode: Node<T> | null = null;
|
|
316
|
+
let currentNode = collection.getItem(firstChildKey);
|
|
317
|
+
|
|
318
|
+
while (currentNode != null) {
|
|
319
|
+
let newNode: Mutable<CollectionNode<T>> | null = (currentNode as CollectionNode<T>).filter(collection, newCollection, filterFn);
|
|
320
|
+
if (newNode != null) {
|
|
321
|
+
newNode.nextKey = null;
|
|
322
|
+
if (lastNode) {
|
|
323
|
+
newNode.prevKey = lastNode.key;
|
|
324
|
+
lastNode.nextKey = newNode.key;
|
|
310
325
|
}
|
|
311
|
-
}
|
|
312
326
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (lastNode.prevKey != null) {
|
|
316
|
-
lastSection = newCollection.getItem(lastNode.prevKey) as Mutable<CollectionNode<T>>;
|
|
317
|
-
lastSection.nextKey = null;
|
|
327
|
+
if (firstNode == null) {
|
|
328
|
+
firstNode = newNode;
|
|
318
329
|
}
|
|
319
|
-
newCollection.removeNode(lastNode.key);
|
|
320
|
-
lastNode = lastSection;
|
|
321
|
-
}
|
|
322
330
|
|
|
323
|
-
|
|
331
|
+
newCollection.addNode(newNode);
|
|
332
|
+
lastNode = newNode;
|
|
333
|
+
}
|
|
324
334
|
|
|
325
|
-
|
|
335
|
+
currentNode = currentNode.nextKey ? collection.getItem(currentNode.nextKey) : null;
|
|
326
336
|
}
|
|
327
|
-
}
|
|
328
337
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
let
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
+
// TODO: this is pretty specific to dividers but doesn't feel like there is a good way to get around it since we only can know
|
|
339
|
+
// to filter the last separator in a collection only after performing a filter for the rest of the contents after it
|
|
340
|
+
// Its gross that it needs to live here, might be nice if somehow we could have this live in the separator code
|
|
341
|
+
if (lastNode && lastNode.type === 'separator') {
|
|
342
|
+
let prevKey = lastNode.prevKey;
|
|
343
|
+
newCollection.removeNode(lastNode.key);
|
|
344
|
+
|
|
345
|
+
if (prevKey) {
|
|
346
|
+
lastNode = newCollection.getItem(prevKey) as Mutable<CollectionNode<T>>;
|
|
347
|
+
lastNode.nextKey = null;
|
|
338
348
|
} else {
|
|
339
|
-
|
|
349
|
+
lastNode = null;
|
|
340
350
|
}
|
|
341
|
-
} else if (node.type === 'header') {
|
|
342
|
-
return true;
|
|
343
|
-
} else {
|
|
344
|
-
return filterFn(node.textValue);
|
|
345
351
|
}
|
|
352
|
+
|
|
353
|
+
return [firstNode?.key ?? null, lastNode?.key ?? null];
|
|
346
354
|
}
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {BaseCollection} from './BaseCollection';
|
|
13
|
+
import {BaseCollection, CollectionNode} from './BaseCollection';
|
|
14
14
|
import {BaseNode, Document, ElementNode} from './Document';
|
|
15
15
|
import {CachedChildrenOptions, useCachedChildren} from './useCachedChildren';
|
|
16
16
|
import {createPortal} from 'react-dom';
|
|
17
17
|
import {FocusableContext} from '@react-aria/interactions';
|
|
18
|
-
import {forwardRefType, Node} from '@react-types/shared';
|
|
18
|
+
import {forwardRefType, Key, Node} from '@react-types/shared';
|
|
19
19
|
import {Hidden} from './Hidden';
|
|
20
20
|
import React, {createContext, ForwardedRef, forwardRef, JSX, ReactElement, ReactNode, useCallback, useContext, useMemo, useRef, useState} from 'react';
|
|
21
21
|
import {useIsSSR} from '@react-aria/ssr';
|
|
@@ -116,6 +116,7 @@ function useCollectionDocument<T extends object, C extends BaseCollection<T>>(cr
|
|
|
116
116
|
let collection = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
117
117
|
useLayoutEffect(() => {
|
|
118
118
|
document.isMounted = true;
|
|
119
|
+
document.isInitialRender = false;
|
|
119
120
|
return () => {
|
|
120
121
|
// Mark unmounted so we can skip all of the collection updates caused by
|
|
121
122
|
// React calling removeChild on every item in the collection.
|
|
@@ -127,22 +128,39 @@ function useCollectionDocument<T extends object, C extends BaseCollection<T>>(cr
|
|
|
127
128
|
|
|
128
129
|
const SSRContext = createContext<BaseNode<any> | null>(null);
|
|
129
130
|
|
|
130
|
-
|
|
131
|
+
export type CollectionNodeClass<T> = {
|
|
132
|
+
new (key: Key): CollectionNode<T>,
|
|
133
|
+
readonly type: string
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
function createCollectionNodeClass(type: string): CollectionNodeClass<any> {
|
|
137
|
+
let NodeClass = class extends CollectionNode<any> {
|
|
138
|
+
static readonly type = type;
|
|
139
|
+
};
|
|
140
|
+
return NodeClass;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function useSSRCollectionNode<T extends Element>(CollectionNodeClass: CollectionNodeClass<T> | string, props: object, ref: ForwardedRef<T>, rendered?: any, children?: ReactNode, render?: (node: Node<any>) => ReactElement) {
|
|
144
|
+
// To prevent breaking change, if CollectionNodeClass is a string, create a CollectionNodeClass using the string as the type
|
|
145
|
+
if (typeof CollectionNodeClass === 'string') {
|
|
146
|
+
CollectionNodeClass = createCollectionNodeClass(CollectionNodeClass);
|
|
147
|
+
}
|
|
148
|
+
|
|
131
149
|
// During SSR, portals are not supported, so the collection children will be wrapped in an SSRContext.
|
|
132
150
|
// Since SSR occurs only once, we assume that the elements are rendered in order and never re-render.
|
|
133
151
|
// Therefore we can create elements in our collection document during render so that they are in the
|
|
134
152
|
// collection by the time we need to use the collection to render to the real DOM.
|
|
135
153
|
// After hydration, we switch to client rendering using the portal.
|
|
136
154
|
let itemRef = useCallback((element: ElementNode<any> | null) => {
|
|
137
|
-
element?.setProps(props, ref, rendered, render);
|
|
138
|
-
}, [props, ref, rendered, render]);
|
|
155
|
+
element?.setProps(props, ref, CollectionNodeClass, rendered, render);
|
|
156
|
+
}, [props, ref, rendered, render, CollectionNodeClass]);
|
|
139
157
|
let parentNode = useContext(SSRContext);
|
|
140
158
|
if (parentNode) {
|
|
141
159
|
// Guard against double rendering in strict mode.
|
|
142
160
|
let element = parentNode.ownerDocument.nodesByProps.get(props);
|
|
143
161
|
if (!element) {
|
|
144
|
-
element = parentNode.ownerDocument.createElement(
|
|
145
|
-
element.setProps(props, ref, rendered, render);
|
|
162
|
+
element = parentNode.ownerDocument.createElement(CollectionNodeClass.type);
|
|
163
|
+
element.setProps(props, ref, CollectionNodeClass, rendered, render);
|
|
146
164
|
parentNode.appendChild(element);
|
|
147
165
|
parentNode.ownerDocument.updateCollection();
|
|
148
166
|
parentNode.ownerDocument.nodesByProps.set(props, element);
|
|
@@ -154,12 +172,12 @@ function useSSRCollectionNode<T extends Element>(Type: string, props: object, re
|
|
|
154
172
|
}
|
|
155
173
|
|
|
156
174
|
// @ts-ignore
|
|
157
|
-
return <
|
|
175
|
+
return <CollectionNodeClass.type ref={itemRef}>{children}</CollectionNodeClass.type>;
|
|
158
176
|
}
|
|
159
177
|
|
|
160
|
-
export function createLeafComponent<T extends object, P extends object, E extends Element>(
|
|
161
|
-
export function createLeafComponent<T extends object, P extends object, E extends Element>(
|
|
162
|
-
export function createLeafComponent<P extends object, E extends Element>(
|
|
178
|
+
export function createLeafComponent<T extends object, P extends object, E extends Element>(CollectionNodeClass: CollectionNodeClass<any> | string, render: (props: P, ref: ForwardedRef<E>) => ReactElement | null): (props: P & React.RefAttributes<T>) => ReactElement | null;
|
|
179
|
+
export function createLeafComponent<T extends object, P extends object, E extends Element>(CollectionNodeClass: CollectionNodeClass<any> | string, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactElement | null): (props: P & React.RefAttributes<T>) => ReactElement | null;
|
|
180
|
+
export function createLeafComponent<P extends object, E extends Element>(CollectionNodeClass: CollectionNodeClass<any> | string, render: (props: P, ref: ForwardedRef<E>, node?: any) => ReactElement | null): (props: P & React.RefAttributes<any>) => ReactElement | null {
|
|
163
181
|
let Component = ({node}) => render(node.props, node.props.ref, node);
|
|
164
182
|
let Result = (forwardRef as forwardRefType)((props: P, ref: ForwardedRef<E>) => {
|
|
165
183
|
let focusableProps = useContext(FocusableContext);
|
|
@@ -172,7 +190,7 @@ export function createLeafComponent<P extends object, E extends Element>(type: s
|
|
|
172
190
|
}
|
|
173
191
|
|
|
174
192
|
return useSSRCollectionNode(
|
|
175
|
-
|
|
193
|
+
CollectionNodeClass,
|
|
176
194
|
props,
|
|
177
195
|
ref,
|
|
178
196
|
'children' in props ? props.children : null,
|
|
@@ -190,11 +208,11 @@ export function createLeafComponent<P extends object, E extends Element>(type: s
|
|
|
190
208
|
return Result;
|
|
191
209
|
}
|
|
192
210
|
|
|
193
|
-
export function createBranchComponent<T extends object, P extends {children?: any}, E extends Element>(
|
|
211
|
+
export function createBranchComponent<T extends object, P extends {children?: any}, E extends Element>(CollectionNodeClass: CollectionNodeClass<any> | string, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactElement | null, useChildren: (props: P) => ReactNode = useCollectionChildren): (props: P & React.RefAttributes<E>) => ReactElement | null {
|
|
194
212
|
let Component = ({node}) => render(node.props, node.props.ref, node);
|
|
195
213
|
let Result = (forwardRef as forwardRefType)((props: P, ref: ForwardedRef<E>) => {
|
|
196
214
|
let children = useChildren(props);
|
|
197
|
-
return useSSRCollectionNode(
|
|
215
|
+
return useSSRCollectionNode(CollectionNodeClass, props, ref, null, children, node => <Component node={node} />) ?? <></>;
|
|
198
216
|
});
|
|
199
217
|
// @ts-ignore
|
|
200
218
|
Result.displayName = render.name;
|
package/src/Document.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {BaseCollection, CollectionNode, Mutable} from './BaseCollection';
|
|
14
|
+
import {CollectionNodeClass} from './CollectionBuilder';
|
|
14
15
|
import {CSSProperties, ForwardedRef, ReactElement, ReactNode} from 'react';
|
|
15
16
|
import {Node} from '@react-types/shared';
|
|
16
17
|
|
|
@@ -256,15 +257,14 @@ export class BaseNode<T> {
|
|
|
256
257
|
*/
|
|
257
258
|
export class ElementNode<T> extends BaseNode<T> {
|
|
258
259
|
nodeType = 8; // COMMENT_NODE (we'd use ELEMENT_NODE but React DevTools will fail to get its dimensions)
|
|
259
|
-
|
|
260
|
+
private _node: CollectionNode<T> | null;
|
|
260
261
|
isMutated = true;
|
|
261
262
|
private _index: number = 0;
|
|
262
|
-
hasSetProps = false;
|
|
263
263
|
isHidden = false;
|
|
264
264
|
|
|
265
265
|
constructor(type: string, ownerDocument: Document<T, any>) {
|
|
266
266
|
super(ownerDocument);
|
|
267
|
-
this.
|
|
267
|
+
this._node = null;
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
get index(): number {
|
|
@@ -278,12 +278,24 @@ export class ElementNode<T> extends BaseNode<T> {
|
|
|
278
278
|
|
|
279
279
|
get level(): number {
|
|
280
280
|
if (this.parentNode instanceof ElementNode) {
|
|
281
|
-
return this.parentNode.level + (this.node
|
|
281
|
+
return this.parentNode.level + (this.node?.type === 'item' ? 1 : 0);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
return 0;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
get node(): CollectionNode<T> {
|
|
288
|
+
if (this._node == null) {
|
|
289
|
+
throw Error('Attempted to access node before it was defined. Check if setProps wasn\'t called before attempting to access the node.');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return this._node;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
set node(node: CollectionNode<T>) {
|
|
296
|
+
this._node = node;
|
|
297
|
+
}
|
|
298
|
+
|
|
287
299
|
/**
|
|
288
300
|
* Lazily gets a mutable instance of a Node. If the node has already
|
|
289
301
|
* been cloned during this update cycle, it just returns the existing one.
|
|
@@ -321,9 +333,16 @@ export class ElementNode<T> extends BaseNode<T> {
|
|
|
321
333
|
}
|
|
322
334
|
}
|
|
323
335
|
|
|
324
|
-
setProps<E extends Element>(obj: {[key: string]: any}, ref: ForwardedRef<E>, rendered?: ReactNode, render?: (node: Node<T>) => ReactElement): void {
|
|
325
|
-
let node
|
|
336
|
+
setProps<E extends Element>(obj: {[key: string]: any}, ref: ForwardedRef<E>, CollectionNodeClass: CollectionNodeClass<any>, rendered?: ReactNode, render?: (node: Node<T>) => ReactElement): void {
|
|
337
|
+
let node;
|
|
326
338
|
let {value, textValue, id, ...props} = obj;
|
|
339
|
+
if (this._node == null) {
|
|
340
|
+
node = new CollectionNodeClass(id ?? `react-aria-${++this.ownerDocument.nodeId}`);
|
|
341
|
+
this.node = node;
|
|
342
|
+
} else {
|
|
343
|
+
node = this.getMutableNode();
|
|
344
|
+
}
|
|
345
|
+
|
|
327
346
|
props.ref = ref;
|
|
328
347
|
node.props = props;
|
|
329
348
|
node.rendered = rendered;
|
|
@@ -331,17 +350,13 @@ export class ElementNode<T> extends BaseNode<T> {
|
|
|
331
350
|
node.value = value;
|
|
332
351
|
node.textValue = textValue || (typeof props.children === 'string' ? props.children : '') || obj['aria-label'] || '';
|
|
333
352
|
if (id != null && id !== node.key) {
|
|
334
|
-
|
|
335
|
-
throw new Error('Cannot change the id of an item');
|
|
336
|
-
}
|
|
337
|
-
node.key = id;
|
|
353
|
+
throw new Error('Cannot change the id of an item');
|
|
338
354
|
}
|
|
339
355
|
|
|
340
356
|
if (props.colSpan != null) {
|
|
341
357
|
node.colSpan = props.colSpan;
|
|
342
358
|
}
|
|
343
359
|
|
|
344
|
-
this.hasSetProps = true;
|
|
345
360
|
if (this.isConnected) {
|
|
346
361
|
this.ownerDocument.queueUpdate();
|
|
347
362
|
}
|
|
@@ -395,12 +410,13 @@ export class ElementNode<T> extends BaseNode<T> {
|
|
|
395
410
|
*/
|
|
396
411
|
export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extends BaseNode<T> {
|
|
397
412
|
nodeType = 11; // DOCUMENT_FRAGMENT_NODE
|
|
398
|
-
ownerDocument = this;
|
|
413
|
+
ownerDocument: Document<T, C> = this;
|
|
399
414
|
dirtyNodes: Set<BaseNode<T>> = new Set();
|
|
400
415
|
isSSR = false;
|
|
401
416
|
nodeId = 0;
|
|
402
|
-
nodesByProps = new WeakMap<object, ElementNode<T>>();
|
|
417
|
+
nodesByProps: WeakMap<object, ElementNode<T>> = new WeakMap<object, ElementNode<T>>();
|
|
403
418
|
isMounted = true;
|
|
419
|
+
isInitialRender = true;
|
|
404
420
|
private collection: C;
|
|
405
421
|
private nextCollection: C | null = null;
|
|
406
422
|
private subscriptions: Set<() => void> = new Set();
|
|
@@ -446,7 +462,7 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
|
|
|
446
462
|
}
|
|
447
463
|
}
|
|
448
464
|
|
|
449
|
-
collection.addNode(element.node);
|
|
465
|
+
collection.addNode(element.node!);
|
|
450
466
|
}
|
|
451
467
|
|
|
452
468
|
private removeNode(node: ElementNode<T>): void {
|
|
@@ -507,6 +523,10 @@ export class Document<T, C extends BaseCollection<T> = BaseCollection<T>> extend
|
|
|
507
523
|
this.nextCollection = null;
|
|
508
524
|
}
|
|
509
525
|
}
|
|
526
|
+
|
|
527
|
+
if (this.isInitialRender) {
|
|
528
|
+
this.collection.isComplete = false;
|
|
529
|
+
}
|
|
510
530
|
}
|
|
511
531
|
|
|
512
532
|
queueUpdate(): void {
|
package/src/Hidden.tsx
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {forwardRefType} from '@react-types/shared';
|
|
14
|
-
import React, {createContext, forwardRef, JSX, ReactElement, ReactNode, useContext} from 'react';
|
|
14
|
+
import React, {Context, createContext, forwardRef, JSX, ReactElement, ReactNode, useContext} from 'react';
|
|
15
15
|
|
|
16
16
|
// React doesn't understand the <template> element, which doesn't have children like a normal element.
|
|
17
17
|
// It will throw an error during hydration when it expects the firstChild to contain content rendered
|
|
@@ -33,7 +33,7 @@ if (typeof HTMLTemplateElement !== 'undefined') {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export const HiddenContext = createContext<boolean>(false);
|
|
36
|
+
export const HiddenContext: Context<boolean> = createContext<boolean>(false);
|
|
37
37
|
|
|
38
38
|
export function Hidden(props: {children: ReactNode}): JSX.Element {
|
|
39
39
|
let isHidden = useContext(HiddenContext);
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
export {CollectionBuilder, Collection, createLeafComponent, createBranchComponent} from './CollectionBuilder';
|
|
14
14
|
export {createHideableComponent, useIsHidden} from './Hidden';
|
|
15
15
|
export {useCachedChildren} from './useCachedChildren';
|
|
16
|
-
export {BaseCollection, CollectionNode} from './BaseCollection';
|
|
16
|
+
export {BaseCollection, CollectionNode, ItemNode, SectionNode, FilterableNode, LoaderNode, HeaderNode} from './BaseCollection';
|
|
17
17
|
|
|
18
18
|
export type {CollectionBuilderProps, CollectionProps} from './CollectionBuilder';
|
|
19
19
|
export type {CachedChildrenOptions} from './useCachedChildren';
|