Skip to content

Commit 6eb1753

Browse files
committed
update types and class node structure
1 parent 639acd0 commit 6eb1753

File tree

16 files changed

+136
-89
lines changed

16 files changed

+136
-89
lines changed

packages/@react-aria/collections/src/BaseCollection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ export class BaseCollection<T> implements ICollection<Node<T>> {
233233
this.frozen = !isSSR;
234234
}
235235

236-
UNSTABLE_filter(filterFn: (textValue: string) => boolean): BaseCollection<T> {
236+
filter(filterFn: (textValue: string) => boolean): BaseCollection<T> {
237237
let newCollection = new BaseCollection<T>();
238238
let [firstKey, lastKey] = filterChildren(this, newCollection, this.firstKey, filterFn);
239239
newCollection.firstKey = firstKey;

packages/@react-aria/collections/src/CollectionBuilder.tsx

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {BaseCollection} from './BaseCollection';
13+
import {BaseCollection, CollectionNode} from './BaseCollection';
1414
import {BaseNode, Document, ElementNode} from './Document';
1515
import {CachedChildrenOptions, useCachedChildren} from './useCachedChildren';
1616
import {createPortal} from 'react-dom';
1717
import {FocusableContext} from '@react-aria/interactions';
18-
import {forwardRefType, Node} from '@react-types/shared';
18+
import {forwardRefType, Key, Node} from '@react-types/shared';
1919
import {Hidden} from './Hidden';
2020
import React, {createContext, ForwardedRef, forwardRef, JSX, ReactElement, ReactNode, useCallback, useContext, useMemo, useRef, useState} from 'react';
2121
import {useIsSSR} from '@react-aria/ssr';
@@ -127,24 +127,29 @@ function useCollectionDocument<T extends object, C extends BaseCollection<T>>(cr
127127

128128
const SSRContext = createContext<BaseNode<any> | null>(null);
129129

130-
// TODO: make this any for now, but should be a node class
131-
function useSSRCollectionNode<T extends Element>(Type: any, props: object, ref: ForwardedRef<T>, rendered?: any, children?: ReactNode, render?: (node: Node<T>) => ReactElement) {
132-
// function useSSRCollectionNode<T extends Element>(Type: string, props: object, ref: ForwardedRef<T>, rendered?: any, children?: ReactNode, render?: (node: Node<T>) => ReactElement) {
130+
export type CollectionNodeClass<T> = {
131+
new (key: Key): CollectionNode<T>,
132+
readonly type: string
133+
};
134+
135+
// TODO: discuss the former Type arg, renamed to CollectionNodeClass
136+
function useSSRCollectionNode<T extends Element>(CollectionNodeClass: CollectionNodeClass<T>, props: object, ref: ForwardedRef<T>, rendered?: any, children?: ReactNode, render?: (node: Node<any>) => ReactElement) {
133137
// During SSR, portals are not supported, so the collection children will be wrapped in an SSRContext.
134138
// Since SSR occurs only once, we assume that the elements are rendered in order and never re-render.
135139
// Therefore we can create elements in our collection document during render so that they are in the
136140
// collection by the time we need to use the collection to render to the real DOM.
137141
// After hydration, we switch to client rendering using the portal.
138142
let itemRef = useCallback((element: ElementNode<any> | null) => {
139-
// TODO: now we get the proper node class aka TreeNode
140-
element?.setProps(props, ref, rendered, render, Type);
141-
}, [props, ref, rendered, render, Type]);
143+
// TODO: check setProps api
144+
element?.setProps(props, ref, rendered, render, CollectionNodeClass);
145+
}, [props, ref, rendered, render, CollectionNodeClass]);
142146
let parentNode = useContext(SSRContext);
143147
if (parentNode) {
144148
// Guard against double rendering in strict mode.
145149
let element = parentNode.ownerDocument.nodesByProps.get(props);
146150
if (!element) {
147-
element = parentNode.ownerDocument.createElement(Type);
151+
// TODO: check this, maybe should just pass the CollectionNodeClass as a whole?
152+
element = parentNode.ownerDocument.createElement(CollectionNodeClass.type);
148153
element.setProps(props, ref, rendered, render);
149154
parentNode.appendChild(element);
150155
parentNode.ownerDocument.updateCollection();
@@ -156,15 +161,16 @@ function useSSRCollectionNode<T extends Element>(Type: any, props: object, ref:
156161
: null;
157162
}
158163

164+
// console.log('type', CollectionNodeClass, CollectionNodeClass.type)
159165
// @ts-ignore
160-
// TODO: make div for now, may not actually matter
161-
return <div ref={itemRef}>{children}</div>;
166+
// TODO: could just make this a div perhaps, but keep it in line with how it used to work
167+
return <CollectionNodeClass.type ref={itemRef}>{children}</CollectionNodeClass.type>;
162168
}
163169

164-
// TODO: changed all of these to be any, but should be node class type
165-
export function createLeafComponent<T extends object, P extends object, E extends Element>(type: any, render: (props: P, ref: ForwardedRef<E>) => ReactElement | null): (props: P & React.RefAttributes<T>) => ReactElement | null;
166-
export function createLeafComponent<T extends object, P extends object, E extends Element>(type: any, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactElement | null): (props: P & React.RefAttributes<T>) => ReactElement | null;
167-
export function createLeafComponent<P extends object, E extends Element>(type: any, render: (props: P, ref: ForwardedRef<E>, node?: any) => ReactElement | null): (props: P & React.RefAttributes<any>) => ReactElement | null {
170+
// TODO: check the signature of the CollectionNodeClass here and other places (aka useSSRCollectionNode and branchCompoennt). If I use the generic it complains. Perhaps it should be unknown? Or maybe the definitions in Listbox and stuff shouldn't use a generic?
171+
export function createLeafComponent<T extends object, P extends object, E extends Element>(CollectionNodeClass: CollectionNodeClass<any>, render: (props: P, ref: ForwardedRef<E>) => ReactElement | null): (props: P & React.RefAttributes<T>) => ReactElement | null;
172+
export function createLeafComponent<T extends object, P extends object, E extends Element>(CollectionNodeClass: CollectionNodeClass<any>, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactElement | null): (props: P & React.RefAttributes<T>) => ReactElement | null;
173+
export function createLeafComponent<P extends object, E extends Element>(CollectionNodeClass: CollectionNodeClass<any>, render: (props: P, ref: ForwardedRef<E>, node?: any) => ReactElement | null): (props: P & React.RefAttributes<any>) => ReactElement | null {
168174
let Component = ({node}) => render(node.props, node.props.ref, node);
169175
let Result = (forwardRef as forwardRefType)((props: P, ref: ForwardedRef<E>) => {
170176
let focusableProps = useContext(FocusableContext);
@@ -177,7 +183,7 @@ export function createLeafComponent<P extends object, E extends Element>(type: a
177183
}
178184

179185
return useSSRCollectionNode(
180-
type,
186+
CollectionNodeClass,
181187
props,
182188
ref,
183189
'children' in props ? props.children : null,
@@ -195,12 +201,12 @@ export function createLeafComponent<P extends object, E extends Element>(type: a
195201
return Result;
196202
}
197203

198-
// TODO: changed all of these to be any, but should be node class type
199-
export function createBranchComponent<T extends object, P extends {children?: any}, E extends Element>(type: any, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactElement | null, useChildren: (props: P) => ReactNode = useCollectionChildren): (props: P & React.RefAttributes<E>) => ReactElement | null {
204+
// TODO: check the signature of this too
205+
export function createBranchComponent<T extends object, P extends {children?: any}, E extends Element>(CollectionNodeClass: CollectionNodeClass<any>, render: (props: P, ref: ForwardedRef<E>, node: Node<T>) => ReactElement | null, useChildren: (props: P) => ReactNode = useCollectionChildren): (props: P & React.RefAttributes<E>) => ReactElement | null {
200206
let Component = ({node}) => render(node.props, node.props.ref, node);
201207
let Result = (forwardRef as forwardRefType)((props: P, ref: ForwardedRef<E>) => {
202208
let children = useChildren(props);
203-
return useSSRCollectionNode(type, props, ref, null, children, node => <Component node={node} />) ?? <></>;
209+
return useSSRCollectionNode(CollectionNodeClass, props, ref, null, children, node => <Component node={node} />) ?? <></>;
204210
});
205211
// @ts-ignore
206212
Result.displayName = render.name;

packages/@react-aria/collections/src/Document.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212

1313
import {BaseCollection, CollectionNode, Mutable} from './BaseCollection';
14+
import {CollectionNodeClass} from './CollectionBuilder';
1415
import {CSSProperties, ForwardedRef, ReactElement, ReactNode} from 'react';
1516
import {Node} from '@react-types/shared';
1617

@@ -267,10 +268,6 @@ export class ElementNode<T> extends BaseNode<T> {
267268
constructor(type: string, ownerDocument: Document<T, any>) {
268269
super(ownerDocument);
269270
this.node = null;
270-
// TODO: move this line to setProps
271-
// if ()
272-
// TODO: this is called by Document, seems like we need it?
273-
// this.node = new CollectionNode(type, `react-aria-${++ownerDocument.nodeId}`);
274271
}
275272

276273
get index(): number {
@@ -327,22 +324,20 @@ export class ElementNode<T> extends BaseNode<T> {
327324
}
328325
}
329326

330-
setProps<E extends Element>(obj: {[key: string]: any}, ref: ForwardedRef<E>, rendered?: ReactNode, render?: (node: Node<T>) => ReactElement, type?: any): void {
327+
// TODO
328+
setProps<E extends Element>(obj: {[key: string]: any}, ref: ForwardedRef<E>, rendered?: ReactNode, render?: (node: Node<T>) => ReactElement, CollectionNodeClass?: CollectionNodeClass<any>): void {
331329
let node = this.getMutableNode();
332330
let {value, textValue, id, ...props} = obj;
333331

334332

335-
// if called for first time, aka this.node is undef, call
336-
// this.node = new CollectionNode(type, `react-aria-${++ownerDocument.nodeId}`); but make new TreeNode instead of COllectionNode
333+
// TODO: Flow here is that if this called for first time, aka this.node is undef, call
334+
// this.node = new CollectionNode(type, `react-aria-${++ownerDocument.nodeId}`); but make new TreeNode/MenuNode/etc instead of CollectionNode
337335
// Caveat is this assumes we don't need a node before setProps is called on it
338-
// TODO: will get rid of type function check here when we migrate everything to use the class
339-
if (node == null && typeof type === 'function') {
340-
node = new type(`react-aria-${++this.ownerDocument.nodeId}`);
336+
if (node == null && CollectionNodeClass) {
337+
node = new CollectionNodeClass(`react-aria-${++this.ownerDocument.nodeId}`);
341338
this.node = node;
342-
// node.key = id;
343-
// console.log('making node', node.type, node)
344339
}
345-
// console.log('setting props', props, node, node.props)
340+
346341
props.ref = ref;
347342
node.props = props;
348343
node.rendered = rendered;
@@ -353,7 +348,6 @@ export class ElementNode<T> extends BaseNode<T> {
353348
// TODO: still need to use this.hasSetProps so this can run twice (?) instead of setting node.key above
354349
// If we set node.key = id and change this to if (this.node), setting refs fails. If we just check (this.node here), it will fail if the user provides an id
355350
if (this.hasSetProps) {
356-
// if (this.node) {
357351
throw new Error('Cannot change the id of an item');
358352
}
359353
node.key = id;

packages/@react-aria/collections/test/CollectionBuilder.test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import React from 'react';
33
import {render} from '@testing-library/react';
44

55
class ItemNode extends CollectionNode {
6+
static type = 'item';
7+
68
constructor(key) {
7-
super('item', key);
9+
super(ItemNode.type, key);
810
}
911
}
1012

packages/@react-stately/list/src/useListState.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ export function useListState<T extends object>(props: ListProps<T>): ListState<T
7373
/**
7474
* Filters a collection using the provided filter function and returns a new ListState.
7575
*/
76-
export function UNSTABLE_useFilteredListState<T extends object>(state: ListState<T>, filter: ((nodeValue: string) => boolean) | null | undefined): ListState<T> {
77-
let collection = useMemo(() => filter ? state.collection.UNSTABLE_filter!(filter) : state.collection, [state.collection, filter]);
76+
export function UNSTABLE_useFilteredListState<T extends object>(state: ListState<T>, filterFn: ((nodeValue: string) => boolean) | null | undefined): ListState<T> {
77+
let collection = useMemo(() => filterFn ? state.collection.filter!(filterFn) : state.collection, [state.collection, filterFn]);
7878
let selectionManager = state.selectionManager.withCollection(collection);
7979
useFocusedKeyReset(collection, selectionManager);
8080
return {

packages/@react-types/shared/src/collections.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export interface Collection<T> extends Iterable<T> {
183183
getTextValue?(key: Key): string,
184184

185185
/** Filters the collection using the given function. */
186-
UNSTABLE_filter?(filterFn: (nodeValue: string) => boolean): Collection<T>
186+
filter?(filterFn: (nodeValue: string) => boolean): Collection<T>
187187
}
188188

189189
export interface Node<T> {

packages/react-aria-components/src/Breadcrumbs.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ export interface BreadcrumbProps extends RenderProps<BreadcrumbRenderProps>, Glo
7575

7676
// TODO: perhaps this should be reuse ItemNode, for now just have it separate
7777
class BreadcrumbNode extends CollectionNode<any> {
78+
static readonly type = 'item';
79+
7880
constructor(key: Key) {
79-
super('item', key);
81+
super(BreadcrumbNode.type, key);
8082
}
8183
}
8284

packages/react-aria-components/src/GridList.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,13 @@ export interface GridListItemProps<T = object> extends RenderProps<GridListItemR
279279
onAction?: () => void
280280
}
281281

282+
// TODO: add filter funct to this
282283
class GridListNode extends CollectionNode<any> {
284+
static readonly type = 'item';
283285
constructor(key: Key) {
284-
super('item', key);
286+
super(GridListNode.type, key);
285287
}
288+
286289
}
287290

288291
/**
@@ -519,14 +522,17 @@ export interface GridListLoadMoreItemProps extends Omit<LoadMoreSentinelProps, '
519522
isLoading?: boolean
520523
}
521524

522-
// TODO: can probably reuse ListBox's loaderNode
523-
class GridLoaderNode extends CollectionNode<any> {
525+
// TODO: can probably reuse ListBox's loaderNode, but might keep separate
526+
// add filter logic
527+
class GridListLoaderNode extends CollectionNode<any> {
528+
static readonly type = 'loader';
529+
524530
constructor(key: Key) {
525-
super('loader', key);
531+
super(GridListLoaderNode.type, key);
526532
}
527533
}
528534

529-
export const GridListLoadMoreItem = createLeafComponent(GridLoaderNode, function GridListLoadingIndicator(props: GridListLoadMoreItemProps, ref: ForwardedRef<HTMLDivElement>, item: Node<object>) {
535+
export const GridListLoadMoreItem = createLeafComponent(GridListLoaderNode, function GridListLoadingIndicator(props: GridListLoadMoreItemProps, ref: ForwardedRef<HTMLDivElement>, item: Node<object>) {
530536
let state = useContext(ListStateContext)!;
531537
let {isVirtualized} = useContext(CollectionRendererContext);
532538
let {isLoading, onLoadMore, scrollOffset, ...otherProps} = props;

packages/react-aria-components/src/Header.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import React, {createContext, ForwardedRef, HTMLAttributes} from 'react';
1818
export const HeaderContext = createContext<ContextValue<HTMLAttributes<HTMLElement>, HTMLElement>>({});
1919

2020
class HeaderNode extends CollectionNode<any> {
21+
static readonly type = 'header';
22+
2123
constructor(key: Key) {
22-
super('header', key);
24+
super(HeaderNode.type, key);
2325
}
2426

2527
filter(): CollectionNode<any> {

packages/react-aria-components/src/ListBox.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -306,11 +306,12 @@ function ListBoxSectionInner<T extends object>(props: ListBoxSectionProps<T>, re
306306
}
307307

308308

309-
// todo make a class here
309+
// TODO: reuse
310+
export class ListBoxSectionNode<T> extends CollectionNode<T> {
311+
static readonly type = 'section';
310312

311-
class SectionNode<T> extends CollectionNode<T> {
312313
constructor(key: Key) {
313-
super('section', key);
314+
super(ListBoxSectionNode.type, key);
314315
}
315316

316317
filter(collection: BaseCollection<T>, newCollection: BaseCollection<T>, filterFn: (textValue: string) => boolean): CollectionNode<T> | null {
@@ -331,7 +332,7 @@ class SectionNode<T> extends CollectionNode<T> {
331332
/**
332333
* A ListBoxSection represents a section within a ListBox.
333334
*/
334-
export const ListBoxSection = /*#__PURE__*/ createBranchComponent(SectionNode, ListBoxSectionInner);
335+
export const ListBoxSection = /*#__PURE__*/ createBranchComponent(ListBoxSectionNode, ListBoxSectionInner);
335336

336337
export interface ListBoxItemRenderProps extends ItemRenderProps {}
337338

@@ -353,12 +354,12 @@ export interface ListBoxItemProps<T = object> extends RenderProps<ListBoxItemRen
353354
onAction?: () => void
354355
}
355356

357+
// TODO: reusue
358+
class ListBoxItemNode<T> extends CollectionNode<T> {
359+
static readonly type = 'item';
356360

357-
// TODO create item type here
358-
359-
class ItemNode<T> extends CollectionNode<T> {
360361
constructor(key: Key) {
361-
super('item', key);
362+
super(ListBoxItemNode.type, key);
362363
}
363364

364365
filter(_, __, filterFn: (textValue: string) => boolean): CollectionNode<T> | null {
@@ -373,7 +374,7 @@ class ItemNode<T> extends CollectionNode<T> {
373374
/**
374375
* A ListBoxItem represents an individual option in a ListBox.
375376
*/
376-
export const ListBoxItem = /*#__PURE__*/ createLeafComponent(ItemNode, function ListBoxItem<T extends object>(props: ListBoxItemProps<T>, forwardedRef: ForwardedRef<HTMLDivElement>, item: Node<T>) {
377+
export const ListBoxItem = /*#__PURE__*/ createLeafComponent(ListBoxItemNode, function ListBoxItem<T extends object>(props: ListBoxItemProps<T>, forwardedRef: ForwardedRef<HTMLDivElement>, item: Node<T>) {
377378
let ref = useObjectRef<any>(forwardedRef);
378379
let state = useContext(ListStateContext)!;
379380
let {dragAndDropHooks, dragState, dropState} = useContext(DragAndDropContext)!;
@@ -511,9 +512,11 @@ function ListBoxDropIndicator(props: ListBoxDropIndicatorProps, ref: ForwardedRe
511512
}
512513

513514
// TODO: can reuse this most likely
514-
class LoaderNode extends CollectionNode<any> {
515+
class ListBoxLoaderNode extends CollectionNode<any> {
516+
static readonly type = 'loader';
517+
515518
constructor(key: Key) {
516-
super('loader', key);
519+
super(ListBoxLoaderNode.type, key);
517520
}
518521

519522
filter(): CollectionNode<any> | null {
@@ -534,7 +537,7 @@ export interface ListBoxLoadMoreItemProps extends Omit<LoadMoreSentinelProps, 'c
534537
isLoading?: boolean
535538
}
536539

537-
export const ListBoxLoadMoreItem = createLeafComponent(LoaderNode, function ListBoxLoadingIndicator(props: ListBoxLoadMoreItemProps, ref: ForwardedRef<HTMLDivElement>, item: Node<object>) {
540+
export const ListBoxLoadMoreItem = createLeafComponent(ListBoxLoaderNode, function ListBoxLoadingIndicator(props: ListBoxLoadMoreItemProps, ref: ForwardedRef<HTMLDivElement>, item: Node<object>) {
538541
let state = useContext(ListStateContext)!;
539542
let {isLoading, onLoadMore, scrollOffset, ...otherProps} = props;
540543

0 commit comments

Comments
 (0)