From 903ba603425f1a2bbc66c4a1b3065a08dce812f2 Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 30 Jan 2025 17:29:24 +0000 Subject: [PATCH 01/16] [5574] - add moveBefore and moveAfter to useTreeData --- .../@react-stately/data/src/useTreeData.ts | 61 ++++++++++++++++ .../data/test/useTreeData.test.js | 70 +++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/packages/@react-stately/data/src/useTreeData.ts b/packages/@react-stately/data/src/useTreeData.ts index 580279b8e4c..170ba439ec2 100644 --- a/packages/@react-stately/data/src/useTreeData.ts +++ b/packages/@react-stately/data/src/useTreeData.ts @@ -107,6 +107,22 @@ export interface TreeData { */ move(key: Key, toParentKey: Key | null, index: number): void, + /** + * Moves an item before a node within the tree. + * @param key - The key of the item to move. + * @param toParentKey - The key of the new parent to insert into. `null` for the root. + * @param index - The index within the new parent to insert before. + */ + moveBefore(key: Key, toParentKey: Key | null, index: number): void, + + /** + * Moves an item aftera node within the tree. + * @param key - The key of the item to move. + * @param toParentKey - The key of the new parent to insert into. `null` for the root. + * @param index - The index within the new parent to insert before. + */ + moveAfter(key: Key, toParentKey: Key | null, index: number): void, + /** * Updates an item in the tree. * @param key - The key of the item to update. @@ -373,6 +389,51 @@ export function useTreeData(options: TreeOptions): TreeData }), newMap); }); }, + moveBefore(key: Key, toParentKey: Key | null, index: number) { + this.move(key, toParentKey, index); + }, + moveAfter(key: Key, toParentKey: Key | null, index: number) { + setItems(({items, nodeMap: originalMap}) => { + let node = originalMap.get(key); + if (!node) { + return {items, nodeMap: originalMap}; + } + + let {items: newItems, nodeMap: newMap} = updateTree(items, key, () => null, originalMap); + + const movedNode = { + ...node, + parentKey: toParentKey + }; + + const afterIndex = items.length === index ? index : index + 1; + // If parentKey is null, insert into the root. + if (toParentKey == null) { + newMap.set(movedNode.key, movedNode); + return {items: [ + ...newItems.slice(0, afterIndex), + movedNode, + ...newItems.slice(afterIndex) + ], nodeMap: newMap}; + } + + // Otherwise, update the parent node and its ancestors. + return updateTree(newItems, toParentKey, parentNode => { + console.log('parent node children ', parentNode.children); + const c = [ + ...parentNode.children!.slice(0, afterIndex), + movedNode, + ...parentNode.children!.slice(afterIndex) + ]; + return { + key: parentNode.key, + parentKey: parentNode.parentKey, + value: parentNode.value, + children: c + }; + }, newMap); + }); + }, update(oldKey: Key, newValue: T) { setItems(({items, nodeMap: originalMap}) => updateTree(items, oldKey, oldNode => { let node: TreeNode = { diff --git a/packages/@react-stately/data/test/useTreeData.test.js b/packages/@react-stately/data/test/useTreeData.test.js index 033303b5077..c47d54916aa 100644 --- a/packages/@react-stately/data/test/useTreeData.test.js +++ b/packages/@react-stately/data/test/useTreeData.test.js @@ -676,4 +676,74 @@ describe('useTreeData', function () { expect(result.current.items[1].value).toEqual(initialResult.items[2].value); expect(result.current.items[2]).toEqual(initialResult.items[1]); }); + + + it('should move an item within its same level before the target', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) + ); + act(() => { + result.current.moveBefore('Eli', null, 0); + }); + expect(result.current.items[0].key).toEqual('Eli'); + expect(result.current.items[1].key).toEqual('David'); + expect(result.current.items[2].key).toEqual('Emily'); + expect(result.current.items.length).toEqual(initialItems.length); + }); + + it('should move an item to a different level before the target', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) + ); + act(() => { + result.current.moveBefore('Eli', 'David', 1); + }); + expect(result.current.items[0].key).toEqual('David'); + expect(result.current.items[0].children[0].key).toEqual('John'); + expect(result.current.items[0].children[1].key).toEqual('Eli'); + expect(result.current.items[1].key).toEqual('Emily'); + expect(result.current.items.length).toEqual(2); + }); + + it.only('should move an item to a different level after the target', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) + ); + + act(() => { + result.current.moveAfter('Eli', 'David', 1); + }); + expect(result.current.items[0].key).toEqual('David'); + + expect(result.current.items[0].children[0].key).toEqual('John'); + expect(result.current.items[0].children[1].key).toEqual('Sam'); + expect(result.current.items[0].children[2].key).toEqual('Eli'); + expect(result.current.items[1].key).toEqual('Emily'); + expect(result.current.items.length).toEqual(2); + }); + + it.only('should move an item to a different level at the end when the index is greater than the node list length', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + console.log('initialItems', initialItems[0]); + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) + ); + + act(() => { + result.current.moveAfter('Eli', 'David', 100); + }); + expect(result.current.items[0].key).toEqual('David'); + + expect(result.current.items[0].children[0].key).toEqual('John'); + expect(result.current.items[0].children[1].key).toEqual('Sam'); + expect(result.current.items[0].children[2].key).toEqual('Jane'); + expect(result.current.items[0].children[3].key).toEqual('Eli'); + expect(result.current.items[1].key).toEqual('Emily'); + expect(result.current.items.length).toEqual(2); + }); }); From 8f14593534cfff36864a3f1cec32465514db6d4f Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 30 Jan 2025 17:52:03 +0000 Subject: [PATCH 02/16] add docs --- .../@react-stately/data/docs/useTreeData.mdx | 27 +++++++++++++++++++ .../@react-stately/data/src/useTreeData.ts | 4 +-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/@react-stately/data/docs/useTreeData.mdx b/packages/@react-stately/data/docs/useTreeData.mdx index 9ec0c4c3ceb..024c9872d7a 100644 --- a/packages/@react-stately/data/docs/useTreeData.mdx +++ b/packages/@react-stately/data/docs/useTreeData.mdx @@ -182,6 +182,33 @@ tree.move('Sam', 'Animals', 1); tree.move('Sam', null, 1); ``` +### Move before +An alias to move + +```tsx +// Move an item within the same parent +tree.moveBefore('Sam', 'People', 0); + +// Move an item to a different parent +tree.moveBefore('Sam', 'Animals', 1); + +// Move an item to the root +tree.moveBefore('Sam', null, 1); +``` + +### Move after + +```tsx +// Move an item within the same parent +tree.moveAfter('Sam', 'People', 0); + +// Move an item to a different parent +tree.moveAfter('Sam', 'Animals', 1); + +// Move an item to the root +tree.moveAfter('Sam', null, 1); +``` + ### Updating items ```tsx diff --git a/packages/@react-stately/data/src/useTreeData.ts b/packages/@react-stately/data/src/useTreeData.ts index 170ba439ec2..4fe47dd59eb 100644 --- a/packages/@react-stately/data/src/useTreeData.ts +++ b/packages/@react-stately/data/src/useTreeData.ts @@ -116,10 +116,10 @@ export interface TreeData { moveBefore(key: Key, toParentKey: Key | null, index: number): void, /** - * Moves an item aftera node within the tree. + * Moves an item after ia node within the tree. * @param key - The key of the item to move. * @param toParentKey - The key of the new parent to insert into. `null` for the root. - * @param index - The index within the new parent to insert before. + * @param index - The index within the new parent to insert after. */ moveAfter(key: Key, toParentKey: Key | null, index: number): void, From 768e2530fff8677e451c6d59df3cd8bb607040cc Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 30 Jan 2025 17:58:58 +0000 Subject: [PATCH 03/16] remove onlys --- packages/@react-stately/data/test/useTreeData.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@react-stately/data/test/useTreeData.test.js b/packages/@react-stately/data/test/useTreeData.test.js index c47d54916aa..ae685f66c98 100644 --- a/packages/@react-stately/data/test/useTreeData.test.js +++ b/packages/@react-stately/data/test/useTreeData.test.js @@ -709,7 +709,7 @@ describe('useTreeData', function () { expect(result.current.items.length).toEqual(2); }); - it.only('should move an item to a different level after the target', function () { + it('should move an item to a different level after the target', function () { const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; let {result} = renderHook(() => useTreeData({initialItems, getChildren, getKey}) @@ -727,7 +727,7 @@ describe('useTreeData', function () { expect(result.current.items.length).toEqual(2); }); - it.only('should move an item to a different level at the end when the index is greater than the node list length', function () { + it('should move an item to a different level at the end when the index is greater than the node list length', function () { const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; console.log('initialItems', initialItems[0]); let {result} = renderHook(() => From aa2d3622753a8116eb23a8040bac49603f4640bb Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 30 Jan 2025 18:00:19 +0000 Subject: [PATCH 04/16] remove console logs --- packages/@react-stately/data/src/useTreeData.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/@react-stately/data/src/useTreeData.ts b/packages/@react-stately/data/src/useTreeData.ts index 4fe47dd59eb..5bcaa98b9f3 100644 --- a/packages/@react-stately/data/src/useTreeData.ts +++ b/packages/@react-stately/data/src/useTreeData.ts @@ -116,7 +116,7 @@ export interface TreeData { moveBefore(key: Key, toParentKey: Key | null, index: number): void, /** - * Moves an item after ia node within the tree. + * Moves an item after a node within the tree. * @param key - The key of the item to move. * @param toParentKey - The key of the new parent to insert into. `null` for the root. * @param index - The index within the new parent to insert after. @@ -419,7 +419,6 @@ export function useTreeData(options: TreeOptions): TreeData // Otherwise, update the parent node and its ancestors. return updateTree(newItems, toParentKey, parentNode => { - console.log('parent node children ', parentNode.children); const c = [ ...parentNode.children!.slice(0, afterIndex), movedNode, From d9b4d571a042542702489816bdc1682af172959d Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 30 Jan 2025 22:44:32 +0000 Subject: [PATCH 05/16] [wip] add intial tree drag and drop --- .../@react-spectrum/tree/src/TreeView.tsx | 9 +- .../tree/stories/TreeView.stories.tsx | 125 +++++++++++++- .../data/test/useTreeData.test.js | 1 - packages/react-aria-components/package.json | 3 + packages/react-aria-components/src/Tree.tsx | 153 +++++++++++++++++- 5 files changed, 277 insertions(+), 14 deletions(-) diff --git a/packages/@react-spectrum/tree/src/TreeView.tsx b/packages/@react-spectrum/tree/src/TreeView.tsx index ccf90601ec0..3dc58c29e08 100644 --- a/packages/@react-spectrum/tree/src/TreeView.tsx +++ b/packages/@react-spectrum/tree/src/TreeView.tsx @@ -11,7 +11,7 @@ */ import {AriaTreeGridListProps} from '@react-aria/tree'; -import {ButtonContext, Collection, TreeItemContentRenderProps, TreeItemProps, TreeItemRenderProps, TreeRenderProps, UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeItemContent, useContextProps} from 'react-aria-components'; +import {ButtonContext, Collection, DragAndDropHooks, TreeItemContentRenderProps, TreeItemProps, TreeItemRenderProps, TreeRenderProps, UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeItemContent, useContextProps} from 'react-aria-components'; import {Checkbox} from '@react-spectrum/checkbox'; import ChevronLeftMedium from '@spectrum-icons/ui/ChevronLeftMedium'; import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium'; @@ -24,7 +24,7 @@ import {Text} from '@react-spectrum/text'; import {useButton} from '@react-aria/button'; import {useLocale} from '@react-aria/i18n'; -export interface SpectrumTreeViewProps extends Omit, 'children'>, StyleProps, SpectrumSelectionProps, Expandable { +export interface SpectrumTreeViewProps extends Omit, 'children'>, StyleProps, SpectrumSelectionProps, Expandable { /** Provides content to display when there are no items in the tree. */ renderEmptyState?: () => JSX.Element, /** @@ -35,7 +35,10 @@ export interface SpectrumTreeViewProps extends Omit, /** * The contents of the tree. */ - children?: ReactNode | ((item: T) => ReactNode) + children?: ReactNode | ((item: T) => ReactNode), + + /** The drag and drop hooks returned by `useDragAndDrop` used to enable drag and drop behavior for the ListBox. */ + dragAndDropHooks?: DragAndDropHooks } export interface SpectrumTreeViewItemProps extends Omit { diff --git a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx index 9f1b3b10473..5eef11aef3f 100644 --- a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx +++ b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx @@ -14,16 +14,22 @@ import {action} from '@storybook/addon-actions'; import {ActionGroup, Item} from '@react-spectrum/actiongroup'; import {ActionMenu} from '@react-spectrum/menu'; import Add from '@spectrum-icons/workflow/Add'; +import {classNames} from '@react-spectrum/utils'; import {Content} from '@react-spectrum/view'; import Delete from '@spectrum-icons/workflow/Delete'; +import {DragItem} from '@react-types/shared'; +import dropIndicatorStyles from '@adobe/spectrum-css-temp/components/dropindicator/vars.css'; import Edit from '@spectrum-icons/workflow/Edit'; import FileTxt from '@spectrum-icons/workflow/FileTxt'; import Folder from '@spectrum-icons/workflow/Folder'; import {Heading, Text} from '@react-spectrum/text'; import {IllustratedMessage} from '@react-spectrum/illustratedmessage'; import {Link} from '@react-spectrum/link'; -import React from 'react'; +import React, {JSX} from 'react'; import {SpectrumTreeViewProps, TreeView, TreeViewItem} from '../src'; +import {useDragAndDrop} from 'react-aria-components'; +import {TreeData, useTreeData} from 'react-stately'; + export default { title: 'TreeView', @@ -163,7 +169,13 @@ TreeExampleStatic.story = { } }; -let rows = [ +type Node = { + id: string, + name: string, + icon: JSX.Element, + childItems?: Node[] +}; +let rows: Node[] = [ {id: 'projects', name: 'Projects', icon: , childItems: [ {id: 'project-1', name: 'Project 1', icon: }, {id: 'project-2', name: 'Project 2', icon: , childItems: [ @@ -221,6 +233,99 @@ TreeExampleDynamic.story = { parameters: null }; +export const TreeExampleDynamicDragNDrop = ( + args: SpectrumTreeViewProps +) => { + const list = useTreeData({ + initialItems: rows, + getChildren: (item) => { + return item.childItems ?? []; + } + }); + // @TODO internalise inside Tree ? + let {dragAndDropHooks} = useDragAndDrop({ + getItems: (keys) => { + return [...keys].map((key) => { + return { + 'text/plain': list.getItem(key)?.key ?? '' + } as DragItem; + }); + + }, + renderDropIndicator() { + return ; + }, + onReorder(e) { + const k = e.keys.values().next().value; + const parent = list.getItem(e.target.key)?.parentKey ?? null; + if (!k) { + return; + } + + // node list index... + let i = 0; + if (parent) { + const parentNode = list.getItem(parent); + i = (parentNode?.children ?? []).findIndex( + (c) => c.key === e.target.key + ); + } + if (e.target.dropPosition === 'before') { + list.moveBefore(k, parent, i); + } else if (e.target.dropPosition === 'after') { + list.moveAfter(k, parent, i); + } + } + }); + return ( +
+ + {(item: any) => { + if (!item.value) { + return; + } + return ( + + {item.value.name} + {item.value.icon} + + + + Edit + + + + Delete + + + + ); + }} + +
+ ); +}; + +TreeExampleDynamic.story = { + ...TreeExampleStatic.story, + parameters: null +}; export const WithActions = { render: TreeExampleDynamic, @@ -310,3 +415,19 @@ export const WithActionMenu = (args: SpectrumTreeViewProps) => ( ); + + +function InsertionIndicator() { + return ( +
+ ); +} diff --git a/packages/@react-stately/data/test/useTreeData.test.js b/packages/@react-stately/data/test/useTreeData.test.js index ae685f66c98..eaffc8ad200 100644 --- a/packages/@react-stately/data/test/useTreeData.test.js +++ b/packages/@react-stately/data/test/useTreeData.test.js @@ -729,7 +729,6 @@ describe('useTreeData', function () { it('should move an item to a different level at the end when the index is greater than the node list length', function () { const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; - console.log('initialItems', initialItems[0]); let {result} = renderHook(() => useTreeData({initialItems, getChildren, getKey}) ); diff --git a/packages/react-aria-components/package.json b/packages/react-aria-components/package.json index f29b0767168..6481c6bbdd8 100644 --- a/packages/react-aria-components/package.json +++ b/packages/react-aria-components/package.json @@ -37,6 +37,7 @@ "url": "https://github.com/adobe/react-spectrum" }, "dependencies": { + "@adobe/spectrum-css-temp": "3.0.0-alpha.1", "@internationalized/date": "^3.7.0", "@internationalized/string": "^3.2.5", "@react-aria/autocomplete": "3.0.0-alpha.37", @@ -52,6 +53,8 @@ "@react-aria/tree": "3.0.0-beta.3", "@react-aria/utils": "^3.27.0", "@react-aria/virtualizer": "^4.1.1", + "@react-spectrum/dnd": "^3.5.1", + "@react-spectrum/utils": "^3.12.1", "@react-stately/autocomplete": "3.0.0-alpha.0", "@react-stately/color": "^3.8.2", "@react-stately/disclosure": "^3.0.1", diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index 053dfaa0b95..4da990a6b71 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -16,10 +16,12 @@ import {CheckboxContext} from './RSPContexts'; import {Collection, CollectionBuilder, CollectionNode, createBranchComponent, createLeafComponent, useCachedChildren} from '@react-aria/collections'; import {CollectionProps, CollectionRendererContext, DefaultCollectionRenderer, ItemRenderProps, usePersistedKeys} from './Collection'; import {ContextValue, DEFAULT_SLOT, Provider, RenderProps, ScrollableProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps} from './utils'; -import {DisabledBehavior, Expandable, forwardRefType, HoverEvents, Key, LinkDOMProps, RefObject} from '@react-types/shared'; +import {DisabledBehavior, DragPreviewRenderer, Expandable, forwardRefType, HoverEvents, Key, KeyboardDelegate, LinkDOMProps, RefObject} from '@react-types/shared'; +import {DragAndDropContext, DropIndicatorContext, DropIndicatorProps, useRenderDropIndicator} from './DragAndDrop'; +import {DragAndDropHooks} from './useDragAndDrop'; +import {DraggableCollectionState, DroppableCollectionState, Collection as ICollection, Node, SelectionBehavior, TreeState, useTreeState} from 'react-stately'; +import {DraggableItemResult, DroppableCollectionResult, DroppableItemResult, FocusScope, ListKeyboardDelegate, mergeProps, useCollator, useFocusRing, useGridListSelectionCheckbox, useHover, useLocale} from 'react-aria'; import {filterDOMProps, useObjectRef} from '@react-aria/utils'; -import {FocusScope, mergeProps, useFocusRing, useGridListSelectionCheckbox, useHover} from 'react-aria'; -import {Collection as ICollection, Node, SelectionBehavior, TreeState, useTreeState} from 'react-stately'; import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes, ReactNode, useContext, useEffect, useMemo, useRef} from 'react'; import {useControlledState} from '@react-stately/utils'; @@ -129,7 +131,14 @@ export interface TreeProps extends Omit, 'children'> * Whether `disabledKeys` applies to all interactions, or only selection. * @default 'selection' */ - disabledBehavior?: DisabledBehavior + disabledBehavior?: DisabledBehavior, + dragAndDropHooks?: DragAndDropHooks, + + /** + * An optional keyboard delegate implementation for type to select, + * to override the default. + */ + keyboardDelegate?: KeyboardDelegate } @@ -158,6 +167,11 @@ interface TreeInnerProps { } function TreeInner({props, collection, treeRef: ref}: TreeInnerProps) { + const {dragAndDropHooks} = props; + let {direction} = useLocale(); + let collator = useCollator({usage: 'search', sensitivity: 'base'}); + let isListDraggable = !!dragAndDropHooks?.useDraggableCollectionState; + let isListDroppable = !!dragAndDropHooks?.useDroppableCollectionState; let { selectionMode = 'none', expandedKeys: propExpandedKeys, @@ -189,6 +203,22 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne disabledBehavior }); + let keyboardDelegate = useMemo( + () => + props.keyboardDelegate || + new ListKeyboardDelegate({ + collection: state.collection, + collator, + ref, + disabledKeys: state.selectionManager.disabledKeys, + disabledBehavior: state.selectionManager.disabledBehavior, + layout: 'stack', + direction, + layoutDelegate + }), + [collator, ref, state.selectionManager.disabledKeys, direction, state.collection, state.selectionManager.disabledBehavior, props.keyboardDelegate, layoutDelegate] + ); + let {gridProps} = useTreeGridList({ ...props, isVirtualized, @@ -230,13 +260,49 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne
); } + let dragState: DraggableCollectionState | undefined = undefined; + let dropState: DroppableCollectionState | undefined = undefined; + let droppableCollection: DroppableCollectionResult | undefined = undefined; + let preview = useRef(null); + + if (isListDraggable && dragAndDropHooks) { + dragState = dragAndDropHooks.useDraggableCollectionState!({ + collection: state.collection, + selectionManager: state.selectionManager, + preview: dragAndDropHooks.renderDragPreview ? preview : undefined + }); + dragAndDropHooks.useDraggableCollection!({}, dragState, ref); + } + + if (isListDroppable && dragAndDropHooks) { + dropState = dragAndDropHooks.useDroppableCollectionState!({ + collection: state.collection, + selectionManager: state.selectionManager + }); + + let dropTargetDelegate = new dragAndDropHooks.ListDropTargetDelegate( + state.collection, + ref, + {layout: 'stack', direction} + ); + droppableCollection = dragAndDropHooks.useDroppableCollection!( + {keyboardDelegate, dropTargetDelegate}, + dropState, + ref + ); + } return (
({props, collection, treeRef: ref}: TreeInne data-focus-visible={isFocusVisible || undefined}> + scrollRef={ref} + renderDropIndicator={useRenderDropIndicator(dragAndDropHooks, dropState)} /> {emptyState}
@@ -341,6 +410,18 @@ export const UNSTABLE_TreeItem = /*#__PURE__*/ createBranchComponent('item', (() => ({ ...states, isHovered, @@ -400,7 +481,15 @@ export const UNSTABLE_TreeItem = /*#__PURE__*/ createBranchComponent('item',
(collection: TreeCollection, opts: TreeGridCollectionO keyMap }; } + +export function TreeDropIndicatorWrapper(props: DropIndicatorProps, ref: ForwardedRef) { + ref = useObjectRef(ref); + let {dragAndDropHooks, dropState} = useContext(DragAndDropContext)!; + let {dropIndicatorProps, isHidden, isDropTarget} = dragAndDropHooks!.useDropIndicator!( + props, + dropState!, + ref + ); + + if (isHidden) { + return null; + } + return ( + + ); +} + +interface TreeDropIndicatorProps extends DropIndicatorProps { + dropIndicatorProps: React.HTMLAttributes, + isDropTarget: boolean +} + +function TreeDropIndicator(props: TreeDropIndicatorProps, ref: ForwardedRef) { + let { + dropIndicatorProps, + isDropTarget, + ...otherProps + } = props; + let renderProps = useRenderProps({ + ...otherProps, + defaultClassName: 'react-aria-DropIndicator', + values: { + isDropTarget + } + }); + return ( +
} + data-drop-target={isDropTarget || undefined} /> + ); +} + +const TreeDropIndicatorForwardRef = forwardRef(TreeDropIndicator); From 22b88d50edc0d59b95fed670bfcc407da450aac9 Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 31 Jan 2025 12:27:48 +0000 Subject: [PATCH 06/16] useTreeData - add getDescendantKeys method which is used to determine if a parent node can be dropped into its children --- .../tree/stories/TreeView.stories.tsx | 6 +++ .../@react-stately/data/src/useTreeData.ts | 20 +++++++ .../data/test/useTreeData.test.js | 54 +++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx index 5eef11aef3f..024f83ca78c 100644 --- a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx +++ b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx @@ -262,6 +262,12 @@ export const TreeExampleDynamicDragNDrop = ( return; } + // you shouldn't be able to drop a parent into a child + const dragNode = list.getItem(k); + const childTreeKeys = list.getDescendantKeys(dragNode); + if (childTreeKeys.includes(e.target.key)) { + return null; + } // node list index... let i = 0; if (parent) { diff --git a/packages/@react-stately/data/src/useTreeData.ts b/packages/@react-stately/data/src/useTreeData.ts index 5bcaa98b9f3..5c3f42bbafa 100644 --- a/packages/@react-stately/data/src/useTreeData.ts +++ b/packages/@react-stately/data/src/useTreeData.ts @@ -51,6 +51,7 @@ export interface TreeData { */ getItem(key: Key): TreeNode | undefined, + getDescendantKeys(node?: TreeNode): Key[], /** * Inserts an item into a parent node as a child. * @param parentKey - The key of the parent item to insert into. `null` for the root. @@ -250,10 +251,29 @@ export function useTreeData(options: TreeOptions): TreeData } } + function getDescendantKeys(node?: TreeNode): Key[] { + let descendantKeys: Key[] = []; + if (!node) { + return descendantKeys; + } + function recurse(currentNode: TreeNode) { + if (currentNode.children) { + for (let child of currentNode.children) { + descendantKeys.push(child.key); + recurse(child); + } + } + } + + recurse(node); + return descendantKeys; + } + return { items, selectedKeys, setSelectedKeys, + getDescendantKeys, getItem(key: Key) { return nodeMap.get(key); }, diff --git a/packages/@react-stately/data/test/useTreeData.test.js b/packages/@react-stately/data/test/useTreeData.test.js index eaffc8ad200..563410bd6c3 100644 --- a/packages/@react-stately/data/test/useTreeData.test.js +++ b/packages/@react-stately/data/test/useTreeData.test.js @@ -745,4 +745,58 @@ describe('useTreeData', function () { expect(result.current.items[1].key).toEqual('Emily'); expect(result.current.items.length).toEqual(2); }); + + it('gets the decentants of a node', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) + ); + let decendants; + act(() => { + const top = result.current.getItem('David'); + decendants = result.current.getDescendantKeys(top); + }); + expect(decendants).toEqual(['John', 'Suzie', 'Sam', 'Stacy', 'Brad', 'Jane']); + }); + + + it('gets the decentants of a child node', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) + ); + let descendants; + act(() => { + const top = result.current.getItem('Sam'); + descendants = result.current.getDescendantKeys(top); + }); + expect(descendants).toEqual(['Stacy', 'Brad']); + }); + + it('returns an empty array when getting the decendant keys for a leaf node', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) + ); + let descendants; + act(() => { + const top = result.current.getItem('Eli'); + descendants = result.current.getDescendantKeys(top); + }); + expect(descendants).toEqual([]); + }); + + it('returns an empty array when an undefined key is supplied', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) + ); + let descendants; + act(() => { + descendants = result.current.getDescendantKeys(undefined); + }); + expect(descendants).toEqual([]); + }); + + }); From 121712a8a0e78615a916648b01ed0d83d6501011 Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 31 Jan 2025 12:33:16 +0000 Subject: [PATCH 07/16] revert packlog json change --- packages/react-aria-components/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/react-aria-components/package.json b/packages/react-aria-components/package.json index 596a4de3f8f..a6ba52df7d3 100644 --- a/packages/react-aria-components/package.json +++ b/packages/react-aria-components/package.json @@ -37,7 +37,6 @@ "url": "https://github.com/adobe/react-spectrum" }, "dependencies": { - "@adobe/spectrum-css-temp": "3.0.0-alpha.1", "@internationalized/date": "^3.7.0", "@internationalized/string": "^3.2.5", "@react-aria/autocomplete": "3.0.0-alpha.37", @@ -53,8 +52,6 @@ "@react-aria/tree": "3.0.0-beta.3", "@react-aria/utils": "^3.27.0", "@react-aria/virtualizer": "^4.1.1", - "@react-spectrum/dnd": "^3.5.1", - "@react-spectrum/utils": "^3.12.1", "@react-stately/autocomplete": "3.0.0-alpha.0", "@react-stately/color": "^3.8.2", "@react-stately/disclosure": "^3.0.1", From 02e07de2134df3c150fd825af06b489a9de522a7 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Tue, 11 Mar 2025 11:07:58 -0500 Subject: [PATCH 08/16] updates + story --- packages/react-aria-components/src/Tree.tsx | 27 +++++- .../stories/Tree.stories.tsx | 97 ++++++++++++++++++- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index 196c497d36d..e791a355ead 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -17,12 +17,12 @@ import {Collection, CollectionBuilder, CollectionNode, createBranchComponent, cr import {CollectionProps, CollectionRendererContext, DefaultCollectionRenderer, ItemRenderProps, usePersistedKeys} from './Collection'; import {ContextValue, DEFAULT_SLOT, Provider, RenderProps, ScrollableProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps} from './utils'; import {DisabledBehavior, DragPreviewRenderer, Expandable, forwardRefType, HoverEvents, Key, KeyboardDelegate, LinkDOMProps, RefObject} from '@react-types/shared'; -import {DragAndDropContext, DropIndicatorContext, DropIndicatorProps, useRenderDropIndicator} from './DragAndDrop'; +import {DragAndDropContext, DropIndicatorContext, DropIndicatorProps, useDndPersistedKeys, useRenderDropIndicator} from './DragAndDrop'; import {DragAndDropHooks} from './useDragAndDrop'; import {DraggableCollectionState, DroppableCollectionState, Collection as ICollection, Node, SelectionBehavior, TreeState, useTreeState} from 'react-stately'; import {DraggableItemResult, DroppableCollectionResult, DroppableItemResult, FocusScope, ListKeyboardDelegate, mergeProps, useCollator, useFocusRing, useGridListSelectionCheckbox, useHover, useLocale} from 'react-aria'; import {filterDOMProps, useObjectRef} from '@react-aria/utils'; -import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes, ReactNode, useContext, useEffect, useMemo, useRef} from 'react'; +import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes, JSX, ReactNode, useContext, useEffect, useMemo, useRef} from 'react'; import {useControlledState} from '@react-stately/utils'; class TreeCollection implements ICollection> { @@ -263,6 +263,8 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne let dragState: DraggableCollectionState | undefined = undefined; let dropState: DroppableCollectionState | undefined = undefined; let droppableCollection: DroppableCollectionResult | undefined = undefined; + let isRootDropTarget = false; + let dragPreview: JSX.Element | null = null; let preview = useRef(null); if (isListDraggable && dragAndDropHooks) { @@ -272,6 +274,11 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne preview: dragAndDropHooks.renderDragPreview ? preview : undefined }); dragAndDropHooks.useDraggableCollection!({}, dragState, ref); + + let DragPreview = dragAndDropHooks.DragPreview!; + dragPreview = dragAndDropHooks.renderDragPreview + ? {dragAndDropHooks.renderDragPreview} + : null; } if (isListDroppable && dragAndDropHooks) { @@ -291,6 +298,8 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne dropState, ref ); + + isRootDropTarget = dropState.isDropTarget({type: 'root'}); } return ( @@ -317,7 +326,7 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne ]}> @@ -426,6 +435,8 @@ export const UNSTABLE_TreeItem = /*#__PURE__*/ createBranchComponent('item', (() => ({ ...states, isHovered, @@ -437,7 +448,10 @@ export const UNSTABLE_TreeItem = /*#__PURE__*/ createBranchComponent('item', + data-selection-mode={state.selectionManager.selectionMode === 'none' ? undefined : state.selectionManager.selectionMode} + data-allows-dragging={!!dragState || undefined} + data-dragging={isDragging || undefined} + data-drop-target={droppableItem?.isDropTarget || undefined}>
flattenTree(items), [items]); + + let getItems = (keys) => [...keys].map(key => { + let item = flattenedItems.get(key); + return { + 'text/plain': item?.name + }; + }); + + let {dragAndDropHooks} = useDragAndDrop({ + getItems, + onReorder(e) { + let draggedKeys = [...e.keys]; + let targetKey = e.target.key; + + setItems((prevItems) => { + let newItems = JSON.parse(JSON.stringify(prevItems)); + let targetLocation = findParentAndIndex(newItems, targetKey); + if (!targetLocation){ + // Target not found + return prevItems + } + + let draggedItems = []; + for (let key of draggedKeys) { + let location = findParentAndIndex(newItems, key); + if (location) { + let [item] = location.parent.splice(location.index, 1); + draggedItems.push(item); + } + } + + let insertIndex = e.target.dropPosition === 'before' + ? targetLocation.index + : targetLocation.index + 1; + + // Insert the dragged items at the new position + targetLocation.parent.splice(insertIndex, 0, ...draggedItems); + + return newItems; + }); + } + }); + + return ( + + {(item: any) => ( + + {item.name} + + )} + + ); +} + +export const TreeWithDragAndDrop = { + ...TreeExampleDynamic, + render: TreeDragAndDropExample +}; From d7506e9ff253cdd79e5c82d23d573ec921ee7343 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 12 Mar 2025 10:24:27 -0500 Subject: [PATCH 09/16] remove RSP TreeView dnd for now --- .../@react-spectrum/tree/src/TreeView.tsx | 8 +- .../tree/stories/TreeView.stories.tsx | 119 ------------------ 2 files changed, 2 insertions(+), 125 deletions(-) diff --git a/packages/@react-spectrum/tree/src/TreeView.tsx b/packages/@react-spectrum/tree/src/TreeView.tsx index 121b8a8c259..e778af46682 100644 --- a/packages/@react-spectrum/tree/src/TreeView.tsx +++ b/packages/@react-spectrum/tree/src/TreeView.tsx @@ -28,12 +28,11 @@ import ChevronLeftMedium from '@spectrum-icons/ui/ChevronLeftMedium'; import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium'; import {DOMRef, Expandable, Key, SelectionBehavior, SpectrumSelectionProps, StyleProps} from '@react-types/shared'; import {isAndroid} from '@react-aria/utils'; -import React, {createContext, JSX, JSXElementConstructor, ReactElement, ReactNode, useRef} from 'react'; +import React, {createContext, JSX, JSXElementConstructor, ReactElement, ReactNode, useEffect, useRef} from 'react'; import {SlotProvider, useDOMRef, useStyleProps} from '@react-spectrum/utils'; import {style} from '@react-spectrum/style-macro-s1' with {type: 'macro'}; import {useButton} from '@react-aria/button'; import {useLocale} from '@react-aria/i18n'; -import type {DragAndDropHooks} from '@react-spectrum/dnd'; export interface SpectrumTreeViewProps extends Omit, 'children'>, StyleProps, SpectrumSelectionProps, Expandable { /** Provides content to display when there are no items in the tree. */ @@ -46,10 +45,7 @@ export interface SpectrumTreeViewProps extends Omit, 'childr /** * The contents of the tree. */ - children?: ReactNode | ((item: T) => ReactNode), - - /** The drag and drop hooks returned by `useDragAndDrop` used to enable drag and drop behavior for the ListBox. */ - dragAndDropHooks?: DragAndDropHooks['dragAndDropHooks'] + children?: ReactNode | ((item: T) => ReactNode) } export interface SpectrumTreeViewItemProps extends Omit { diff --git a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx index acf121f5f27..430a8ff938b 100644 --- a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx +++ b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx @@ -23,11 +23,8 @@ import {IllustratedMessage} from '@react-spectrum/illustratedmessage'; import {Link} from '@react-spectrum/link'; import React, { JSX } from 'react'; import {SpectrumTreeViewProps, TreeView, TreeViewItem, TreeViewItemContent} from '../src'; -import { useTreeData } from 'react-stately'; -import { useDragAndDrop } from '@react-spectrum/dnd'; import { classNames } from '@react-spectrum/utils'; import dropIndicatorStyles from "@adobe/spectrum-css-temp/components/dropindicator/vars.css"; -import { DragItem } from '@react-types/shared'; export default { title: "TreeView", @@ -328,103 +325,6 @@ TreeExampleDynamic.story = { parameters: null, }; -export const TreeExampleDynamicDragNDrop = ( - args: SpectrumTreeViewProps -) => { - const list = useTreeData({ - initialItems: rows, - getChildren: (item) => { - return item.childItems ?? []; - }, - }); - // @TODO internalise inside Tree ? - let { dragAndDropHooks } = useDragAndDrop({ - getItems: (keys) => { - return [...keys].map((key) => { - return { - "text/plain": list.getItem(key)?.key ?? "", - } as DragItem; - }); - }, - // renderDropIndicator() { - // return ; - // }, - onReorder(e) { - const k = e.keys.values().next().value; - const parent = list.getItem(e.target.key)?.parentKey ?? null; - if (!k) { - return; - } - - // you shouldn't be able to drop a parent into a child - const dragNode = list.getItem(k); - const childTreeKeys = list.getDescendantKeys(dragNode); - if (childTreeKeys.includes(e.target.key)) { - return null; - } - // node list index... - let i = 0; - if (parent) { - const parentNode = list.getItem(parent); - i = (parentNode?.children ?? []).findIndex( - (c) => c.key === e.target.key - ); - } - if (e.target.dropPosition === "before") { - list.moveBefore(k, parent, i); - } else if (e.target.dropPosition === "after") { - list.moveAfter(k, parent, i); - } - }, - }); - return ( -
- - {(item: any) => { - if (!item.value) { - return; - } - return ( - - {item.value.name} - {item.value.icon} - - - - Edit - - - - Delete - - - - ); - }} - -
- ); -}; - TreeExampleDynamic.story = { ...TreeExampleStatic.story, parameters: null, @@ -527,22 +427,3 @@ export const WithActionMenu = (args: SpectrumTreeViewProps) => (
); -function InsertionIndicator() { - return ( -
- ); -} From 34073ba7db25b7d665ccfbd839e3cfdb4730e1c9 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 12 Mar 2025 10:35:43 -0500 Subject: [PATCH 10/16] cleanup --- packages/react-aria-components/src/Tree.tsx | 28 ++++++++++++--------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index 460b224ab85..e40e5f987fe 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -169,8 +169,18 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne const {dragAndDropHooks} = props; let {direction} = useLocale(); let collator = useCollator({usage: 'search', sensitivity: 'base'}); - let isListDraggable = !!dragAndDropHooks?.useDraggableCollectionState; - let isListDroppable = !!dragAndDropHooks?.useDroppableCollectionState; + let hasDragHooks = !!dragAndDropHooks?.useDraggableCollectionState; + let hasDropHooks = !!dragAndDropHooks?.useDroppableCollectionState; + let dragHooksProvided = useRef(hasDragHooks); + let dropHooksProvided = useRef(hasDropHooks); + useEffect(() => { + if (dragHooksProvided.current !== hasDragHooks) { + console.warn('Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.'); + } + if (dropHooksProvided.current !== hasDropHooks) { + console.warn('Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.'); + } + }, [hasDragHooks, hasDropHooks]); let { selectionMode = 'none', expandedKeys: propExpandedKeys, @@ -178,7 +188,7 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne onExpandedChange, disabledBehavior = 'all' } = props; - let {CollectionRoot, isVirtualized, layoutDelegate} = useContext(CollectionRendererContext); + let {CollectionRoot, isVirtualized, layoutDelegate, dropTargetDelegate: ctxDropTargetDelegate} = useContext(CollectionRendererContext); // Kinda annoying that we have to replicate this code here as well as in useTreeState, but don't want to add // flattenCollection stuff to useTreeState. Think about this later @@ -250,7 +260,7 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne let dragPreview: JSX.Element | null = null; let preview = useRef(null); - if (isListDraggable && dragAndDropHooks) { + if (hasDragHooks && dragAndDropHooks) { dragState = dragAndDropHooks.useDraggableCollectionState!({ collection: state.collection, selectionManager: state.selectionManager, @@ -264,18 +274,12 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne : null; } - if (isListDroppable && dragAndDropHooks) { + if (hasDropHooks && dragAndDropHooks) { dropState = dragAndDropHooks.useDroppableCollectionState!({ collection: state.collection, selectionManager: state.selectionManager }); - - let dropTargetDelegate = new dragAndDropHooks.ListDropTargetDelegate( - state.collection, - ref, - {layout: 'stack', direction} - ); - + let dropTargetDelegate = dragAndDropHooks.dropTargetDelegate || ctxDropTargetDelegate || new dragAndDropHooks.ListDropTargetDelegate(state.collection, ref, {layout: 'stack', direction}); let keyboardDelegate = useMemo( () => props.keyboardDelegate || From efd525984e87d08a210abc22f5a6309387f1453d Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 12 Mar 2025 10:51:51 -0500 Subject: [PATCH 11/16] fix story --- .../stories/Tree.stories.tsx | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/react-aria-components/stories/Tree.stories.tsx b/packages/react-aria-components/stories/Tree.stories.tsx index 767f6cb03ca..576ca80710b 100644 --- a/packages/react-aria-components/stories/Tree.stories.tsx +++ b/packages/react-aria-components/stories/Tree.stories.tsx @@ -532,6 +532,20 @@ function findParentAndIndex(items, key) { return null; }; +function isParentOf(possibleParent, childKey) { + if (!possibleParent || !possibleParent.childItems) { + return false; + } + + for (let child of possibleParent.childItems) { + if (child.id === childKey || isParentOf(child, childKey)) { + return true; + } + } + + return false; +} + function TreeDragAndDropExample(args) { let [items, setItems] = useState(rows); let flattenedItems = useMemo(() => flattenTree(items), [items]); @@ -557,22 +571,39 @@ function TreeDragAndDropExample(args) { return prevItems } + let targetParent = targetLocation.parent; + let dropPosition = e.target.dropPosition; + let targetIndex = targetLocation.index; + + // Prevent dragging parent into its own children + for (let key of draggedKeys) { + if (isParentOf(flattenedItems.get(key), targetKey)) { + return prevItems; + } + } + let draggedItems = []; + let adjustIndexOffset = 0; + for (let key of draggedKeys) { let location = findParentAndIndex(newItems, key); if (location) { + // Adjust target index if we're removing from the same parent + // and the removed item is before the target + if (location.parent === targetParent && location.index < targetIndex) { + adjustIndexOffset++; + } + let [item] = location.parent.splice(location.index, 1); draggedItems.push(item); } } - let insertIndex = e.target.dropPosition === 'before' - ? targetLocation.index - : targetLocation.index + 1; - - // Insert the dragged items at the new position - targetLocation.parent.splice(insertIndex, 0, ...draggedItems); + let insertIndex = dropPosition === 'before' + ? targetIndex - adjustIndexOffset + : targetIndex - adjustIndexOffset + 1; + targetParent.splice(insertIndex, 0, ...draggedItems); return newItems; }); } From 5b3817579da0ec27070ba990c86836a8f1312ae6 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 12 Mar 2025 11:22:28 -0500 Subject: [PATCH 12/16] lint --- .../@react-spectrum/tree/src/TreeView.tsx | 2 +- .../tree/stories/TreeView.stories.tsx | 262 ++++++------------ .../stories/Tree.stories.tsx | 10 +- 3 files changed, 92 insertions(+), 182 deletions(-) diff --git a/packages/@react-spectrum/tree/src/TreeView.tsx b/packages/@react-spectrum/tree/src/TreeView.tsx index e778af46682..d4fbe6e6962 100644 --- a/packages/@react-spectrum/tree/src/TreeView.tsx +++ b/packages/@react-spectrum/tree/src/TreeView.tsx @@ -28,7 +28,7 @@ import ChevronLeftMedium from '@spectrum-icons/ui/ChevronLeftMedium'; import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium'; import {DOMRef, Expandable, Key, SelectionBehavior, SpectrumSelectionProps, StyleProps} from '@react-types/shared'; import {isAndroid} from '@react-aria/utils'; -import React, {createContext, JSX, JSXElementConstructor, ReactElement, ReactNode, useEffect, useRef} from 'react'; +import React, {createContext, JSX, JSXElementConstructor, ReactElement, ReactNode, useRef} from 'react'; import {SlotProvider, useDOMRef, useStyleProps} from '@react-spectrum/utils'; import {style} from '@react-spectrum/style-macro-s1' with {type: 'macro'}; import {useButton} from '@react-aria/button'; diff --git a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx index 430a8ff938b..5fdc80f2277 100644 --- a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx +++ b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx @@ -21,45 +21,37 @@ import Folder from '@spectrum-icons/workflow/Folder'; import {Heading, Text} from '@react-spectrum/text'; import {IllustratedMessage} from '@react-spectrum/illustratedmessage'; import {Link} from '@react-spectrum/link'; -import React, { JSX } from 'react'; +import React from 'react'; import {SpectrumTreeViewProps, TreeView, TreeViewItem, TreeViewItemContent} from '../src'; -import { classNames } from '@react-spectrum/utils'; -import dropIndicatorStyles from "@adobe/spectrum-css-temp/components/dropindicator/vars.css"; export default { - title: "TreeView", - excludeStories: ["renderEmptyState"], + title: 'TreeView', + excludeStories: [ + 'renderEmptyState' + ], argTypes: { items: { table: { - disable: true, - }, + disable: true + } }, renderEmptyState: { table: { - disable: true, - }, - }, - }, + disable: true + } + } + } }; // TODO: This story crashes on save and story switch, not sure why or if only local... export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( -
- +
+ Photos - + Edit @@ -75,7 +67,7 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Projects - + Edit @@ -90,7 +82,7 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Projects-1 - + Edit @@ -105,7 +97,7 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Projects-1A - + Edit @@ -122,7 +114,7 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Projects-2 - + Edit @@ -138,7 +130,7 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Projects-3 - + Edit @@ -157,115 +149,70 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( TreeExampleStatic.story = { args: { - selectionMode: "none", - selectionStyle: "checkbox", - disabledBehavior: "selection", + selectionMode: 'none', + selectionStyle: 'checkbox', + disabledBehavior: 'selection' }, argTypes: { selectionMode: { - control: "radio", - options: ["none", "single", "multiple"], + control: 'radio', + options: ['none', 'single', 'multiple'] }, selectionStyle: { - control: "radio", - options: ["checkbox", "highlight"], + control: 'radio', + options: ['checkbox', 'highlight'] }, disabledBehavior: { - control: "radio", - options: ["selection", "all"], + control: 'radio', + options: ['selection', 'all'] }, disallowEmptySelection: { control: { - type: "boolean", - }, - }, - }, + type: 'boolean' + } + } + } }; -type Node = { - id: string; - name: string; - icon: JSX.Element; - childItems?: Node[]; -}; -let rows: Node[] = [ - { - id: "projects", - name: "Projects", - icon: , - childItems: [ - { id: "project-1", name: "Project 1", icon: }, - { - id: "project-2", - name: "Project 2", - icon: , - childItems: [ - { id: "project-2A", name: "Project 2A", icon: }, - { id: "project-2B", name: "Project 2B", icon: }, - { id: "project-2C", name: "Project 2C", icon: }, - ], - }, - { id: "project-3", name: "Project 3", icon: }, - { id: "project-4", name: "Project 4", icon: }, - { - id: "project-5", - name: "Project 5", - icon: , - childItems: [ - { id: "project-5A", name: "Project 5A", icon: }, - { id: "project-5B", name: "Project 5B", icon: }, - { id: "project-5C", name: "Project 5C", icon: }, - ], - }, - ], - }, - { - id: "reports", - name: "Reports", - icon: , - childItems: [ - { - id: "reports-1", - name: "Reports 1", - icon: , - childItems: [ - { - id: "reports-1A", - name: "Reports 1A", - icon: , - childItems: [ - { - id: "reports-1AB", - name: "Reports 1AB", - icon: , - childItems: [ - { - id: "reports-1ABC", - name: "Reports 1ABC", - icon: , - }, - ], - }, - ], - }, - { id: "reports-1B", name: "Reports 1B", icon: }, - { id: "reports-1C", name: "Reports 1C", icon: }, - ], - }, - { id: "reports-2", name: "Reports 2", icon: }, - ], - }, +let rows = [ + {id: 'projects', name: 'Projects', icon: , childItems: [ + {id: 'project-1', name: 'Project 1', icon: }, + {id: 'project-2', name: 'Project 2', icon: , childItems: [ + {id: 'project-2A', name: 'Project 2A', icon: }, + {id: 'project-2B', name: 'Project 2B', icon: }, + {id: 'project-2C', name: 'Project 2C', icon: } + ]}, + {id: 'project-3', name: 'Project 3', icon: }, + {id: 'project-4', name: 'Project 4', icon: }, + {id: 'project-5', name: 'Project 5', icon: , childItems: [ + {id: 'project-5A', name: 'Project 5A', icon: }, + {id: 'project-5B', name: 'Project 5B', icon: }, + {id: 'project-5C', name: 'Project 5C', icon: } + ]} + ]}, + {id: 'reports', name: 'Reports', icon: , childItems: [ + {id: 'reports-1', name: 'Reports 1', icon: , childItems: [ + {id: 'reports-1A', name: 'Reports 1A', icon: , childItems: [ + {id: 'reports-1AB', name: 'Reports 1AB', icon: , childItems: [ + {id: 'reports-1ABC', name: 'Reports 1ABC', icon: } + ]} + ]}, + {id: 'reports-1B', name: 'Reports 1B', icon: }, + {id: 'reports-1C', name: 'Reports 1C', icon: } + ]}, + {id: 'reports-2', name: 'Reports 2', icon: } + ]} ]; const DynamicTreeItem = (props) => { - let { childItems, name, icon } = props; + let {childItems, name, icon} = props; return ( <> - + {name} {icon} - + Edit @@ -284,8 +231,7 @@ const DynamicTreeItem = (props) => { childItems={item.childItems} textValue={item.name} name={item.name} - href={props.href} - > + href={props.href}> {item.name} )} @@ -296,25 +242,15 @@ const DynamicTreeItem = (props) => { }; export const TreeExampleDynamic = (args: SpectrumTreeViewProps) => ( -
- +
+ {(item: any) => ( + name={item.name} /> )}
@@ -322,36 +258,23 @@ export const TreeExampleDynamic = (args: SpectrumTreeViewProps) => ( TreeExampleDynamic.story = { ...TreeExampleStatic.story, - parameters: null, + parameters: null }; -TreeExampleDynamic.story = { - ...TreeExampleStatic.story, - parameters: null, -}; export const WithActions = { render: TreeExampleDynamic, ...TreeExampleDynamic, args: { - onAction: action("onAction"), - ...TreeExampleDynamic.story.args, + onAction: action('onAction'), + ...TreeExampleDynamic.story.args }, - name: "Tree with actions", + name: 'Tree with actions' }; export const WithLinks = (args: SpectrumTreeViewProps) => ( -
- +
+ {(item) => ( ) => ( childItems={item.childItems} textValue={item.name} name={item.name} - href="https://adobe.com/" - /> + href="https://adobe.com/" /> )}
@@ -368,12 +290,12 @@ export const WithLinks = (args: SpectrumTreeViewProps) => ( WithLinks.story = { ...TreeExampleDynamic.story, - name: "Tree with links", + name: 'Tree with links', parameters: { description: { - data: "every tree item should link to adobe.com", - }, - }, + data: 'every tree item should link to adobe.com' + } + } }; export function renderEmptyState() { @@ -383,10 +305,7 @@ export function renderEmptyState() { No results - - No results found, press here{" "} - for more info. - + No results found, press here for more info. ); } @@ -397,33 +316,22 @@ export const EmptyTree = { args: { ...TreeExampleDynamic.story.args, items: [], - renderEmptyState, + renderEmptyState }, - name: "Empty Tree", + name: 'Empty Tree' }; export const WithActionMenu = (args: SpectrumTreeViewProps) => ( -
- +
+ {(item) => ( + name={item.name} /> )}
-); - +); \ No newline at end of file diff --git a/packages/react-aria-components/stories/Tree.stories.tsx b/packages/react-aria-components/stories/Tree.stories.tsx index 576ca80710b..810538e3ad2 100644 --- a/packages/react-aria-components/stories/Tree.stories.tsx +++ b/packages/react-aria-components/stories/Tree.stories.tsx @@ -504,7 +504,9 @@ export const VirtualizedTree = { function flattenTree(items) { let flattened = new Map(); function traverse(items) { - if (!items) return; + if (!items) { + return; + } for (let item of items) { flattened.set(item.id, item); if (item.childItems) { @@ -519,7 +521,7 @@ function flattenTree(items) { function findParentAndIndex(items, key) { for (let i = 0; i < items.length; i++) { if (items[i].id === key) { - return { parent: items, index: i }; + return {parent: items, index: i}; } if (items[i].childItems) { @@ -566,9 +568,9 @@ function TreeDragAndDropExample(args) { setItems((prevItems) => { let newItems = JSON.parse(JSON.stringify(prevItems)); let targetLocation = findParentAndIndex(newItems, targetKey); - if (!targetLocation){ + if (!targetLocation) { // Target not found - return prevItems + return prevItems; } let targetParent = targetLocation.parent; From e0731ed6688fd6b1bef17d9eb90e31fcdd687de2 Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 12 Mar 2025 11:43:39 -0500 Subject: [PATCH 13/16] lint --- .../tree/stories/TreeView.stories.tsx | 2 +- .../data/test/useTreeData.test.js | 886 ++++++------------ packages/react-aria-components/src/Tree.tsx | 188 ++-- 3 files changed, 405 insertions(+), 671 deletions(-) diff --git a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx index 5fdc80f2277..466da584255 100644 --- a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx +++ b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx @@ -334,4 +334,4 @@ export const WithActionMenu = (args: SpectrumTreeViewProps) => ( )}
-); \ No newline at end of file +); diff --git a/packages/@react-stately/data/test/useTreeData.test.js b/packages/@react-stately/data/test/useTreeData.test.js index 9ec4d9739f1..e157c308bd6 100644 --- a/packages/@react-stately/data/test/useTreeData.test.js +++ b/packages/@react-stately/data/test/useTreeData.test.js @@ -10,647 +10,444 @@ * governing permissions and limitations under the License. */ -import { - actHook as act, - renderHook, -} from "@react-spectrum/test-utils-internal"; -import React from "react"; -import { useTreeData } from "../src/useTreeData"; +import {actHook as act, renderHook} from '@react-spectrum/test-utils-internal'; +import {useTreeData} from '../src/useTreeData'; const initial = [ { - name: "David", + name: 'David', children: [ - { name: "John", children: [{ name: "Suzie" }] }, - { name: "Sam", children: [{ name: "Stacy" }, { name: "Brad" }] }, - { name: "Jane" }, - ], - }, + {name: 'John', children: [ + {name: 'Suzie'} + ]}, + {name: 'Sam', children: [ + {name: 'Stacy'}, + {name: 'Brad'} + ]}, + {name: 'Jane'} + ] + } ]; let getKey = (item) => item.name; let getChildren = (item) => item.children; -describe("useTreeData", function () { - it("should construct a tree using initial data", function () { - let { result } = renderHook(() => - useTreeData({ - initialItems: initial, - getChildren, - getKey, - initialSelectedKeys: ["John", "Stacy"], - }) - ); +describe('useTreeData', function () { + it('should construct a tree using initial data', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey, initialSelectedKeys: ['John', 'Stacy']})); expect(result.current.items[0].value).toBe(initial[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0].value).toBe( - initial[0].children[0] - ); + expect(result.current.items[0].children[0].value).toBe(initial[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(1); expect(result.current.items[0].children[1].children).toHaveLength(2); expect(result.current.items[0].children[2].children).toHaveLength(0); - expect(result.current.selectedKeys).toEqual(new Set(["John", "Stacy"])); + expect(result.current.selectedKeys).toEqual(new Set(['John', 'Stacy'])); }); - it("should get a node by key", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); - expect(result.current.getItem("Sam").value).toBe(initial[0].children[1]); - expect(result.current.getItem("David").value).toBe(initial[0]); + it('should get a node by key', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); + expect(result.current.getItem('Sam').value).toBe(initial[0].children[1]); + expect(result.current.getItem('David').value).toBe(initial[0]); }); - it("should insert an item into a child node", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert an item into a child node', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insert("John", 1, { name: "Devon" }); + result.current.insert('John', 1, {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(2); - expect(result.current.items[0].children[0].children[0]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[0].children[1].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[0].children[1].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should insert multiple items into a child node", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert multiple items into a child node', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insert("John", 1, { name: "Devon" }, { name: "Danni" }); + result.current.insert('John', 1, {name: 'Devon'}, {name: 'Danni'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(3); - expect(result.current.items[0].children[0].children[0]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[0].children[1].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[0].children[2].value).toEqual({ - name: "Danni", - }); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[0].children[1].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[0].children[2].value).toEqual({name: 'Danni'}); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should insert an item into the root", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert an item into the root', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insert(null, 1, { name: "Devon" }); + result.current.insert(null, 1, {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(2); expect(result.current.items[0]).toBe(initialResult.items[0]); - expect(result.current.items[1].value).toEqual({ name: "Devon" }); + expect(result.current.items[1].value).toEqual({name: 'Devon'}); }); - it("should insert multiple items into the root", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert multiple items into the root', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insert(null, 1, { name: "Devon" }, { name: "Danni" }); + result.current.insert(null, 1, {name: 'Devon'}, {name: 'Danni'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(3); expect(result.current.items[0]).toBe(initialResult.items[0]); - expect(result.current.items[1].value).toEqual({ name: "Devon" }); - expect(result.current.items[2].value).toEqual({ name: "Danni" }); + expect(result.current.items[1].value).toEqual({name: 'Devon'}); + expect(result.current.items[2].value).toEqual({name: 'Danni'}); }); - it("should insert an item into a child node before another item", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert an item into a child node before another item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insertBefore("Suzie", { name: "Devon" }); + result.current.insertBefore('Suzie', {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(2); - expect(result.current.items[0].children[0].children[0].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[0].children[1]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[0].children[1]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should insert multiple items into a child node before another item", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert multiple items into a child node before another item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insertBefore( - "Suzie", - { name: "Devon" }, - { name: "Danni" } - ); + result.current.insertBefore('Suzie', {name: 'Devon'}, {name: 'Danni'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(3); - expect(result.current.items[0].children[0].children[0].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[0].children[1].value).toEqual({ - name: "Danni", - }); - expect(result.current.items[0].children[0].children[2]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[0].children[1].value).toEqual({name: 'Danni'}); + expect(result.current.items[0].children[0].children[2]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should insert an item into the root before another item", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert an item into the root before another item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insertBefore("David", { name: "Devon" }); + result.current.insertBefore('David', {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(2); - expect(result.current.items[0].value).toEqual({ name: "Devon" }); + expect(result.current.items[0].value).toEqual({name: 'Devon'}); expect(result.current.items[1]).toBe(initialResult.items[0]); }); - it("should insert multiple items into the root before another item", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert multiple items into the root before another item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insertBefore( - "David", - { name: "Devon" }, - { name: "Danni" } - ); + result.current.insertBefore('David', {name: 'Devon'}, {name: 'Danni'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(3); - expect(result.current.items[0].value).toEqual({ name: "Devon" }); - expect(result.current.items[1].value).toEqual({ name: "Danni" }); + expect(result.current.items[0].value).toEqual({name: 'Devon'}); + expect(result.current.items[1].value).toEqual({name: 'Danni'}); expect(result.current.items[2]).toBe(initialResult.items[0]); }); - it("should insert an item into a child node after another item", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert an item into a child node after another item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insertAfter("Suzie", { name: "Devon" }); + result.current.insertAfter('Suzie', {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(2); - expect(result.current.items[0].children[0].children[0]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[0].children[1].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[0].children[1].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should insert multiple items into a child node after another item", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert multiple items into a child node after another item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insertAfter("Suzie", { name: "Devon" }, { name: "Danni" }); + result.current.insertAfter('Suzie', {name: 'Devon'}, {name: 'Danni'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(3); - expect(result.current.items[0].children[0].children[0]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[0].children[1].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[0].children[2].value).toEqual({ - name: "Danni", - }); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[0].children[1].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[0].children[2].value).toEqual({name: 'Danni'}); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should insert an item into the root after another item", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert an item into the root after another item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insertAfter("David", { name: "Devon" }); + result.current.insertAfter('David', {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(2); expect(result.current.items[0]).toBe(initialResult.items[0]); - expect(result.current.items[1].value).toEqual({ name: "Devon" }); + expect(result.current.items[1].value).toEqual({name: 'Devon'}); }); - it("should insert multiple items into the root after another item", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should insert multiple items into the root after another item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.insertAfter("David", { name: "Devon" }, { name: "Danni" }); + result.current.insertAfter('David', {name: 'Devon'}, {name: 'Danni'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(3); expect(result.current.items[0]).toBe(initialResult.items[0]); - expect(result.current.items[1].value).toEqual({ name: "Devon" }); - expect(result.current.items[2].value).toEqual({ name: "Danni" }); + expect(result.current.items[1].value).toEqual({name: 'Devon'}); + expect(result.current.items[2].value).toEqual({name: 'Danni'}); }); - it("should prepend an item into a child node", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should prepend an item into a child node', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.prepend("John", { name: "Devon" }); + result.current.prepend('John', {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(2); - expect(result.current.items[0].children[0].children[0].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[0].children[1]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[0].children[1]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should prepend multiple items into a child node", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should prepend multiple items into a child node', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.prepend("John", { name: "Devon" }, { name: "Danni" }); + result.current.prepend('John', {name: 'Devon'}, {name: 'Danni'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(3); - expect(result.current.items[0].children[0].children[0].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[0].children[1].value).toEqual({ - name: "Danni", - }); - expect(result.current.items[0].children[0].children[2]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[0].children[1].value).toEqual({name: 'Danni'}); + expect(result.current.items[0].children[0].children[2]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should prepend an item into the root", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should prepend an item into the root', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.prepend(null, { name: "Devon" }); + result.current.prepend(null, {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(2); - expect(result.current.items[0].value).toEqual({ name: "Devon" }); + expect(result.current.items[0].value).toEqual({name: 'Devon'}); expect(result.current.items[1]).toBe(initialResult.items[0]); }); - it("should prepend multiple items into the root", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should prepend multiple items into the root', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.prepend(null, { name: "Devon" }, { name: "Danni" }); + result.current.prepend(null, {name: 'Devon'}, {name: 'Danni'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(3); - expect(result.current.items[0].value).toEqual({ name: "Devon" }); - expect(result.current.items[1].value).toEqual({ name: "Danni" }); + expect(result.current.items[0].value).toEqual({name: 'Devon'}); + expect(result.current.items[1].value).toEqual({name: 'Danni'}); expect(result.current.items[2]).toBe(initialResult.items[0]); }); - it("should append an item into a child node", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should append an item into a child node', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.append("John", { name: "Devon" }); + result.current.append('John', {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(2); - expect(result.current.items[0].children[0].children[0]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[0].children[1].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[0].children[1].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should append multiple items into a child node", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should append multiple items into a child node', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.append("John", { name: "Devon" }, { name: "Danni" }); + result.current.append('John', {name: 'Devon'}, {name: 'Danni'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(3); - expect(result.current.items[0].children[0].children[0]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[0].children[1].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[0].children[2].value).toEqual({ - name: "Danni", - }); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[0].children[1].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[0].children[2].value).toEqual({name: 'Danni'}); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should append an item into the root", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should append an item into the root', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.append(null, { name: "Devon" }); + result.current.append(null, {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(2); expect(result.current.items[0]).toBe(initialResult.items[0]); - expect(result.current.items[1].value).toEqual({ name: "Devon" }); + expect(result.current.items[1].value).toEqual({name: 'Devon'}); }); - it("should append multiple items into the root", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should append multiple items into the root', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.append(null, { name: "Devon" }, { name: "Danni" }); + result.current.append(null, {name: 'Devon'}, {name: 'Danni'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(3); expect(result.current.items[0]).toBe(initialResult.items[0]); - expect(result.current.items[1].value).toEqual({ name: "Devon" }); - expect(result.current.items[2].value).toEqual({ name: "Danni" }); + expect(result.current.items[1].value).toEqual({name: 'Devon'}); + expect(result.current.items[2].value).toEqual({name: 'Danni'}); }); - it("should remove an item", function () { - let { result } = renderHook(() => - useTreeData({ - initialItems: initial, - getChildren, - getKey, - initialSelectedKeys: ["Suzie", "Sam"], - }) - ); + it('should remove an item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey, initialSelectedKeys: ['Suzie', 'Sam']})); let initialResult = result.current; act(() => { - result.current.remove("Suzie"); + result.current.remove('Suzie'); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(0); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); expect(result.current.selectedKeys).not.toBe(initialResult.selectedKeys); - expect(result.current.selectedKeys).toEqual(new Set(["Sam"])); + expect(result.current.selectedKeys).toEqual(new Set(['Sam'])); }); - it("should remove multiple items", function () { - let { result } = renderHook(() => - useTreeData({ - initialItems: initial, - getChildren, - getKey, - initialSelectedKeys: ["Brad", "Sam"], - }) - ); + it('should remove multiple items', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey, initialSelectedKeys: ['Brad', 'Sam']})); let initialResult = result.current; act(() => { - result.current.remove("Suzie", "Brad"); + result.current.remove('Suzie', 'Brad'); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(0); - expect(result.current.items[0].children[1]).not.toBe( - initialResult.items[0].children[1] - ); + expect(result.current.items[0].children[1]).not.toBe(initialResult.items[0].children[1]); expect(result.current.items[0].children[1].children).toHaveLength(1); - expect(result.current.items[0].children[1].children[0]).toBe( - initialResult.items[0].children[1].children[0] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[1].children[0]).toBe(initialResult.items[0].children[1].children[0]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); expect(result.current.selectedKeys).not.toBe(initialResult.selectedKeys); - expect(result.current.selectedKeys).toEqual(new Set(["Sam"])); + expect(result.current.selectedKeys).toEqual(new Set(['Sam'])); }); - it("should remove an item from the root", function () { - let { result } = renderHook(() => - useTreeData({ - initialItems: initial, - getChildren, - getKey, - initialSelectedKeys: ["David", "Suzie"], - }) - ); + it('should remove an item from the root', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey, initialSelectedKeys: ['David', 'Suzie']})); let initialResult = result.current; act(() => { - result.current.remove("David"); + result.current.remove('David'); }); expect(result.current.items).not.toBe(initialResult.items); @@ -659,15 +456,8 @@ describe("useTreeData", function () { expect(result.current.selectedKeys).toEqual(new Set()); }); - it("should remove the selected items", function () { - let { result } = renderHook(() => - useTreeData({ - initialItems: initial, - getChildren, - getKey, - initialSelectedKeys: ["Suzie", "Brad"], - }) - ); + it('should remove the selected items', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey, initialSelectedKeys: ['Suzie', 'Brad']})); let initialResult = result.current; act(() => { @@ -678,114 +468,75 @@ describe("useTreeData", function () { expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(0); - expect(result.current.items[0].children[1]).not.toBe( - initialResult.items[0].children[1] - ); + expect(result.current.items[0].children[1]).not.toBe(initialResult.items[0].children[1]); expect(result.current.items[0].children[1].children).toHaveLength(1); - expect(result.current.items[0].children[1].children[0]).toBe( - initialResult.items[0].children[1].children[0] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[1].children[0]).toBe(initialResult.items[0].children[1].children[0]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); expect(result.current.selectedKeys).not.toBe(initialResult.selectedKeys); expect(result.current.selectedKeys).toEqual(new Set()); }); - it("should update an root item", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should update an root item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.update("David", { expanded: true, name: "Danny" }); + result.current.update('David', {expanded: true, name: 'Danny'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); - expect(result.current.items[0].value).toEqual({ - expanded: true, - name: "Danny", - }); + expect(result.current.items[0].value).toEqual({expanded: true, name: 'Danny'}); expect(result.current.items[0].parentKey).toBeUndefined(); }); - it("should update an item", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should update an item', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.update("Suzie", { name: "Devon" }); + result.current.update('Suzie', {name: 'Devon'}); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(1); - expect(result.current.items[0].children[0].children[0]).not.toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[0].children[0].value).toEqual({ - name: "Devon", - }); - expect(result.current.items[0].children[1]).toBe( - initialResult.items[0].children[1] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[0].children[0]).not.toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[0].children[0].value).toEqual({name: 'Devon'}); + expect(result.current.items[0].children[1]).toBe(initialResult.items[0].children[1]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should move an item within the same parent", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should move an item within the same parent', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.move("Brad", "Sam", 0); + result.current.move('Brad', 'Sam', 0); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).toBe( - initialResult.items[0].children[0] - ); - expect(result.current.items[0].children[1]).not.toBe( - initialResult.items[0].children[1] - ); + expect(result.current.items[0].children[0]).toBe(initialResult.items[0].children[0]); + expect(result.current.items[0].children[1]).not.toBe(initialResult.items[0].children[1]); expect(result.current.items[0].children[1].children).toHaveLength(2); - expect(result.current.items[0].children[1].children[0]).toEqual( - initialResult.items[0].children[1].children[1] - ); - expect(result.current.items[0].children[1].children[1]).toBe( - initialResult.items[0].children[1].children[0] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[1].children[0]).toEqual(initialResult.items[0].children[1].children[1]); + expect(result.current.items[0].children[1].children[1]).toBe(initialResult.items[0].children[1].children[0]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("update parentKey when a node is moved to another parent", function () { - const { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('update parentKey when a node is moved to another parent', function () { + const {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); act(() => { - result.current.move("Brad", "John", 0); + result.current.move('Brad', 'John', 0); }); const john = result.current.items[0].children[0]; @@ -793,57 +544,43 @@ describe("useTreeData", function () { expect(brad.parentKey).toBe(john.key); }); - it("should move an item to a different parent", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should move an item to a different parent', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); let initialResult = result.current; act(() => { - result.current.move("Brad", "John", 0); + result.current.move('Brad', 'John', 0); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(1); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(3); - expect(result.current.items[0].children[0]).not.toBe( - initialResult.items[0].children[0] - ); + expect(result.current.items[0].children[0]).not.toBe(initialResult.items[0].children[0]); expect(result.current.items[0].children[0].children).toHaveLength(2); - expect(result.current.items[0].children[0].children[0].value).toBe( - initialResult.items[0].children[1].children[1].value - ); - expect(result.current.items[0].children[0].children[1]).toBe( - initialResult.items[0].children[0].children[0] - ); - expect(result.current.items[0].children[1]).not.toBe( - initialResult.items[0].children[1] - ); + expect(result.current.items[0].children[0].children[0].value).toBe(initialResult.items[0].children[1].children[1].value); + expect(result.current.items[0].children[0].children[1]).toBe(initialResult.items[0].children[0].children[0]); + expect(result.current.items[0].children[1]).not.toBe(initialResult.items[0].children[1]); expect(result.current.items[0].children[1].children).toHaveLength(1); - expect(result.current.items[0].children[1].children[0]).toBe( - initialResult.items[0].children[1].children[0] - ); - expect(result.current.items[0].children[2]).toBe( - initialResult.items[0].children[2] - ); + expect(result.current.items[0].children[1].children[0]).toBe(initialResult.items[0].children[1].children[0]); + expect(result.current.items[0].children[2]).toBe(initialResult.items[0].children[2]); }); - it("should move an item to the root", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) + it('should move an item to the root', function () { + let {result} = renderHook(() => + useTreeData({initialItems: initial, getChildren, getKey}) ); let initialResult = result.current; act(() => { - result.current.move("Stacy", null, 0); + result.current.move('Stacy', null, 0); }); expect(result.current.items).not.toBe(initialResult.items); expect(result.current.items).toHaveLength(2); expect(result.current.items[0]).not.toBe(initialResult.items[0]); expect(result.current.items[0].children).toHaveLength(0); - expect(result.current.items[0].value).toEqual({ name: "Stacy" }); + expect(result.current.items[0].value).toEqual({name: 'Stacy'}); expect(result.current.items[1]).not.toBe(initialResult.items[0]); expect(result.current.items[1].children).toHaveLength(3); expect(result.current.items[1].children[0]).toBe( @@ -861,208 +598,203 @@ describe("useTreeData", function () { ); }); - it("should move an item to a new index within its current parent", function () { - let { result } = renderHook(() => - useTreeData({ initialItems: initial, getChildren, getKey }) - ); + it('should move an item to a new index within its current parent', function () { + let {result} = renderHook(() => useTreeData({initialItems: initial, getChildren, getKey})); - expect(result.current.items[0].children[0].key).toBe("John"); - expect(result.current.items[0].children[1].key).toBe("Sam"); - expect(result.current.items[0].children[2].key).toBe("Jane"); + expect(result.current.items[0].children[0].key).toBe('John'); + expect(result.current.items[0].children[1].key).toBe('Sam'); + expect(result.current.items[0].children[2].key).toBe('Jane'); act(() => { - result.current.move("Sam", "David", 2); + result.current.move('Sam', 'David', 2); }); - expect(result.current.items[0].children[0].key).toBe("John"); - expect(result.current.items[0].children[1].key).toBe("Jane"); - expect(result.current.items[0].children[2].key).toBe("Sam"); + expect(result.current.items[0].children[0].key).toBe('John'); + expect(result.current.items[0].children[1].key).toBe('Jane'); + expect(result.current.items[0].children[2].key).toBe('Sam'); act(() => { - result.current.move("Sam", "David", 0); + result.current.move('Sam', 'David', 0); }); - expect(result.current.items[0].children[0].key).toBe("Sam"); - expect(result.current.items[0].children[1].key).toBe("John"); - expect(result.current.items[0].children[2].key).toBe("Jane"); + expect(result.current.items[0].children[0].key).toBe('Sam'); + expect(result.current.items[0].children[1].key).toBe('John'); + expect(result.current.items[0].children[2].key).toBe('Jane'); act(() => { - result.current.move("Sam", "David", 1); + result.current.move('Sam', 'David', 1); }); - expect(result.current.items[0].children[0].key).toBe("John"); - expect(result.current.items[0].children[1].key).toBe("Sam"); - expect(result.current.items[0].children[2].key).toBe("Jane"); + expect(result.current.items[0].children[0].key).toBe('John'); + expect(result.current.items[0].children[1].key).toBe('Sam'); + expect(result.current.items[0].children[2].key).toBe('Jane'); }); - it("should move an item to a new index within the root", function () { - const initialItems = [...initial, { name: "Emily" }, { name: "Eli" }]; + it('should move an item to a new index within the root', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; - let { result } = renderHook(() => - useTreeData({ initialItems, getChildren, getKey }) + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) ); let initialResult = result.current; act(() => { - result.current.move("Eli", null, 1); + result.current.move('Eli', null, 1); }); expect(result.current.items).not.toEqual(initialResult.items); expect(result.current.items).toHaveLength(initialResult.items.length); expect(result.current.items[0]).toEqual(initialResult.items[0]); - expect(result.current.items[1].children).toEqual( - initialResult.items[2].children - ); + expect(result.current.items[1].children).toEqual(initialResult.items[2].children); expect(result.current.items[1].key).toEqual(initialResult.items[2].key); expect(result.current.items[1].parentKey).toEqual(null); expect(result.current.items[1].value).toEqual(initialResult.items[2].value); expect(result.current.items[2]).toEqual(initialResult.items[1]); }); - it("should move an item multiple times within the root", function () { - const initialItems = [...initial, { name: "Emily" }, { name: "Eli" }]; + it('should move an item multiple times within the root', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; - let { result } = renderHook(() => - useTreeData({ initialItems, getChildren, getKey }) + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) ); let initialResult = result.current; act(() => { - result.current.move("Eli", null, 2); - result.current.move("Eli", null, 3); - result.current.move("Eli", null, 1); + result.current.move('Eli', null, 2); + result.current.move('Eli', null, 3); + result.current.move('Eli', null, 1); }); expect(result.current.items).not.toEqual(initialResult.items); expect(result.current.items).toHaveLength(initialResult.items.length); expect(result.current.items[0]).toEqual(initialResult.items[0]); - expect(result.current.items[1].children).toEqual( - initialResult.items[2].children - ); + expect(result.current.items[1].children).toEqual(initialResult.items[2].children); expect(result.current.items[1].key).toEqual(initialResult.items[2].key); expect(result.current.items[1].parentKey).toEqual(null); expect(result.current.items[1].value).toEqual(initialResult.items[2].value); expect(result.current.items[2]).toEqual(initialResult.items[1]); }); - it("should move an item within its same level before the target", function () { - const initialItems = [...initial, { name: "Emily" }, { name: "Eli" }]; - let { result } = renderHook(() => - useTreeData({ initialItems, getChildren, getKey }) + it('should move an item within its same level before the target', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) ); act(() => { - result.current.moveBefore("Eli", null, 0); + result.current.move('Eli', null, 0); }); - expect(result.current.items[0].key).toEqual("Eli"); - expect(result.current.items[1].key).toEqual("David"); - expect(result.current.items[2].key).toEqual("Emily"); + expect(result.current.items[0].key).toEqual('Eli'); + expect(result.current.items[1].key).toEqual('David'); + expect(result.current.items[2].key).toEqual('Emily'); expect(result.current.items.length).toEqual(initialItems.length); }); - it("should move an item to a different level before the target", function () { - const initialItems = [...initial, { name: "Emily" }, { name: "Eli" }]; + it('should move an item to a different level before the target', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; - let { result } = renderHook(() => - useTreeData({ initialItems, getChildren, getKey }) + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) ); act(() => { - result.current.moveBefore("Eli", "David", 1); + result.current.move('Eli', 'David', 1); }); - expect(result.current.items[0].key).toEqual("David"); - expect(result.current.items[0].children[0].key).toEqual("John"); - expect(result.current.items[0].children[1].key).toEqual("Eli"); - expect(result.current.items[1].key).toEqual("Emily"); + expect(result.current.items[0].key).toEqual('David'); + expect(result.current.items[0].children[0].key).toEqual('John'); + expect(result.current.items[0].children[1].key).toEqual('Eli'); + expect(result.current.items[1].key).toEqual('Emily'); expect(result.current.items.length).toEqual(2); }); - it("should move an item to a different level after the target", function () { - const initialItems = [...initial, { name: "Emily" }, { name: "Eli" }]; - let { result } = renderHook(() => - useTreeData({ initialItems, getChildren, getKey }) + it('should move an item to a different level after the target', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) ); act(() => { - result.current.moveAfter("Eli", "David", 1); + result.current.move('Eli', 'David', 2); }); - expect(result.current.items[0].key).toEqual("David"); + expect(result.current.items[0].key).toEqual('David'); - expect(result.current.items[0].children[0].key).toEqual("John"); - expect(result.current.items[0].children[1].key).toEqual("Sam"); - expect(result.current.items[0].children[2].key).toEqual("Eli"); - expect(result.current.items[1].key).toEqual("Emily"); + expect(result.current.items[0].children[0].key).toEqual('John'); + expect(result.current.items[0].children[1].key).toEqual('Sam'); + expect(result.current.items[0].children[2].key).toEqual('Eli'); + expect(result.current.items[1].key).toEqual('Emily'); expect(result.current.items.length).toEqual(2); }); - it("should move an item to a different level at the end when the index is greater than the node list length", function () { - const initialItems = [...initial, { name: "Emily" }, { name: "Eli" }]; - let { result } = renderHook(() => - useTreeData({ initialItems, getChildren, getKey }) + it('should move an item to a different level at the end when the index is greater than the node list length', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) ); act(() => { - result.current.moveAfter("Eli", "David", 100); + result.current.move('Eli', 'David', 101); }); - expect(result.current.items[0].key).toEqual("David"); + expect(result.current.items[0].key).toEqual('David'); - expect(result.current.items[0].children[0].key).toEqual("John"); - expect(result.current.items[0].children[1].key).toEqual("Sam"); - expect(result.current.items[0].children[2].key).toEqual("Jane"); - expect(result.current.items[0].children[3].key).toEqual("Eli"); - expect(result.current.items[1].key).toEqual("Emily"); + expect(result.current.items[0].children[0].key).toEqual('John'); + expect(result.current.items[0].children[1].key).toEqual('Sam'); + expect(result.current.items[0].children[2].key).toEqual('Jane'); + expect(result.current.items[0].children[3].key).toEqual('Eli'); + expect(result.current.items[1].key).toEqual('Emily'); expect(result.current.items.length).toEqual(2); }); - it("gets the decentants of a node", function () { - const initialItems = [...initial, { name: "Emily" }, { name: "Eli" }]; - let { result } = renderHook(() => - useTreeData({ initialItems, getChildren, getKey }) + it('gets the decentants of a node', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) ); let decendants; act(() => { - const top = result.current.getItem("David"); + const top = result.current.getItem('David'); decendants = result.current.getDescendantKeys(top); }); expect(decendants).toEqual([ - "John", - "Suzie", - "Sam", - "Stacy", - "Brad", - "Jane", + 'John', + 'Suzie', + 'Sam', + 'Stacy', + 'Brad', + 'Jane' ]); }); - it("gets the decentants of a child node", function () { - const initialItems = [...initial, { name: "Emily" }, { name: "Eli" }]; - let { result } = renderHook(() => - useTreeData({ initialItems, getChildren, getKey }) + it('gets the decentants of a child node', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) ); let descendants; act(() => { - const top = result.current.getItem("Sam"); + const top = result.current.getItem('Sam'); descendants = result.current.getDescendantKeys(top); }); - expect(descendants).toEqual(["Stacy", "Brad"]); + expect(descendants).toEqual(['Stacy', 'Brad']); }); - it("returns an empty array when getting the decendant keys for a leaf node", function () { - const initialItems = [...initial, { name: "Emily" }, { name: "Eli" }]; - let { result } = renderHook(() => - useTreeData({ initialItems, getChildren, getKey }) + it('returns an empty array when getting the decendant keys for a leaf node', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) ); let descendants; act(() => { - const top = result.current.getItem("Eli"); + const top = result.current.getItem('Eli'); descendants = result.current.getDescendantKeys(top); }); expect(descendants).toEqual([]); }); - it("returns an empty array when an undefined key is supplied", function () { - const initialItems = [...initial, { name: "Emily" }, { name: "Eli" }]; - let { result } = renderHook(() => - useTreeData({ initialItems, getChildren, getKey }) + it('returns an empty array when an undefined key is supplied', function () { + const initialItems = [...initial, {name: 'Emily'}, {name: 'Eli'}]; + let {result} = renderHook(() => + useTreeData({initialItems, getChildren, getKey}) ); let descendants; act(() => { diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index e40e5f987fe..1657e80e6a4 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -17,12 +17,12 @@ import {Collection, CollectionBuilder, CollectionNode, createBranchComponent, cr import {CollectionProps, CollectionRendererContext, DefaultCollectionRenderer, ItemRenderProps} from './Collection'; import {ContextValue, DEFAULT_SLOT, Provider, RenderProps, ScrollableProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps} from './utils'; import {DisabledBehavior, DragPreviewRenderer, Expandable, forwardRefType, HoverEvents, Key, KeyboardDelegate, LinkDOMProps, MultipleSelection, RefObject} from '@react-types/shared'; -import {filterDOMProps, useObjectRef} from '@react-aria/utils'; +import {DragAndDropContext, DropIndicatorContext, useDndPersistedKeys, useRenderDropIndicator} from './DragAndDrop'; +import {DragAndDropHooks} from './useDragAndDrop'; import {DraggableCollectionState, DroppableCollectionState, Collection as ICollection, Node, SelectionBehavior, TreeState, useTreeState} from 'react-stately'; +import {filterDOMProps, useObjectRef} from '@react-aria/utils'; import React, {createContext, ForwardedRef, forwardRef, HTMLAttributes, JSX, ReactNode, useContext, useEffect, useMemo, useRef} from 'react'; import {useControlledState} from '@react-stately/utils'; -import { DragAndDropHooks } from './useDragAndDrop'; -import { DragAndDropContext, DropIndicatorContext, useDndPersistedKeys, useRenderDropIndicator } from './DragAndDrop'; class TreeCollection implements ICollection> { private flattenedRows: Node[]; @@ -173,14 +173,14 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne let hasDropHooks = !!dragAndDropHooks?.useDroppableCollectionState; let dragHooksProvided = useRef(hasDragHooks); let dropHooksProvided = useRef(hasDropHooks); - useEffect(() => { - if (dragHooksProvided.current !== hasDragHooks) { - console.warn('Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.'); - } - if (dropHooksProvided.current !== hasDropHooks) { - console.warn('Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.'); - } - }, [hasDragHooks, hasDropHooks]); + useEffect(() => { + if (dragHooksProvided.current !== hasDragHooks) { + console.warn('Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.'); + } + if (dropHooksProvided.current !== hasDropHooks) { + console.warn('Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.'); + } + }, [hasDragHooks, hasDropHooks]); let { selectionMode = 'none', expandedKeys: propExpandedKeys, @@ -218,41 +218,6 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne layoutDelegate }, state, ref); - let {focusProps, isFocused, isFocusVisible} = useFocusRing(); - let renderValues = { - isEmpty: state.collection.size === 0, - isFocused, - isFocusVisible, - state - }; - - let renderProps = useRenderProps({ - className: props.className, - style: props.style, - defaultClassName: 'react-aria-Tree', - values: renderValues - }); - - let emptyState: ReactNode = null; - let emptyStatePropOverrides: HTMLAttributes | null = null; - if (state.collection.size === 0 && props.renderEmptyState) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - let {isEmpty, ...values} = renderValues; - let content = props.renderEmptyState({...values}); - let treeGridRowProps = { - 'aria-level': 1, - 'aria-posinset': 1, - 'aria-setsize': 1 - }; - - emptyState = ( -
-
- {content} -
-
- ); - } let dragState: DraggableCollectionState | undefined = undefined; let dropState: DroppableCollectionState | undefined = undefined; let droppableCollection: DroppableCollectionResult | undefined = undefined; @@ -280,22 +245,17 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne selectionManager: state.selectionManager }); let dropTargetDelegate = dragAndDropHooks.dropTargetDelegate || ctxDropTargetDelegate || new dragAndDropHooks.ListDropTargetDelegate(state.collection, ref, {layout: 'stack', direction}); - let keyboardDelegate = useMemo( - () => - props.keyboardDelegate || - new ListKeyboardDelegate({ - collection: state.collection, - collator, - ref, - disabledKeys: state.selectionManager.disabledKeys, - disabledBehavior: state.selectionManager.disabledBehavior, - layout: 'stack', - direction, - layoutDelegate - }), - [collator, ref, state.selectionManager.disabledKeys, direction, state.collection, state.selectionManager.disabledBehavior, props.keyboardDelegate, layoutDelegate] - ); - + let keyboardDelegate = props.keyboardDelegate || + new ListKeyboardDelegate({ + collection: state.collection, + collator, + ref, + disabledKeys: state.selectionManager.disabledKeys, + disabledBehavior: state.selectionManager.disabledBehavior, + layout: 'stack', + direction, + layoutDelegate + }); droppableCollection = dragAndDropHooks.useDroppableCollection!( {keyboardDelegate, dropTargetDelegate}, dropState, @@ -304,38 +264,80 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne isRootDropTarget = dropState.isDropTarget({type: 'root'}); } - return ( - -
- - - - {emptyState} + + let {focusProps, isFocused, isFocusVisible} = useFocusRing(); + let renderValues = { + isEmpty: state.collection.size === 0, + isFocused, + isFocusVisible, + isDropTarget: isRootDropTarget, + state, + }; + + let renderProps = useRenderProps({ + className: props.className, + style: props.style, + defaultClassName: 'react-aria-Tree', + values: renderValues + }); + + let emptyState: ReactNode = null; + let emptyStatePropOverrides: HTMLAttributes | null = null; + if (state.collection.size === 0 && props.renderEmptyState) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let {isEmpty, ...values} = renderValues; + let content = props.renderEmptyState({...values}); + let treeGridRowProps = { + 'aria-level': 1, + 'aria-posinset': 1, + 'aria-setsize': 1 + }; + + emptyState = ( +
+
+ {content} +
- + ); + } + + return ( + <> + +
+ + + + {emptyState} +
+
+ {dragPreview} + ); } From 2cbccd9a6a7f40e985bf08dd1385c88c12ac3f8e Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 12 Mar 2025 12:06:30 -0500 Subject: [PATCH 14/16] lint --- packages/react-aria-components/src/Tree.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index 32c5bbb73d9..cbe2835e91f 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -271,7 +271,7 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne isFocused, isFocusVisible, isDropTarget: isRootDropTarget, - state, + state }; let renderProps = useRenderProps({ From 8ba1b4221932778dcc591c888e236f70093ed93f Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 12 Mar 2025 12:46:38 -0500 Subject: [PATCH 15/16] ts --- packages/react-aria-components/stories/Tree.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-aria-components/stories/Tree.stories.tsx b/packages/react-aria-components/stories/Tree.stories.tsx index 28fc943096f..6bfdbbe6cf2 100644 --- a/packages/react-aria-components/stories/Tree.stories.tsx +++ b/packages/react-aria-components/stories/Tree.stories.tsx @@ -600,7 +600,7 @@ function TreeDragAndDropExample(args) { } } - let draggedItems = []; + let draggedItems: any[] = []; let adjustIndexOffset = 0; for (let key of draggedKeys) { From 050499152933e8b46b6166cf6140204fd2977c7a Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Wed, 12 Mar 2025 12:47:41 -0500 Subject: [PATCH 16/16] fix listMapData destructure --- packages/@react-aria/gridlist/src/useGridListItem.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/@react-aria/gridlist/src/useGridListItem.ts b/packages/@react-aria/gridlist/src/useGridListItem.ts index 42064ecee4b..90f6443afec 100644 --- a/packages/@react-aria/gridlist/src/useGridListItem.ts +++ b/packages/@react-aria/gridlist/src/useGridListItem.ts @@ -69,7 +69,8 @@ export function useGridListItem(props: AriaGridListItemOptions, state: ListSt // let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/gridlist'); let {direction} = useLocale(); - let {onAction, linkBehavior, keyboardNavigationBehavior} = listMap.get(state)!; + const listMapData = listMap.get(state); + let {onAction, linkBehavior = 'action', keyboardNavigationBehavior = 'arrow'} = listMapData || {}; let descriptionId = useSlotId(); // We need to track the key of the item at the time it was last focused so that we force