diff --git a/modules/docs/content/_index.md b/modules/docs/content/_index.md index 543a222..772c854 100644 --- a/modules/docs/content/_index.md +++ b/modules/docs/content/_index.md @@ -330,3 +330,12 @@ return ( /> ); ``` + + +## Filtering + +I need to decide where to do the filtering on the dones. Some things to consider are this. When the tree is filtered, we probably want have different open state. For example, if everything is closed and we type a search, we want all the folders to suddenly be open. But if they clear out their search, it would be good to leave all the folders closed again. + +So the tree needs to know if it is filtered or not. So the filtering should happen in the tree. + +this.rows = ConstructNodes diff --git a/modules/docs/content/terms.md b/modules/docs/content/terms.md index 964148d..86e9818 100644 --- a/modules/docs/content/terms.md +++ b/modules/docs/content/terms.md @@ -2,14 +2,14 @@ This is the list of the common domain models found in react-arborist. -* **Tree View**: The main component that renders the UI. -* **Source Data**: Any data you bring from the outside world. -* **Node Object**: An interface that the TreeManager and TreeController expects -* **Source Data Proxy**: A wrapper around SourceData conforming to the NodeObject with methods to mutating SourceData. -* **Tree Manager:** Responsible for responding to change events from the TreeView -* **Tree Controller**: The programming API for developers to interact with the tree. -* **Node Controller**: The programming API for developers to interact with the node. -* **Partial Controller**: An object with the properties `value` and `onChange`, used for managing a slice of the component's state. +- **Tree View**: The main component that renders the UI. +- **Source Data**: Any data you bring from the outside world. +- **Node Object**: An interface that the TreeManager and TreeController expects +- **Source Data Proxy**: A wrapper around SourceData conforming to the NodeObject with methods to mutating SourceData. +- **Tree Manager:** Responsible for responding to change events from the TreeView +- **Tree Controller**: The programming API for developers to interact with the tree. +- **Node Controller**: The programming API for developers to interact with the node. +- **Partial Controller**: An object with the properties `value` and `onChange`, used for managing a slice of the component's state. ## Details @@ -17,8 +17,6 @@ When you reach for react-arborist you usually will bring with you some _source d The _TreeView_ component receives many props, one of which is called "nodes". The "nodes" prop is a _partial controller_ object with `value` and `onChange` properties. The nodes partial controller value must be an array of _node objects_. - - A _node object_ is anything with the following interface: ```ts @@ -29,10 +27,9 @@ type NodeObject = { children: NodeObject[] | null; isLeaf: boolean; level: number; -} +}; ``` - You can convert the _source data_ into _node objects_ yourself, or you can use a helper function provided by react-arborist called `createTreeManager(sourceData, options)`. It will return a _TreeManager_ instance. The _tree manager_ will have a property called "nodes" which will return an array of objects that conform to the _node object_ interface. However, they will be instances of the _SourceDataProxy_ class. These objects have all the properties required of _node objects_ along with methods to mutate the _source data_ it contains. The _TreeManager_ also has methods for mutating the _source data_ in response to change events. ### Internals @@ -41,4 +38,4 @@ Now let's see the internals of the _TreeView_ component. All props are given to During a render, the _tree controller_ will flatten the _node objects_ into an array of _node controllers_. This array can be accessed with the `.nodes` property. -To create a _NodeController_ instance, you will need the parent _tree controller_, the relevant _node object_ and the _row index_ where it will be rendered in UI. That class provides a bunch of convince methods for developers to use when rendering the UI. \ No newline at end of file +To create a _NodeController_ instance, you will need the parent _tree controller_, the relevant _node object_ and the _row index_ where it will be rendered in UI. That class provides a bunch of convince methods for developers to use when rendering the UI. diff --git a/modules/react-arborist/package.json b/modules/react-arborist/package.json index f60dde6..d3e88b0 100644 --- a/modules/react-arborist/package.json +++ b/modules/react-arborist/package.json @@ -2,14 +2,12 @@ "name": "react-arborist", "version": "3.4.0", "license": "MIT", - "source": "src/index.ts", - "main": "dist/main/index.js", - "module": "dist/module/index.js", + "type": "module", + "exports": "./dist/module/index.js", "types": "dist/module/index.d.ts", "sideEffects": false, "scripts": { - "build:cjs": "tsc --outDir dist/main", - "build:es": "tsc --outDir dist/module --module es2022 --moduleResolution node", + "build:es": "tsc --outDir dist/module", "build": "npm-run-all clean -p 'build:**'", "clean": "rimraf dist", "prepack": "yarn build", @@ -38,11 +36,9 @@ "filterable" ], "dependencies": { - "react-dnd": "^14.0.3", - "react-dnd-html5-backend": "^14.0.3", + "react-aria": "^3.32.1", "react-window": "^1.8.10", - "redux": "^5.0.0", - "use-sync-external-store": "^1.2.0" + "when-clause": "0.0.3" }, "peerDependencies": { "react": ">= 16.14", diff --git a/modules/react-arborist/src/commands/default-commands.ts b/modules/react-arborist/src/commands/default-commands.ts new file mode 100644 index 0000000..5fb0d87 --- /dev/null +++ b/modules/react-arborist/src/commands/default-commands.ts @@ -0,0 +1,164 @@ +import { NodeController } from "../controllers/node-controller.js"; +import { TreeController } from "../controllers/tree-controller.js"; +import { NodeType } from "../nodes/types.js"; +import { focusNextElement, focusPrevElement } from "../utils.js"; + +export type Tree = TreeController; + +export function focusFirst(tree: Tree) { + if (tree.firstNode) tree.focus(tree.firstNode.id); +} + +export function focusLast(tree: Tree) { + if (tree.lastNode) tree.focus(tree.lastNode.id); +} + +export function focusNext(tree: Tree) { + const next = tree.nextNode || tree.firstNode; + if (next) tree.focus(next.id); +} + +export function focusPrev(tree: Tree) { + const prev = tree.prevNode || tree.lastNode; + if (prev) tree.focus(prev.id); +} + +export function focusPrevPage(tree: Tree) { + const start = tree.visibleStartIndex; + const stop = tree.visibleStopIndex; + const page = stop - start; + let index = tree.focusedNode?.rowIndex ?? 0; + if (index > start) { + index = start; + } else { + index = Math.max(start - page, 0); + } + const node = tree.rows.at(index); + if (node) tree.focus(node.id); +} + +export function focusNextPage(tree: Tree) { + const start = tree.visibleStartIndex; + const stop = tree.visibleStopIndex; + const page = stop - start; + let index = tree.focusedNode?.rowIndex ?? 0; + if (index < stop) { + index = stop; + } else { + index = Math.min(index + page, tree.rows.length - 1); + } + const node = tree.rows.at(index); + if (node) tree.focus(node.id); +} + +export function focusParent(tree: Tree) { + const parentId = tree.focusedNode?.parentId; + if (parentId) tree.focus(parentId); +} + +export function close(tree: Tree) { + tree.focusedNode?.close(); +} + +export function open(tree: Tree) { + tree.focusedNode?.open(); +} + +export function focusOutsideNext(tree: Tree) { + if (tree.element) focusNextElement(tree.element); +} + +export function focusOutsidePrev(tree: Tree) { + if (tree.element) focusPrevElement(tree.element); +} + +export function destroy(tree: Tree) { + if (confirm("Are you sure you want to delete?")) { + if (tree.selectedIds.length) { + tree.destroy(tree.selectedIds); + } else if (tree.focusedNode) { + tree.destroy([tree.focusedNode.id]); + } + } +} + +function create(tree: Tree, nodeType: NodeType) { + const node = tree.focusedNode; + const parentId = getInsertParentId(node); + const index = getInsertIndex(tree, node); + const data = tree.props.nodes.initialize({ nodeType }); + tree.create({ parentId, index, data }); + tree.edit(data.id); + tree.focus(data.id); +} + +export function createLeaf(tree: Tree) { + create(tree, "leaf"); +} + +export function createInternal(tree: Tree) { + create(tree, "internal"); +} + +function getInsertParentId(focus: NodeController | null) { + if (!focus) return null; + if (focus.isOpen) return focus.id; + return focus.parentId; +} + +function getInsertIndex(tree: Tree, focus: NodeController | null) { + if (!focus) return tree.props.nodes.value.length; + if (focus.isOpen) return 0; + return focus.childIndex + 1; +} + +export function edit(tree: Tree) { + const node = tree.focusedNode; + if (node) tree.edit(node.id); +} + +export function moveSelectionStart(tree: Tree) { + const prev = tree.prevNode; + if (prev) { + tree.focus(prev.id); + tree.selectContiguous(prev.id); + } +} + +export function moveSelectionEnd(tree: Tree) { + const next = tree.nextNode; + if (next) { + tree.focus(next.id); + tree.selectContiguous(next.id); + } +} + +export function select(tree: Tree) { + const node = tree.focusedNode; + if (node) tree.select(node.id); +} + +export function selectAll(tree: Tree) { + tree.selectAll(); +} + +export function toggle(tree: Tree) { + const node = tree.focusedNode; + if (node) { + node.isOpen ? tree.close(node.id) : tree.open(node.id); + } +} + +export function openSiblings(tree: Tree) { + const node = tree.focusedNode; + if (!node) return; + const parent = node.parent; + if (!parent) return; + + for (let sibling of parent.object.children!) { + if (!sibling.isLeaf) { + node.isOpen ? tree.close(sibling.id) : tree.open(sibling.id); + } + } + tree.scrollTo(node.rowIndex); +} diff --git a/modules/react-arborist/src/commands/types.ts b/modules/react-arborist/src/commands/types.ts new file mode 100644 index 0000000..bd2de20 --- /dev/null +++ b/modules/react-arborist/src/commands/types.ts @@ -0,0 +1,7 @@ +import { TreeController } from "../controllers/tree-controller.js"; + +export type CommandObject = { + [name: string]: CommandBody; +}; + +export type CommandBody = (tree: TreeController) => void; diff --git a/modules/react-arborist/src/components/cursor.tsx b/modules/react-arborist/src/components/cursor.tsx deleted file mode 100644 index 658664c..0000000 --- a/modules/react-arborist/src/components/cursor.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useDndContext, useTreeApi } from "../context"; - -export function Cursor() { - const tree = useTreeApi(); - const state = useDndContext(); - const cursor = state.cursor; - if (!cursor || cursor.type !== "line") return null; - const indent = tree.indent; - const top = - tree.rowHeight * cursor.index + - (tree.props.padding ?? tree.props.paddingTop ?? 0); - const left = indent * cursor.level; - const Cursor = tree.renderCursor; - return ; -} diff --git a/modules/react-arborist/src/components/default-container.tsx b/modules/react-arborist/src/components/default-container.tsx deleted file mode 100644 index f92dd5c..0000000 --- a/modules/react-arborist/src/components/default-container.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import { FixedSizeList } from "react-window"; -import { useDataUpdates, useTreeApi } from "../context"; -import { focusNextElement, focusPrevElement } from "../utils"; -import { ListOuterElement } from "./list-outer-element"; -import { ListInnerElement } from "./list-inner-element"; -import { RowContainer } from "./row-container"; - -let focusSearchTerm = ""; -let timeoutId: any = null; - -/** - * All these keyboard shortcuts seem like they should be configurable. - * Each operation should be a given a name and separated from - * the event handler. Future clean up welcome. - */ -export function DefaultContainer() { - useDataUpdates(); - const tree = useTreeApi(); - return ( -
{ - if (!e.currentTarget.contains(e.relatedTarget)) { - tree.onFocus(); - } - }} - onBlur={(e) => { - if (!e.currentTarget.contains(e.relatedTarget)) { - tree.onBlur(); - } - }} - onKeyDown={(e) => { - if (tree.isEditing) { - return; - } - if (e.key === "Backspace") { - if (!tree.props.onDelete) return; - const ids = Array.from(tree.selectedIds); - if (ids.length > 1) { - let nextFocus = tree.mostRecentNode; - while (nextFocus && nextFocus.isSelected) { - nextFocus = nextFocus.nextSibling; - } - if (!nextFocus) nextFocus = tree.lastNode; - tree.focus(nextFocus, { scroll: false }); - tree.delete(Array.from(ids)); - } else { - const node = tree.focusedNode; - if (node) { - const sib = node.nextSibling; - const parent = node.parent; - tree.focus(sib || parent, { scroll: false }); - tree.delete(node); - } - } - return; - } - if (e.key === "Tab" && !e.shiftKey) { - e.preventDefault(); - focusNextElement(e.currentTarget); - return; - } - if (e.key === "Tab" && e.shiftKey) { - e.preventDefault(); - focusPrevElement(e.currentTarget); - return; - } - if (e.key === "ArrowDown") { - e.preventDefault(); - const next = tree.nextNode; - if (e.metaKey) { - tree.select(tree.focusedNode); - tree.activate(tree.focusedNode); - return; - } else if (!e.shiftKey || tree.props.disableMultiSelection) { - tree.focus(next); - return; - } else { - if (!next) return; - const current = tree.focusedNode; - if (!current) { - tree.focus(tree.firstNode); - } else if (current.isSelected) { - tree.selectContiguous(next); - } else { - tree.selectMulti(next); - } - return; - } - } - if (e.key === "ArrowUp") { - e.preventDefault(); - const prev = tree.prevNode; - if (!e.shiftKey || tree.props.disableMultiSelection) { - tree.focus(prev); - return; - } else { - if (!prev) return; - const current = tree.focusedNode; - if (!current) { - tree.focus(tree.lastNode); // ? - } else if (current.isSelected) { - tree.selectContiguous(prev); - } else { - tree.selectMulti(prev); - } - return; - } - } - if (e.key === "ArrowRight") { - const node = tree.focusedNode; - if (!node) return; - if (node.isInternal && node.isOpen) { - tree.focus(tree.nextNode); - } else if (node.isInternal) tree.open(node.id); - return; - } - if (e.key === "ArrowLeft") { - const node = tree.focusedNode; - if (!node || node.isRoot) return; - if (node.isInternal && node.isOpen) tree.close(node.id); - else if (!node.parent?.isRoot) { - tree.focus(node.parent); - } - return; - } - if (e.key === "a" && e.metaKey && !tree.props.disableMultiSelection) { - e.preventDefault(); - tree.selectAll(); - return; - } - if (e.key === "a" && !e.metaKey && tree.props.onCreate) { - tree.createLeaf(); - return; - } - if (e.key === "A" && !e.metaKey) { - if (!tree.props.onCreate) return; - tree.createInternal(); - return; - } - - if (e.key === "Home") { - // add shift keys - e.preventDefault(); - tree.focus(tree.firstNode); - return; - } - if (e.key === "End") { - // add shift keys - e.preventDefault(); - tree.focus(tree.lastNode); - return; - } - if (e.key === "Enter") { - const node = tree.focusedNode; - if (!node) return; - if (!node.isEditable || !tree.props.onRename) return; - setTimeout(() => { - if (node) tree.edit(node); - }); - return; - } - if (e.key === " ") { - e.preventDefault(); - const node = tree.focusedNode; - if (!node) return; - if (node.isLeaf) { - node.select(); - node.activate(); - } else { - node.toggle(); - } - return; - } - if (e.key === "*") { - const node = tree.focusedNode; - if (!node) return; - tree.openSiblings(node); - return; - } - if (e.key === "PageUp") { - e.preventDefault(); - tree.pageUp(); - return; - } - if (e.key === "PageDown") { - e.preventDefault(); - tree.pageDown(); - } - - // If they type a sequence of characters - // collect them. Reset them after a timeout. - // Use it to search the tree for a node, then focus it. - // Clean this up a bit later - clearTimeout(timeoutId); - focusSearchTerm += e.key; - timeoutId = setTimeout(() => { - focusSearchTerm = ""; - }, 600); - const node = tree.visibleNodes.find((n) => { - // @ts-ignore - const name = n.data.name; - if (typeof name === "string") { - return name.toLowerCase().startsWith(focusSearchTerm); - } else return false; - }); - if (node) tree.focus(node.id); - }} - > - {/* @ts-ignore */} - tree.visibleNodes[index]?.id || index} - outerElementType={ListOuterElement} - innerElementType={ListInnerElement} - onScroll={tree.props.onScroll} - onItemsRendered={tree.onItemsRendered.bind(tree)} - ref={tree.list} - > - {RowContainer} - -
- ); -} diff --git a/modules/react-arborist/src/components/default-cursor.tsx b/modules/react-arborist/src/components/default-cursor.tsx index 00565ea..d40a204 100644 --- a/modules/react-arborist/src/components/default-cursor.tsx +++ b/modules/react-arborist/src/components/default-cursor.tsx @@ -1,5 +1,5 @@ import React, { CSSProperties } from "react"; -import { CursorProps } from "../types/renderers"; +import { CursorRendererProps } from "../types/renderers.js"; const placeholderStyle = { display: "flex", @@ -21,17 +21,17 @@ const circleStyle = { borderRadius: "50%", }; -export const DefaultCursor = React.memo(function DefaultCursor({ +export const DefaultCursorRenderer = React.memo(function DefaultCursorRenderer({ top, left, indent, -}: CursorProps) { +}: CursorRendererProps) { const style: CSSProperties = { position: "absolute", pointerEvents: "none", - top: top - 2 + "px", - left: left + "px", - right: indent + "px", + insetBlockStart: top - 2 + "px", + insetInlineStart: left + "px", + insetInlineEnd: indent + "px", }; return (
diff --git a/modules/react-arborist/src/components/default-drag-preview.tsx b/modules/react-arborist/src/components/default-drag-preview.tsx deleted file mode 100644 index fb3b28a..0000000 --- a/modules/react-arborist/src/components/default-drag-preview.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { CSSProperties, memo } from "react"; -import { XYCoord } from "react-dnd"; -import { useTreeApi } from "../context"; -import { DragPreviewProps } from "../types/renderers"; -import { IdObj } from "../types/utils"; - -const layerStyles: CSSProperties = { - position: "fixed", - pointerEvents: "none", - zIndex: 100, - left: 0, - top: 0, - width: "100%", - height: "100%", -}; - -const getStyle = (offset: XYCoord | null) => { - if (!offset) return { display: "none" }; - const { x, y } = offset; - return { transform: `translate(${x}px, ${y}px)` }; -}; - -const getCountStyle = (offset: XYCoord | null) => { - if (!offset) return { display: "none" }; - const { x, y } = offset; - return { transform: `translate(${x + 10}px, ${y + 10}px)` }; -}; - -export function DefaultDragPreview({ - offset, - mouse, - id, - dragIds, - isDragging, -}: DragPreviewProps) { - return ( - - - - - - - ); -} - -const Overlay = memo(function Overlay(props: { - children: JSX.Element[]; - isDragging: boolean; -}) { - if (!props.isDragging) return null; - return
{props.children}
; -}); - -function Position(props: { children: JSX.Element; offset: XYCoord | null }) { - return ( -
- {props.children} -
- ); -} - -function Count(props: { count: number; mouse: XYCoord | null }) { - const { count, mouse } = props; - if (count > 1) - return ( -
- {count} -
- ); - else return null; -} - -const PreviewNode = memo(function PreviewNode(props: { - id: string | null; - dragIds: string[]; -}) { - const tree = useTreeApi(); - const node = tree.get(props.id); - if (!node) return null; - return ( - - ); -}); diff --git a/modules/react-arborist/src/components/default-node.tsx b/modules/react-arborist/src/components/default-node.tsx deleted file mode 100644 index 48a40b8..0000000 --- a/modules/react-arborist/src/components/default-node.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useEffect, useRef } from "react"; -import { NodeRendererProps } from "../types/renderers"; -import { IdObj } from "../types/utils"; - -export function DefaultNode(props: NodeRendererProps) { - return ( -
- { - e.stopPropagation(); - props.node.toggle(); - }} - > - {props.node.isLeaf ? "🌳" : props.node.isOpen ? "🗁" : "🗀"} - {" "} - {props.node.isEditing ? : } -
- ); -} - -function Show(props: NodeRendererProps) { - return ( - <> - {/* @ts-ignore */} - {props.node.data.name} - - ); -} - -function Edit({ node }: NodeRendererProps) { - const input = useRef(); - - useEffect(() => { - input.current?.focus(); - input.current?.select(); - }, []); - - return ( - node.reset()} - onKeyDown={(e) => { - if (e.key === "Escape") node.reset(); - if (e.key === "Enter") node.submit(input.current?.value || ""); - }} - > - ); -} diff --git a/modules/react-arborist/src/components/default-row.tsx b/modules/react-arborist/src/components/default-row.tsx deleted file mode 100644 index 9935bf6..0000000 --- a/modules/react-arborist/src/components/default-row.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import { RowRendererProps } from "../types/renderers"; -import { IdObj } from "../types/utils"; - -export function DefaultRow({ - node, - attrs, - innerRef, - children, -}: RowRendererProps) { - return ( -
e.stopPropagation()} - onClick={node.handleClick} - > - {children} -
- ); -} diff --git a/modules/react-arborist/src/components/drag-preview-container.tsx b/modules/react-arborist/src/components/drag-preview-container.tsx deleted file mode 100644 index 1c174fe..0000000 --- a/modules/react-arborist/src/components/drag-preview-container.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useDragLayer } from "react-dnd"; -import { useDndContext, useTreeApi } from "../context"; -import { DefaultDragPreview } from "./default-drag-preview"; - -export function DragPreviewContainer() { - const tree = useTreeApi(); - const { offset, mouse, item, isDragging } = useDragLayer((m) => { - return { - offset: m.getSourceClientOffset(), - mouse: m.getClientOffset(), - item: m.getItem(), - isDragging: m.isDragging(), - }; - }); - - const DragPreview = tree.props.renderDragPreview || DefaultDragPreview; - return ( - - ); -} diff --git a/modules/react-arborist/src/components/list-inner-element.tsx b/modules/react-arborist/src/components/list-inner-element.tsx deleted file mode 100644 index e4ba3b9..0000000 --- a/modules/react-arborist/src/components/list-inner-element.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import { forwardRef } from "react"; -import { useTreeApi } from "../context"; - -export const ListInnerElement = forwardRef(function InnerElement( - { style, ...rest }, - ref -) { - const tree = useTreeApi(); - const paddingTop = tree.props.padding ?? tree.props.paddingTop ?? 0; - const paddingBottom = tree.props.padding ?? tree.props.paddingBottom ?? 0; - return ( -
- ); -}); diff --git a/modules/react-arborist/src/components/list-outer-element.tsx b/modules/react-arborist/src/components/list-outer-element.tsx deleted file mode 100644 index 672419e..0000000 --- a/modules/react-arborist/src/components/list-outer-element.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { forwardRef } from "react"; -import { useTreeApi } from "../context"; -import { treeBlur } from "../state/focus-slice"; -import { Cursor } from "./cursor"; - -export const ListOuterElement = forwardRef(function Outer( - props: React.HTMLProps, - ref -) { - const { children, ...rest } = props; - const tree = useTreeApi(); - return ( -
{ - if (e.currentTarget === e.target) tree.deselectAll(); - }} - > - - {children} -
- ); -}); - -const DropContainer = () => { - const tree = useTreeApi(); - return ( -
- -
- ); -}; diff --git a/modules/react-arborist/src/components/outer-drop.ts b/modules/react-arborist/src/components/outer-drop.ts deleted file mode 100644 index 944d08c..0000000 --- a/modules/react-arborist/src/components/outer-drop.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ReactElement } from "react"; -import { useOuterDrop } from "../dnd/outer-drop-hook"; - -export function OuterDrop(props: { children: ReactElement }) { - useOuterDrop(); - return props.children; -} diff --git a/modules/react-arborist/src/components/provider.tsx b/modules/react-arborist/src/components/provider.tsx deleted file mode 100644 index 0326c82..0000000 --- a/modules/react-arborist/src/components/provider.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { - ReactNode, - useEffect, - useImperativeHandle, - useMemo, - useRef, -} from "react"; -import { useSyncExternalStore } from "use-sync-external-store/shim"; -import { FixedSizeList } from "react-window"; -import { - DataUpdatesContext, - DndContext, - NodesContext, - TreeApiContext, -} from "../context"; -import { TreeApi } from "../interfaces/tree-api"; -import { initialState } from "../state/initial"; -import { Actions, rootReducer, RootState } from "../state/root-reducer"; -import { HTML5Backend } from "react-dnd-html5-backend"; -import { DndProvider } from "react-dnd"; -import { TreeProps } from "../types/tree-props"; -import { createStore, Store } from "redux"; -import { actions as visibility } from "../state/open-slice"; - -type Props = { - treeProps: TreeProps; - imperativeHandle: React.Ref | undefined>; - children: ReactNode; -}; - -const SERVER_STATE = initialState(); - -export function TreeProvider({ - treeProps, - imperativeHandle, - children, -}: Props) { - const list = useRef(null); - const listEl = useRef(null); - const store = useRef>( - // @ts-ignore - createStore(rootReducer, initialState(treeProps)) - ); - const state = useSyncExternalStore( - store.current.subscribe, - store.current.getState, - () => SERVER_STATE - ); - - /* The tree api object is stable. */ - const api = useMemo(() => { - return new TreeApi(store.current, treeProps, list, listEl); - }, []); - - /* Make sure the tree instance stays in sync */ - const updateCount = useRef(0); - useMemo(() => { - updateCount.current += 1; - api.update(treeProps); - }, [...Object.values(treeProps), state.nodes.open]); - - /* Expose the tree api */ - useImperativeHandle(imperativeHandle, () => api); - - /* Change selection based on props */ - useEffect(() => { - if (api.props.selection) { - api.select(api.props.selection, { focus: false }); - } else { - api.deselectAll(); - } - }, [api.props.selection]); - - /* Clear visability for filtered nodes */ - useEffect(() => { - if (!api.props.searchTerm) { - store.current.dispatch(visibility.clear(true)); - } - }, [api.props.searchTerm]); - - return ( - - - - - - {children} - - - - - - ); -} diff --git a/modules/react-arborist/src/components/row-container.tsx b/modules/react-arborist/src/components/row-container.tsx deleted file mode 100644 index 11f6e53..0000000 --- a/modules/react-arborist/src/components/row-container.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef } from "react"; -import { useDataUpdates, useNodesContext, useTreeApi } from "../context"; -import { useDragHook } from "../dnd/drag-hook"; -import { useDropHook } from "../dnd/drop-hook"; -import { useFreshNode } from "../hooks/use-fresh-node"; - -type Props = { - style: React.CSSProperties; - index: number; -}; - -export const RowContainer = React.memo(function RowContainer({ - index, - style, -}: Props) { - /* When will the will re-render. - * - * The row component is memo'd so it will only render - * when a new instance of the NodeApi class is passed - * to it. - * - * The TreeApi instance is stable. It does not - * change when the internal state changes. - * - * The TreeApi has all the references to the nodes. - * We need to clone the nodes when their state - * changes. The node class contains no state itself, - * It always checks the tree for state. The tree's - * state will always be up to date. - */ - - useDataUpdates(); // Re-render when tree props or visability changes - const _ = useNodesContext(); // So that we re-render appropriately - const tree = useTreeApi(); // Tree already has the fresh state - const node = useFreshNode(index); - - const el = useRef(null); - const dragRef = useDragHook(node); - const dropRef = useDropHook(el, node); - const innerRef = useCallback( - (n: any) => { - el.current = n; - dropRef(n); - }, - [dropRef] - ); - - const indent = tree.indent * node.level; - const nodeStyle = useMemo(() => ({ paddingLeft: indent }), [indent]); - const rowStyle = useMemo( - () => ({ - ...style, - top: - parseFloat(style.top as string) + - (tree.props.padding ?? tree.props.paddingTop ?? 0), - }), - [style, tree.props.padding, tree.props.paddingTop] - ); - const rowAttrs: React.HTMLAttributes = { - role: "treeitem", - "aria-level": node.level + 1, - "aria-selected": node.isSelected, - style: rowStyle, - tabIndex: -1, - className: tree.props.rowClassName, - }; - - useEffect(() => { - if (!node.isEditing && node.isFocused) { - el.current?.focus({ preventScroll: true }); - } - }, [node.isEditing, node.isFocused, el.current]); - - const Node = tree.renderNode; - const Row = tree.renderRow; - - return ( - - - - ); -}); diff --git a/modules/react-arborist/src/components/tree-container.tsx b/modules/react-arborist/src/components/tree-container.tsx deleted file mode 100644 index d21bd98..0000000 --- a/modules/react-arborist/src/components/tree-container.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import { useTreeApi } from "../context"; -import { DefaultContainer } from "./default-container"; - -export function TreeContainer() { - const tree = useTreeApi(); - const Container = tree.props.renderContainer || DefaultContainer; - return ( - <> - - - ); -} diff --git a/modules/react-arborist/src/components/tree-view.tsx b/modules/react-arborist/src/components/tree-view.tsx new file mode 100644 index 0000000..4629721 --- /dev/null +++ b/modules/react-arborist/src/components/tree-view.tsx @@ -0,0 +1,224 @@ +import React, { forwardRef, useContext, useRef } from "react"; +import { TreeController } from "../controllers/tree-controller.js"; +import { TreeViewProps } from "../types/tree-view-props.js"; +import { FixedSizeList } from "react-window"; +import { NodeController } from "../controllers/node-controller.js"; +import { createRowAttributes } from "../row/attributes.js"; +import { DefaultCursorRenderer } from "./default-cursor.js"; +import { useCursorProps } from "../cursor/use-cursor-props.js"; +import { useCursorContainerStyle } from "../cursor/use-cursor-container-style.js"; +import { useListInnerStyle } from "../list/use-list-inner-style.js"; +import { useNodeDrag } from "../dnd/use-node-drag.js"; +import { useNodeDrop } from "../dnd/use-node-drop.js"; +import { useDefaultProps } from "../props/use-default-props.js"; +import { useRowFocus } from "../focus/use-row-focus.js"; +import { createTreeViewAttributes } from "../tree-view/attributes.js"; +import { useTreeDrop } from "../dnd/use-tree-drop.js"; +import { NodeRendererProps, RowRendererProps } from "../types/renderers.js"; + +export function TreeView(props: Partial>) { + const filledProps = useDefaultProps(props); + const tree = new TreeController(filledProps); + return ( + + + + ); +} + +const Context = React.createContext | null>(null); + +function TreeViewProvider(props: { + tree: TreeController; + children: any; +}) { + return ( + }> + {props.children} + + ); +} + +export function useTree(): TreeController { + const value = useContext(Context); + if (!value) throw new Error("No context provided"); + return value; +} + +function TreeViewContainer() { + const tree = useTree(); + const attrs = createTreeViewAttributes(tree); + const outerRef = useRef(); + const dropProps = useTreeDrop(tree, outerRef); + + return ( +
(tree.element = node)} {...dropProps}> + {/* @ts-ignore */} + tree.rows[index]?.id || index} + outerElementType={ListOuter as any} + outerRef={outerRef} + innerElementType={ListInner as any} + direction={tree.props.direction} + onScroll={tree.props.onScroll} + ref={(node) => { + tree.listElement = node; + tree.visibleStartIndex; + }} + > + {RowContainer as any} + +
+ ); +} + +const ListOuter = forwardRef(function ListOuter( + { children, ...rest }: any, + ref, +) { + return ( +
+ + {children} +
+ ); +}); + +const ListInner = forwardRef(function ListInner( + { children, ...rest }: any, + ref, +) { + const tree = useTree(); + const style = useListInnerStyle(tree, rest.style); + return ( +
+ {children} +
+ ); +}); + +function CursorContainer() { + const tree = useTree(); + const style = useCursorContainerStyle(tree); + const props = useCursorProps(tree); + if (!props) return null; + return ( +
+ +
+ ); +} + +function RowContainer(props: { style: React.CSSProperties; index: number }) { + const tree = useTree(); + const node = tree.rows[props.index]; + const attrs = createRowAttributes(tree, node, props.style); + const ref = useRef(); + const dropProps = useNodeDrop(node, ref); + const RowRenderer = tree.props.renderRow; + useRowFocus(node, ref); + + return ( + + + + ); +} + +export function DefaultRowRenderer(props: RowRendererProps) { + return ( +
e.stopPropagation()} + ref={props.innerRef} + > + {props.children} +
+ ); +} + +function NodeContainer(props: { node: NodeController }) { + const { node } = props; + const indent = node.tree.indent * node.level; + const style = { paddingInlineStart: indent + 10 }; + const dragProps = useNodeDrag(node); + const NodeRenderer = node.tree.props.renderNode; + + return ( + + ); +} + +export function DefaultNodeRenderer(props: NodeRendererProps) { + const { node, attrs } = props; + + function onSubmit(e: any) { + e.preventDefault(); + const data = new FormData(e.currentTarget); + const changes = Object.fromEntries(data.entries()); + node.submit(changes as Partial); + } + + function onClick(e: any) { + if (e.metaKey && e.shiftKey) { + node.tree.selectAll(); + } else if (e.metaKey) { + node.isSelected ? node.deselect() : node.selectMulti(); + } else if (e.shiftKey) { + node.selectContiguous(); + } else { + node.select(); + } + } + + let classNames = ""; + for (const key in node.state) { + if (node.state[key]) classNames += " " + key; + } + + return ( +
+ { + e.stopPropagation(); + node.toggle(); + }} + > + {node.isLeaf ? "" : node.isOpen ? "📂" : "📁"} + {" "} + {node.isEditing ? ( +
+ +
+ ) : ( + + + { + /* @ts-ignore */ + node.data.name + } + + + )} +
+ ); +} diff --git a/modules/react-arborist/src/components/tree.tsx b/modules/react-arborist/src/components/tree.tsx deleted file mode 100644 index 82601a7..0000000 --- a/modules/react-arborist/src/components/tree.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { forwardRef } from "react"; -import { TreeProvider } from "./provider"; -import { TreeApi } from "../interfaces/tree-api"; -import { OuterDrop } from "./outer-drop"; -import { TreeContainer } from "./tree-container"; -import { DragPreviewContainer } from "./drag-preview-container"; -import { TreeProps } from "../types/tree-props"; -import { IdObj } from "../types/utils"; -import { useValidatedProps } from "../hooks/use-validated-props"; - -function TreeComponent( - props: TreeProps, - ref: React.Ref | undefined> -) { - const treeProps = useValidatedProps(props); - return ( - - - - - - - ); -} - -export const Tree = forwardRef(TreeComponent) as ( - props: TreeProps & { ref?: React.ForwardedRef | undefined> } -) => ReturnType; diff --git a/modules/react-arborist/src/context.ts b/modules/react-arborist/src/context.ts deleted file mode 100644 index f0c83f7..0000000 --- a/modules/react-arborist/src/context.ts +++ /dev/null @@ -1,36 +0,0 @@ -import React, { createContext, useContext, useMemo } from "react"; -import { TreeApi } from "./interfaces/tree-api"; -import { RootState } from "./state/root-reducer"; -import { IdObj } from "./types/utils"; - -export const TreeApiContext = createContext | null>(null); - -export function useTreeApi() { - const value = useContext | null>( - TreeApiContext as unknown as React.Context | null> - ); - if (value === null) throw new Error("No Tree Api Provided"); - return value; -} - -export const NodesContext = createContext(null); - -export function useNodesContext() { - const value = useContext(NodesContext); - if (value === null) throw new Error("Provide a NodesContext"); - return value; -} - -export const DndContext = createContext(null); - -export function useDndContext() { - const value = useContext(DndContext); - if (value === null) throw new Error("Provide a DnDContext"); - return value; -} - -export const DataUpdatesContext = createContext(0); - -export function useDataUpdates() { - useContext(DataUpdatesContext); -} diff --git a/modules/react-arborist/src/controllers/node-controller.ts b/modules/react-arborist/src/controllers/node-controller.ts new file mode 100644 index 0000000..6ada001 --- /dev/null +++ b/modules/react-arborist/src/controllers/node-controller.ts @@ -0,0 +1,192 @@ +import { NodeObject } from "../nodes/types.js"; +import { TreeController } from "./tree-controller.js"; + +export class NodeController { + static constructRows(tree: TreeController, objects: NodeObject[]) { + const queue = [...objects]; + const rows = []; + let index = 0; + while (queue.length > 0) { + const object = queue.shift()!; + if (tree.isVisible(object.id)) { + const node = new NodeController(tree, object, index++); + rows.push(node); + if (node.isOpen) queue.unshift(...node.object.children!); + } + } + return rows; + } + + constructor( + public tree: TreeController, + public object: NodeObject, + public rowIndex: number, + ) {} + + /* Data Access */ + get id() { + return this.object.id; + } + + get isInternal() { + return !this.isLeaf; + } + + get isLeaf() { + return this.object.isLeaf; + } + + get level() { + return this.object.level; + } + + get parentId() { + return this.object.parent?.id || null; + } + + get parent(): NodeController | null { + if (this.parentId) { + return this.tree.get(this.parentId); + } else { + return null; + } + } + + get childIndex() { + return this.object.childIndex; + } + + get data() { + return this.object.sourceData; + } + + get next() { + return this.tree.nodeAfter(this); + } + + get prev() { + return this.tree.nodeBefore(this); + } + + get state() { + return { + /* type */ + isLeaf: this.isLeaf, + isInternal: this.isInternal, + /* open */ + isOpen: this.isOpen, + isClosed: this.isClosed, + /* selection */ + isSelected: this.isSelected, + isSelectedStart: this.isSelectedStart, + isSelectedEnd: this.isSelectedEnd, + isOnlySelection: this.isOnlySelection, + /* edit */ + isEditing: this.isEditing, + /* focus */ + isFocused: this.isFocused, + /* dnd */ + willReceiveDrop: this.willReceiveDrop, + isDragging: this.isDragging, + } as Record; + } + + isDescendantOf(node: NodeController) { + let cursor: NodeController | null = this; + while (cursor) { + if (cursor.id === node.id) return true; + cursor = cursor.parent; + } + return false; + } + + /* Open State */ + get isOpen() { + return this.isInternal && this.tree.isOpen(this.id); + } + + get isClosed() { + return this.isInternal && !this.isOpen; + } + + open() { + this.tree.open(this.id); + } + + close() { + this.tree.close(this.id); + } + + toggle() { + if (this.isInternal) { + this.isOpen ? this.close() : this.open(); + } + } + + /* Edit State */ + get isEditing() { + return this.tree.isEditId(this.id); + } + + edit() { + this.tree.edit(this.id); + } + + submit(changes: Partial) { + this.tree.submit(this.id, changes); + } + + /* Selection State */ + + get isSelected() { + return this.tree.isSelected(this.id); + } + + get isSelectedStart() { + return this.isSelected && !this.prev?.isSelected; + } + + get isSelectedEnd() { + return this.isSelected && !this.next?.isSelected; + } + + get isOnlySelection() { + return this.isSelected && this.tree.hasOneSelection; + } + + select() { + this.tree.select(this.id); + } + + selectMulti() { + this.tree.selectMulti(this.id); + } + + selectContiguous() { + this.tree.selectContiguous(this.id); + } + + deselect() { + this.tree.deselect(this.id); + } + + /* Drag and Drop State */ + + get isDraggable() { + return this.tree.isDraggable(this); + } + + get isDragging() { + return this.tree.isDragging(this.id); + } + + get willReceiveDrop() { + return this.tree.willReceiveDrop(this.id); + } + + /* Focus */ + + get isFocused() { + return this.tree.isFocused(this.id); + } +} diff --git a/modules/react-arborist/src/controllers/tree-controller.ts b/modules/react-arborist/src/controllers/tree-controller.ts new file mode 100644 index 0000000..140b4f6 --- /dev/null +++ b/modules/react-arborist/src/controllers/tree-controller.ts @@ -0,0 +1,403 @@ +import { Align, FixedSizeList } from "react-window"; +import { CursorState } from "../cursor/types.js"; +import { safeToDrop } from "../dnd/safe-to-drop.js"; +import { ShortcutManager } from "../shortcuts/shortcut-manager.js"; +import { TreeViewProps } from "../types/tree-view-props.js"; +import { NodeController } from "./node-controller.js"; + +export class TreeController { + rows: NodeController[]; + element: HTMLDivElement | null = null; + listElement: FixedSizeList | null = null; + + constructor(public props: TreeViewProps) { + this.rows = NodeController.constructRows(this, props.nodes.value); + } + + /* Dimensions */ + + get width() { + return this.props.width ?? 300; + } + + get height() { + return this.props.height ?? 500; + } + + get listHeight() { + return this.rows.length * this.rowHeight; + } + + get rowHeight() { + return this.props.rowHeight ?? 24; + } + + get overscanCount() { + return this.props.overscanCount ?? 1; + } + + get indent() { + return this.props.indent ?? 24; + } + + get paddingTop() { + return this.props.padding ?? this.props.paddingTop ?? 0; + } + + get paddingBottom() { + return this.props.padding ?? this.props.paddingBottom ?? 0; + } + + get visibleStartIndex() { + // @ts-ignore + const offset = this.listElement?.state?.scrollOffset || 0; + const rowsAbove = Math.ceil(offset / this.rowHeight); + return rowsAbove; + } + + get visibleStopIndex() { + // @ts-ignore + const offset = this.listElement?.state?.scrollOffset || 0; + const bottom = offset + this.height; + const rowsAbove = Math.floor(bottom / this.rowHeight); + return rowsAbove; + } + + /* Node Getters */ + + get rootNodeObjects() { + return this.props.nodes.value; + } + + get focusedNode() { + return this.get(this.props.focus.value.id); + } + + get firstNode() { + return this.rows[0] || null; + } + + get lastNode() { + const len = this.rows.length; + return len === 0 ? null : this.rows[len - 1] || null; + } + + get nextNode() { + if (this.focusedNode) { + return this.rows[this.focusedNode.rowIndex + 1] || null; + } + return null; + } + + get prevNode() { + if (this.focusedNode) { + return this.rows[this.focusedNode.rowIndex - 1] || null; + } + return null; + } + + get dragNodes() { + return this.getAll(this.props.dnd.value.dragItems); + } + + get dragSourceNode() { + return this.get(this.props.dnd.value.dragSourceId); + } + + get dropTargetParentNode() { + return this.get(this.props.dnd.value.targetParentId); + } + + get dropTargetIndex() { + return this.props.dnd.value.targetIndex; + } + + get(id: string | null): NodeController | null { + if (id === null) return null; + const index = this.indexOf(id); + if (index !== null) { + return this.rows[index] || null; + } else { + return null; + } + } + + at(index: number) { + return this.rows[index] || null; + } + + getAll(ids: string[]) { + return ids + .map((id) => this.get(id)) + .filter((n) => !!n) as NodeController[]; + } + + nodeBefore(node: NodeController) { + return this.rows[node.rowIndex - 1] || null; + } + + nodeAfter(node: NodeController) { + return this.rows[node.rowIndex + 1] || null; + } + + nodesBetween(startId: string | null, endId: string | null) { + if (startId === null || endId === null) return []; + const index1 = this.indexOf(startId) ?? 0; + const index2 = this.indexOf(endId); + if (index2 === null) return []; + const start = Math.min(index1, index2); + const end = Math.max(index1, index2); + return this.rows.slice(start, end + 1); + } + + indexOf(id: string) { + if (!id) return null; + const index = this.rows.findIndex((node) => node.id === id); + return index === -1 ? null : index; + } + + /* Open State */ + + isOpen(id: string) { + if (id in this.props.opens.value) { + return this.props.opens.value[id] || false; + } else { + return this.props.openByDefault; // default open state + } + } + + open(id: string) { + this.props.opens.onChange({ + value: { ...this.props.opens.value, [id]: true }, + type: "open", + ids: [id], + }); + } + + close(id: string) { + this.props.opens.onChange({ + value: { ...this.props.opens.value, [id]: false }, + type: "close", + ids: [id], // maybe move this to payload + }); + } + + /* CRUD Operations */ + + create(args: { parentId: string | null; index: number; data: T }) { + this.props.nodes.onChange({ type: "create", ...args }); + } + + update(args: { id: string; changes: Partial }) { + this.props.nodes.onChange({ type: "update", ...args }); + } + + destroy(ids: string[]) { + this.props.nodes.onChange({ type: "destroy", ids: ids }); + } + + /* Edit State */ + + get isEditing() { + return this.props.edit.value !== null; + } + + isEditId(id: string) { + return this.props.edit.value === id; + } + + edit(id: string) { + this.props.edit.onChange({ value: id }); + } + + submit(id: string, changes: Partial) { + this.update({ id, changes }); + this.props.edit.onChange({ value: null }); + this.focus(id); + } + + /* Selection State */ + + get selectedIds() { + const ids = []; + for (const id in this.props.selection.value) { + if (this.props.selection.value[id] === true) ids.push(id); + } + return ids; + } + + get hasOneSelection() { + return this.selectedIds.length === 1; + } + + isSelected(id: string) { + return this.props.selection.value[id] === true; + } + + select(id: string) { + this.props.selection.onChange({ type: "select", id }); + this.focus(id); + } + + selectMulti(id: string) { + this.props.selection.onChange({ type: "select-multi", id }); + this.focus(id); + } + + selectContiguous(id: string) { + this.props.selection.onChange({ + type: "select-contiguous", + id, + tree: this, + }); + this.focus(id); + } + + selectAll() { + return this.props.selection.onChange({ + type: "select-all", + tree: this, + }); + } + + deselect(id: string) { + this.props.selection.onChange({ type: "deselect", id }); + this.focus(id); + } + + /* Drag and Drop State */ + + isDraggable(node: NodeController) { + // todo + return true; + } + + isDragging(id: string) { + return this.props.dnd.value.dragSourceId === id; + } + + willReceiveDrop(id: string) { + const { targetParentId, targetIndex } = this.props.dnd.value; + return id === targetParentId && targetIndex === null; + } + + dragStart(id: string) { + const ids = this.isSelected(id) ? this.selectedIds : [id]; + this.props.dnd.onChange({ + type: "drag-start", + dragSourceId: id, + dragItems: ids, + }); + } + + draggingOver(parentId: string | null, index: number | null) { + this.props.dnd.onChange({ + type: "dragging-over", + targetParentId: parentId, + targetIndex: index, + }); + } + + canDrop() { + return safeToDrop(this); + } + + drop() { + const dnd = this.props.dnd.value; + this.props.nodes.onChange({ + type: "move", + sourceIds: dnd.dragItems, + targetParentId: dnd.targetParentId, + targetIndex: dnd.targetIndex || 0, + }); + if (dnd.targetParentId) this.open(dnd.targetParentId); + } + + dragEnd() { + this.props.dnd.onChange({ type: "drag-end" }); + this.props.cursor.onChange({ value: null }); + } + + /* Drop Cursor State */ + + showCursor(value: CursorState) { + this.props.cursor.onChange({ value }); + } + + hideCursor() { + this.props.cursor.onChange({ value: null }); + } + + /* Focus */ + + isFocused(id: string) { + return this.hasFocus && this.props.focus.value.id === id; + } + + get hasFocus() { + return this.props.focus.value.isWithinTree; + } + + focus(id: string) { + const node = this.get(id); + if (node) { + this.props.focus.onChange({ id, type: "node-focus" }); + this.scrollTo(node.rowIndex); + } + } + + onFocus() { + this.props.focus.onChange({ type: "tree-focus" }); + this.focus(this.focusedNode?.id || this.firstNode?.id); + } + + onBlur() { + this.props.focus.onChange({ type: "tree-blur" }); + } + + /* Scroll Positions */ + + scrollTo(index: number, align: Align = "smart") { + this.listElement?.scrollToItem(index, align); + } + + /* Visibility */ + + isVisible(id: string) { + if (id in this.props.visible.value) { + return this.props.visible.value[id] === true; + } else { + return true; // default visible state + } + } + + /* Commands */ + exec(command: string) { + if (command in this.props.commands) { + return this.props.commands[command](this); + } else { + throw new Error( + `Command not found: "${command}". To fix, ensure that you pass an object to the commands prop in the component that has a key with then name "${command}"`, + ); + } + } + + /* Keyboard Shortcuts */ + handleKeyDown(e: React.KeyboardEvent) { + if (this.isEditing) return; + const focused = this.focusedNode; + const shortcut = new ShortcutManager(this.props.shortcuts).find( + e.nativeEvent, + { + isOpen: focused?.isOpen, + isClosed: focused?.isClosed, + isInternal: focused?.isInternal, + isLeaf: focused?.isLeaf, + }, + ); + if (shortcut) { + e.preventDefault(); + this.exec(shortcut.command); + } + } +} diff --git a/modules/react-arborist/src/cursor/types.ts b/modules/react-arborist/src/cursor/types.ts new file mode 100644 index 0000000..0181a6d --- /dev/null +++ b/modules/react-arborist/src/cursor/types.ts @@ -0,0 +1,19 @@ +import { PartialController } from "../types/utils.js"; + +export type LineCursor = { + type: "line"; + index: number; + level: number; +}; + +export type HighlightCursor = { + type: "highlight"; + id: string; +}; + +export type CursorState = HighlightCursor | LineCursor | null; +export type CursorOnChangeEvent = { value: CursorState }; +export type CursorPartialController = PartialController< + CursorState, + CursorOnChangeEvent +>; diff --git a/modules/react-arborist/src/cursor/use-cursor-container-style.ts b/modules/react-arborist/src/cursor/use-cursor-container-style.ts new file mode 100644 index 0000000..e4f771b --- /dev/null +++ b/modules/react-arborist/src/cursor/use-cursor-container-style.ts @@ -0,0 +1,12 @@ +import { CSSProperties } from "react"; +import { TreeController } from "../controllers/tree-controller.js"; + +export function useCursorContainerStyle(tree: TreeController) { + return { + height: tree.listHeight, + width: "100%", + position: "absolute", + insetInlineStart: "0", + insetBlockStart: "0", + } as CSSProperties; +} diff --git a/modules/react-arborist/src/cursor/use-cursor-props.ts b/modules/react-arborist/src/cursor/use-cursor-props.ts new file mode 100644 index 0000000..90177b9 --- /dev/null +++ b/modules/react-arborist/src/cursor/use-cursor-props.ts @@ -0,0 +1,14 @@ +import { TreeController } from "../controllers/tree-controller.js"; + +export function useCursorProps(tree: TreeController) { + const cursor = tree.props.cursor.value; + if (cursor && cursor.type === "line") { + return { + top: tree.rowHeight * cursor.index + tree.paddingTop, + left: tree.indent * cursor.level, + indent: tree.indent, + }; + } else { + return null; + } +} diff --git a/modules/react-arborist/src/cursor/use-cursor.ts b/modules/react-arborist/src/cursor/use-cursor.ts new file mode 100644 index 0000000..051ee62 --- /dev/null +++ b/modules/react-arborist/src/cursor/use-cursor.ts @@ -0,0 +1,11 @@ +import { useState } from "react"; +import { CursorPartialController, CursorState } from "./types.js"; + +export function useCursor(): CursorPartialController { + const [value, setValue] = useState(null); + + return { + value, + onChange: (e) => setValue(e.value), + }; +} diff --git a/modules/react-arborist/src/data/create-index.ts b/modules/react-arborist/src/data/create-index.ts deleted file mode 100644 index c92ceed..0000000 --- a/modules/react-arborist/src/data/create-index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NodeApi } from "../interfaces/node-api"; -import { IdObj } from "../types/utils"; - -export const createIndex = (nodes: NodeApi[]) => { - return nodes.reduce<{ [id: string]: number }>((map, node, index) => { - map[node.id] = index; - return map; - }, {}); -}; diff --git a/modules/react-arborist/src/data/create-list.ts b/modules/react-arborist/src/data/create-list.ts deleted file mode 100644 index 5da1fa1..0000000 --- a/modules/react-arborist/src/data/create-list.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { NodeApi } from "../interfaces/node-api"; -import { TreeApi } from "../interfaces/tree-api"; -import { IdObj } from "../types/utils"; - -export function createList(tree: TreeApi) { - if (tree.isFiltered) { - return flattenAndFilterTree(tree.root, tree.isMatch.bind(tree)); - } else { - return flattenTree(tree.root); - } -} - -function flattenTree(root: NodeApi): NodeApi[] { - const list: NodeApi[] = []; - function collect(node: NodeApi) { - if (node.level >= 0) { - list.push(node); - } - if (node.isOpen) { - node.children?.forEach(collect); - } - } - collect(root); - list.forEach(assignRowIndex); - return list; -} - -function flattenAndFilterTree( - root: NodeApi, - isMatch: (n: NodeApi) => boolean -): NodeApi[] { - const matches: Record = {}; - const list: NodeApi[] = []; - - function markMatch(node: NodeApi) { - const yes = !node.isRoot && isMatch(node); - if (yes) { - matches[node.id] = true; - let parent = node.parent; - while (parent) { - matches[parent.id] = true; - parent = parent.parent; - } - } - if (node.children) { - for (let child of node.children) markMatch(child); - } - } - - function collect(node: NodeApi) { - if (node.level >= 0 && matches[node.id]) { - list.push(node); - } - if (node.isOpen) { - node.children?.forEach(collect); - } - } - - markMatch(root); - collect(root); - list.forEach(assignRowIndex); - return list; -} - -function assignRowIndex(node: NodeApi, index: number) { - node.rowIndex = index; -} diff --git a/modules/react-arborist/src/data/create-root.ts b/modules/react-arborist/src/data/create-root.ts deleted file mode 100644 index ca567b3..0000000 --- a/modules/react-arborist/src/data/create-root.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { IdObj } from "../types/utils"; -import { NodeApi } from "../interfaces/node-api"; -import { TreeApi } from "../interfaces/tree-api"; - -export const ROOT_ID = "__REACT_ARBORIST_INTERNAL_ROOT__"; - -export function createRoot(tree: TreeApi): NodeApi { - function visitSelfAndChildren( - data: T, - level: number, - parent: NodeApi | null - ) { - const id = tree.accessId(data); - const node = new NodeApi({ - tree, - data, - level, - parent, - id, - children: null, - isDraggable: tree.isDraggable(data), - rowIndex: null, - }); - const children = tree.accessChildren(data); - if (children) { - node.children = children.map((child: T) => - visitSelfAndChildren(child, level + 1, node) - ); - } - return node; - } - - const root = new NodeApi({ - tree, - id: ROOT_ID, - // @ts-ignore - data: { id: ROOT_ID }, - level: -1, - parent: null, - children: null, - isDraggable: true, - rowIndex: null, - }); - - const data: readonly T[] = tree.props.data ?? []; - - root.children = data.map((child) => { - return visitSelfAndChildren(child, 0, root); - }); - - return root; -} diff --git a/modules/react-arborist/src/data/make-tree.ts b/modules/react-arborist/src/data/make-tree.ts deleted file mode 100644 index dae38d9..0000000 --- a/modules/react-arborist/src/data/make-tree.ts +++ /dev/null @@ -1,37 +0,0 @@ -// A function that turns a string of text into a tree -// Each line is a node -// The number of spaces at the beginning indicate the level - -export function makeTree(string: string) { - const root = { id: "ROOT", name: "ROOT", isOpen: true }; - let prevNode = root; - let prevLevel = -1; - let id = 1; - string.split("\n").forEach((line) => { - const name = line.trimStart(); - const level = line.length - name.length; - const diff = level - prevLevel; - const node = { id: (id++).toString(), name, isOpen: false }; - if (diff === 1) { - // First child - //@ts-ignore - node.parent = prevNode; - //@ts-ignore - prevNode.children = [node]; - } else { - // Find the parent and go up - //@ts-ignore - let parent = prevNode.parent; - for (let i = diff; i < 0; i++) { - parent = parent.parent; - } - //@ts-ignore - node.parent = parent; - parent.children.push(node); - } - prevNode = node; - prevLevel = level; - }); - - return root; -} diff --git a/modules/react-arborist/src/data/simple-tree.ts b/modules/react-arborist/src/data/simple-tree.ts deleted file mode 100644 index 9cf747e..0000000 --- a/modules/react-arborist/src/data/simple-tree.ts +++ /dev/null @@ -1,103 +0,0 @@ -type SimpleData = { id: string; name: string; children?: SimpleData[] }; - -export class SimpleTree { - root: SimpleNode; - constructor(data: T[]) { - this.root = createRoot(data); - } - - get data() { - return this.root.children?.map((node) => node.data) ?? []; - } - - create(args: { parentId: string | null; index: number; data: T }) { - const parent = args.parentId ? this.find(args.parentId) : this.root; - if (!parent) return null; - parent.addChild(args.data, args.index); - } - - move(args: { id: string; parentId: string | null; index: number }) { - const src = this.find(args.id); - const parent = args.parentId ? this.find(args.parentId) : this.root; - if (!src || !parent) return; - parent.addChild(src.data, args.index); - src.drop(); - } - - update(args: { id: string; changes: Partial }) { - const node = this.find(args.id); - if (node) node.update(args.changes); - } - - drop(args: { id: string }) { - const node = this.find(args.id); - if (node) node.drop(); - } - - find(id: string, node: SimpleNode = this.root): SimpleNode | null { - if (!node) return null; - if (node.id === id) return node as SimpleNode; - if (node.children) { - for (let child of node.children) { - const found = this.find(id, child); - if (found) return found; - } - return null; - } - return null; - } -} - -function createRoot(data: T[]) { - const root = new SimpleNode({ id: "ROOT" } as T, null); - root.children = data.map((d) => createNode(d as T, root)); - return root; -} - -function createNode(data: T, parent: SimpleNode) { - const node = new SimpleNode(data, parent); - if (data.children) - node.children = data.children.map((d) => createNode(d as T, node)); - return node; -} - -class SimpleNode { - id: string; - children?: SimpleNode[]; - constructor(public data: T, public parent: SimpleNode | null) { - this.id = data.id; - } - - hasParent(): this is this & { parent: SimpleNode } { - return !!this.parent; - } - - get childIndex(): number { - return this.hasParent() ? this.parent.children!.indexOf(this) : -1; - } - - addChild(data: T, index: number) { - const node = createNode(data, this); - this.children = this.children ?? []; - this.children.splice(index, 0, node); - this.data.children = this.data.children ?? []; - this.data.children.splice(index, 0, data); - } - - removeChild(index: number) { - this.children?.splice(index, 1); - this.data.children?.splice(index, 1); - } - - update(changes: Partial) { - if (this.hasParent()) { - const i = this.childIndex; - this.parent.addChild({ ...this.data, ...changes }, i); - this.drop(); - } - } - - drop() { - if (this.hasParent()) this.parent.removeChild(this.childIndex); - } -} diff --git a/modules/react-arborist/src/dnd/compute-drop.ts b/modules/react-arborist/src/dnd/compute-drop.ts index 4f4a69b..3589eef 100644 --- a/modules/react-arborist/src/dnd/compute-drop.ts +++ b/modules/react-arborist/src/dnd/compute-drop.ts @@ -1,36 +1,14 @@ -import { XYCoord } from "react-dnd"; -import { NodeApi } from "../interfaces/node-api"; -import { - bound, - indexOf, - isClosed, - isItem, - isOpenWithEmptyChildren, -} from "../utils"; -import { DropResult } from "./drop-hook"; - -function measureHover(el: HTMLElement, offset: XYCoord) { - const rect = el.getBoundingClientRect(); - const x = offset.x - Math.round(rect.x); - const y = offset.y - Math.round(rect.y); - const height = rect.height; - const inTopHalf = y < height / 2; - const inBottomHalf = !inTopHalf; - const pad = height / 4; - const inMiddle = y > pad && y < height - pad; - const atTop = !inMiddle && inTopHalf; - const atBottom = !inMiddle && inBottomHalf; - return { x, inTopHalf, inBottomHalf, inMiddle, atTop, atBottom }; -} - -type HoverData = ReturnType; +import { bound, isOpenWithEmptyChildren } from "../utils.js"; +import { NodeController } from "../controllers/node-controller.js"; +import { HoverData, measureHover } from "./measure-hover.js"; +import { XY } from "./types.js"; function getNodesAroundCursor( - node: NodeApi | null, - prev: NodeApi | null, - next: NodeApi | null, - hover: HoverData -): [NodeApi | null, NodeApi | null] { + node: NodeController | null, + prev: NodeController | null, + next: NodeController | null, + hover: HoverData, +): [NodeController | null, NodeController | null] { if (!node) { // We're hovering over the empty part of the list, not over an item, // Put the cursor below the last item which is "prev" @@ -53,13 +31,19 @@ function getNodesAroundCursor( } } +export type DropResult = { + parentId: string | null; + index: number | null; +}; + type Args = { element: HTMLElement; - offset: XYCoord; + offset: XY; indent: number; - node: NodeApi | null; - prevNode: NodeApi | null; - nextNode: NodeApi | null; + node: NodeController | null; + prevNode: NodeController | null; + nextNode: NodeController | null; + direction: "ltr" | "rtl"; }; export type ComputedDrop = { @@ -67,11 +51,8 @@ export type ComputedDrop = { cursor: Cursor | null; }; -function dropAt( - parentId: string | undefined, - index: number | null -): DropResult { - return { parentId: parentId || null, index }; +function dropAt(parentId: string | null, index: number | null): DropResult { + return { parentId, index }; } function lineCursor(index: number, level: number) { @@ -82,12 +63,6 @@ function lineCursor(index: number, level: number) { }; } -function noCursor() { - return { - type: "none" as "none", - }; -} - function highlightCursor(id: string) { return { type: "highlight" as "highlight", @@ -95,26 +70,25 @@ function highlightCursor(id: string) { }; } -function walkUpFrom(node: NodeApi, level: number) { +function walkUpFrom(node: NodeController, level: number) { let drop = node; while (drop.parent && drop.level > level) { drop = drop.parent; } - const parentId = drop.parent?.id || null; - const index = indexOf(drop) + 1; + const parentId = drop.parentId; + const index = drop.childIndex + 1; return { parentId, index }; } export type LineCursor = ReturnType; -export type NoCursor = ReturnType; export type HighlightCursor = ReturnType; -export type Cursor = LineCursor | NoCursor | HighlightCursor; +export type Cursor = LineCursor | HighlightCursor | null; /** * This is the most complex, tricky function in the whole repo. */ export function computeDrop(args: Args): ComputedDrop { - const hover = measureHover(args.element, args.offset); + const hover = measureHover(args.element, args.offset, args.direction); const indent = args.indent; const hoverLevel = Math.round(Math.max(0, hover.x - indent) / indent); const { node, nextNode, prevNode } = args; @@ -136,13 +110,13 @@ export function computeDrop(args: Args): ComputedDrop { /* There is no node above the cursor line */ if (!above) { return { - drop: dropAt(below?.parent?.id, 0), + drop: dropAt(below?.parentId || null, 0), cursor: lineCursor(0, 0), }; } /* The node above the cursor line is an item */ - if (isItem(above)) { + if (above.isLeaf) { const level = bound(hoverLevel, below?.level || 0, above.level); return { drop: walkUpFrom(above, level), @@ -151,7 +125,7 @@ export function computeDrop(args: Args): ComputedDrop { } /* The node above the cursor line is a closed folder */ - if (isClosed(above)) { + if (above.isClosed) { const level = bound(hoverLevel, below?.level || 0, above.level); return { drop: walkUpFrom(above, level), diff --git a/modules/react-arborist/src/dnd/drag-hook.ts b/modules/react-arborist/src/dnd/drag-hook.ts deleted file mode 100644 index 22ac564..0000000 --- a/modules/react-arborist/src/dnd/drag-hook.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useEffect } from "react"; -import { ConnectDragSource, useDrag } from "react-dnd"; -import { getEmptyImage } from "react-dnd-html5-backend"; -import { useTreeApi } from "../context"; -import { NodeApi } from "../interfaces/node-api"; -import { DragItem } from "../types/dnd"; -import { DropResult } from "./drop-hook"; -import { actions as dnd } from "../state/dnd-slice"; -import { safeRun } from "../utils"; -import { ROOT_ID } from "../data/create-root"; - -export function useDragHook(node: NodeApi): ConnectDragSource { - const tree = useTreeApi(); - const ids = tree.selectedIds; - const [_, ref, preview] = useDrag( - () => ({ - canDrag: () => node.isDraggable, - type: "NODE", - item: () => { - // This is fired once at the begging of a drag operation - const dragIds = tree.isSelected(node.id) ? Array.from(ids) : [node.id]; - tree.dispatch(dnd.dragStart(node.id, dragIds)); - return { id: node.id }; - }, - end: () => { - tree.hideCursor(); - let { parentId, index, dragIds } = tree.state.dnd; - // If they held down meta, we need to create a copy - // if (drop.dropEffect === "copy") - if (tree.canDrop()) { - safeRun(tree.props.onMove, { - dragIds, - parentId: parentId === ROOT_ID ? null : parentId, - index: index === null ? 0 : index, // When it's null it was dropped over a folder - dragNodes: tree.dragNodes, - parentNode: tree.get(parentId), - }); - tree.open(parentId); - } - tree.dispatch(dnd.dragEnd()); - }, - }), - [ids, node] - ); - - useEffect(() => { - preview(getEmptyImage()); - }, [preview]); - - return ref; -} diff --git a/modules/react-arborist/src/dnd/drop-hook.ts b/modules/react-arborist/src/dnd/drop-hook.ts deleted file mode 100644 index 6be259d..0000000 --- a/modules/react-arborist/src/dnd/drop-hook.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { RefObject } from "react"; -import { ConnectDropTarget, useDrop } from "react-dnd"; -import { useTreeApi } from "../context"; -import { NodeApi } from "../interfaces/node-api"; -import { DragItem } from "../types/dnd"; -import { computeDrop } from "./compute-drop"; -import { actions as dnd } from "../state/dnd-slice"; - -export type DropResult = { - parentId: string | null; - index: number | null; -}; - -export function useDropHook( - el: RefObject, - node: NodeApi -): ConnectDropTarget { - const tree = useTreeApi(); - const [_, dropRef] = useDrop( - () => ({ - accept: "NODE", - canDrop: () => tree.canDrop(), - hover: (_item, m) => { - const offset = m.getClientOffset(); - if (!el.current || !offset) return; - const { cursor, drop } = computeDrop({ - element: el.current, - offset: offset, - indent: tree.indent, - node: node, - prevNode: node.prev, - nextNode: node.next, - }); - if (drop) tree.dispatch(dnd.hovering(drop.parentId, drop.index)); - - if (m.canDrop()) { - if (cursor) tree.showCursor(cursor); - } else { - tree.hideCursor(); - } - }, - drop: (_, m) => { - if (!m.canDrop()) return null; - }, - }), - [node, el.current, tree.props] - ); - - return dropRef; -} diff --git a/modules/react-arborist/src/dnd/measure-hover.ts b/modules/react-arborist/src/dnd/measure-hover.ts index 78cd5c9..8b349cf 100644 --- a/modules/react-arborist/src/dnd/measure-hover.ts +++ b/modules/react-arborist/src/dnd/measure-hover.ts @@ -1,26 +1,23 @@ -import { XYCoord } from "react-dnd"; -import { bound } from "../utils"; +import { XY } from "./types.js"; -export function measureHover(el: HTMLElement, offset: XYCoord, indent: number) { - const nextEl = el.nextElementSibling as HTMLElement | null; - const prevEl = el.previousElementSibling as HTMLElement | null; +export function measureHover( + el: HTMLElement, + offset: XY, + direction: "ltr" | "rtl", +) { const rect = el.getBoundingClientRect(); - const x = offset.x - Math.round(rect.x); - const y = offset.y - Math.round(rect.y); + const x = direction === "ltr" ? offset.x : rect.width - offset.x; + const y = offset.y; const height = rect.height; const inTopHalf = y < height / 2; const inBottomHalf = !inTopHalf; const pad = height / 4; const inMiddle = y > pad && y < height - pad; - const maxLevel = Number( - inBottomHalf ? el.dataset.level : prevEl ? prevEl.dataset.level : 0 - ); - const minLevel = Number( - inTopHalf ? el.dataset.level : nextEl ? nextEl.dataset.level : 0 - ); - const level = bound(Math.floor(x / indent), minLevel, maxLevel); + const atTop = !inMiddle && inTopHalf; + const atBottom = !inMiddle && inBottomHalf; - return { level, inTopHalf, inBottomHalf, inMiddle }; + const result = { x, inTopHalf, inBottomHalf, inMiddle, atTop, atBottom }; + return result; } export type HoverData = ReturnType; diff --git a/modules/react-arborist/src/dnd/outer-drop-hook.ts b/modules/react-arborist/src/dnd/outer-drop-hook.ts deleted file mode 100644 index b150f1a..0000000 --- a/modules/react-arborist/src/dnd/outer-drop-hook.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useDrop } from "react-dnd"; -import { useTreeApi } from "../context"; -import { DragItem } from "../types/dnd"; -import { computeDrop } from "./compute-drop"; -import { DropResult } from "./drop-hook"; -import { actions as dnd } from "../state/dnd-slice"; - -export function useOuterDrop() { - const tree = useTreeApi(); - - // In case we drop an item at the bottom of the list - const [, drop] = useDrop( - () => ({ - accept: "NODE", - canDrop: (_item, m) => { - if (!m.isOver({ shallow: true })) return false; - return tree.canDrop(); - }, - hover: (_item, m) => { - if (!m.isOver({ shallow: true })) return; - const offset = m.getClientOffset(); - if (!tree.listEl.current || !offset) return; - const { cursor, drop } = computeDrop({ - element: tree.listEl.current, - offset: offset, - indent: tree.indent, - node: null, - prevNode: tree.visibleNodes[tree.visibleNodes.length - 1], - nextNode: null, - }); - if (drop) tree.dispatch(dnd.hovering(drop.parentId, drop.index)); - - if (m.canDrop()) { - if (cursor) tree.showCursor(cursor); - } else { - tree.hideCursor(); - } - }, - }), - [tree] - ); - - drop(tree.listEl); -} diff --git a/modules/react-arborist/src/dnd/safe-to-drop.ts b/modules/react-arborist/src/dnd/safe-to-drop.ts new file mode 100644 index 0000000..5baf07f --- /dev/null +++ b/modules/react-arborist/src/dnd/safe-to-drop.ts @@ -0,0 +1,31 @@ +import { TreeController } from "../controllers/tree-controller.js"; + +export function safeToDrop(tree: TreeController) { + const targetParentNode = tree.dropTargetParentNode; + const targetIndex = tree.dropTargetIndex; + const dragNodes = tree.dragNodes; + + /* Basic Defaul Check */ + if (targetParentNode === null && targetIndex === null) return false; + + for (const draggingNode of tree.dragNodes) { + if ( + draggingNode.isInternal && + targetParentNode?.isDescendantOf(draggingNode) + ) { + return false; + } + } + + /* User Provided Check */ + const disableCheck = tree.props.disableDrop; + if (typeof disableCheck == "function") { + return !disableCheck({ dragNodes, targetParentNode, targetIndex }); + } else if (typeof disableCheck == "string" && targetParentNode) { + return !targetParentNode.data[disableCheck]; + } else if (typeof disableCheck === "boolean") { + return !disableCheck; + } else { + return true; + } +} diff --git a/modules/react-arborist/src/dnd/types.ts b/modules/react-arborist/src/dnd/types.ts new file mode 100644 index 0000000..58b329c --- /dev/null +++ b/modules/react-arborist/src/dnd/types.ts @@ -0,0 +1,37 @@ +import { NodeController } from "../controllers/node-controller.js"; +import { PartialController } from "../types/utils.js"; + +export type DndState = { + dragSourceId: string | null; + dragItems: string[]; + targetParentId: string | null; + targetIndex: number | null; +}; + +export type DndOnChangeEvent = + | { + type: "drag-start"; + dragSourceId: string; + dragItems: string[]; + } + | { + type: "dragging-over"; + targetParentId: string | null; + targetIndex: number | null; + } + | { + type: "drag-end"; + }; + +export type DndPartialController = PartialController< + DndState, + DndOnChangeEvent +>; + +export type DisableDropCheck = (args: { + dragNodes: NodeController[]; + targetParentNode: NodeController | null; + targetIndex: number | null; +}) => boolean; + +export type XY = { x: number; y: number }; diff --git a/modules/react-arborist/src/dnd/use-dnd.ts b/modules/react-arborist/src/dnd/use-dnd.ts new file mode 100644 index 0000000..cac6e2a --- /dev/null +++ b/modules/react-arborist/src/dnd/use-dnd.ts @@ -0,0 +1,40 @@ +import { DndPartialController, DndState } from "./types.js"; +import { useState } from "react"; + +function getInitialState(): DndState { + return { + dragSourceId: null, + dragItems: [], + targetParentId: null, + targetIndex: null, + }; +} + +export function useDnd(): DndPartialController { + const [value, setValue] = useState(getInitialState); + return { + value: value, + onChange: (e) => { + switch (e.type) { + case "drag-start": + setValue({ + dragSourceId: e.dragSourceId, + dragItems: e.dragItems, + targetIndex: null, + targetParentId: null, + }); + break; + case "dragging-over": + setValue((prev) => ({ + ...prev, + targetParentId: e.targetParentId, + targetIndex: e.targetIndex, + })); + break; + case "drag-end": + setValue(getInitialState()); + break; + } + }, + }; +} diff --git a/modules/react-arborist/src/dnd/use-node-drag.ts b/modules/react-arborist/src/dnd/use-node-drag.ts new file mode 100644 index 0000000..581cb20 --- /dev/null +++ b/modules/react-arborist/src/dnd/use-node-drag.ts @@ -0,0 +1,24 @@ +import { useDrag } from "react-aria"; +import { NodeController } from "../controllers/node-controller.js"; + +export function useNodeDrag(node: NodeController) { + const { tree } = node; + const { dragProps } = useDrag({ + getItems() { + return [{ nodeId: node.id }]; + }, + onDragStart(e) { + tree.dragStart(node.id); + }, + onDragMove(mouse) { + const el = tree.element!.getBoundingClientRect(); + const withinY = mouse.y > el.y && mouse.y < el.y + el.height; + const withinX = mouse.x > el.x && mouse.x < el.x + el.width; + if (!(withinY && withinX)) tree.hideCursor(); + }, + onDragEnd(e) { + tree.dragEnd(); + }, + }); + return dragProps; +} diff --git a/modules/react-arborist/src/dnd/use-node-drop.ts b/modules/react-arborist/src/dnd/use-node-drop.ts new file mode 100644 index 0000000..190bb7f --- /dev/null +++ b/modules/react-arborist/src/dnd/use-node-drop.ts @@ -0,0 +1,58 @@ +import { useDrop } from "react-aria"; +import { NodeController } from "../controllers/node-controller.js"; +import { ComputedDrop, computeDrop } from "./compute-drop.js"; +import { useRef } from "react"; +import { Timer } from "../utils.js"; + +export function useNodeDrop(node: NodeController, ref: any) { + const { tree } = node; + const timer = useRef(new Timer()).current; + + function openAfterDelay(id: string) { + if (timer.hasStarted) return; + timer.start(() => tree.open(id), 750); + } + + const { dropProps } = useDrop({ + ref, + onDropExit() { + timer.cancel(); + }, + onDropMove(e) { + // x, y is where the mouse position is relative + // to the draggable element + const { drop, cursor } = computeDrop({ + element: ref.current, + offset: { x: e.x, y: e.y }, + indent: node.tree.indent, + node: node, + nextNode: node.next, + prevNode: node.prev, + direction: tree.props.direction, + }); + if (drop) { + node.tree.draggingOver(drop.parentId, drop.index!); + if (overFolder(drop)) { + openAfterDelay(drop.parentId!); + } else { + timer.cancel(); + } + } + + if (node.tree.canDrop()) { + if (cursor) node.tree.showCursor(cursor); + } else { + node.tree.hideCursor(); + } + }, + onDrop() { + if (tree.canDrop()) tree.drop(); + }, + }); + + return dropProps; +} + +function overFolder(drop: ComputedDrop["drop"]) { + return drop && drop.parentId && drop.index === null; +} diff --git a/modules/react-arborist/src/dnd/use-tree-drop.ts b/modules/react-arborist/src/dnd/use-tree-drop.ts new file mode 100644 index 0000000..4d9ab6d --- /dev/null +++ b/modules/react-arborist/src/dnd/use-tree-drop.ts @@ -0,0 +1,32 @@ +import { useDrop } from "react-aria"; +import { computeDrop } from "./compute-drop.js"; +import { TreeController } from "../controllers/tree-controller.js"; + +export function useTreeDrop(tree: TreeController, ref: any) { + const { dropProps } = useDrop({ + ref, + onDropMove(e) { + const { cursor, drop } = computeDrop({ + element: ref.current, + offset: { x: e.x, y: e.y }, + indent: tree.indent, + node: null, + prevNode: tree.lastNode, + nextNode: null, + direction: tree.props.direction, + }); + if (drop) tree.draggingOver(drop.parentId, drop.index!); + + if (tree.canDrop()) { + if (cursor) tree.showCursor(cursor); + } else { + tree.hideCursor(); + } + }, + onDrop() { + if (tree.canDrop()) tree.drop(); + }, + }); + + return dropProps; +} diff --git a/modules/react-arborist/src/edit/types.ts b/modules/react-arborist/src/edit/types.ts new file mode 100644 index 0000000..879a73c --- /dev/null +++ b/modules/react-arborist/src/edit/types.ts @@ -0,0 +1,10 @@ +import { PartialController } from "../types/utils.js"; + +export type EditState = string | null; + +export type EditOnChangeEvent = { value: EditState }; + +export type EditPartialController = PartialController< + EditState, + EditOnChangeEvent +>; diff --git a/modules/react-arborist/src/edit/use-edit.ts b/modules/react-arborist/src/edit/use-edit.ts new file mode 100644 index 0000000..0dbec8c --- /dev/null +++ b/modules/react-arborist/src/edit/use-edit.ts @@ -0,0 +1,11 @@ +import { useState } from "react"; +import { EditPartialController, EditState } from "./types.js"; + +export function useEdit(): EditPartialController { + const [value, setValue] = useState(null); + + return { + value, + onChange: (e) => setValue(e.value), + }; +} diff --git a/modules/react-arborist/src/filter/match.ts b/modules/react-arborist/src/filter/match.ts new file mode 100644 index 0000000..48c404a --- /dev/null +++ b/modules/react-arborist/src/filter/match.ts @@ -0,0 +1,10 @@ +import { NodeObject } from "../nodes/types.js"; + +export function matchesStringProperties(node: NodeObject, term: string) { + const haystack = Array.from(Object.values(node.sourceData as any)) + .filter((value) => typeof value === "string") + .join(" ") + .toLocaleLowerCase(); + + return haystack.includes(term.toLocaleLowerCase()); +} diff --git a/modules/react-arborist/src/filter/tree-filter.ts b/modules/react-arborist/src/filter/tree-filter.ts new file mode 100644 index 0000000..5764fda --- /dev/null +++ b/modules/react-arborist/src/filter/tree-filter.ts @@ -0,0 +1,43 @@ +import { NodeObject } from "../nodes/types.js"; +import { matchesStringProperties } from "./match.js"; +import { FilterOptions } from "./use-filter.js"; + +type BoolMap = Record; + +export class TreeFilter { + term: string; + isMatch: (node: NodeObject, term: string) => boolean; + + constructor(opts: FilterOptions) { + this.term = (opts.term || "").trim(); + this.isMatch = opts.isMatch || matchesStringProperties; + } + + get isPresent() { + return this.term.length > 0; + } + + getVisibility(nodes: NodeObject[], value: BoolMap = {}) { + for (const node of nodes) { + if (this.isMatch(node, this.term)) { + for (const id of [node.id, ...this.ancestorIds(node)]) { + value[id] = true; + } + } else { + value[node.id] = false; + } + if (node.children) this.getVisibility(node.children, value); + } + return value; + } + + ancestorIds(node: NodeObject) { + const ids = []; + let parent = node.parent; + while (parent) { + ids.push(parent.id); + parent = parent.parent; + } + return ids; + } +} diff --git a/modules/react-arborist/src/filter/use-filter.ts b/modules/react-arborist/src/filter/use-filter.ts new file mode 100644 index 0000000..69f080c --- /dev/null +++ b/modules/react-arborist/src/filter/use-filter.ts @@ -0,0 +1,35 @@ +import { useState } from "react"; +import { OpensOnChangeEvent, OpensState } from "../opens/types.js"; +import { TreeFilter } from "./tree-filter.js"; +import { NodeObject } from "../nodes/types.js"; + +export type FilterOptions = { + term?: string; + isMatch?: (node: NodeObject, term: string) => boolean; + openByDefault?: (isFiltered: boolean) => boolean; +}; + +export function useFilter( + nodes: NodeObject[], + options: FilterOptions = {}, +) { + const filter = new TreeFilter(options); + const [opens, setOpens] = useState({}); + const [filteredOpens, setFilteredOpens] = useState({}); + + return { + visible: { + value: filter.isPresent ? filter.getVisibility(nodes) : {}, + onChange: () => {}, + }, + opens: { + value: filter.isPresent ? filteredOpens : opens, + onChange: (e: OpensOnChangeEvent) => { + filter.isPresent ? setFilteredOpens(e.value) : setOpens(e.value); + }, + }, + openByDefault: options.openByDefault + ? options.openByDefault(filter.isPresent) + : true, + }; +} diff --git a/modules/react-arborist/src/focus/types.ts b/modules/react-arborist/src/focus/types.ts new file mode 100644 index 0000000..38d8ac5 --- /dev/null +++ b/modules/react-arborist/src/focus/types.ts @@ -0,0 +1,16 @@ +import { PartialController } from "../types/utils.js"; + +export type FocusState = { + id: string | null; + isWithinTree: boolean; +}; + +export type FocusOnChangeEvent = + | { type: "tree-focus" } + | { type: "tree-blur" } + | { type: "node-focus"; id: string }; + +export type FocusPartialController = PartialController< + FocusState, + FocusOnChangeEvent +>; diff --git a/modules/react-arborist/src/focus/use-focus.ts b/modules/react-arborist/src/focus/use-focus.ts new file mode 100644 index 0000000..583e5f8 --- /dev/null +++ b/modules/react-arborist/src/focus/use-focus.ts @@ -0,0 +1,26 @@ +import { useState } from "react"; +import { FocusPartialController, FocusState } from "./types.js"; + +export function useFocus(): FocusPartialController { + const [value, setValue] = useState({ + isWithinTree: false, + id: null, + }); + + return { + value, + onChange(e) { + switch (e.type) { + case "tree-focus": + setValue((prev) => ({ ...prev, isWithinTree: true })); + break; + case "tree-blur": + setValue((prev) => ({ ...prev, isWithinTree: false })); + break; + case "node-focus": + setValue({ isWithinTree: true, id: e.id }); + break; + } + }, + }; +} diff --git a/modules/react-arborist/src/focus/use-row-focus.ts b/modules/react-arborist/src/focus/use-row-focus.ts new file mode 100644 index 0000000..fef34f1 --- /dev/null +++ b/modules/react-arborist/src/focus/use-row-focus.ts @@ -0,0 +1,13 @@ +import { MutableRefObject, useEffect } from "react"; +import { NodeController } from "../controllers/node-controller.js"; + +export function useRowFocus( + node: NodeController, + ref: MutableRefObject, +) { + useEffect(() => { + if (!node.isEditing && node.isFocused) { + ref.current?.focus({ preventScroll: true }); + } + }, [node.isEditing, node.isFocused, ref.current]); +} diff --git a/modules/react-arborist/src/hooks/use-fresh-node.ts b/modules/react-arborist/src/hooks/use-fresh-node.ts deleted file mode 100644 index 5ac27ad..0000000 --- a/modules/react-arborist/src/hooks/use-fresh-node.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useMemo } from "react"; -import { useTreeApi } from "../context"; -import { IdObj } from "../types/utils"; - -export function useFreshNode(index: number) { - const tree = useTreeApi(); - const original = tree.at(index); - if (!original) throw new Error(`Could not find node for index: ${index}`); - - return useMemo(() => { - const fresh = original.clone(); - tree.visibleNodes[index] = fresh; // sneaky - return fresh; - // Return a fresh instance if the state values change - }, [...Object.values(original.state), original]); -} diff --git a/modules/react-arborist/src/hooks/use-simple-tree.ts b/modules/react-arborist/src/hooks/use-simple-tree.ts deleted file mode 100644 index 4848e25..0000000 --- a/modules/react-arborist/src/hooks/use-simple-tree.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { useMemo, useState } from "react"; -import { SimpleTree } from "../data/simple-tree"; -import { - CreateHandler, - DeleteHandler, - MoveHandler, - RenameHandler, -} from "../types/handlers"; -import { IdObj } from "../types/utils"; - -export type SimpleTreeData = { - id: string; - name: string; - children?: SimpleTreeData[]; -}; - -let nextId = 0; - -export function useSimpleTree(initialData: readonly T[]) { - const [data, setData] = useState(initialData); - const tree = useMemo( - () => - new SimpleTree(data), - [data] - ); - - const onMove: MoveHandler = (args: { - dragIds: string[]; - parentId: null | string; - index: number; - }) => { - for (const id of args.dragIds) { - tree.move({ id, parentId: args.parentId, index: args.index }); - } - setData(tree.data); - }; - - const onRename: RenameHandler = ({ name, id }) => { - tree.update({ id, changes: { name } as any }); - setData(tree.data); - }; - - const onCreate: CreateHandler = ({ parentId, index, type }) => { - const data = { id: `simple-tree-id-${nextId++}`, name: "" } as any; - if (type === "internal") data.children = []; - tree.create({ parentId, index, data }); - setData(tree.data); - return data; - }; - - const onDelete: DeleteHandler = (args: { ids: string[] }) => { - args.ids.forEach((id) => tree.drop({ id })); - setData(tree.data); - }; - - const controller = { onMove, onRename, onCreate, onDelete }; - - return [data, controller] as const; -} diff --git a/modules/react-arborist/src/hooks/use-validated-props.ts b/modules/react-arborist/src/hooks/use-validated-props.ts deleted file mode 100644 index 1329a09..0000000 --- a/modules/react-arborist/src/hooks/use-validated-props.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { TreeProps } from "../types/tree-props"; -import { IdObj } from "../types/utils"; -import { SimpleTreeData, useSimpleTree } from "./use-simple-tree"; - -export function useValidatedProps(props: TreeProps): TreeProps { - if (props.initialData && props.data) { - throw new Error( - `React Arborist Tree => Provide either a data or initialData prop, but not both.` - ); - } - if ( - props.initialData && - (props.onCreate || props.onDelete || props.onMove || props.onRename) - ) { - throw new Error( - `React Arborist Tree => You passed the initialData prop along with a data handler. -Use the data prop if you want to provide your own handlers.` - ); - } - if (props.initialData) { - /** - * Let's break the rules of hooks here. If the initialData prop - * is provided, we will assume it will not change for the life of - * the component. - * - * We will provide the real data and the handlers to update it. - * */ - const [data, controller] = useSimpleTree(props.initialData); - return { ...props, ...controller, data }; - } else { - return props; - } -} diff --git a/modules/react-arborist/src/index.ts b/modules/react-arborist/src/index.ts index 854860f..b609ba5 100644 --- a/modules/react-arborist/src/index.ts +++ b/modules/react-arborist/src/index.ts @@ -1,9 +1,10 @@ /* The Public Api */ -export { Tree } from "./components/tree"; -export * from "./types/handlers"; -export * from "./types/renderers"; -export * from "./types/state"; -export * from "./interfaces/node-api"; -export * from "./interfaces/tree-api"; -export * from "./data/simple-tree"; -export * from "./hooks/use-simple-tree"; +export type * from "./types/handlers.js"; +export type * from "./types/renderers.js"; + +export * from "./components/tree-view.js"; +export * from "./nodes/tree-model.js"; +export * from "./nodes/use-nodes.js"; +export * from "./selection/use-multi-selection.js"; +export * from "./dnd/use-dnd.js"; +export * from "./filter/use-filter.js"; diff --git a/modules/react-arborist/src/interfaces/node-api.ts b/modules/react-arborist/src/interfaces/node-api.ts deleted file mode 100644 index 34d7138..0000000 --- a/modules/react-arborist/src/interfaces/node-api.ts +++ /dev/null @@ -1,209 +0,0 @@ -import React from "react"; -import { TreeApi } from "./tree-api"; -import { IdObj } from "../types/utils"; -import { ROOT_ID } from "../data/create-root"; - -type Params = { - id: string; - data: T; - level: number; - children: NodeApi[] | null; - parent: NodeApi | null; - isDraggable: boolean; - rowIndex: number | null; - tree: TreeApi; -}; - -export class NodeApi { - tree: TreeApi; - id: string; - data: T; - level: number; - children: NodeApi[] | null; - parent: NodeApi | null; - isDraggable: boolean; - rowIndex: number | null; - - constructor(params: Params) { - this.tree = params.tree; - this.id = params.id; - this.data = params.data; - this.level = params.level; - this.children = params.children; - this.parent = params.parent; - this.isDraggable = params.isDraggable; - this.rowIndex = params.rowIndex; - } - - get isRoot() { - return this.id === ROOT_ID; - } - - get isLeaf() { - return !Array.isArray(this.children); - } - - get isInternal() { - return !this.isLeaf; - } - - get isOpen() { - return this.isLeaf ? false : this.tree.isOpen(this.id); - } - - get isClosed() { - return this.isLeaf ? false : !this.tree.isOpen(this.id); - } - - get isEditable() { - return this.tree.isEditable(this.data); - } - - get isEditing() { - return this.tree.editingId === this.id; - } - - get isSelected() { - return this.tree.isSelected(this.id); - } - - get isOnlySelection() { - return this.isSelected && this.tree.hasOneSelection; - } - - get isSelectedStart() { - return this.isSelected && !this.prev?.isSelected; - } - - get isSelectedEnd() { - return this.isSelected && !this.next?.isSelected; - } - - get isFocused() { - return this.tree.isFocused(this.id); - } - - get isDragging() { - return this.tree.isDragging(this.id); - } - - get willReceiveDrop() { - return this.tree.willReceiveDrop(this.id); - } - - get state() { - return { - isClosed: this.isClosed, - isDragging: this.isDragging, - isEditing: this.isEditing, - isFocused: this.isFocused, - isInternal: this.isInternal, - isLeaf: this.isLeaf, - isOpen: this.isOpen, - isSelected: this.isSelected, - isSelectedEnd: this.isSelectedEnd, - isSelectedStart: this.isSelectedStart, - willReceiveDrop: this.willReceiveDrop, - }; - } - - get childIndex() { - if (this.parent && this.parent.children) { - return this.parent.children.findIndex((child) => child.id === this.id); - } else { - return -1; - } - } - - get next(): NodeApi | null { - if (this.rowIndex === null) return null; - return this.tree.at(this.rowIndex + 1); - } - - get prev(): NodeApi | null { - if (this.rowIndex === null) return null; - return this.tree.at(this.rowIndex - 1); - } - - get nextSibling(): NodeApi | null { - const i = this.childIndex; - return this.parent?.children![i + 1] ?? null; - } - - isAncestorOf(node: NodeApi | null) { - if (!node) return false; - let ancestor: NodeApi | null = node; - while (ancestor) { - if (ancestor.id === this.id) return true; - ancestor = ancestor.parent; - } - return false; - } - - select() { - this.tree.select(this); - } - - deselect() { - this.tree.deselect(this); - } - - selectMulti() { - this.tree.selectMulti(this); - } - - selectContiguous() { - this.tree.selectContiguous(this); - } - - activate() { - this.tree.activate(this); - } - - focus() { - this.tree.focus(this); - } - - toggle() { - this.tree.toggle(this); - } - - open() { - this.tree.open(this); - } - - openParents() { - this.tree.openParents(this); - } - - close() { - this.tree.close(this); - } - - submit(value: string) { - this.tree.submit(this, value); - } - - reset() { - this.tree.reset(); - } - - clone() { - return new NodeApi({ ...this }); - } - - edit() { - return this.tree.edit(this); - } - - handleClick = (e: React.MouseEvent) => { - if (e.metaKey && !this.tree.props.disableMultiSelection) { - this.isSelected ? this.deselect() : this.selectMulti(); - } else if (e.shiftKey && !this.tree.props.disableMultiSelection) { - this.selectContiguous(); - } else { - this.select(); - this.activate(); - } - }; -} diff --git a/modules/react-arborist/src/interfaces/tree-api.test.ts b/modules/react-arborist/src/interfaces/tree-api.test.ts deleted file mode 100644 index 100a18e..0000000 --- a/modules/react-arborist/src/interfaces/tree-api.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createStore } from "redux"; -import { rootReducer } from "../state/root-reducer"; -import { TreeProps } from "../types/tree-props"; -import { TreeApi } from "./tree-api"; - -function setupApi(props: TreeProps) { - const store = createStore(rootReducer); - return new TreeApi(store, props, { current: null }, { current: null }); -} - -test("tree.canDrop()", () => { - expect(setupApi({ disableDrop: true }).canDrop()).toBe(false); - expect(setupApi({ disableDrop: () => false }).canDrop()).toBe(true); - expect(setupApi({ disableDrop: false }).canDrop()).toBe(true); -}); diff --git a/modules/react-arborist/src/interfaces/tree-api.ts b/modules/react-arborist/src/interfaces/tree-api.ts deleted file mode 100644 index 50e03a8..0000000 --- a/modules/react-arborist/src/interfaces/tree-api.ts +++ /dev/null @@ -1,662 +0,0 @@ -import { EditResult } from "../types/handlers"; -import { Identity, IdObj } from "../types/utils"; -import { TreeProps } from "../types/tree-props"; -import { MutableRefObject } from "react"; -import { Align, FixedSizeList, ListOnItemsRenderedProps } from "react-window"; -import * as utils from "../utils"; -import { DefaultCursor } from "../components/default-cursor"; -import { DefaultRow } from "../components/default-row"; -import { DefaultNode } from "../components/default-node"; -import { NodeApi } from "./node-api"; -import { edit } from "../state/edit-slice"; -import { Actions, RootState } from "../state/root-reducer"; -import { focus, treeBlur } from "../state/focus-slice"; -import { createRoot, ROOT_ID } from "../data/create-root"; -import { actions as visibility } from "../state/open-slice"; -import { actions as selection } from "../state/selection-slice"; -import { actions as dnd } from "../state/dnd-slice"; -import { DefaultDragPreview } from "../components/default-drag-preview"; -import { DefaultContainer } from "../components/default-container"; -import { Cursor } from "../dnd/compute-drop"; -import { Store } from "redux"; -import { createList } from "../data/create-list"; -import { createIndex } from "../data/create-index"; - -const { safeRun, identify, identifyNull } = utils; -export class TreeApi { - static editPromise: null | ((args: EditResult) => void); - root: NodeApi; - visibleNodes: NodeApi[]; - visibleStartIndex: number = 0; - visibleStopIndex: number = 0; - idToIndex: { [id: string]: number }; - - constructor( - public store: Store, - public props: TreeProps, - public list: MutableRefObject, - public listEl: MutableRefObject - ) { - /* Changes here must also be made in update() */ - this.root = createRoot(this); - this.visibleNodes = createList(this); - this.idToIndex = createIndex(this.visibleNodes); - } - - /* Changes here must also be made in constructor() */ - update(props: TreeProps) { - this.props = props; - this.root = createRoot(this); - this.visibleNodes = createList(this); - this.idToIndex = createIndex(this.visibleNodes); - } - - /* Store helpers */ - - dispatch(action: Actions) { - return this.store.dispatch(action); - } - - get state() { - return this.store.getState(); - } - - get openState() { - return this.state.nodes.open.unfiltered; - } - - /* Tree Props */ - - get width() { - return this.props.width ?? 300; - } - - get height() { - return this.props.height ?? 500; - } - - get indent() { - return this.props.indent ?? 24; - } - - get rowHeight() { - return this.props.rowHeight ?? 24; - } - - get overscanCount() { - return this.props.overscanCount ?? 1; - } - - get searchTerm() { - return (this.props.searchTerm || "").trim(); - } - - get matchFn() { - const match = - this.props.searchMatch ?? - ((node, term) => { - const string = JSON.stringify( - Object.values(node.data as { [k: string]: unknown }) - ); - return string.toLocaleLowerCase().includes(term.toLocaleLowerCase()); - }); - return (node: NodeApi) => match(node, this.searchTerm); - } - - accessChildren(data: T) { - const get = this.props.childrenAccessor || "children"; - return utils.access(data, get) ?? null; - } - - accessId(data: T) { - const get = this.props.idAccessor || "id"; - const id = utils.access(data, get); - if (!id) - throw new Error( - "Data must contain an 'id' property or props.idAccessor must return a string" - ); - return id; - } - - /* Node Access */ - - get firstNode() { - return this.visibleNodes[0] ?? null; - } - - get lastNode() { - return this.visibleNodes[this.visibleNodes.length - 1] ?? null; - } - - get focusedNode() { - return this.get(this.state.nodes.focus.id) ?? null; - } - - get mostRecentNode() { - return this.get(this.state.nodes.selection.mostRecent) ?? null; - } - - get nextNode() { - const index = this.indexOf(this.focusedNode); - if (index === null) return null; - else return this.at(index + 1); - } - - get prevNode() { - const index = this.indexOf(this.focusedNode); - if (index === null) return null; - else return this.at(index - 1); - } - - get(id: string | null): NodeApi | null { - if (!id) return null; - if (id in this.idToIndex) - return this.visibleNodes[this.idToIndex[id]] || null; - else return null; - } - - at(index: number): NodeApi | null { - return this.visibleNodes[index] || null; - } - - nodesBetween(startId: string | null, endId: string | null) { - if (startId === null || endId === null) return []; - const index1 = this.indexOf(startId) ?? 0; - const index2 = this.indexOf(endId); - if (index2 === null) return []; - const start = Math.min(index1, index2); - const end = Math.max(index1, index2); - return this.visibleNodes.slice(start, end + 1); - } - - indexOf(id: string | null | IdObj) { - const key = utils.identifyNull(id); - if (!key) return null; - return this.idToIndex[key]; - } - - /* Data Operations */ - - get editingId() { - return this.state.nodes.edit.id; - } - - createInternal() { - return this.create({ type: "internal" }); - } - - createLeaf() { - return this.create({ type: "leaf" }); - } - - async create( - opts: { - type?: "internal" | "leaf"; - parentId?: null | string; - index?: null | number; - } = {} - ) { - const parentId = - opts.parentId === undefined - ? utils.getInsertParentId(this) - : opts.parentId; - const index = opts.index ?? utils.getInsertIndex(this); - const type = opts.type ?? "leaf"; - const data = await safeRun(this.props.onCreate, { - type, - parentId, - index, - parentNode: this.get(parentId), - }); - if (data) { - this.focus(data); - setTimeout(() => { - this.edit(data).then(() => { - this.select(data); - this.activate(data); - }); - }); - } - } - - async delete(node: string | IdObj | null | string[] | IdObj[]) { - if (!node) return; - const idents = Array.isArray(node) ? node : [node]; - const ids = idents.map(identify); - const nodes = ids.map((id) => this.get(id)!).filter((n) => !!n); - await safeRun(this.props.onDelete, { nodes, ids }); - } - - edit(node: string | IdObj): Promise { - const id = identify(node); - this.resolveEdit({ cancelled: true }); - this.scrollTo(id); - this.dispatch(edit(id)); - return new Promise((resolve) => { - TreeApi.editPromise = resolve; - }); - } - - async submit(identity: Identity, value: string) { - if (!identity) return; - const id = identify(identity); - await safeRun(this.props.onRename, { - id, - name: value, - node: this.get(id)!, - }); - this.dispatch(edit(null)); - this.resolveEdit({ cancelled: false, value }); - setTimeout(() => this.onFocus()); // Return focus to element; - } - - reset() { - this.dispatch(edit(null)); - this.resolveEdit({ cancelled: true }); - setTimeout(() => this.onFocus()); // Return focus to element; - } - - activate(id: string | IdObj | null) { - const node = this.get(identifyNull(id)); - if (!node) return; - safeRun(this.props.onActivate, node); - } - - private resolveEdit(value: EditResult) { - const resolve = TreeApi.editPromise; - if (resolve) resolve(value); - TreeApi.editPromise = null; - } - - /* Focus and Selection */ - - get selectedIds() { - return this.state.nodes.selection.ids; - } - - get selectedNodes() { - let nodes = []; - for (let id of Array.from(this.selectedIds)) { - const node = this.get(id); - if (node) nodes.push(node); - } - return nodes; - } - - focus(node: Identity, opts: { scroll?: boolean } = {}) { - if (!node) return; - /* Focus is responsible for scrolling, while selection is - * responsible for focus. If selectionFollowsFocus, then - * just select it. */ - if (this.props.selectionFollowsFocus) { - this.select(node); - } else { - this.dispatch(focus(identify(node))); - if (opts.scroll !== false) this.scrollTo(node); - if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode); - } - } - - pageUp() { - const start = this.visibleStartIndex; - const stop = this.visibleStopIndex; - const page = stop - start; - let index = this.focusedNode?.rowIndex ?? 0; - if (index > start) { - index = start; - } else { - index = Math.max(start - page, 0); - } - this.focus(this.at(index)); - } - - pageDown() { - const start = this.visibleStartIndex; - const stop = this.visibleStopIndex; - const page = stop - start; - let index = this.focusedNode?.rowIndex ?? 0; - if (index < stop) { - index = stop; - } else { - index = Math.min(index + page, this.visibleNodes.length - 1); - } - this.focus(this.at(index)); - } - - select(node: Identity, opts: { align?: Align; focus?: boolean } = {}) { - if (!node) return; - const changeFocus = opts.focus !== false; - const id = identify(node); - if (changeFocus) this.dispatch(focus(id)); - this.dispatch(selection.only(id)); - this.dispatch(selection.anchor(id)); - this.dispatch(selection.mostRecent(id)); - this.scrollTo(id, opts.align); - if (this.focusedNode && changeFocus) { - safeRun(this.props.onFocus, this.focusedNode); - } - safeRun(this.props.onSelect, this.selectedNodes); - } - - deselect(node: Identity) { - if (!node) return; - const id = identify(node); - this.dispatch(selection.remove(id)); - } - - selectMulti(identity: Identity) { - const node = this.get(identifyNull(identity)); - if (!node) return; - this.dispatch(focus(node.id)); - this.dispatch(selection.add(node.id)); - this.dispatch(selection.anchor(node.id)); - this.dispatch(selection.mostRecent(node.id)); - this.scrollTo(node); - if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode); - safeRun(this.props.onSelect, this.selectedNodes); - } - - selectContiguous(identity: Identity) { - if (!identity) return; - const id = identify(identity); - const { anchor, mostRecent } = this.state.nodes.selection; - this.dispatch(focus(id)); - this.dispatch(selection.remove(this.nodesBetween(anchor, mostRecent))); - this.dispatch(selection.add(this.nodesBetween(anchor, identifyNull(id)))); - this.dispatch(selection.mostRecent(id)); - this.scrollTo(id); - if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode); - safeRun(this.props.onSelect, this.selectedNodes); - } - - deselectAll() { - this.setSelection({ ids: [], anchor: null, mostRecent: null }); - safeRun(this.props.onSelect, this.selectedNodes); - } - - selectAll() { - this.setSelection({ - ids: Object.keys(this.idToIndex), - anchor: this.firstNode, - mostRecent: this.lastNode, - }); - this.dispatch(focus(this.lastNode?.id)); - if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode); - safeRun(this.props.onSelect, this.selectedNodes); - } - - setSelection(args: { - ids: (IdObj | string)[] | null; - anchor: IdObj | string | null; - mostRecent: IdObj | string | null; - }) { - const ids = new Set(args.ids?.map(identify)); - const anchor = identifyNull(args.anchor); - const mostRecent = identifyNull(args.mostRecent); - this.dispatch(selection.set({ ids, anchor, mostRecent })); - safeRun(this.props.onSelect, this.selectedNodes); - } - - /* Drag and Drop */ - - get cursorParentId() { - const { cursor } = this.state.dnd; - switch (cursor.type) { - case "highlight": - return cursor.id; - default: - return null; - } - } - - get cursorOverFolder() { - return this.state.dnd.cursor.type === "highlight"; - } - - get dragNodes() { - return this.state.dnd.dragIds - .map((id) => this.get(id)) - .filter((n) => !!n) as NodeApi[]; - } - - get dragNode() { - return this.get(this.state.nodes.drag.id); - } - - get dragDestinationParent() { - return this.get(this.state.nodes.drag.destinationParentId); - } - - get dragDestinationIndex() { - return this.state.nodes.drag.destinationIndex; - } - - canDrop() { - if (this.isFiltered) return false; - const parentNode = this.get(this.state.dnd.parentId) ?? this.root; - const dragNodes = this.dragNodes; - const isDisabled = this.props.disableDrop; - - for (const drag of dragNodes) { - if (!drag) return false; - if (!parentNode) return false; - if (drag.isInternal && utils.isDescendant(parentNode, drag)) return false; - } - - // Allow the user to insert their own logic - if (typeof isDisabled == "function") { - return !isDisabled({ - parentNode, - dragNodes: this.dragNodes, - index: this.state.dnd.index || 0, - }); - } else if (typeof isDisabled == "string") { - // @ts-ignore - return !parentNode.data[isDisabled]; - } else if (typeof isDisabled === "boolean") { - return !isDisabled; - } else { - return true; - } - } - - hideCursor() { - this.dispatch(dnd.cursor({ type: "none" })); - } - - showCursor(cursor: Cursor) { - this.dispatch(dnd.cursor(cursor)); - } - - /* Visibility */ - - open(identity: Identity) { - const id = identifyNull(identity); - if (!id) return; - if (this.isOpen(id)) return; - this.dispatch(visibility.open(id, this.isFiltered)); - safeRun(this.props.onToggle, id); - } - - close(identity: Identity) { - const id = identifyNull(identity); - if (!id) return; - if (!this.isOpen(id)) return; - this.dispatch(visibility.close(id, this.isFiltered)); - safeRun(this.props.onToggle, id); - } - - toggle(identity: Identity) { - const id = identifyNull(identity); - if (!id) return; - return this.isOpen(id) ? this.close(id) : this.open(id); - } - - openParents(identity: Identity) { - const id = identifyNull(identity); - if (!id) return; - const node = utils.dfs(this.root, id); - let parent = node?.parent; - - while (parent) { - this.open(parent.id); - parent = parent.parent; - } - } - - openSiblings(node: NodeApi) { - const parent = node.parent; - if (!parent) { - this.toggle(node.id); - } else if (parent.children) { - const isOpen = node.isOpen; - for (let sibling of parent.children) { - if (sibling.isInternal) { - isOpen ? this.close(sibling.id) : this.open(sibling.id); - } - } - this.scrollTo(this.focusedNode); - } - } - - openAll() { - utils.walk(this.root, (node) => { - if (node.isInternal) node.open(); - }); - } - - closeAll() { - utils.walk(this.root, (node) => { - if (node.isInternal) node.close(); - }); - } - - /* Scrolling */ - - scrollTo(identity: Identity, align: Align = "smart") { - if (!identity) return; - const id = identify(identity); - this.openParents(id); - return utils - .waitFor(() => id in this.idToIndex) - .then(() => { - const index = this.idToIndex[id]; - if (index === undefined) return; - this.list.current?.scrollToItem(index, align); - }) - .catch(() => { - // Id: ${id} never appeared in the list. - }); - } - - /* State Checks */ - - get isEditing() { - return this.state.nodes.edit.id !== null; - } - - get isFiltered() { - return !!this.props.searchTerm?.trim(); - } - - get hasFocus() { - return this.state.nodes.focus.treeFocused; - } - - get hasNoSelection() { - return this.state.nodes.selection.ids.size === 0; - } - - get hasOneSelection() { - return this.state.nodes.selection.ids.size === 1; - } - - get hasMultipleSelections() { - return this.state.nodes.selection.ids.size > 1; - } - - isSelected(id?: string) { - if (!id) return false; - return this.state.nodes.selection.ids.has(id); - } - - isOpen(id?: string) { - if (!id) return false; - if (id === ROOT_ID) return true; - const def = this.props.openByDefault ?? true; - if (this.isFiltered) { - return this.state.nodes.open.filtered[id] ?? true; // Filtered folders are always opened by default - } else { - return this.state.nodes.open.unfiltered[id] ?? def; - } - } - - isEditable(data: T) { - const check = this.props.disableEdit || (() => false); - return !utils.access(data, check) ?? true; - } - - isDraggable(data: T) { - const check = this.props.disableDrag || (() => false); - return !utils.access(data, check) ?? true; - } - - isDragging(node: string | IdObj | null) { - const id = identifyNull(node); - if (!id) return false; - return this.state.nodes.drag.id === id; - } - - isFocused(id: string) { - return this.hasFocus && this.state.nodes.focus.id === id; - } - - isMatch(node: NodeApi) { - return this.matchFn(node); - } - - willReceiveDrop(node: string | IdObj | null) { - const id = identifyNull(node); - if (!id) return false; - const { destinationParentId, destinationIndex } = this.state.nodes.drag; - return id === destinationParentId && destinationIndex === null; - } - - /* Tree Event Handlers */ - - onFocus() { - const node = this.focusedNode || this.firstNode; - if (node) this.dispatch(focus(node.id)); - } - - onBlur() { - this.dispatch(treeBlur()); - } - - onItemsRendered(args: ListOnItemsRenderedProps) { - this.visibleStartIndex = args.visibleStartIndex; - this.visibleStopIndex = args.visibleStopIndex; - } - - /* Get Renderers */ - - get renderContainer() { - return this.props.renderContainer || DefaultContainer; - } - - get renderRow() { - return this.props.renderRow || DefaultRow; - } - - get renderNode() { - return this.props.children || DefaultNode; - } - - get renderDragPreview() { - return this.props.renderDragPreview || DefaultDragPreview; - } - - get renderCursor() { - return this.props.renderCursor || DefaultCursor; - } -} diff --git a/modules/react-arborist/src/list/use-list-inner-style.ts b/modules/react-arborist/src/list/use-list-inner-style.ts new file mode 100644 index 0000000..a16864a --- /dev/null +++ b/modules/react-arborist/src/list/use-list-inner-style.ts @@ -0,0 +1,8 @@ +import { TreeController } from "../controllers/tree-controller.js"; + +export function useListInnerStyle(tree: TreeController, style: any) { + return { + ...style, + height: `${parseFloat(style.height) + tree.paddingTop + tree.paddingBottom}px`, + }; +} diff --git a/modules/react-arborist/src/nodes/accessor.ts b/modules/react-arborist/src/nodes/accessor.ts new file mode 100644 index 0000000..84b7f87 --- /dev/null +++ b/modules/react-arborist/src/nodes/accessor.ts @@ -0,0 +1,46 @@ +import { Accessors } from "./types.js"; + +export function createAccessor( + accessors: Partial>, +): Accessors { + return { + ...createDefaultAccessors(), + ...accessors, + }; +} + +function createDefaultAccessors(): Accessors { + return { + id: (d: T) => { + if (d && typeof d === "object" && "id" in d) { + return d.id as string; + } else { + throw new Error("No id found for node data. Specify an id accessor."); + } + }, + children: (d: T) => { + if (d && typeof d === "object" && "children" in d) { + return d.children as T[]; + } else { + return null; + } + }, + isLeaf: (d: T) => { + if (d && typeof d === "object" && "children" in d) { + return false; + } else { + return true; + } + }, + initialize: ({ nodeType }) => { + const data = { + id: new Date().getTime().toString(), + name: "", + } as any; + if (nodeType === "internal") data.children = []; + return data; + }, + sortBy: [], + sortOrder: [], + }; +} diff --git a/modules/react-arborist/src/nodes/comparator.ts b/modules/react-arborist/src/nodes/comparator.ts new file mode 100644 index 0000000..3dfd5a8 --- /dev/null +++ b/modules/react-arborist/src/nodes/comparator.ts @@ -0,0 +1,22 @@ +import { GetSortField, SortOrder } from "./types.js"; + +export class Comparator { + constructor( + public getField: GetSortField, + public order: SortOrder = "asc", + ) {} + + compare(a: T, b: T) { + const first = this.getField(a); + const second = this.getField(b); + + if (this.order === "asc") { + if (first < second) return -1; + if (first > second) return 1; + } else { + if (first < second) return 1; + if (first > second) return -1; + } + return 0; + } +} diff --git a/modules/react-arborist/src/nodes/create-node-model.ts b/modules/react-arborist/src/nodes/create-node-model.ts new file mode 100644 index 0000000..c642fd0 --- /dev/null +++ b/modules/react-arborist/src/nodes/create-node-model.ts @@ -0,0 +1,37 @@ +import { NodeModel } from "./node-model.js"; +import { Sorter } from "./sorter.js"; +import { Accessors } from "./types.js"; + +export function createNodeModel( + parent: NodeModel, + sourceData: T, + access: Accessors, +) { + const node = new NodeModel({ + id: access.id(sourceData), + level: parent.level + 1, + isLeaf: access.isLeaf(sourceData), + sourceData, + sourceChildren: access.children(sourceData), + parent, + children: null /* for now until later */, + access, + }); + node.attrs.children = createChildren( + node, + access.children(sourceData), + access, + ); + return node; +} + +export function createChildren( + parent: NodeModel, + sourceArray: T[] | null, + access: Accessors, +) { + if (!sourceArray) return null; + return new Sorter(access) + .sort(sourceArray) + .map((sourceData) => createNodeModel(parent, sourceData, access)); +} diff --git a/modules/react-arborist/src/nodes/default-node-objects.ts b/modules/react-arborist/src/nodes/default-node-objects.ts new file mode 100644 index 0000000..f9a832c --- /dev/null +++ b/modules/react-arborist/src/nodes/default-node-objects.ts @@ -0,0 +1,55 @@ +type DefaultNodeObject = { + id: string; + name: string; + children?: DefaultNodeObject[]; +}; + +export const defaultNodeObjects: DefaultNodeObject[] = [ + { + id: "1", + name: "Welcome", + }, + { id: "2", name: "To" }, + { id: "3", name: "React" }, + { id: "4", name: "Arborist" }, + { + id: "5", + name: "References", + children: [ + { + id: "6", + name: "Documentation", + }, + { + id: "7", + name: "Repository", + }, + { + id: "8", + name: "Brim Data", + }, + { + id: "9", + name: "Contact the Author", + children: [ + { + id: "10", + name: "Website", + }, + { + id: "11", + name: "Mastodon", + }, + { + id: "12", + name: "GitHub Profile", + }, + { + id: "13", + name: "Email", + }, + ], + }, + ], + }, +]; diff --git a/modules/react-arborist/src/nodes/node-model.ts b/modules/react-arborist/src/nodes/node-model.ts new file mode 100644 index 0000000..e599392 --- /dev/null +++ b/modules/react-arborist/src/nodes/node-model.ts @@ -0,0 +1,78 @@ +import { createChildren } from "./create-node-model.js"; +import { NodeObject, Accessors } from "./types.js"; + +type Attrs = { + id: string; + isLeaf: boolean; + level: number; + + sourceData: T; + sourceChildren: T[] | null; + + parent: NodeModel; + children: NodeModel[] | null; + + access: Accessors; +}; + +export class NodeModel implements NodeObject { + constructor(public attrs: Attrs) {} + + insertChildren(index: number, ...data: T[]) { + const nodes = createChildren(this, data, this.access) ?? []; + this.children?.splice(index, 0, ...nodes); + this.sourceChildren?.splice(index, 0, ...data); + } + + update(changes: Partial) { + for (const key in changes) { + // @ts-ignore + this.sourceData[key] = changes[key]; + } + } + + deleteChild(index: number) { + this.children?.splice(index, 1); + this.sourceChildren?.splice(index, 1); + } + + drop() { + this.parent!.deleteChild(this.childIndex); + } + + get id() { + return this.attrs.id; + } + + get isLeaf() { + return this.attrs.isLeaf; + } + + get level() { + return this.attrs.level; + } + + get sourceData() { + return this.attrs.sourceData; + } + + get sourceChildren() { + return this.attrs.sourceChildren; + } + + get parent() { + return this.attrs.parent; + } + + get children() { + return this.attrs.children; + } + + get access() { + return this.attrs.access; + } + + get childIndex() { + return this.parent.children!.indexOf(this); + } +} diff --git a/modules/react-arborist/src/nodes/root-node-model.ts b/modules/react-arborist/src/nodes/root-node-model.ts new file mode 100644 index 0000000..bc76baa --- /dev/null +++ b/modules/react-arborist/src/nodes/root-node-model.ts @@ -0,0 +1,20 @@ +import { createChildren } from "./create-node-model.js"; +import { NodeModel } from "./node-model.js"; +import { Accessors } from "./types.js"; + +export class RootNodeModel extends NodeModel { + constructor(sourceChildren: T[], access: Accessors) { + super({ + id: null!, + isLeaf: false, + parent: null!, + level: -1, + sourceData: null!, + sourceChildren, + access, + children: null, + }); + + this.attrs.children = createChildren(this, sourceChildren, access); + } +} diff --git a/modules/react-arborist/src/nodes/sorter.ts b/modules/react-arborist/src/nodes/sorter.ts new file mode 100644 index 0000000..0d41b58 --- /dev/null +++ b/modules/react-arborist/src/nodes/sorter.ts @@ -0,0 +1,20 @@ +import { toArray } from "../utils.js"; +import { Comparator } from "./comparator.js"; +import { Accessors } from "./types.js"; + +export class Sorter { + constructor(public access: Accessors) {} + + sort(array: T[]) { + const orders = toArray(this.access.sortOrder); + const fields = toArray(this.access.sortBy); + + return array.sort((a, b) => { + for (let i = 0; i < fields.length; i++) { + const result = new Comparator(fields[i], orders[i]).compare(a, b); + if (result !== 0) return result; + } + return 0; + }); + } +} diff --git a/modules/react-arborist/src/nodes/tree-model.ts b/modules/react-arborist/src/nodes/tree-model.ts new file mode 100644 index 0000000..2582faf --- /dev/null +++ b/modules/react-arborist/src/nodes/tree-model.ts @@ -0,0 +1,79 @@ +import { createAccessor } from "./accessor.js"; +import { NodeModel } from "./node-model.js"; +import { RootNodeModel } from "./root-node-model.js"; +import { Accessors, NodeType } from "./types.js"; + +export class TreeModel { + root: RootNodeModel; + nodes: NodeModel[]; + access: Accessors; + + constructor( + public sourceData: T[], + accessors: Partial>, + ) { + this.access = createAccessor(accessors); + this.root = new RootNodeModel(this.sourceData, this.access); + this.nodes = this.root.children!; + } + + initialize(args: { nodeType: NodeType }): T { + return this.access.initialize(args); + } + + create(args: { parentId: string | null; index: number; data: T }) { + this.findParent(args.parentId)?.insertChildren(args.index, args.data); + } + + update(args: { id: string; changes: Partial }) { + this.find(args.id)?.update(args.changes); + } + + move(args: { + sourceIds: string[]; + targetParentId: string | null; + targetIndex: number; + }) { + const sourceNodes = this.findAll(args.sourceIds); + const targetParent = this.findParent(args.targetParentId); + if (!targetParent) return; + targetParent.insertChildren( + args.targetIndex, + ...sourceNodes.map((node) => node.sourceData), + ); + sourceNodes.forEach((node) => node.drop()); + } + + destroy(args: { ids: string[] }) { + this.findAll(args.ids).forEach((node) => node.drop()); + } + + find(id: string) { + for (let cursor of this.nodes) { + const found = this.recursiveFind(id, cursor); + if (found) return found; + } + return null; + } + + findAll(ids: string[]) { + return ids + .map((id) => this.find(id)) + .filter((node) => !!node) as NodeModel[]; + } + + findParent(id: string | null) { + if (id === null) return this.root; + else return this.find(id); + } + + recursiveFind(id: string, cursor: NodeModel): NodeModel | null { + if (!cursor) return null; + if (cursor.id === id) return cursor; + for (let child of cursor.children ?? []) { + const found = this.recursiveFind(id, child); + if (found) return found; + } + return null; + } +} diff --git a/modules/react-arborist/src/nodes/types.ts b/modules/react-arborist/src/nodes/types.ts new file mode 100644 index 0000000..a4538bf --- /dev/null +++ b/modules/react-arborist/src/nodes/types.ts @@ -0,0 +1,66 @@ +import { PartialController } from "../types/utils.js"; + +export type NodeObject = { + id: string; + sourceData: T; + children: NodeObject[] | null; + parent: NodeObject | null; + isLeaf: boolean; + level: number; + childIndex: number; +}; + +export type CreateEvent = { + type: "create"; + parentId: string | null; + index: number; + data: T; +}; + +export type UpdateEvent = { + type: "update"; + id: string; + changes: Partial; +}; + +export type MoveEvent = { + type: "move"; + sourceIds: string[]; + targetParentId: string | null; + targetIndex: number; +}; + +export type DestroyEvent = { + type: "destroy"; + ids: string[]; +}; + +export type NodesOnChangeEvent = + | CreateEvent + | UpdateEvent + | MoveEvent + | DestroyEvent; + +export type NodesState = NodeObject[]; + +export type NodesPartialController = PartialController< + NodesState, + NodesOnChangeEvent +> & { initialize: (args: { nodeType: NodeType }) => T }; + +export type GetSortField = (d: T) => number | string | boolean; + +export type SortOrder = "asc" | "desc"; + +export type Initializer = (args: { nodeType: NodeType }) => T; + +export type NodeType = "leaf" | "internal"; + +export type Accessors = { + id: (d: T) => string; + children: (d: T) => T[] | null; + isLeaf: (d: T) => boolean; + initialize: Initializer; + sortBy: GetSortField | GetSortField[]; + sortOrder: SortOrder | SortOrder[]; +}; diff --git a/modules/react-arborist/src/nodes/use-nodes.ts b/modules/react-arborist/src/nodes/use-nodes.ts new file mode 100644 index 0000000..7f0859c --- /dev/null +++ b/modules/react-arborist/src/nodes/use-nodes.ts @@ -0,0 +1,40 @@ +import { useState } from "react"; +import { + Accessors, + NodeObject, + NodeType, + NodesOnChangeEvent, + NodesPartialController, +} from "./types.js"; +import { TreeModel } from "./tree-model.js"; + +export function useNodes( + initialData: T[], + options: Partial> = {}, +) { + const [sourceData, setSourceData] = useState(initialData); + const treeManager = new TreeModel(sourceData, options); + + return { + setSourceData, + initialize: (args: { nodeType: NodeType }) => treeManager.initialize(args), + value: treeManager.nodes as NodeObject[], + onChange: (event: NodesOnChangeEvent) => { + switch (event.type) { + case "create": + treeManager.create(event); + break; + case "move": + treeManager.move(event); + break; + case "update": + treeManager.update(event); + break; + case "destroy": + treeManager.destroy(event); + break; + } + setSourceData([...treeManager.sourceData]); + }, + }; +} diff --git a/modules/react-arborist/src/opens/types.ts b/modules/react-arborist/src/opens/types.ts new file mode 100644 index 0000000..8fb5ff0 --- /dev/null +++ b/modules/react-arborist/src/opens/types.ts @@ -0,0 +1,14 @@ +import { PartialController } from "../types/utils.js"; + +export type OpensState = Record; + +export type OpensOnChangeEvent = { + value: OpensState; + type: "open" | "close"; + ids: string[]; +}; + +export type OpensPartialController = PartialController< + OpensState, + OpensOnChangeEvent +>; diff --git a/modules/react-arborist/src/opens/use-opens.ts b/modules/react-arborist/src/opens/use-opens.ts new file mode 100644 index 0000000..3222331 --- /dev/null +++ b/modules/react-arborist/src/opens/use-opens.ts @@ -0,0 +1,11 @@ +import { useState } from "react"; +import { OpensPartialController, OpensState } from "./types.js"; + +export function useOpens(): OpensPartialController { + const [value, setValue] = useState({}); + + return { + value, + onChange: (e) => setValue(e.value), + }; +} diff --git a/modules/react-arborist/src/props/use-default-props.ts b/modules/react-arborist/src/props/use-default-props.ts new file mode 100644 index 0000000..59a80de --- /dev/null +++ b/modules/react-arborist/src/props/use-default-props.ts @@ -0,0 +1,62 @@ +import * as defaultCommands from "../commands/default-commands.js"; +import { DefaultCursorRenderer } from "../components/default-cursor.js"; +import { + DefaultNodeRenderer, + DefaultRowRenderer, +} from "../components/tree-view.js"; +import { useCursor } from "../cursor/use-cursor.js"; +import { useDnd } from "../dnd/use-dnd.js"; +import { useEdit } from "../edit/use-edit.js"; +import { useFocus } from "../focus/use-focus.js"; +import { defaultNodeObjects } from "../nodes/default-node-objects.js"; +import { useNodes } from "../nodes/use-nodes.js"; +import { useOpens } from "../opens/use-opens.js"; +import { useMultiSelection } from "../selection/use-multi-selection.js"; +import { defaultShortcuts } from "../shortcuts/default-shortcuts.js"; +import { TreeViewProps } from "../types/tree-view-props.js"; + +export function useDefaultProps( + props: Partial>, +): TreeViewProps { + return { + /* Partial State Controllers */ + nodes: props.nodes ?? useNodes(defaultNodeObjects as T[]), + opens: props.opens ?? useOpens(), + edit: props.edit ?? useEdit(), + selection: props.selection ?? useMultiSelection(), + dnd: props.dnd ?? useDnd(), + cursor: props.cursor ?? useCursor(), + focus: props.focus ?? useFocus(), + visible: props.visible ?? { value: {}, onChange: () => {} }, + + /* Commands and Shortcuts */ + shortcuts: props.shortcuts ?? defaultShortcuts, + commands: props.commands ?? defaultCommands, + + /* Dimensions */ + width: props.width ?? 300, + height: props.height ?? 500, + indent: props.indent ?? 24, + rowHeight: props.rowHeight ?? 32, + paddingTop: props.paddingTop ?? props.padding ?? 0, + paddingBottom: props.paddingBottom ?? props.padding ?? 0, + padding: props.padding ?? 0, + overscanCount: props.overscanCount ?? 1, + direction: "ltr", + + /* Renderers */ + renderRow: props.renderRow ?? DefaultRowRenderer, + renderNode: props.renderNode ?? DefaultNodeRenderer, + renderCursor: props.renderCursor ?? DefaultCursorRenderer, + + /* Callbacks */ + onScroll: props.onScroll ?? (() => {}), + + /* Class names */ + className: props.className, + rowClassName: props.rowClassName, + + /* Flags */ + openByDefault: props.openByDefault ?? true, + }; +} diff --git a/modules/react-arborist/src/row/attributes.ts b/modules/react-arborist/src/row/attributes.ts new file mode 100644 index 0000000..df02d1e --- /dev/null +++ b/modules/react-arborist/src/row/attributes.ts @@ -0,0 +1,21 @@ +import { CSSProperties } from "react"; +import { NodeController } from "../controllers/node-controller.js"; +import { TreeController } from "../controllers/tree-controller.js"; + +export function createRowAttributes( + tree: TreeController, + node: NodeController, + style: CSSProperties, +): React.HTMLAttributes { + const top = parseFloat(style.top as string); + const pad = tree.props.padding ?? tree.props.paddingTop ?? 0; + + return { + role: "treeitem", + "aria-level": node.level + 1, + "aria-selected": node.isSelected, + tabIndex: -1, + className: tree.props.rowClassName, + style: { ...style, top: top + pad }, + }; +} diff --git a/modules/react-arborist/src/selection/types.ts b/modules/react-arborist/src/selection/types.ts new file mode 100644 index 0000000..04a3168 --- /dev/null +++ b/modules/react-arborist/src/selection/types.ts @@ -0,0 +1,15 @@ +import { TreeController } from "../controllers/tree-controller.js"; +import { PartialController } from "../types/utils.js"; + +export type SelectionState = Record; +export type SelectionOnChangeEvent = + | { type: "select"; id: string } + | { type: "select-multi"; id: string } + | { type: "select-contiguous"; id: string; tree: TreeController } + | { type: "deselect"; id: string } + | { type: "select-all"; tree: TreeController }; + +export type SelectionPartialController = PartialController< + SelectionState, + SelectionOnChangeEvent +>; diff --git a/modules/react-arborist/src/selection/use-multi-selection.ts b/modules/react-arborist/src/selection/use-multi-selection.ts new file mode 100644 index 0000000..488ac6e --- /dev/null +++ b/modules/react-arborist/src/selection/use-multi-selection.ts @@ -0,0 +1,55 @@ +import { useState } from "react"; +import { + SelectionOnChangeEvent, + SelectionPartialController, + SelectionState, +} from "./types.js"; + +export function useMultiSelection(): SelectionPartialController { + const [anchor, setAnchor] = useState(null); + const [mostRecent, setMostRecent] = useState(null); + const [value, setValue] = useState({}); + + function onChange(e: SelectionOnChangeEvent) { + switch (e.type) { + case "select": + setValue({ [e.id]: true }); + setAnchor(e.id); + setMostRecent(e.id); + break; + + case "deselect": + setValue((prev: any) => ({ ...prev, [e.id]: false })); + break; + + case "select-multi": + setValue((prev: any) => ({ ...prev, [e.id]: true })); + setAnchor(e.id); + setMostRecent(e.id); + break; + + case "select-contiguous": + const next = { ...value }; + for (const node of e.tree.nodesBetween(anchor, mostRecent)) { + next[node.id] = false; + } + for (const node of e.tree.nodesBetween(anchor, e.id)) { + next[node.id] = true; + } + setMostRecent(e.id); + setValue(next); + break; + + case "select-all": + const all: Record = {}; + for (const node of e.tree.rows) { + all[node.id] = true; + } + setAnchor(e.tree.firstNode?.id || null); + setMostRecent(e.tree.lastNode?.id || null); + setValue(all); + } + } + + return { value, onChange }; +} diff --git a/modules/react-arborist/src/shortcuts/default-shortcuts.ts b/modules/react-arborist/src/shortcuts/default-shortcuts.ts new file mode 100644 index 0000000..9bedec5 --- /dev/null +++ b/modules/react-arborist/src/shortcuts/default-shortcuts.ts @@ -0,0 +1,36 @@ +import { ShortcutAttrs } from "./types.js"; + +export const defaultShortcuts: ShortcutAttrs[] = [ + /* Keyboard Navigation */ + { key: "ArrowDown", command: "focusNext" }, + { key: "ArrowUp", command: "focusPrev" }, + { key: "ArrowLeft", command: "focusParent", when: "isLeaf || isClosed" }, + { key: "ArrowLeft", command: "close", when: "isOpen" }, + { key: "ArrowRight", command: "open", when: "isClosed" }, + { key: "ArrowRight", command: "focusNext", when: "isOpen" }, + { key: "Home", command: "focusFirst" }, + { key: "End", command: "focusLast" }, + { key: "PageDown", command: "focusNextPage" }, + { key: "PageUp", command: "focusPrevPage" }, + + /* Tabbing Around */ + { key: "Tab", command: "focusOutsideNext" }, + { key: "Shift+Tab", command: "focusOutsidePrev" }, + + /* CRUD */ + { key: "Backspace", command: "destroy" }, + { key: "a", command: "createLeaf" }, + { key: "Shift+A", command: "createInternal" }, + { key: "Enter", command: "edit" }, + + /* Selection */ + { key: "Shift+ArrowUp", command: "moveSelectionStart" }, + { key: "Shift+ArrowDown", command: "moveSelectionEnd" }, + { key: " ", command: "toggle", when: "isInternal" }, + { key: " ", command: "select", when: "isLeaf" }, + { key: "Meta+a", command: "selectAll" }, + { key: "Control+a", command: "selectAll" }, + + /* Opening */ + { key: "*", command: "openSiblings" }, +]; diff --git a/modules/react-arborist/src/shortcuts/index.ts b/modules/react-arborist/src/shortcuts/index.ts new file mode 100644 index 0000000..e504ae1 --- /dev/null +++ b/modules/react-arborist/src/shortcuts/index.ts @@ -0,0 +1,3 @@ +// These are the keys that should be allowed +// https://code.visualstudio.com/docs/getstarted/keybindings#_accepted-keys + diff --git a/modules/react-arborist/src/shortcuts/press.test.ts b/modules/react-arborist/src/shortcuts/press.test.ts new file mode 100644 index 0000000..fda0cbb --- /dev/null +++ b/modules/react-arborist/src/shortcuts/press.test.ts @@ -0,0 +1,42 @@ +import { Press } from "./press.js"; + +const cases = [ + { + event: { + key: "ArrowDown", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: false, + }, + string: "ArrowDown", + }, + { + event: { + key: "A", + altKey: false, + ctrlKey: false, + metaKey: false, + shiftKey: true, + }, + string: "Shift+A", + }, + { + event: { + key: "F", + altKey: true, + ctrlKey: true, + metaKey: true, + shiftKey: true, + }, + string: "Control+Alt+Shift+Meta+F", + }, +]; + +for (const testCase of cases) { + test("press to string", () => { + const press = new Press(testCase.event); + expect(press.toString()).toEqual(testCase.string); + expect(press.isEqual(testCase.string)); + }); +} diff --git a/modules/react-arborist/src/shortcuts/press.ts b/modules/react-arborist/src/shortcuts/press.ts new file mode 100644 index 0000000..1a1718c --- /dev/null +++ b/modules/react-arborist/src/shortcuts/press.ts @@ -0,0 +1,31 @@ +type KeyPressData = { + key: string; + altKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + shiftKey: boolean; +}; + +export class Press { + constructor(public event: KeyPressData) {} + + toArray() { + let array = []; + if (this.event.ctrlKey) array.push("Control"); + if (this.event.altKey) array.push("Alt"); + if (this.event.shiftKey) array.push("Shift"); + if (this.event.metaKey) array.push("Meta"); + if (!array.includes(this.event.key)) array.push(this.event.key); + return array; + } + + toString() { + return this.toArray().join("+"); + } + + isEqual(shortcutKey: string) { + const other = shortcutKey.split("+").sort().join(); + const mine = this.toArray().sort().join(); + return mine === other; + } +} diff --git a/modules/react-arborist/src/shortcuts/shortcut-manager.ts b/modules/react-arborist/src/shortcuts/shortcut-manager.ts new file mode 100644 index 0000000..52a7baa --- /dev/null +++ b/modules/react-arborist/src/shortcuts/shortcut-manager.ts @@ -0,0 +1,18 @@ +import { Press } from "./press.js"; +import { Shortcut } from "./shortcut.js"; +import { ShortcutAttrs } from "./types.js"; + +export class ShortcutManager { + shortcuts: Shortcut[]; + + constructor(shortcuts: ShortcutAttrs[]) { + this.shortcuts = shortcuts.map((attrs) => new Shortcut(attrs)); + } + + find(event: KeyboardEvent, context: Context) { + const press = new Press(event); + return this.shortcuts + .filter((shortcut) => press.isEqual(shortcut.key)) + .find((shortcut) => shortcut.matches(context)); + } +} diff --git a/modules/react-arborist/src/shortcuts/shortcut.ts b/modules/react-arborist/src/shortcuts/shortcut.ts new file mode 100644 index 0000000..8cf84f9 --- /dev/null +++ b/modules/react-arborist/src/shortcuts/shortcut.ts @@ -0,0 +1,24 @@ +import { ShortcutAttrs } from "./types.js"; +import { evaluate } from "when-clause"; + +export class Shortcut { + constructor(public attrs: ShortcutAttrs) {} + + get key() { + return this.attrs.key; + } + + get command() { + return this.attrs.command; + } + + get when() { + return this.attrs.when; + } + + matches(context: Context) { + const { when } = this; + if (!when) return true; + return evaluate(when, context); + } +} diff --git a/modules/react-arborist/src/shortcuts/types.ts b/modules/react-arborist/src/shortcuts/types.ts new file mode 100644 index 0000000..12b242c --- /dev/null +++ b/modules/react-arborist/src/shortcuts/types.ts @@ -0,0 +1 @@ +export type ShortcutAttrs = { key: string; command: string; when?: string }; diff --git a/modules/react-arborist/src/state/dnd-slice.ts b/modules/react-arborist/src/state/dnd-slice.ts deleted file mode 100644 index afe0fb7..0000000 --- a/modules/react-arborist/src/state/dnd-slice.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Cursor } from "../dnd/compute-drop"; -import { ActionTypes } from "../types/utils"; -import { initialState } from "./initial"; - -/* Types */ -export type DndState = { - dragId: null | string; - cursor: Cursor; - dragIds: string[]; - parentId: null | string; - index: number | null; -}; - -/* Actions */ -export const actions = { - cursor(cursor: Cursor) { - return { type: "DND_CURSOR" as const, cursor }; - }, - dragStart(id: string, dragIds: string[]) { - return { type: "DND_DRAG_START" as const, id, dragIds }; - }, - dragEnd() { - return { type: "DND_DRAG_END" as const }; - }, - hovering(parentId: string | null, index: number | null) { - return { type: "DND_HOVERING" as const, parentId, index }; - }, -}; - -/* Reducer */ -export function reducer( - state: DndState = initialState()["dnd"], - action: ActionTypes -): DndState { - switch (action.type) { - case "DND_CURSOR": - return { ...state, cursor: action.cursor }; - case "DND_DRAG_START": - return { ...state, dragId: action.id, dragIds: action.dragIds }; - case "DND_DRAG_END": - return initialState()["dnd"]; - case "DND_HOVERING": - return { ...state, parentId: action.parentId, index: action.index }; - default: - return state; - } -} diff --git a/modules/react-arborist/src/state/drag-slice.ts b/modules/react-arborist/src/state/drag-slice.ts deleted file mode 100644 index 517adc6..0000000 --- a/modules/react-arborist/src/state/drag-slice.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ActionTypes } from "../types/utils"; -import { actions as dnd } from "./dnd-slice"; -import { initialState } from "./initial"; - -/* Types */ - -export type DragSlice = { - id: string | null; - selectedIds: string[]; - destinationParentId: string | null; - destinationIndex: number | null; -}; - -/* Reducer */ - -export function reducer( - state: DragSlice = initialState().nodes.drag, - action: ActionTypes -): DragSlice { - switch (action.type) { - case "DND_DRAG_START": - return { ...state, id: action.id, selectedIds: action.dragIds }; - case "DND_DRAG_END": - return { - ...state, - id: null, - destinationParentId: null, - destinationIndex: null, - selectedIds: [], - }; - case "DND_HOVERING": - if ( - action.parentId !== state.destinationParentId || - action.index != state.destinationIndex - ) { - return { - ...state, - destinationParentId: action.parentId, - destinationIndex: action.index, - }; - } else { - return state; - } - default: - return state; - } -} diff --git a/modules/react-arborist/src/state/edit-slice.ts b/modules/react-arborist/src/state/edit-slice.ts deleted file mode 100644 index 9820de2..0000000 --- a/modules/react-arborist/src/state/edit-slice.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* Types */ -export type EditState = { id: string | null }; - -/* Actions */ -export function edit(id: string | null) { - return { type: "EDIT" as const, id }; -} - -/* Reducer */ -export function reducer( - state: EditState = { id: null }, - action: ReturnType -) { - if (action.type === "EDIT") { - return { ...state, id: action.id }; - } else { - return state; - } -} diff --git a/modules/react-arborist/src/state/focus-slice.ts b/modules/react-arborist/src/state/focus-slice.ts deleted file mode 100644 index de51303..0000000 --- a/modules/react-arborist/src/state/focus-slice.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* Types */ - -export type FocusState = { id: string | null; treeFocused: boolean }; - -/* Actions */ - -export function focus(id: string | null) { - return { type: "FOCUS" as const, id }; -} - -export function treeBlur() { - return { type: "TREE_BLUR" } as const; -} - -/* Reducer */ - -export function reducer( - state: FocusState = { id: null, treeFocused: false }, - action: ReturnType | ReturnType -) { - if (action.type === "FOCUS") { - return { ...state, id: action.id, treeFocused: true }; - } else if (action.type === "TREE_BLUR") { - return { ...state, treeFocused: false }; - } else { - return state; - } -} diff --git a/modules/react-arborist/src/state/initial.ts b/modules/react-arborist/src/state/initial.ts deleted file mode 100644 index 7a2e027..0000000 --- a/modules/react-arborist/src/state/initial.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TreeProps } from "../types/tree-props"; -import { RootState } from "./root-reducer"; - -export const initialState = (props?: TreeProps): RootState => ({ - nodes: { - // Changes together - open: { filtered: {}, unfiltered: props?.initialOpenState ?? {} }, - focus: { id: null, treeFocused: false }, - edit: { id: null }, - drag: { - id: null, - selectedIds: [], - destinationParentId: null, - destinationIndex: null, - }, - selection: { ids: new Set(), anchor: null, mostRecent: null }, - }, - dnd: { - cursor: { type: "none" }, - dragId: null, - dragIds: [], - parentId: null, - index: -1, - }, -}); diff --git a/modules/react-arborist/src/state/open-slice.ts b/modules/react-arborist/src/state/open-slice.ts deleted file mode 100644 index def75e5..0000000 --- a/modules/react-arborist/src/state/open-slice.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { ActionTypes } from "../types/utils"; - -/* Types */ -export type OpenMap = { [id: string]: boolean }; -export type OpenSlice = { unfiltered: OpenMap; filtered: OpenMap }; - -/* Actions */ -export const actions = { - open(id: string, filtered: boolean) { - return { type: "VISIBILITY_OPEN" as const, id, filtered }; - }, - close(id: string, filtered: boolean) { - return { type: "VISIBILITY_CLOSE" as const, id, filtered }; - }, - toggle(id: string, filtered: boolean) { - return { type: "VISIBILITY_TOGGLE" as const, id, filtered }; - }, - clear(filtered: boolean) { - return { type: "VISIBILITY_CLEAR" as const, filtered }; - }, -}; - -/* Reducer */ - -function openMapReducer( - state: OpenMap = {}, - action: ActionTypes -) { - if (action.type === "VISIBILITY_OPEN") { - return { ...state, [action.id]: true }; - } else if (action.type === "VISIBILITY_CLOSE") { - return { ...state, [action.id]: false }; - } else if (action.type === "VISIBILITY_TOGGLE") { - const prev = state[action.id]; - return { ...state, [action.id]: !prev }; - } else if (action.type === "VISIBILITY_CLEAR") { - return {}; - } else { - return state; - } -} - -export function reducer( - state: OpenSlice = { filtered: {}, unfiltered: {} }, - action: ActionTypes -): OpenSlice { - if (!action.type.startsWith("VISIBILITY")) return state; - if (action.filtered) { - return { ...state, filtered: openMapReducer(state.filtered, action) }; - } else { - return { ...state, unfiltered: openMapReducer(state.unfiltered, action) }; - } -} diff --git a/modules/react-arborist/src/state/root-reducer.ts b/modules/react-arborist/src/state/root-reducer.ts deleted file mode 100644 index 637262c..0000000 --- a/modules/react-arborist/src/state/root-reducer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ActionFromReducer, combineReducers } from "redux"; -import { reducer as focus } from "./focus-slice"; -import { reducer as edit } from "./edit-slice"; -import { reducer as dnd } from "./dnd-slice"; -import { reducer as selection } from "./selection-slice"; -import { reducer as open } from "./open-slice"; -import { reducer as drag } from "./drag-slice"; - -export const rootReducer = combineReducers({ - nodes: combineReducers({ - focus, - edit, - open, - selection, - drag, - }), - dnd, -}); - -export type RootState = ReturnType; -export type Actions = ActionFromReducer; diff --git a/modules/react-arborist/src/state/selection-slice.ts b/modules/react-arborist/src/state/selection-slice.ts deleted file mode 100644 index 62e2493..0000000 --- a/modules/react-arborist/src/state/selection-slice.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ActionTypes, IdObj } from "../types/utils"; -import { identify } from "../utils"; -import { initialState } from "./initial"; - -/* Types */ -export type SelectionState = { - ids: Set; - anchor: string | null; - mostRecent: string | null; -}; - -/* Actions */ -export const actions = { - clear: () => ({ type: "SELECTION_CLEAR" as const }), - - only: (id: string | IdObj) => ({ - type: "SELECTION_ONLY" as const, - id: identify(id), - }), - - add: (id: string | string[] | IdObj | IdObj[]) => ({ - type: "SELECTION_ADD" as const, - ids: (Array.isArray(id) ? id : [id]).map(identify), - }), - - remove: (id: string | string[] | IdObj | IdObj[]) => ({ - type: "SELECTION_REMOVE" as const, - ids: (Array.isArray(id) ? id : [id]).map(identify), - }), - - set: (args: { - ids: Set; - anchor: string | null; - mostRecent: string | null; - }) => ({ - type: "SELECTION_SET" as const, - ...args, - }), - - mostRecent: (id: string | null | IdObj) => ({ - type: "SELECTION_MOST_RECENT" as const, - id: id === null ? null : identify(id), - }), - - anchor: (id: string | null | IdObj) => ({ - type: "SELECTION_ANCHOR" as const, - id: id === null ? null : identify(id), - }), -}; - -/* Reducer */ -export function reducer( - state: SelectionState = initialState()["nodes"]["selection"], - action: ActionTypes -): SelectionState { - const ids = state.ids; - switch (action.type) { - case "SELECTION_CLEAR": - return { ...state, ids: new Set() }; - case "SELECTION_ONLY": - return { ...state, ids: new Set([action.id]) }; - case "SELECTION_ADD": - if (action.ids.length === 0) return state; - action.ids.forEach((id) => ids.add(id)); - return { ...state, ids: new Set(ids) }; - case "SELECTION_REMOVE": - if (action.ids.length === 0) return state; - action.ids.forEach((id) => ids.delete(id)); - return { ...state, ids: new Set(ids) }; - case "SELECTION_SET": - return { - ...state, - ids: action.ids, - mostRecent: action.mostRecent, - anchor: action.anchor, - }; - case "SELECTION_MOST_RECENT": - return { ...state, mostRecent: action.id }; - case "SELECTION_ANCHOR": - return { ...state, anchor: action.id }; - default: - return state; - } -} diff --git a/modules/react-arborist/src/tree-view/attributes.ts b/modules/react-arborist/src/tree-view/attributes.ts new file mode 100644 index 0000000..40a1526 --- /dev/null +++ b/modules/react-arborist/src/tree-view/attributes.ts @@ -0,0 +1,29 @@ +import { HTMLAttributes } from "react"; +import { TreeController } from "../controllers/tree-controller.js"; + +export function createTreeViewAttributes(tree: TreeController) { + return { + role: "tree", + tabIndex: 0, + dir: "rtl", + style: { + inlineSize: tree.width, + blockSize: tree.height, + minInlineSize: 0, + minBlockSize: 0, + }, + onKeyDown(e) { + tree.handleKeyDown(e); + }, + onFocus(e) { + if (!e.currentTarget.contains(e.relatedTarget)) { + tree.onFocus(); + } + }, + onBlur(e) { + if (!e.currentTarget.contains(e.relatedTarget)) { + tree.onBlur(); + } + }, + } as HTMLAttributes; +} diff --git a/modules/react-arborist/src/types/handlers.ts b/modules/react-arborist/src/types/handlers.ts index 2e3636f..6d934a7 100644 --- a/modules/react-arborist/src/types/handlers.ts +++ b/modules/react-arborist/src/types/handlers.ts @@ -1,30 +1,30 @@ -import { NodeApi } from "../interfaces/node-api"; -import { IdObj } from "./utils"; +import { NodeController } from "../controllers/node-controller.js"; +import { IdObj } from "./utils.js"; export type CreateHandler = (args: { parentId: string | null; - parentNode: NodeApi | null; + parentNode: NodeController | null; index: number; type: "internal" | "leaf"; }) => (IdObj | null) | Promise; export type MoveHandler = (args: { dragIds: string[]; - dragNodes: NodeApi[]; + dragNodes: NodeController[]; parentId: string | null; - parentNode: NodeApi | null; + parentNode: NodeController | null; index: number; }) => void | Promise; export type RenameHandler = (args: { id: string; name: string; - node: NodeApi; + node: NodeController; }) => void | Promise; export type DeleteHandler = (args: { ids: string[]; - nodes: NodeApi[]; + nodes: NodeController[]; }) => void | Promise; export type EditResult = diff --git a/modules/react-arborist/src/types/renderers.ts b/modules/react-arborist/src/types/renderers.ts index 356ad57..0a47ad5 100644 --- a/modules/react-arborist/src/types/renderers.ts +++ b/modules/react-arborist/src/types/renderers.ts @@ -1,33 +1,38 @@ -import { CSSProperties, HTMLAttributes, ReactElement } from "react"; -import { IdObj } from "./utils"; -import { NodeApi } from "../interfaces/node-api"; -import { TreeApi } from "../interfaces/tree-api"; -import { XYCoord } from "react-dnd"; +import { + CSSProperties, + HTMLAttributes, + MutableRefObject, + ReactElement, +} from "react"; +import { TreeController } from "../controllers/tree-controller.js"; +import { NodeController } from "../controllers/node-controller.js"; +import { XY } from "../dnd/types.js"; export type NodeRendererProps = { + attrs: HTMLAttributes; style: CSSProperties; - node: NodeApi; - tree: TreeApi; + node: NodeController; + tree: TreeController; dragHandle?: (el: HTMLDivElement | null) => void; preview?: boolean; }; export type RowRendererProps = { - node: NodeApi; - innerRef: (el: HTMLDivElement | null) => void; + node: NodeController; + innerRef: MutableRefObject; attrs: HTMLAttributes; children: ReactElement; }; export type DragPreviewProps = { - offset: XYCoord | null; - mouse: XYCoord | null; + offset: XY | null; + mouse: XY | null; id: string | null; dragIds: string[]; isDragging: boolean; }; -export type CursorProps = { +export type CursorRendererProps = { top: number; left: number; indent: number; diff --git a/modules/react-arborist/src/types/state.ts b/modules/react-arborist/src/types/state.ts deleted file mode 100644 index 14fa221..0000000 --- a/modules/react-arborist/src/types/state.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { NodeApi } from "../interfaces/node-api"; - -export type NodeState = typeof NodeApi.prototype["state"]; diff --git a/modules/react-arborist/src/types/tree-props.ts b/modules/react-arborist/src/types/tree-props.ts index 440bcc4..4028bc5 100644 --- a/modules/react-arborist/src/types/tree-props.ts +++ b/modules/react-arborist/src/types/tree-props.ts @@ -1,11 +1,9 @@ -import { BoolFunc } from "./utils"; -import * as handlers from "./handlers"; -import * as renderers from "./renderers"; +import { BoolFunc } from "./utils.js"; +import * as handlers from "./handlers.js"; +import * as renderers from "./renderers.js"; import { ElementType, MouseEventHandler } from "react"; import { ListOnScrollProps } from "react-window"; -import { NodeApi } from "../interfaces/node-api"; -import { OpenMap } from "../state/open-slice"; -import { useDragDropManager } from "react-dnd"; +import { NodeController } from "../controllers/node-controller.js"; export interface TreeProps { /* Data Options */ @@ -22,7 +20,7 @@ export interface TreeProps { children?: ElementType>; renderRow?: ElementType>; renderDragPreview?: ElementType; - renderCursor?: ElementType; + renderCursor?: ElementType; renderContainer?: ElementType<{}>; /* Sizes */ @@ -47,34 +45,32 @@ export interface TreeProps { | string | boolean | ((args: { - parentNode: NodeApi; - dragNodes: NodeApi[]; + parentNode: NodeController; + dragNodes: NodeController[]; index: number; }) => boolean); /* Event Handlers */ - onActivate?: (node: NodeApi) => void; - onSelect?: (nodes: NodeApi[]) => void; + onActivate?: (node: NodeController) => void; + onSelect?: (nodes: NodeController[]) => void; onScroll?: (props: ListOnScrollProps) => void; onToggle?: (id: string) => void; - onFocus?: (node: NodeApi) => void; + onFocus?: (node: NodeController) => void; /* Selection */ selection?: string; /* Open State */ - initialOpenState?: OpenMap; + initialOpenState?: Record; /* Search */ searchTerm?: string; - searchMatch?: (node: NodeApi, searchTerm: string) => boolean; + searchMatch?: (node: NodeController, searchTerm: string) => boolean; /* Extra */ className?: string | undefined; rowClassName?: string | undefined; - dndRootElement?: globalThis.Node | null; onClick?: MouseEventHandler; onContextMenu?: MouseEventHandler; - dndManager?: ReturnType; } diff --git a/modules/react-arborist/src/types/tree-view-props.ts b/modules/react-arborist/src/types/tree-view-props.ts new file mode 100644 index 0000000..e768c9b --- /dev/null +++ b/modules/react-arborist/src/types/tree-view-props.ts @@ -0,0 +1,58 @@ +import { NodesPartialController } from "../nodes/types.js"; +import { OpensPartialController } from "../opens/types.js"; +import { EditPartialController } from "../edit/types.js"; +import { SelectionPartialController } from "../selection/types.js"; +import { DisableDropCheck, DndPartialController } from "../dnd/types.js"; +import { CursorPartialController } from "../cursor/types.js"; +import { FocusPartialController } from "../focus/types.js"; +import { VisiblePartialController } from "../visible/types.js"; +import { ShortcutAttrs } from "../shortcuts/types.js"; +import { CommandObject } from "../commands/types.js"; +import { ListOnScrollProps } from "react-window"; +import { + CursorRendererProps, + NodeRendererProps, + RowRendererProps, +} from "./renderers.js"; + +export type TreeViewProps = { + /* Partial Controllers */ + nodes: NodesPartialController; + opens: OpensPartialController; + edit: EditPartialController; + selection: SelectionPartialController; + dnd: DndPartialController; + cursor: CursorPartialController; + focus: FocusPartialController; + visible: VisiblePartialController; + + /* Commands and Shortcuts */ + shortcuts: ShortcutAttrs[]; + commands: CommandObject; + + /* Dimensions and Sizes */ + width: number | string; + height: number; + rowHeight: number; + indent: number; + overscanCount: number; + paddingTop: number; + paddingBottom: number; + padding: number; + direction: "rtl" | "ltr"; + + /* Callbacks */ + onScroll: (args: ListOnScrollProps) => void; + + /* Class Names */ + className?: string; + rowClassName?: string; + + /* Configurations */ + openByDefault: boolean; + disableDrop?: string | boolean | DisableDropCheck; + + renderRow: (props: RowRendererProps) => any; + renderNode: (props: NodeRendererProps) => any; + renderCursor: (props: CursorRendererProps) => any; +}; diff --git a/modules/react-arborist/src/types/utils.ts b/modules/react-arborist/src/types/utils.ts index 10c6418..af627ae 100644 --- a/modules/react-arborist/src/types/utils.ts +++ b/modules/react-arborist/src/types/utils.ts @@ -1,6 +1,3 @@ -import { AnyAction } from "redux"; -import { NodeApi } from "../interfaces/node-api"; - export interface IdObj { id: string; } @@ -9,10 +6,7 @@ export type Identity = string | IdObj | null; export type BoolFunc = (data: T) => boolean; -export type ActionTypes< - Actions extends { [name: string]: (...args: any[]) => AnyAction } -> = ReturnType; - -export type SelectOptions = { multi?: boolean; contiguous?: boolean }; - -export type NodesById = { [id: string]: NodeApi }; +export type PartialController = { + value: Value; + onChange: (event: Event) => void; +}; diff --git a/modules/react-arborist/src/utils.ts b/modules/react-arborist/src/utils.ts index 4d69307..3789eab 100644 --- a/modules/react-arborist/src/utils.ts +++ b/modules/react-arborist/src/utils.ts @@ -1,66 +1,15 @@ -import { NodeApi } from "./interfaces/node-api"; -import { TreeApi } from "./interfaces/tree-api"; -import { IdObj } from "./types/utils"; +import { NodeController } from "./controllers/node-controller.js"; export function bound(n: number, min: number, max: number) { return Math.max(Math.min(n, max), min); } -export function isItem(node: NodeApi | null) { - return node && node.isLeaf; +export function isOpenWithEmptyChildren(node: NodeController | null) { + return node && node.isOpen && !node.object.children?.length; } -export function isClosed(node: NodeApi | null) { - return node && node.isInternal && !node.isOpen; -} - -export function isOpenWithEmptyChildren(node: NodeApi | null) { - return node && node.isOpen && !node.children?.length; -} - -/** - * Is first param a descendant of the second param - */ -export const isDescendant = (a: NodeApi, b: NodeApi) => { - let n: NodeApi | null = a; - while (n) { - if (n.id === b.id) return true; - n = n.parent; - } - return false; -}; - -export const indexOf = (node: NodeApi) => { - if (!node.parent) throw Error("Node does not have a parent"); - return node.parent.children!.findIndex((c) => c.id === node.id); -}; - export function noop() {} -export function dfs(node: NodeApi, id: string): NodeApi | null { - if (!node) return null; - if (node.id === id) return node; - if (node.children) { - for (let child of node.children) { - const result = dfs(child, id); - if (result) return result; - } - } - return null; -} - -export function walk( - node: NodeApi, - fn: (node: NodeApi) => void -): void { - fn(node); - if (node.children) { - for (let child of node.children) { - walk(child, fn); - } - } -} - export function focusNextElement(target: HTMLElement) { const elements = getFocusable(target); @@ -110,73 +59,32 @@ function prevItem(list: HTMLElement[], index: number) { function getFocusable(target: HTMLElement) { return Array.from( document.querySelectorAll( - 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), details:not([disabled]), summary:not(:disabled)' - ) + 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), details:not([disabled]), summary:not(:disabled)', + ), ).filter((e) => e === target || !target.contains(e)) as HTMLElement[]; } -export function access( - obj: any, - accessor: string | boolean | Function -): T { - if (typeof accessor === "boolean") return accessor as unknown as T; - if (typeof accessor === "string") return obj[accessor] as T; - return accessor(obj) as T; -} - -export function identifyNull(obj: string | IdObj | null) { - if (obj === null) return null; - else return identify(obj); -} - -export function identify(obj: string | IdObj) { - return typeof obj === "string" ? obj : obj.id; -} - -export function mergeRefs(...refs: any) { - return (instance: any) => { - refs.forEach((ref: any) => { - if (typeof ref === "function") { - ref(instance); - } else if (ref != null) { - ref.current = instance; - } - }); - }; +export function toArray(itemOrArray: T | T[]): T[] { + if (Array.isArray(itemOrArray)) return itemOrArray; + else return [itemOrArray]; } -export function safeRun any>( - fn: T | undefined, - ...args: Parameters -) { - if (fn) return fn(...args); -} +export class Timer { + id: number | undefined; -export function waitFor(fn: () => boolean) { - return new Promise((resolve, reject) => { - let tries = 0; - function check() { - tries += 1; - if (tries === 100) reject(); - if (fn()) resolve(); - else setTimeout(check, 10); - } - check(); - }); -} + cancel() { + clearTimeout(this.id); + this.id = undefined; + } -export function getInsertIndex(tree: TreeApi) { - const focus = tree.focusedNode; - if (!focus) return tree.root.children?.length ?? 0; - if (focus.isOpen) return 0; - if (focus.parent) return focus.childIndex + 1; - return 0; -} + start(callback: () => void, delay: number) { + this.id = setTimeout(() => { + callback(); + this.id = undefined; + }, delay) as unknown as number; + } -export function getInsertParentId(tree: TreeApi) { - const focus = tree.focusedNode; - if (!focus) return null; - if (focus.isOpen) return focus.id; - if (focus.parent && !focus.parent.isRoot) return focus.parent.id; - return null; + get hasStarted() { + return this.id !== undefined; + } } diff --git a/modules/react-arborist/src/visible/types.ts b/modules/react-arborist/src/visible/types.ts new file mode 100644 index 0000000..ec4b895 --- /dev/null +++ b/modules/react-arborist/src/visible/types.ts @@ -0,0 +1,12 @@ +import { PartialController } from "../types/utils.js"; + +export type VisibleState = Record; + +export type VisibleOnChangeEvent = { + value: VisibleState; +}; + +export type VisiblePartialController = PartialController< + VisibleState, + VisibleOnChangeEvent +>; diff --git a/modules/react-arborist/tsconfig.json b/modules/react-arborist/tsconfig.json index fe94429..dc24eb9 100644 --- a/modules/react-arborist/tsconfig.json +++ b/modules/react-arborist/tsconfig.json @@ -11,9 +11,9 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - "jsx": "react-jsx", /* Specify what JSX code is generated. */ + "jsx": "react-jsx" /* Specify what JSX code is generated. */, // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ @@ -25,9 +25,9 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ + "module": "nodenext" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */, // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ @@ -49,13 +49,13 @@ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, // "declarationMap": true, /* Create sourcemaps for d.ts files. */ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, // "removeComments": true, /* Disable emitting comments. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ @@ -77,12 +77,12 @@ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ @@ -104,6 +104,6 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ } } diff --git a/modules/showcase/package.json b/modules/showcase/package.json index 99cc1e2..69a1269 100644 --- a/modules/showcase/package.json +++ b/modules/showcase/package.json @@ -12,7 +12,7 @@ "dependencies": { "clsx": "^2.0.0", "nanoid": "^5.0.4", - "next": "^14.0.4", + "next": "^14.1.3", "react": "^18.2.0", "react-arborist": "workspace:*", "react-dom": "^18.2.0", diff --git a/modules/showcase/pages/_app.tsx b/modules/showcase/pages/_app.tsx index e49f1a8..efee00a 100644 --- a/modules/showcase/pages/_app.tsx +++ b/modules/showcase/pages/_app.tsx @@ -1,5 +1,10 @@ import Head from "next/head"; +import "../styles/variables.css"; import "../styles/globals.css"; +import "../styles/compositions.css"; +import "../styles/blocks.css"; +import "../styles/utilities.css"; + import type { AppProps } from "next/app"; function MyApp({ Component, pageProps }: AppProps) { diff --git a/modules/showcase/pages/_document.tsx b/modules/showcase/pages/_document.tsx index 090aca5..c6cdcef 100644 --- a/modules/showcase/pages/_document.tsx +++ b/modules/showcase/pages/_document.tsx @@ -8,7 +8,6 @@ export default function Document() { href="https://fonts.googleapis.com/css2?family=Roboto&display=optional" rel="stylesheet" /> -
diff --git a/modules/showcase/pages/version4.tsx b/modules/showcase/pages/version4.tsx new file mode 100644 index 0000000..8efabf3 --- /dev/null +++ b/modules/showcase/pages/version4.tsx @@ -0,0 +1,29 @@ +import { TreeView, useNodes, useFilter } from "react-arborist"; +import { gmailData } from "../data/gmail"; +import { useState } from "react"; + +export default function Version4() { + const [term, setSearchTerm] = useState(""); + const nodes = useNodes(gmailData); + const filterProps = useFilter(nodes.value, { term }); + + return ( +
+
+ + setSearchTerm(e.target.value)} + /> +
+
+ ); +} diff --git a/modules/showcase/styles/blocks.css b/modules/showcase/styles/blocks.css new file mode 100644 index 0000000..5836642 --- /dev/null +++ b/modules/showcase/styles/blocks.css @@ -0,0 +1,8 @@ +.tree { + border-style: solid; +} + +.tree-row { + display: flex; + align-items: center; +} diff --git a/modules/showcase/styles/compositions.css b/modules/showcase/styles/compositions.css new file mode 100644 index 0000000..2418dc9 --- /dev/null +++ b/modules/showcase/styles/compositions.css @@ -0,0 +1,13 @@ +.wrap { + padding-inline: var(--gutter); + max-width: var(--width-page); + margin-inline: auto; +} + +.region { + padding-block: var(--region-space, 5rem); +} + +.flow > * + * { + margin-block-start: var(--flow-space, 1.5em); +} diff --git a/modules/showcase/styles/globals.css b/modules/showcase/styles/globals.css index 5f48346..0d66282 100644 --- a/modules/showcase/styles/globals.css +++ b/modules/showcase/styles/globals.css @@ -2,8 +2,18 @@ html, body { padding: 0; margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-family: + -apple-system, + BlinkMacSystemFont, + Segoe UI, + Roboto, + Oxygen, + Ubuntu, + Cantarell, + Fira Sans, + Droid Sans, + Helvetica Neue, + sans-serif; } :root { --primaryColor: #d24239; @@ -26,3 +36,9 @@ a { background: black; } } + +*:focus { + outline-color: var(--color-accent); + outline-size: 3px; + outline-offset: 3px; +} diff --git a/modules/showcase/styles/utilities.css b/modules/showcase/styles/utilities.css new file mode 100644 index 0000000..a431036 --- /dev/null +++ b/modules/showcase/styles/utilities.css @@ -0,0 +1,3 @@ +.rtl { + direction: rlt; +} diff --git a/modules/showcase/styles/variables.css b/modules/showcase/styles/variables.css new file mode 100644 index 0000000..4b69303 --- /dev/null +++ b/modules/showcase/styles/variables.css @@ -0,0 +1,5 @@ +:root { + --gutter: 1rem; + --width-page: 1200px; + --color-accent: lightgreen; +} diff --git a/package.json b/package.json index 298878d..927bf64 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,13 @@ ], "scripts": { "build": "yarn workspaces foreach --all run build", + "build:lib": "yarn workspace react-arborist build", "test": "yarn workspaces foreach --all run test", "bump": "yarn workspace react-arborist version", - "publish": "sh bin/publish" + "publish": "sh bin/publish", + "dev:showcase": "yarn workspace showcase start", + "dev:lib": "yarn workspace react-arborist watch", + "dev": "run-p 'dev:*'" }, "private": true, "packageManager": "yarn@4.0.2", diff --git a/yarn.lock b/yarn.lock index 15445b3..5fc9abf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -476,7 +476,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0": version: 7.19.4 resolution: "@babel/runtime@npm:7.19.4" dependencies: @@ -675,6 +675,55 @@ __metadata: languageName: node linkType: hard +"@formatjs/ecma402-abstract@npm:1.18.2": + version: 1.18.2 + resolution: "@formatjs/ecma402-abstract@npm:1.18.2" + dependencies: + "@formatjs/intl-localematcher": "npm:0.5.4" + tslib: "npm:^2.4.0" + checksum: e761653887e4446188daa023f4cb7245790ed65eb56cef4821225467e63f271f1addff386cfcbb4eb73eb67704b1f3a2b35ea4082fcadd4d05cfa0b3be3d5577 + languageName: node + linkType: hard + +"@formatjs/fast-memoize@npm:2.2.0": + version: 2.2.0 + resolution: "@formatjs/fast-memoize@npm:2.2.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 8697fe72a7ece252d600a7d08105f2a2f758e2dd96f54ac0a4c508b1205a559fc08835635e1f8e5ca9dcc3ee61ce1fca4a0e7047b402f29fc96051e293a280ff + languageName: node + linkType: hard + +"@formatjs/icu-messageformat-parser@npm:2.7.6": + version: 2.7.6 + resolution: "@formatjs/icu-messageformat-parser@npm:2.7.6" + dependencies: + "@formatjs/ecma402-abstract": "npm:1.18.2" + "@formatjs/icu-skeleton-parser": "npm:1.8.0" + tslib: "npm:^2.4.0" + checksum: 5baf9c1cf4b3f70d95bbac602b0695fcf67c6e2ff098e39dd53bdad0a16d192b9b5fe74dbdbeb76404bbdcdc95628d2623d24f786736074751fef13490cb6237 + languageName: node + linkType: hard + +"@formatjs/icu-skeleton-parser@npm:1.8.0": + version: 1.8.0 + resolution: "@formatjs/icu-skeleton-parser@npm:1.8.0" + dependencies: + "@formatjs/ecma402-abstract": "npm:1.18.2" + tslib: "npm:^2.4.0" + checksum: 8cd96d9075d1d369e4746dfaea6e3f478d21ed0672f4b777c4ee53b2660ef8c9a081976e6a8c73bba889eddc7edc52dba6eeea5fd62a8c03aa73e266b3cd89e9 + languageName: node + linkType: hard + +"@formatjs/intl-localematcher@npm:0.5.4": + version: 0.5.4 + resolution: "@formatjs/intl-localematcher@npm:0.5.4" + dependencies: + tslib: "npm:^2.4.0" + checksum: 780cb29b42e1ea87f2eb5db268577fcdc53da52d9f096871f3a1bb78603b4ba81d208ea0b0b9bc21548797c941ce435321f62d2522795b83b740f90b0ceb5778 + languageName: node + linkType: hard + "@gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -730,6 +779,43 @@ __metadata: languageName: node linkType: hard +"@internationalized/date@npm:^3.5.2": + version: 3.5.2 + resolution: "@internationalized/date@npm:3.5.2" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: e37cdea4efa6214e72148f55f42782b3e8cd40bdca29705e52e6c490855f9ccbf38d0182632be005d9555463b50e8bf5fdb0d759cadff1baf7bae4fdaa28e96f + languageName: node + linkType: hard + +"@internationalized/message@npm:^3.1.2": + version: 3.1.2 + resolution: "@internationalized/message@npm:3.1.2" + dependencies: + "@swc/helpers": "npm:^0.5.0" + intl-messageformat: "npm:^10.1.0" + checksum: c6b8f9983f1922f27c45586d82500a8fd4e75cab622c367b70047bb9f45749ab8153c77b02fd3da635e3d6649d8609ae6d1df6da710a166361078e32b4516d2e + languageName: node + linkType: hard + +"@internationalized/number@npm:^3.5.1": + version: 3.5.1 + resolution: "@internationalized/number@npm:3.5.1" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 4ad68d98285a18a910d19455a0fa9c3960a919a139f0b01d2d589bfda1a2ebb8378b8c912e17c0d82cf756e7b3f48b0bff8a6decef1644c6c2f894da4e1e7c79 + languageName: node + linkType: hard + +"@internationalized/string@npm:^3.2.1": + version: 3.2.1 + resolution: "@internationalized/string@npm:3.2.1" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 69603641a90fee37fc539adc8f3f5cbdd61909da486515bd4580fcce05495a9f0f303e6d8a36a8accb86c95845d84e78b088e4680ca087928b6b588756eb879b + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -989,268 +1075,1619 @@ __metadata: languageName: node linkType: hard -"@jest/types@npm:^29.4.1": - version: 29.4.1 - resolution: "@jest/types@npm:29.4.1" +"@jest/types@npm:^29.4.1": + version: 29.4.1 + resolution: "@jest/types@npm:29.4.1" + dependencies: + "@jest/schemas": "npm:^29.4.0" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + "@types/istanbul-reports": "npm:^3.0.0" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.8" + chalk: "npm:^4.0.0" + checksum: b3f787ae02bfca86ea0f3d2c77ed6a95ff95c6503cda04d826851b1f87987eaab289d6a5a7dc8c64fb3400b557431efe0ff0d3d8c8a9b5bb0ce2ec5247455195 + languageName: node + linkType: hard + +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + "@types/istanbul-reports": "npm:^3.0.0" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.8" + chalk: "npm:^4.0.0" + checksum: f74bf512fd09bbe2433a2ad460b04668b7075235eea9a0c77d6a42222c10a79b9747dc2b2a623f140ed40d6865a2ed8f538f3cbb75169120ea863f29a7ed76cd + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.1.0": + version: 0.1.1 + resolution: "@jridgewell/gen-mapping@npm:0.1.1" + dependencies: + "@jridgewell/set-array": "npm:^1.0.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + checksum: ba76fae1d8ea52b181474518c705a8eac36405dfc836fb07e9c25730a84d29e05fd6d954f121057742639f3128a24ea45d205c9c989efd464d1114671c19fa6c + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.2": + version: 0.3.2 + resolution: "@jridgewell/gen-mapping@npm:0.3.2" + dependencies: + "@jridgewell/set-array": "npm:^1.0.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.9" + checksum: 7ba0070be1aeda7d7694b09d847c3b95879409b26559b9d7e97a88ec94b838fb380df43ae328ee2d2df4d79e75d7afe6ba315199d18d79aa20839ebdfb739420 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:3.1.0": + version: 3.1.0 + resolution: "@jridgewell/resolve-uri@npm:3.1.0" + checksum: 320ceb37af56953757b28e5b90c34556157676d41e3d0a3ff88769274d62373582bb0f0276a4f2d29c3f4fdd55b82b8be5731f52d391ad2ecae9b321ee1c742d + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.1 + resolution: "@jridgewell/resolve-uri@npm:3.1.1" + checksum: 64d59df8ae1a4e74315eb1b61e012f1c7bc8aac47a3a1e683f6fe7008eab07bc512a742b7aa7c0405685d1421206de58c9c2e6adbfe23832f8bd69408ffc183e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.0.1": + version: 1.1.2 + resolution: "@jridgewell/set-array@npm:1.1.2" + checksum: 69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.14 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" + checksum: 26e768fae6045481a983e48aa23d8fcd23af5da70ebd74b0649000e815e7fbb01ea2bc088c9176b3fffeb9bec02184e58f46125ef3320b30eaa1f4094cfefa38 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: 89960ac087781b961ad918978975bcdf2051cd1741880469783c42de64239703eab9db5230d776d8e6a09d73bb5e4cb964e07d93ee6e2e7aea5a7d726e865c09 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.17 + resolution: "@jridgewell/trace-mapping@npm:0.3.17" + dependencies: + "@jridgewell/resolve-uri": "npm:3.1.0" + "@jridgewell/sourcemap-codec": "npm:1.4.14" + checksum: 790d439c9b271d9fc381dc4a837393ab942920245efedd5db20f65a665c0f778637fa623573337d3241ff784ffdb6724bbadf7fa2b61666bcd4884064b02f113 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.18": + version: 0.3.20 + resolution: "@jridgewell/trace-mapping@npm:0.3.20" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 683117e4e6707ef50c725d6d0ec4234687ff751f36fa46c2b3068931eb6a86b49af374d3030200777666579a992b7470d1bd1c591e9bf64d764dda5295f33093 + languageName: node + linkType: hard + +"@juggle/resize-observer@npm:^3.3.1": + version: 3.4.0 + resolution: "@juggle/resize-observer@npm:3.4.0" + checksum: 73d1d00ee9132fb6f0aea0531940a6b93603e935590bd450fc6285a328d906102eeeb95dea77b2edac0e779031a9708aa8c82502bd298ee4dd26e7dff48f397a + languageName: node + linkType: hard + +"@next/env@npm:14.1.3": + version: 14.1.3 + resolution: "@next/env@npm:14.1.3" + checksum: b95c55530f5fb8cfb2a48a23485fd4cb584bff8620637785d7dd068c0d72d4dc0869023ed509f52fead558839f2efae4f7370a2c2fd87540201629fc39a421a5 + languageName: node + linkType: hard + +"@next/eslint-plugin-next@npm:14.0.4": + version: 14.0.4 + resolution: "@next/eslint-plugin-next@npm:14.0.4" + dependencies: + glob: "npm:7.1.7" + checksum: 17871e2a86b66584b9eff8796a76d0c59ae62626dd8d0ae1cb7ca6977decf6273eb935d67e204f575a9ba0574a1c289329e6bbcb70d866e4c7f2879597cbd55a + languageName: node + linkType: hard + +"@next/swc-darwin-arm64@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-darwin-arm64@npm:14.1.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@next/swc-darwin-x64@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-darwin-x64@npm:14.1.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@next/swc-linux-arm64-gnu@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-linux-arm64-gnu@npm:14.1.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@next/swc-linux-arm64-musl@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-linux-arm64-musl@npm:14.1.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@next/swc-linux-x64-gnu@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-linux-x64-gnu@npm:14.1.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@next/swc-linux-x64-musl@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-linux-x64-musl@npm:14.1.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@next/swc-win32-arm64-msvc@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-win32-arm64-msvc@npm:14.1.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@next/swc-win32-ia32-msvc@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-win32-ia32-msvc@npm:14.1.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@next/swc-win32-x64-msvc@npm:14.1.3": + version: 14.1.3 + resolution: "@next/swc-win32-x64-msvc@npm:14.1.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^2.1.0": + version: 2.1.2 + resolution: "@npmcli/fs@npm:2.1.2" + dependencies: + "@gar/promisify": "npm:^1.1.3" + semver: "npm:^7.3.5" + checksum: c5d4dfee80de2236e1e4ed595d17e217aada72ebd8215183fc46096fa010f583dd2aaaa486758de7cc0b89440dbc31cfe8b276269d75d47af35c716e896f78ec + languageName: node + linkType: hard + +"@npmcli/move-file@npm:^2.0.0": + version: 2.0.1 + resolution: "@npmcli/move-file@npm:2.0.1" + dependencies: + mkdirp: "npm:^1.0.4" + rimraf: "npm:^3.0.2" + checksum: 52dc02259d98da517fae4cb3a0a3850227bdae4939dda1980b788a7670636ca2b4a01b58df03dd5f65c1e3cb70c50fa8ce5762b582b3f499ec30ee5ce1fd9380 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff + languageName: node + linkType: hard + +"@react-aria/breadcrumbs@npm:^3.5.11": + version: 3.5.11 + resolution: "@react-aria/breadcrumbs@npm:3.5.11" + dependencies: + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/link": "npm:^3.6.5" + "@react-aria/utils": "npm:^3.23.2" + "@react-types/breadcrumbs": "npm:^3.7.3" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: baf8ce4c8a5c85dca93463349fb45a151c91ae90475d28cda119083c45588f28a2e043108bc66d8f9d936eb93829743427b026f31f986069e009f0749c70ae10 + languageName: node + linkType: hard + +"@react-aria/button@npm:^3.9.3": + version: 3.9.3 + resolution: "@react-aria/button@npm:3.9.3" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/toggle": "npm:^3.7.2" + "@react-types/button": "npm:^3.9.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: e3c535f00109d7409e1b42e4fc02313f28c7236d3689eb6de2861050c193f51a73dc27b512c8c3c733da7ec4329a7c6ace5a5ea8d75629215e63a9bf625c9db7 + languageName: node + linkType: hard + +"@react-aria/calendar@npm:^3.5.6": + version: 3.5.6 + resolution: "@react-aria/calendar@npm:3.5.6" + dependencies: + "@internationalized/date": "npm:^3.5.2" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/live-announcer": "npm:^3.3.2" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/calendar": "npm:^3.4.4" + "@react-types/button": "npm:^3.9.2" + "@react-types/calendar": "npm:^3.4.4" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 85612ff6bcc20150ee33a5e690e581c636435cdab1c48a56e867207b24e64df464bbec87287a2c912aeffd9a43f9dbb4049ac211900f9fe98f4b33ea42cf0489 + languageName: node + linkType: hard + +"@react-aria/checkbox@npm:^3.14.1": + version: 3.14.1 + resolution: "@react-aria/checkbox@npm:3.14.1" + dependencies: + "@react-aria/form": "npm:^3.0.3" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/label": "npm:^3.7.6" + "@react-aria/toggle": "npm:^3.10.2" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/checkbox": "npm:^3.6.3" + "@react-stately/form": "npm:^3.0.1" + "@react-stately/toggle": "npm:^3.7.2" + "@react-types/checkbox": "npm:^3.7.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: d579d277aff110e5cfecf3f4c25b4f635d227104558af5448d18bce32c5a85663d728ac3814347ca3d5c9088a76f09a04ce858e4c2853d7cc4709e4146ab96f1 + languageName: node + linkType: hard + +"@react-aria/combobox@npm:^3.8.4": + version: 3.8.4 + resolution: "@react-aria/combobox@npm:3.8.4" + dependencies: + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/listbox": "npm:^3.11.5" + "@react-aria/live-announcer": "npm:^3.3.2" + "@react-aria/menu": "npm:^3.13.1" + "@react-aria/overlays": "npm:^3.21.1" + "@react-aria/selection": "npm:^3.17.5" + "@react-aria/textfield": "npm:^3.14.3" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/combobox": "npm:^3.8.2" + "@react-stately/form": "npm:^3.0.1" + "@react-types/button": "npm:^3.9.2" + "@react-types/combobox": "npm:^3.10.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: abe760b78d4b7de41131816f7939a2fceed9acb1531a2c3e471230f9d2de01661ae891faf01ffe95b426bbe5f29960d7bd7ce7f54631e3d85e51a27556775aad + languageName: node + linkType: hard + +"@react-aria/datepicker@npm:^3.9.3": + version: 3.9.3 + resolution: "@react-aria/datepicker@npm:3.9.3" + dependencies: + "@internationalized/date": "npm:^3.5.2" + "@internationalized/number": "npm:^3.5.1" + "@internationalized/string": "npm:^3.2.1" + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/form": "npm:^3.0.3" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/label": "npm:^3.7.6" + "@react-aria/spinbutton": "npm:^3.6.3" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/datepicker": "npm:^3.9.2" + "@react-stately/form": "npm:^3.0.1" + "@react-types/button": "npm:^3.9.2" + "@react-types/calendar": "npm:^3.4.4" + "@react-types/datepicker": "npm:^3.7.2" + "@react-types/dialog": "npm:^3.5.8" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: c6d27d3dd75e9e9fd958d0b08982e8517a49400c02bd37cb75a2e31839b024388d63cb53200cf4a654c83dd61049d5d687f716789ee6d7d915b1b9c45164e9b8 + languageName: node + linkType: hard + +"@react-aria/dialog@npm:^3.5.12": + version: 3.5.12 + resolution: "@react-aria/dialog@npm:3.5.12" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/overlays": "npm:^3.21.1" + "@react-aria/utils": "npm:^3.23.2" + "@react-types/dialog": "npm:^3.5.8" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 3de699980e8582056675fef747746c23f8875940d85bd6dafadacf9e59c0edd0d9b1dc2f011fb1cfbcbdebdf4a2796fa9bc19e953c4c809f671151975031d6bf + languageName: node + linkType: hard + +"@react-aria/dnd@npm:^3.5.3": + version: 3.5.3 + resolution: "@react-aria/dnd@npm:3.5.3" + dependencies: + "@internationalized/string": "npm:^3.2.1" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/live-announcer": "npm:^3.3.2" + "@react-aria/overlays": "npm:^3.21.1" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/dnd": "npm:^3.2.8" + "@react-types/button": "npm:^3.9.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 6fe1cd94956167bfc0f4aeaab6f87edec082a996d8745dde5113ae09c63d5d70e2b0648cb1357d54e0f7a62b34002f2d6a499bf2486f78ba7f7fbfac6db9aa58 + languageName: node + linkType: hard + +"@react-aria/focus@npm:^3.16.2": + version: 3.16.2 + resolution: "@react-aria/focus@npm:3.16.2" + dependencies: + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/utils": "npm:^3.23.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + clsx: "npm:^2.0.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: da25d79534443652ed2ad560ce1e56653a28ac5ccbd5a7be2822c11b748f46e8a544f37bea0bff8ad1a82493c77c6f17c418c86c995abe45df36fbe33bae0156 + languageName: node + linkType: hard + +"@react-aria/form@npm:^3.0.3": + version: 3.0.3 + resolution: "@react-aria/form@npm:3.0.3" + dependencies: + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/form": "npm:^3.0.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 2b4f6f7a33c1cfc1f82f05a2433d5bd9dfda93b2dd365c16631fe63c32d113ca11353ae2151274f6b8e1ad3885aecaf569b357b24cf7464b8436ba6785eca2ee + languageName: node + linkType: hard + +"@react-aria/grid@npm:^3.8.8": + version: 3.8.8 + resolution: "@react-aria/grid@npm:3.8.8" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/live-announcer": "npm:^3.3.2" + "@react-aria/selection": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/grid": "npm:^3.8.5" + "@react-stately/selection": "npm:^3.14.3" + "@react-stately/virtualizer": "npm:^3.6.8" + "@react-types/checkbox": "npm:^3.7.1" + "@react-types/grid": "npm:^3.2.4" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 61e1d82b39c8c7638465060826f6bbf19a971294f6060fcdbc13d3b326c696b8cdc2c88d49d07b66f894cb5aaa76ab324cf8ba8e112e894122d330ab6f4b3a58 + languageName: node + linkType: hard + +"@react-aria/gridlist@npm:^3.7.5": + version: 3.7.5 + resolution: "@react-aria/gridlist@npm:3.7.5" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/grid": "npm:^3.8.8" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/selection": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/list": "npm:^3.10.3" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: dd540ee75105a40a9a1f23dcdea6e6e54d901e823d43a18bc2c1653791cdbf41a375834e9dce6c279703657239cb9651cc9e6c61449e981c0a2d11e260d7abae + languageName: node + linkType: hard + +"@react-aria/i18n@npm:^3.10.2": + version: 3.10.2 + resolution: "@react-aria/i18n@npm:3.10.2" + dependencies: + "@internationalized/date": "npm:^3.5.2" + "@internationalized/message": "npm:^3.1.2" + "@internationalized/number": "npm:^3.5.1" + "@internationalized/string": "npm:^3.2.1" + "@react-aria/ssr": "npm:^3.9.2" + "@react-aria/utils": "npm:^3.23.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: e24558e3f659246b59e5a2862a99debec7cd9ec152c74fbfbfc15c0816a77448d455a131790b954697fcc0bf8633bc102c1b27121a8b7043820563c7b5987095 + languageName: node + linkType: hard + +"@react-aria/interactions@npm:^3.21.1": + version: 3.21.1 + resolution: "@react-aria/interactions@npm:3.21.1" + dependencies: + "@react-aria/ssr": "npm:^3.9.2" + "@react-aria/utils": "npm:^3.23.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: ca0918dca1ee41e7ac9129eeb5a23f02a9043cae55f0ee381dc93bd763ac31928a809e029e8bd144223b0f44275736b29079d99fbd22891c244f09c50d16665b + languageName: node + linkType: hard + +"@react-aria/label@npm:^3.7.6": + version: 3.7.6 + resolution: "@react-aria/label@npm:3.7.6" + dependencies: + "@react-aria/utils": "npm:^3.23.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 812c2b4268f10c9927a6bfff6ef44c836afac0468c2a1e48c71fd5644792c9ee25f4d4b1c5a784cbbfa8c0369893ce0ca9eab8b63e9baf5bf255b240414a1c81 + languageName: node + linkType: hard + +"@react-aria/link@npm:^3.6.5": + version: 3.6.5 + resolution: "@react-aria/link@npm:3.6.5" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/utils": "npm:^3.23.2" + "@react-types/link": "npm:^3.5.3" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: efc425d1991799c8577b2d0166def63b389dc58db7f45204bee4a86c16f127f610bf6d543a57eac7cfad5e85941400ea97bddb8e6b688c1da0c13f8893ca7c0c + languageName: node + linkType: hard + +"@react-aria/listbox@npm:^3.11.5": + version: 3.11.5 + resolution: "@react-aria/listbox@npm:3.11.5" + dependencies: + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/label": "npm:^3.7.6" + "@react-aria/selection": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/list": "npm:^3.10.3" + "@react-types/listbox": "npm:^3.4.7" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: f3f9449c8bfae36b631994ea1beea804711ffae90be7a118c51b862c4a1b40011a504b8ffc537025184acce1ba07a20e4d2f57a1e14d3875b9ec305ea0eab257 + languageName: node + linkType: hard + +"@react-aria/live-announcer@npm:^3.3.2": + version: 3.3.2 + resolution: "@react-aria/live-announcer@npm:3.3.2" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 32af58277cf132970f9974bbc2adc69119be98222757a0e0538a7aa42541d28aad6c084f2b0f0d6b5e8b06727a2ffed61413e448433fbe38a5ff2ce59477f75f + languageName: node + linkType: hard + +"@react-aria/menu@npm:^3.13.1": + version: 3.13.1 + resolution: "@react-aria/menu@npm:3.13.1" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/overlays": "npm:^3.21.1" + "@react-aria/selection": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/menu": "npm:^3.6.1" + "@react-stately/tree": "npm:^3.7.6" + "@react-types/button": "npm:^3.9.2" + "@react-types/menu": "npm:^3.9.7" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 16ff5650950580688f752487126f6637f9f4fb0c44c753f205e43d676fb87dede868b013135795ca66b1a9c4e4ba70fc3a9dbcdbeb124c9231bacfa4a90d2360 + languageName: node + linkType: hard + +"@react-aria/meter@npm:^3.4.11": + version: 3.4.11 + resolution: "@react-aria/meter@npm:3.4.11" + dependencies: + "@react-aria/progress": "npm:^3.4.11" + "@react-types/meter": "npm:^3.3.7" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 026effea67234eb8b2c424625f09af89133947b1cb00a80cddfaa0ad41b14ccb304e707b10162f2e0adfb56b0610b519b0b86ba2d4f640388b1e75db2dda7ea4 + languageName: node + linkType: hard + +"@react-aria/numberfield@npm:^3.11.1": + version: 3.11.1 + resolution: "@react-aria/numberfield@npm:3.11.1" + dependencies: + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/spinbutton": "npm:^3.6.3" + "@react-aria/textfield": "npm:^3.14.3" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/form": "npm:^3.0.1" + "@react-stately/numberfield": "npm:^3.9.1" + "@react-types/button": "npm:^3.9.2" + "@react-types/numberfield": "npm:^3.8.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 2eec4cd6e19b720e4a7801a13e0c06febcd4efcf58f3b6e89fc84ffc4e72e38ed23321863caa226c3e3dd88572fec11888a6884b788a4d98c20fdfe6a4fcfa1a + languageName: node + linkType: hard + +"@react-aria/overlays@npm:^3.21.1": + version: 3.21.1 + resolution: "@react-aria/overlays@npm:3.21.1" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/ssr": "npm:^3.9.2" + "@react-aria/utils": "npm:^3.23.2" + "@react-aria/visually-hidden": "npm:^3.8.10" + "@react-stately/overlays": "npm:^3.6.5" + "@react-types/button": "npm:^3.9.2" + "@react-types/overlays": "npm:^3.8.5" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 3143558dfb6e266194c0581475d10827d1296bb517e3cb3b50e4fe09a5e44a5616440a8f857389ab83572bbb507d738976651fcbf8eec9df0730a93aca159eb7 + languageName: node + linkType: hard + +"@react-aria/progress@npm:^3.4.11": + version: 3.4.11 + resolution: "@react-aria/progress@npm:3.4.11" + dependencies: + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/label": "npm:^3.7.6" + "@react-aria/utils": "npm:^3.23.2" + "@react-types/progress": "npm:^3.5.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 16bc8ce8a33b5bbe0152780f2d1343fb687107323b122e1602e55ac55132d21108be137e7d00f13529356e706e2d24400e78762533c33352b40c6cfc89d7b8d5 + languageName: node + linkType: hard + +"@react-aria/radio@npm:^3.10.2": + version: 3.10.2 + resolution: "@react-aria/radio@npm:3.10.2" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/form": "npm:^3.0.3" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/label": "npm:^3.7.6" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/radio": "npm:^3.10.2" + "@react-types/radio": "npm:^3.7.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 184287a6d54b9f3a6b7f3f4344fc8eaace617e584a29601fbd87bb96f0d732c660438201c4342900ca6fd05fc3bd29764e26c4346b7dbbd6104cd4ca17cf6150 + languageName: node + linkType: hard + +"@react-aria/searchfield@npm:^3.7.3": + version: 3.7.3 + resolution: "@react-aria/searchfield@npm:3.7.3" + dependencies: + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/textfield": "npm:^3.14.3" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/searchfield": "npm:^3.5.1" + "@react-types/button": "npm:^3.9.2" + "@react-types/searchfield": "npm:^3.5.3" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 5f85c001e2c677d7f48ffa389d054411644e2f4c584eb3a00e8eb1207d02e7d5da85f03da356670a8f335ab40385a3fcf4263dc1768372aaf9a0b8eafebbb6e3 + languageName: node + linkType: hard + +"@react-aria/select@npm:^3.14.3": + version: 3.14.3 + resolution: "@react-aria/select@npm:3.14.3" + dependencies: + "@react-aria/form": "npm:^3.0.3" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/label": "npm:^3.7.6" + "@react-aria/listbox": "npm:^3.11.5" + "@react-aria/menu": "npm:^3.13.1" + "@react-aria/selection": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.23.2" + "@react-aria/visually-hidden": "npm:^3.8.10" + "@react-stately/select": "npm:^3.6.2" + "@react-types/button": "npm:^3.9.2" + "@react-types/select": "npm:^3.9.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: d3ddeb22afed02ad323b94420db3b9f87a3f29f9a97a52984d198c48e2465b352897f21dcf0c29eb4f98d078822e8a3c3815c4044c1a8deed21ef87ab1aba791 + languageName: node + linkType: hard + +"@react-aria/selection@npm:^3.17.5": + version: 3.17.5 + resolution: "@react-aria/selection@npm:3.17.5" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/selection": "npm:^3.14.3" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: d6fb38e79a2cac200dc46f22cdf1131188cbbe765a28d815b3503166339987bdebb37ba79b9f4abecdd8b83c2b26669064b8dbfc8913a665105a444d21cab0e5 + languageName: node + linkType: hard + +"@react-aria/separator@npm:^3.3.11": + version: 3.3.11 + resolution: "@react-aria/separator@npm:3.3.11" + dependencies: + "@react-aria/utils": "npm:^3.23.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: e11e5b60a899da3b71c3afb23c666f7ac96090fadcd12b1d03ba278c80b8124023ddcff0d9628a3679db78d315c56d533172c032d87df429e8d8f40bd45991e5 + languageName: node + linkType: hard + +"@react-aria/slider@npm:^3.7.6": + version: 3.7.6 + resolution: "@react-aria/slider@npm:3.7.6" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/label": "npm:^3.7.6" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/slider": "npm:^3.5.2" + "@react-types/shared": "npm:^3.22.1" + "@react-types/slider": "npm:^3.7.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 9cbec4465cc099192ff17075c1cf10660b1567dbfe31a674aa76e16658163cc32af0c5009ca54ed728085d277e462d44852821be166e396a9f44a7cf0a5918ad + languageName: node + linkType: hard + +"@react-aria/spinbutton@npm:^3.6.3": + version: 3.6.3 + resolution: "@react-aria/spinbutton@npm:3.6.3" + dependencies: + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/live-announcer": "npm:^3.3.2" + "@react-aria/utils": "npm:^3.23.2" + "@react-types/button": "npm:^3.9.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: ad39a9408cf1f8cbd7aae64161a3150bea3711b55d78b724c21e17ce46cfe3fb3bcecd31a15d4f380004e5d593c10f91a36ec3be0ff271a049792ef674cf6f0e + languageName: node + linkType: hard + +"@react-aria/ssr@npm:^3.9.2": + version: 3.9.2 + resolution: "@react-aria/ssr@npm:3.9.2" + dependencies: + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: fe4ce0ccc647d14f158724c0605433291f1403a73c82cb6654c323b5153fa3afbf0d36618bb3ecac38217b56837c27490c32b7d2082034b1171de6e95a4382a8 + languageName: node + linkType: hard + +"@react-aria/switch@npm:^3.6.2": + version: 3.6.2 + resolution: "@react-aria/switch@npm:3.6.2" + dependencies: + "@react-aria/toggle": "npm:^3.10.2" + "@react-stately/toggle": "npm:^3.7.2" + "@react-types/switch": "npm:^3.5.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 6eb2268652b92e3d156a1d63e5490ce3527f592b6f310ad725fa06379c5629d4db430b0f7482996ea8b0581db2e6dd083f132c84ae31401d4dcb8393806e61d3 + languageName: node + linkType: hard + +"@react-aria/table@npm:^3.13.5": + version: 3.13.5 + resolution: "@react-aria/table@npm:3.13.5" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/grid": "npm:^3.8.8" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/live-announcer": "npm:^3.3.2" + "@react-aria/utils": "npm:^3.23.2" + "@react-aria/visually-hidden": "npm:^3.8.10" + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/flags": "npm:^3.0.1" + "@react-stately/table": "npm:^3.11.6" + "@react-stately/virtualizer": "npm:^3.6.8" + "@react-types/checkbox": "npm:^3.7.1" + "@react-types/grid": "npm:^3.2.4" + "@react-types/shared": "npm:^3.22.1" + "@react-types/table": "npm:^3.9.3" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 1b7bd3269f38ea745bc5e7356eb81139d6238e039d7e7fbfc43db5ecfabd27d15c6b1251b44cf1f2921ac4e5c82d0b8767ac839c5f4e724ba326c59bd1ed4c2e + languageName: node + linkType: hard + +"@react-aria/tabs@npm:^3.8.5": + version: 3.8.5 + resolution: "@react-aria/tabs@npm:3.8.5" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/selection": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/tabs": "npm:^3.6.4" + "@react-types/shared": "npm:^3.22.1" + "@react-types/tabs": "npm:^3.3.5" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 9a2c569c167c378cec709d1a38cbf628f14ae152c2cfbb0160c883212dd20c03657b172c880f2db79ae5b88e145210ddd150f4920ce2179702f9ba1ea7f501f8 + languageName: node + linkType: hard + +"@react-aria/tag@npm:^3.3.3": + version: 3.3.3 + resolution: "@react-aria/tag@npm:3.3.3" + dependencies: + "@react-aria/gridlist": "npm:^3.7.5" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/label": "npm:^3.7.6" + "@react-aria/selection": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/list": "npm:^3.10.3" + "@react-types/button": "npm:^3.9.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 10aee2f83c1c5ae0a4954c50b8d3153708944ad391bdcf3f4deb0be87bb229a78834cafa9cd860988f3fc3d7a4757cff161becb7d761b4db98b2e280ba6d9e7c + languageName: node + linkType: hard + +"@react-aria/textfield@npm:^3.14.3": + version: 3.14.3 + resolution: "@react-aria/textfield@npm:3.14.3" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/form": "npm:^3.0.3" + "@react-aria/label": "npm:^3.7.6" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/form": "npm:^3.0.1" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/shared": "npm:^3.22.1" + "@react-types/textfield": "npm:^3.9.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: db3ac87112d4da65762805247c7eace92cede25855d589e61a8e00ae0a7593ca6778ddb5d15f3499ff294153f356de6a12bb679d49f3848592df57fd8b4929d6 + languageName: node + linkType: hard + +"@react-aria/toggle@npm:^3.10.2": + version: 3.10.2 + resolution: "@react-aria/toggle@npm:3.10.2" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/toggle": "npm:^3.7.2" + "@react-types/checkbox": "npm:^3.7.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: fd402ca7e83674dcecfb95e18075bece6dbf49250610ac8260ee6543f651d8c07042110ec368747cc1bd82a0915f5e900df5c3a5c6fc55dd244f161661eb5c5b + languageName: node + linkType: hard + +"@react-aria/tooltip@npm:^3.7.2": + version: 3.7.2 + resolution: "@react-aria/tooltip@npm:3.7.2" + dependencies: + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/utils": "npm:^3.23.2" + "@react-stately/tooltip": "npm:^3.4.7" + "@react-types/shared": "npm:^3.22.1" + "@react-types/tooltip": "npm:^3.4.7" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 2f7017b97b01c612ef9e04b10275df0f52df1eaa581910754210f3d250e369596b740befbdac117c4a1ff3df39b4a9cbc9d5c5c5397adc5f261bb4394a3e8f04 + languageName: node + linkType: hard + +"@react-aria/utils@npm:^3.23.2": + version: 3.23.2 + resolution: "@react-aria/utils@npm:3.23.2" + dependencies: + "@react-aria/ssr": "npm:^3.9.2" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + clsx: "npm:^2.0.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 132ac6e2e6f5eb7469a52ebc5a909ad2bdb8606b835c0cc8e5320447dc3cd34f8d0ed3441a75827ae1cd91bef435c0c6e463fec72fe4fa5fe565c7d87576301d + languageName: node + linkType: hard + +"@react-aria/visually-hidden@npm:^3.8.10": + version: 3.8.10 + resolution: "@react-aria/visually-hidden@npm:3.8.10" + dependencies: + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/utils": "npm:^3.23.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: a7f9d8dccfeefb035d01ad8d9db4576f6acf7f0fcb94aad717cec177f113f6507f0dca0c7ee157abe40b358685b4cb84f9bce0c24dab2af753698ec8c1504264 + languageName: node + linkType: hard + +"@react-stately/calendar@npm:^3.4.4": + version: 3.4.4 + resolution: "@react-stately/calendar@npm:3.4.4" + dependencies: + "@internationalized/date": "npm:^3.5.2" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/calendar": "npm:^3.4.4" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: c8beae06f86d52a1644eda56cdf5035ebe14c97e515c2932c101649da30e2447ec530e4de5e0ca0c18ef72c39ae6b12c1760b206d64cec27214ab001c23c4afd + languageName: node + linkType: hard + +"@react-stately/checkbox@npm:^3.6.3": + version: 3.6.3 + resolution: "@react-stately/checkbox@npm:3.6.3" + dependencies: + "@react-stately/form": "npm:^3.0.1" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/checkbox": "npm:^3.7.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: ece58d7f0347e2e4df71475edd92fad38edfa247b940bac4ddac17c8baf2e670b2dd57c63201758cd2389f4ac4efcd40645ca2427463bf2ad1899619078ecbb9 + languageName: node + linkType: hard + +"@react-stately/collections@npm:^3.10.5": + version: 3.10.5 + resolution: "@react-stately/collections@npm:3.10.5" + dependencies: + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: bf447652b19f16831b43c6cc2aa877c638756e08566f574a3b0d7c21cc60c523e90cc0ea7a2c3616dbf1d3b0724c2f354e99ae993c1633ab99b93e4bf35e09eb + languageName: node + linkType: hard + +"@react-stately/combobox@npm:^3.8.2": + version: 3.8.2 + resolution: "@react-stately/combobox@npm:3.8.2" + dependencies: + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/form": "npm:^3.0.1" + "@react-stately/list": "npm:^3.10.3" + "@react-stately/overlays": "npm:^3.6.5" + "@react-stately/select": "npm:^3.6.2" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/combobox": "npm:^3.10.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: e2f5801fe74abe77c72f02768dfc2e79b1c0f253b973a28ed4bf81da8c5880343ca0f8b0fc624ce64f67ba66bd9a8e725476b69c1c1fa000234d14f10204ebca + languageName: node + linkType: hard + +"@react-stately/datepicker@npm:^3.9.2": + version: 3.9.2 + resolution: "@react-stately/datepicker@npm:3.9.2" + dependencies: + "@internationalized/date": "npm:^3.5.2" + "@internationalized/string": "npm:^3.2.1" + "@react-stately/form": "npm:^3.0.1" + "@react-stately/overlays": "npm:^3.6.5" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/datepicker": "npm:^3.7.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 11392885e5d7dbce2ed6df095529785133429f178d47834445211f18e62afa166be05a1a8eacd62a0fa90a87eac33dd6773e2534716c1203b606d2ada4e5ed30 + languageName: node + linkType: hard + +"@react-stately/dnd@npm:^3.2.8": + version: 3.2.8 + resolution: "@react-stately/dnd@npm:3.2.8" + dependencies: + "@react-stately/selection": "npm:^3.14.3" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 007e479c2991fe7cce41d5fecd7f6b94a003857de1e1f9baa40714607ecd12332ffd8e1ea8567f96a6a00c454256e4e0e99e764c9aa68148a455c28d21465ef1 + languageName: node + linkType: hard + +"@react-stately/flags@npm:^3.0.1": + version: 3.0.1 + resolution: "@react-stately/flags@npm:3.0.1" + dependencies: + "@swc/helpers": "npm:^0.4.14" + checksum: 9fd6731a3bb74c613d427a5457a8e1dcec1c596352d912e006005ecf9aeefa51f76b553993456dde927cdbb3237cc6d95bcd7dbd60b2917638c9cd05ad019460 + languageName: node + linkType: hard + +"@react-stately/form@npm:^3.0.1": + version: 3.0.1 + resolution: "@react-stately/form@npm:3.0.1" + dependencies: + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: a734c9a93320d518480114aeff35540204aaca116e64973d9c817a5a73479c0a40eee882ccc0e22979e0bc1cfbec22ab703fc4b10f5197a92a75b73ea8ea69d0 + languageName: node + linkType: hard + +"@react-stately/grid@npm:^3.8.5": + version: 3.8.5 + resolution: "@react-stately/grid@npm:3.8.5" + dependencies: + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/selection": "npm:^3.14.3" + "@react-types/grid": "npm:^3.2.4" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 5d38d707f090d5a1e84038ce0145ca72d509d12bd825407952767842f4b6db1c995f2bd95bb7206f506d4f67c35bde66b89172aaba7f2f495867d0950aaa96b8 + languageName: node + linkType: hard + +"@react-stately/list@npm:^3.10.3": + version: 3.10.3 + resolution: "@react-stately/list@npm:3.10.3" + dependencies: + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/selection": "npm:^3.14.3" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: c9009a30df5607ec618632f11b013c13abfbef6e0e54a1a49aeb4385a97a910cece7568aa1055fe0ba6b888d3f24f9fa0cbad963e4a84637cd8cf0339fdd3982 + languageName: node + linkType: hard + +"@react-stately/menu@npm:^3.6.1": + version: 3.6.1 + resolution: "@react-stately/menu@npm:3.6.1" + dependencies: + "@react-stately/overlays": "npm:^3.6.5" + "@react-types/menu": "npm:^3.9.7" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 21c7c4c1415ce78573aee4dc8e4c64bd5c500ee0203a819a6da5188e12232776eff25111c6fc76214285dcc3778a174f830606173ad08f97958cf585924f3fcb + languageName: node + linkType: hard + +"@react-stately/numberfield@npm:^3.9.1": + version: 3.9.1 + resolution: "@react-stately/numberfield@npm:3.9.1" + dependencies: + "@internationalized/number": "npm:^3.5.1" + "@react-stately/form": "npm:^3.0.1" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/numberfield": "npm:^3.8.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: e8b8833cca6d524f7ea76b7d854d4b1ad5e419355502c220bab5e4bb8e45dbf2094c30c80f68471f814ebeb48bd58fa8050988edd0a48eb62421e299b44eddca + languageName: node + linkType: hard + +"@react-stately/overlays@npm:^3.6.5": + version: 3.6.5 + resolution: "@react-stately/overlays@npm:3.6.5" + dependencies: + "@react-stately/utils": "npm:^3.9.1" + "@react-types/overlays": "npm:^3.8.5" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 83805f078eb42290ddb9f88d8cbd7403a4d5f15177fce4c9f8cec91acf177af1d5a414472c58029fc1f8bf6730d5ca9716a8b3cd750f2afd6b57e592a7f09ef7 + languageName: node + linkType: hard + +"@react-stately/radio@npm:^3.10.2": + version: 3.10.2 + resolution: "@react-stately/radio@npm:3.10.2" + dependencies: + "@react-stately/form": "npm:^3.0.1" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/radio": "npm:^3.7.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 1c6d46c6342e6f96ced268bbf397aa10fcc536b22ee593af9031aea3a16be9f4876fb6b45cdb65aacfe665ad5671f3f64cf029b9c4b4698cbf157a363dddafe3 + languageName: node + linkType: hard + +"@react-stately/searchfield@npm:^3.5.1": + version: 3.5.1 + resolution: "@react-stately/searchfield@npm:3.5.1" + dependencies: + "@react-stately/utils": "npm:^3.9.1" + "@react-types/searchfield": "npm:^3.5.3" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: dfdc257a7e75e2ba81e1ca578570b14d57200472a85d58dd91cfcfb60d372c81a7f79401802a4f611d35831bbd7a619185b812afdd05e20430feb00afff5fbeb + languageName: node + linkType: hard + +"@react-stately/select@npm:^3.6.2": + version: 3.6.2 + resolution: "@react-stately/select@npm:3.6.2" + dependencies: + "@react-stately/form": "npm:^3.0.1" + "@react-stately/list": "npm:^3.10.3" + "@react-stately/overlays": "npm:^3.6.5" + "@react-types/select": "npm:^3.9.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 9df52dba77b4bf383ffd9ce8552442de198f6bb8ece78cdc4a9075a4bea334c4dff2d7d115b2a3258f0169c212cc0dbfa1df623207c1917af908b853c9897769 + languageName: node + linkType: hard + +"@react-stately/selection@npm:^3.14.3": + version: 3.14.3 + resolution: "@react-stately/selection@npm:3.14.3" + dependencies: + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 43bc7c6c21198d93037fdf107e86149ecdbd6f8618d7c0469cfdfc30db450b9b04e9ec9ef238e7e473e04cdfc127af822553eed5d7e2d69921adbb2131bd2cc6 + languageName: node + linkType: hard + +"@react-stately/slider@npm:^3.5.2": + version: 3.5.2 + resolution: "@react-stately/slider@npm:3.5.2" + dependencies: + "@react-stately/utils": "npm:^3.9.1" + "@react-types/shared": "npm:^3.22.1" + "@react-types/slider": "npm:^3.7.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: d1553e12102579972c1b2f07d0dac178af7476f5419117b87dbabb48d631613d9a44ce4528ebb2a459a6d568c7797000dad62a454b1051419ca018fae8f1e101 + languageName: node + linkType: hard + +"@react-stately/table@npm:^3.11.6": + version: 3.11.6 + resolution: "@react-stately/table@npm:3.11.6" dependencies: - "@jest/schemas": "npm:^29.4.0" - "@types/istanbul-lib-coverage": "npm:^2.0.0" - "@types/istanbul-reports": "npm:^3.0.0" - "@types/node": "npm:*" - "@types/yargs": "npm:^17.0.8" - chalk: "npm:^4.0.0" - checksum: b3f787ae02bfca86ea0f3d2c77ed6a95ff95c6503cda04d826851b1f87987eaab289d6a5a7dc8c64fb3400b557431efe0ff0d3d8c8a9b5bb0ce2ec5247455195 + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/flags": "npm:^3.0.1" + "@react-stately/grid": "npm:^3.8.5" + "@react-stately/selection": "npm:^3.14.3" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/grid": "npm:^3.2.4" + "@react-types/shared": "npm:^3.22.1" + "@react-types/table": "npm:^3.9.3" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 4af9888ee1ec0bde3d683aa81a0e768ddca2589323386327bf36328bb47729589b58e81ea504cae69f6193b520cd8fe73e253ea5dbc9f6a9bc7feefc76185550 languageName: node linkType: hard -"@jest/types@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/types@npm:29.6.3" +"@react-stately/tabs@npm:^3.6.4": + version: 3.6.4 + resolution: "@react-stately/tabs@npm:3.6.4" dependencies: - "@jest/schemas": "npm:^29.6.3" - "@types/istanbul-lib-coverage": "npm:^2.0.0" - "@types/istanbul-reports": "npm:^3.0.0" - "@types/node": "npm:*" - "@types/yargs": "npm:^17.0.8" - chalk: "npm:^4.0.0" - checksum: f74bf512fd09bbe2433a2ad460b04668b7075235eea9a0c77d6a42222c10a79b9747dc2b2a623f140ed40d6865a2ed8f538f3cbb75169120ea863f29a7ed76cd + "@react-stately/list": "npm:^3.10.3" + "@react-types/shared": "npm:^3.22.1" + "@react-types/tabs": "npm:^3.3.5" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 28dd71e3e5bf5dd31facd394f4cbae1021faa0a16dec382a5a588918708f0e8167b61905665f07653debfef21d17fa0ad949cb7660887c45a05078a8028dc3bc languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.1.0": - version: 0.1.1 - resolution: "@jridgewell/gen-mapping@npm:0.1.1" +"@react-stately/toggle@npm:^3.7.2": + version: 3.7.2 + resolution: "@react-stately/toggle@npm:3.7.2" dependencies: - "@jridgewell/set-array": "npm:^1.0.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - checksum: ba76fae1d8ea52b181474518c705a8eac36405dfc836fb07e9c25730a84d29e05fd6d954f121057742639f3128a24ea45d205c9c989efd464d1114671c19fa6c + "@react-stately/utils": "npm:^3.9.1" + "@react-types/checkbox": "npm:^3.7.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 6805c874c647fd16331a6ec00cf1a8e5d1c1ca9e91cbda4410e8d5dd17d999810593e24b28e9e34f6a9aa0f5c9828aa5ed392bf99483f6fb133ca3c1743b2883 languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.2": - version: 0.3.2 - resolution: "@jridgewell/gen-mapping@npm:0.3.2" +"@react-stately/tooltip@npm:^3.4.7": + version: 3.4.7 + resolution: "@react-stately/tooltip@npm:3.4.7" dependencies: - "@jridgewell/set-array": "npm:^1.0.1" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - "@jridgewell/trace-mapping": "npm:^0.3.9" - checksum: 7ba0070be1aeda7d7694b09d847c3b95879409b26559b9d7e97a88ec94b838fb380df43ae328ee2d2df4d79e75d7afe6ba315199d18d79aa20839ebdfb739420 + "@react-stately/overlays": "npm:^3.6.5" + "@react-types/tooltip": "npm:^3.4.7" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 05487e629daa27c2789498fbf1f37f416e68f2fd1a527d977d07b49ccaec4e82d7482fa343ab4086e7dd0a468b8bdd75a07dddc4c4b002b5998a06a950a641d5 languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:3.1.0": - version: 3.1.0 - resolution: "@jridgewell/resolve-uri@npm:3.1.0" - checksum: 320ceb37af56953757b28e5b90c34556157676d41e3d0a3ff88769274d62373582bb0f0276a4f2d29c3f4fdd55b82b8be5731f52d391ad2ecae9b321ee1c742d +"@react-stately/tree@npm:^3.7.6": + version: 3.7.6 + resolution: "@react-stately/tree@npm:3.7.6" + dependencies: + "@react-stately/collections": "npm:^3.10.5" + "@react-stately/selection": "npm:^3.14.3" + "@react-stately/utils": "npm:^3.9.1" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: e8e2d5f874e50312b06b41702cbd5a301601b72df37b6edeb4199f64069dd2c07de946316e1150a7a167292c133bb95dd6e6d3a086c82bd4c5c44e1862c47a8e languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.1 - resolution: "@jridgewell/resolve-uri@npm:3.1.1" - checksum: 64d59df8ae1a4e74315eb1b61e012f1c7bc8aac47a3a1e683f6fe7008eab07bc512a742b7aa7c0405685d1421206de58c9c2e6adbfe23832f8bd69408ffc183e +"@react-stately/utils@npm:^3.9.1": + version: 3.9.1 + resolution: "@react-stately/utils@npm:3.9.1" + dependencies: + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 17ddef6415db0950c474c6ad87a0d7b20a98aac817771887922ea6c6a90b9b91eb49205adf021349034f8da012fc0e3c30f6c9b378265ae6d0df93c3b4104b53 languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.0.1": - version: 1.1.2 - resolution: "@jridgewell/set-array@npm:1.1.2" - checksum: 69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e +"@react-stately/virtualizer@npm:^3.6.8": + version: 3.6.8 + resolution: "@react-stately/virtualizer@npm:3.6.8" + dependencies: + "@react-aria/utils": "npm:^3.23.2" + "@react-types/shared": "npm:^3.22.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: d0e26d14aa2c3a31efd61e2d037788e80052439fe364c1f61f13c30cfdd5abc670fc980a0b6479d0921aca4ed7a6cfee40b29193042bd5e09162b2a428d91c72 languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": - version: 1.4.14 - resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" - checksum: 26e768fae6045481a983e48aa23d8fcd23af5da70ebd74b0649000e815e7fbb01ea2bc088c9176b3fffeb9bec02184e58f46125ef3320b30eaa1f4094cfefa38 +"@react-types/breadcrumbs@npm:^3.7.3": + version: 3.7.3 + resolution: "@react-types/breadcrumbs@npm:3.7.3" + dependencies: + "@react-types/link": "npm:^3.5.3" + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 4ec1f9bfd3ce2980d3691a0a2ca9a436212e38599e3b883241a587ecb6575cc60ba12f9f24288987ec98f3f19ededc9fbf021782724e23b0e8d1e87647f08447 languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.14": - version: 1.4.15 - resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" - checksum: 89960ac087781b961ad918978975bcdf2051cd1741880469783c42de64239703eab9db5230d776d8e6a09d73bb5e4cb964e07d93ee6e2e7aea5a7d726e865c09 +"@react-types/button@npm:^3.9.2": + version: 3.9.2 + resolution: "@react-types/button@npm:3.9.2" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 8393ba87dfd6ca73fedf8f7ab3567361f1d6057f640346f2a0cc631e9659ad7c1aa2ddb255e1df6b880d8f6cd209e8c9d1d01c73e2ee2a149f180d8ebaabf1db languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.17 - resolution: "@jridgewell/trace-mapping@npm:0.3.17" +"@react-types/calendar@npm:^3.4.4": + version: 3.4.4 + resolution: "@react-types/calendar@npm:3.4.4" dependencies: - "@jridgewell/resolve-uri": "npm:3.1.0" - "@jridgewell/sourcemap-codec": "npm:1.4.14" - checksum: 790d439c9b271d9fc381dc4a837393ab942920245efedd5db20f65a665c0f778637fa623573337d3241ff784ffdb6724bbadf7fa2b61666bcd4884064b02f113 + "@internationalized/date": "npm:^3.5.2" + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: def8bdf94b38df3c2c49f63fe505734ad24669d87a5e917c061fd198b759e5561a9f89e4137278740ef6d6b441518f6f2dd51e1916a7ddff7d7d0878bd63a5b6 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.18": - version: 0.3.20 - resolution: "@jridgewell/trace-mapping@npm:0.3.20" +"@react-types/checkbox@npm:^3.7.1": + version: 3.7.1 + resolution: "@react-types/checkbox@npm:3.7.1" dependencies: - "@jridgewell/resolve-uri": "npm:^3.1.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 683117e4e6707ef50c725d6d0ec4234687ff751f36fa46c2b3068931eb6a86b49af374d3030200777666579a992b7470d1bd1c591e9bf64d764dda5295f33093 + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: d8d1b860225bf29ae335b6e9e5b814e74f75ef498acc93fc08ad411ada078399e407b146e15a3ff2ab6003b44a34cf0c26f327c1a25f43baaf633cb2999a2836 languageName: node linkType: hard -"@juggle/resize-observer@npm:^3.3.1": - version: 3.4.0 - resolution: "@juggle/resize-observer@npm:3.4.0" - checksum: 73d1d00ee9132fb6f0aea0531940a6b93603e935590bd450fc6285a328d906102eeeb95dea77b2edac0e779031a9708aa8c82502bd298ee4dd26e7dff48f397a +"@react-types/combobox@npm:^3.10.1": + version: 3.10.1 + resolution: "@react-types/combobox@npm:3.10.1" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 5c1fcad50731387da3d77820e55605a0a2e7c6371e2c6b0ef9a567f727cf63856b69aef8d8448765f78c940fd89835391b5afa2fe6a87f2809b6908b615578a7 languageName: node linkType: hard -"@next/env@npm:14.0.4": - version: 14.0.4 - resolution: "@next/env@npm:14.0.4" - checksum: 781eede471730264812d8c744d33eb42da997b4403b06a5b0e58597645152af21f3619a6cb8fc0ba1c1b26d89910c0a8ade6d4242ae13d0b7baa70e3a83cac0f +"@react-types/datepicker@npm:^3.7.2": + version: 3.7.2 + resolution: "@react-types/datepicker@npm:3.7.2" + dependencies: + "@internationalized/date": "npm:^3.5.2" + "@react-types/calendar": "npm:^3.4.4" + "@react-types/overlays": "npm:^3.8.5" + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 4a8495a9079e88a0847ef67d07423e350161239aae4c17657fee765f9929456ad49f061e1a3b3e1ac4936898ed1cc9ac718cb2c24c4afde8068907bed7c08408 languageName: node linkType: hard -"@next/eslint-plugin-next@npm:14.0.4": - version: 14.0.4 - resolution: "@next/eslint-plugin-next@npm:14.0.4" +"@react-types/dialog@npm:^3.5.8": + version: 3.5.8 + resolution: "@react-types/dialog@npm:3.5.8" dependencies: - glob: "npm:7.1.7" - checksum: 17871e2a86b66584b9eff8796a76d0c59ae62626dd8d0ae1cb7ca6977decf6273eb935d67e204f575a9ba0574a1c289329e6bbcb70d866e4c7f2879597cbd55a + "@react-types/overlays": "npm:^3.8.5" + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: c0c387367fd697dff96fa7252cdd1d63fe7c871c93f57ed313c890ef1366e0dd85763966e1e9adc16aa9486414075b349757198572c5c5feb010897f6af9d0bf languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:14.0.4": - version: 14.0.4 - resolution: "@next/swc-darwin-arm64@npm:14.0.4" - conditions: os=darwin & cpu=arm64 +"@react-types/grid@npm:^3.2.4": + version: 3.2.4 + resolution: "@react-types/grid@npm:3.2.4" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 44246cd315f339ed3d1ea5cecf8b56b6677d6c15c26e6bda77cf256b5f148adcc4bbbbfb3adc949e408a5118039474c69038526cf5ff65ec2ff2fccc75b45666 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:14.0.4": - version: 14.0.4 - resolution: "@next/swc-darwin-x64@npm:14.0.4" - conditions: os=darwin & cpu=x64 +"@react-types/link@npm:^3.5.3": + version: 3.5.3 + resolution: "@react-types/link@npm:3.5.3" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 55e23b17ec5935b8246048b99c719645d65bbf179822f818a66e21097bc49a23f9214b2677f631cec799dbe8aaf25c5fb61f30a5d8a30e2c7fdf14445058cb3b languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:14.0.4": - version: 14.0.4 - resolution: "@next/swc-linux-arm64-gnu@npm:14.0.4" - conditions: os=linux & cpu=arm64 & libc=glibc +"@react-types/listbox@npm:^3.4.7": + version: 3.4.7 + resolution: "@react-types/listbox@npm:3.4.7" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 3c8e938fb9e0c4761a770711da7d084222b3fedcf094c49dc37a441fb07bfcd2b10dc25460d29565538f6dcb1e200ff05d1c6c2fb16a0e503003e3dfc2a8a09b languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:14.0.4": - version: 14.0.4 - resolution: "@next/swc-linux-arm64-musl@npm:14.0.4" - conditions: os=linux & cpu=arm64 & libc=musl +"@react-types/menu@npm:^3.9.7": + version: 3.9.7 + resolution: "@react-types/menu@npm:3.9.7" + dependencies: + "@react-types/overlays": "npm:^3.8.5" + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 97cec66432e6c53909dab25d9a7d5c2646d484caeb6c4eff402f152baf667079ef5774c31098f29d66045a1b0f841b0cd579aaa1948353631739ddf61042a0e7 languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:14.0.4": - version: 14.0.4 - resolution: "@next/swc-linux-x64-gnu@npm:14.0.4" - conditions: os=linux & cpu=x64 & libc=glibc +"@react-types/meter@npm:^3.3.7": + version: 3.3.7 + resolution: "@react-types/meter@npm:3.3.7" + dependencies: + "@react-types/progress": "npm:^3.5.2" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: d9e697640efc198f441775b8a007f9653df86451d5dca491db9240cf0437df16c9118014eee4add23bd5015bbf72b63761256b2334597feb1c277fb6573024e5 languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:14.0.4": - version: 14.0.4 - resolution: "@next/swc-linux-x64-musl@npm:14.0.4" - conditions: os=linux & cpu=x64 & libc=musl +"@react-types/numberfield@npm:^3.8.1": + version: 3.8.1 + resolution: "@react-types/numberfield@npm:3.8.1" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: aa647a5573ff8ac52a43c5f2a2dc348d3d39d097f976ac44b8fe8e61bd8fc9f8b1c3447ac7b36d2e39df36f45f8776055a3cb9c0f84566e747cf99188e47572c languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:14.0.4": - version: 14.0.4 - resolution: "@next/swc-win32-arm64-msvc@npm:14.0.4" - conditions: os=win32 & cpu=arm64 +"@react-types/overlays@npm:^3.8.5": + version: 3.8.5 + resolution: "@react-types/overlays@npm:3.8.5" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 6c952fdbe7724b07cade95e8d3fe6bf61cb6e993b730051c1ada33da2afe246e3124a8981127977cc55f6df32124b049504fda7d19593446895559ca00a9f0b9 languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:14.0.4": - version: 14.0.4 - resolution: "@next/swc-win32-ia32-msvc@npm:14.0.4" - conditions: os=win32 & cpu=ia32 +"@react-types/progress@npm:^3.5.2": + version: 3.5.2 + resolution: "@react-types/progress@npm:3.5.2" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 1ddda7a41c51b22dabc1a7b1a6bc7ae10afbd93676a5b288415e600909b6c4ccf762ea4c6c8f7bf04b2ca3216ef389075bf5448400587de675e65840c715bcca languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:14.0.4": - version: 14.0.4 - resolution: "@next/swc-win32-x64-msvc@npm:14.0.4" - conditions: os=win32 & cpu=x64 +"@react-types/radio@npm:^3.7.1": + version: 3.7.1 + resolution: "@react-types/radio@npm:3.7.1" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: a400034d69f9963664ac608ef35e50f5e3090f095800de0d88d78883bb3465392a8c3f2ff043c84c5c08abb8186b5a544f9897ff80e81fd2a07498be8408b86c languageName: node linkType: hard -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" +"@react-types/searchfield@npm:^3.5.3": + version: 3.5.3 + resolution: "@react-types/searchfield@npm:3.5.3" dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b + "@react-types/shared": "npm:^3.22.1" + "@react-types/textfield": "npm:^3.9.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: ee3847b33318cef9ad84ad59c7ae1c326046d24e79a5a94bf1636c9d6d5376bf001454343af797043e2216ecb2573b19877fced24534269b9eb092b410f49869 languageName: node linkType: hard -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 +"@react-types/select@npm:^3.9.2": + version: 3.9.2 + resolution: "@react-types/select@npm:3.9.2" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 13ded3c246304acc250bd21a54211b64732eaf8c9e4127c2ba61f440aa7d172f560d5cb64cf5d580cfbaeaadc54946c0b159097f5066cf69789c4ea776c7a116 languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 +"@react-types/shared@npm:^3.22.1": + version: 3.22.1 + resolution: "@react-types/shared@npm:3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: da5fc6775a79ae8148d80a6cd7025ff0d44462c5b8923cdd072ac34626ac7416049f297ec078ebed29fd49d65fd356f21ede9587517b88f20f9d6236107c1333 languageName: node linkType: hard -"@npmcli/fs@npm:^2.1.0": - version: 2.1.2 - resolution: "@npmcli/fs@npm:2.1.2" +"@react-types/slider@npm:^3.7.1": + version: 3.7.1 + resolution: "@react-types/slider@npm:3.7.1" dependencies: - "@gar/promisify": "npm:^1.1.3" - semver: "npm:^7.3.5" - checksum: c5d4dfee80de2236e1e4ed595d17e217aada72ebd8215183fc46096fa010f583dd2aaaa486758de7cc0b89440dbc31cfe8b276269d75d47af35c716e896f78ec + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 36a53097dfa39c10f53d4bd0af62502785bf958623cab23df6cec26842484db76e64c2ce44cbd3202a60eb41f3dc0733b4b7bb71a4eb124bf9996e84eb6f38d5 languageName: node linkType: hard -"@npmcli/move-file@npm:^2.0.0": - version: 2.0.1 - resolution: "@npmcli/move-file@npm:2.0.1" +"@react-types/switch@npm:^3.5.1": + version: 3.5.1 + resolution: "@react-types/switch@npm:3.5.1" dependencies: - mkdirp: "npm:^1.0.4" - rimraf: "npm:^3.0.2" - checksum: 52dc02259d98da517fae4cb3a0a3850227bdae4939dda1980b788a7670636ca2b4a01b58df03dd5f65c1e3cb70c50fa8ce5762b582b3f499ec30ee5ce1fd9380 + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 8798a3c6f88951dc88bfff7059c243fe0247bf352a1954b78ca0c9fb9ed764415b6fe89849a33aab72b4a2e73f3540b7ca28aefd791e34a1a4053ee723e91b16 languageName: node linkType: hard -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff +"@react-types/table@npm:^3.9.3": + version: 3.9.3 + resolution: "@react-types/table@npm:3.9.3" + dependencies: + "@react-types/grid": "npm:^3.2.4" + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 6483078311ad6fe16cbe3ae1896962ff95509b843641dbb2b2cc0c437fe8a4b1493aecb6cff5b5364f76e43b591e13837c14daef12b6dac087d5395e828594e8 languageName: node linkType: hard -"@react-dnd/asap@npm:^4.0.0": - version: 4.0.1 - resolution: "@react-dnd/asap@npm:4.0.1" - checksum: 4fd8912de1c689f79be653b09ee561ebf1f67878cc69ea5ce54cd30e462853ca581121ef8e632864427cfe5ad206539bb5b59fce08fb471cec16be30886380dd +"@react-types/tabs@npm:^3.3.5": + version: 3.3.5 + resolution: "@react-types/tabs@npm:3.3.5" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 1df4042f8f8eadaa60ab91ffcd5d3c75636c86a6f98660b902e8aeb9c4bcc6ee3581d40bf3c48318dd591690ab8b555b3a0a3ac0d35dea1f3e03a2679008e686 languageName: node linkType: hard -"@react-dnd/invariant@npm:^2.0.0": - version: 2.0.0 - resolution: "@react-dnd/invariant@npm:2.0.0" - checksum: ef1e989920d70b15c80dccb01af9b598081d76993311aa22d2e9a3ec41d10a88540eeec4b4de7a8b2a2ea52dfc3495ab45e39192c2d27795a9258bd6b79d000e +"@react-types/textfield@npm:^3.9.1": + version: 3.9.1 + resolution: "@react-types/textfield@npm:3.9.1" + dependencies: + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 978c3190c0a0e585d948f137b21dabd3cf2c7cbee63598c5bfd9da94cec890193801a27a4b143771609ac26e99dc47039e55d3d3ec86d7aa5488a59b5afd3c49 languageName: node linkType: hard -"@react-dnd/shallowequal@npm:^2.0.0": - version: 2.0.0 - resolution: "@react-dnd/shallowequal@npm:2.0.0" - checksum: b5bbdc795d65945bb7ba2322bed5cf8d4c6fe91dced98c3b10e3d16822c438f558751135ff296f8d1aa1eaa9d0037dacab2b522ca5eb812175123b9996966dcb +"@react-types/tooltip@npm:^3.4.7": + version: 3.4.7 + resolution: "@react-types/tooltip@npm:3.4.7" + dependencies: + "@react-types/overlays": "npm:^3.8.5" + "@react-types/shared": "npm:^3.22.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: cca3b7df6b58514c1f3bb8ef7eb42ae2c8d527472733db80c12f9a898a869a6537022b9a146baf37cca70f6bd88192b26bfb249eb6798e79e66b8c331a75447d languageName: node linkType: hard @@ -1325,6 +2762,25 @@ __metadata: languageName: node linkType: hard +"@swc/helpers@npm:^0.4.14": + version: 0.4.36 + resolution: "@swc/helpers@npm:0.4.36" + dependencies: + legacy-swc-helpers: "npm:@swc/helpers@=0.4.14" + tslib: "npm:^2.4.0" + checksum: fe1e51af79315b58f648d0f377cbd3e8c3cc8c0a6d9b2435a2935c5d1bbb483fb3299e8fcb2f360488b5c4fc4e06494d42c751bf4f853c3582cf467791b2a161 + languageName: node + linkType: hard + +"@swc/helpers@npm:^0.5.0": + version: 0.5.7 + resolution: "@swc/helpers@npm:0.5.7" + dependencies: + tslib: "npm:^2.4.0" + checksum: f9c4cbd2d59ef86dbe9f955651f1d49561cd897ca113d713f370853ebcc44841712b9b4c674508a314cceadc2ef27cdc0979b36cbb3af8b26b727e345ffe1f2e + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -2476,19 +3932,26 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001587, caniuse-lite@npm:^1.0.30001591": - version: 1.0.30001597 - resolution: "caniuse-lite@npm:1.0.30001597" - checksum: 44a268113faeee51e249cbcb3924dc3765f26cd527a134e3bb720ed20d50abd8b9291500a88beee061cc03ae9f15ddc9045d57e30d25a98efeaff4f7bb8965c1 + version: 1.0.30001596 + resolution: "caniuse-lite@npm:1.0.30001596" + checksum: 4124d6e927193ee31bb7237925720263ac837ef0bf6627c67a8217edfe49eefc3a385a4830d4aa92a213576a0aa6006e61c8dd57a644ec65882c15d15f625182 languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001400, caniuse-lite@npm:^1.0.30001406": +"caniuse-lite@npm:^1.0.30001400": version: 1.0.30001568 resolution: "caniuse-lite@npm:1.0.30001568" checksum: 27aa9697e8fccf61702962a3cc48ec2355940e94872e4f0dab108d8a88adb0250e5b96572bef08b90a67a8183d1c704448b2fc69d600d7b6405b3f74dc5dbcb6 languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001579": + version: 1.0.30001599 + resolution: "caniuse-lite@npm:1.0.30001599" + checksum: c9a5ad806fc0d446e4f995d551b840d8fdcbe97958b7f83ff7a255a8ef5e40ca12ca1a508c66b3ab147e19eef932d28772d205c046500dd0740ea9dfb602e2e1 + languageName: node + linkType: hard + "caseless@npm:~0.12.0": version: 0.12.0 resolution: "caseless@npm:0.12.0" @@ -3305,17 +4768,6 @@ __metadata: languageName: node linkType: hard -"dnd-core@npm:14.0.1": - version: 14.0.1 - resolution: "dnd-core@npm:14.0.1" - dependencies: - "@react-dnd/asap": "npm:^4.0.0" - "@react-dnd/invariant": "npm:^2.0.0" - redux: "npm:^4.1.1" - checksum: 6a65bb21efea2b6681ffdb4a143460788bb92879e76758025114a68646a275bd8425cd18bbe4a7688b9b3f85324ff0349170705f338d4ee7000ae416b519dca1 - languageName: node - linkType: hard - "docs-c16982@workspace:modules/docs": version: 0.0.0-use.local resolution: "docs-c16982@workspace:modules/docs" @@ -3436,9 +4888,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.4.668": - version: 1.4.703 - resolution: "electron-to-chromium@npm:1.4.703" - checksum: e7927fbe75e56508dd0b4efeb0e69dfb8ee1e6e6aaf6f07c047b96ff530d8f49e1eaf51cae64c2d3c179e3932fb37661012ccaa4f36956dd96480219f3a23013 + version: 1.4.699 + resolution: "electron-to-chromium@npm:1.4.699" + checksum: dd09cc21a204fee05349348f3399fc2a2a02b770fc43c746f3f117d870e8614495eb05647d01dfea92bb05bf2f0dd374ce19e6acd7623a1ad785951e193942d3 languageName: node linkType: hard @@ -4593,13 +6045,6 @@ __metadata: languageName: node linkType: hard -"glob-to-regexp@npm:^0.4.1": - version: 0.4.1 - resolution: "glob-to-regexp@npm:0.4.1" - checksum: 9009529195a955c40d7b9690794aeff5ba665cc38f1519e111c58bb54366fd0c106bde80acf97ba4e533208eb53422c83b136611a54c5fefb1edd8dc267cb62e - languageName: node - linkType: hard - "glob@npm:7.1.7": version: 7.1.7 resolution: "glob@npm:7.1.7" @@ -4825,15 +6270,6 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.2": - version: 3.3.2 - resolution: "hoist-non-react-statics@npm:3.3.2" - dependencies: - react-is: "npm:^16.7.0" - checksum: 1acbe85f33e5a39f90c822ad4d28b24daeb60f71c545279431dc98c312cd28a54f8d64788e477fe21dc502b0e3cf58589ebe5c1ad22af27245370391c2d24ea6 - languageName: node - linkType: hard - "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -5036,6 +6472,18 @@ __metadata: languageName: node linkType: hard +"intl-messageformat@npm:^10.1.0": + version: 10.5.11 + resolution: "intl-messageformat@npm:10.5.11" + dependencies: + "@formatjs/ecma402-abstract": "npm:1.18.2" + "@formatjs/fast-memoize": "npm:2.2.0" + "@formatjs/icu-messageformat-parser": "npm:2.7.6" + tslib: "npm:^2.4.0" + checksum: 2146f4d3e2c4bcf2c4fa343e4ee070fe1124d3821caa2fa0e7112a68fdefbedbbda6a3778f3ba04e38bbce3db33511ca9eecbb0a7e06013e6699255c153813ce + languageName: node + linkType: hard + "ip@npm:^2.0.0": version: 2.0.0 resolution: "ip@npm:2.0.0" @@ -6158,6 +7606,15 @@ __metadata: languageName: node linkType: hard +"legacy-swc-helpers@npm:@swc/helpers@=0.4.14": + version: 0.4.14 + resolution: "@swc/helpers@npm:0.4.14" + dependencies: + tslib: "npm:^2.4.0" + checksum: 236afd445fb22e3df7aa84336d5c45d29e021ad01917aa7c24267330df8b39ed89c3d8d9836ac2ac7569b46923591d0e49174f72df7fb997aea841d08f374dbd + languageName: node + linkType: hard + "leven@npm:^3.1.0": version: 3.1.0 resolution: "leven@npm:3.1.0" @@ -6692,27 +8149,26 @@ __metadata: languageName: node linkType: hard -"next@npm:^14.0.4": - version: 14.0.4 - resolution: "next@npm:14.0.4" - dependencies: - "@next/env": "npm:14.0.4" - "@next/swc-darwin-arm64": "npm:14.0.4" - "@next/swc-darwin-x64": "npm:14.0.4" - "@next/swc-linux-arm64-gnu": "npm:14.0.4" - "@next/swc-linux-arm64-musl": "npm:14.0.4" - "@next/swc-linux-x64-gnu": "npm:14.0.4" - "@next/swc-linux-x64-musl": "npm:14.0.4" - "@next/swc-win32-arm64-msvc": "npm:14.0.4" - "@next/swc-win32-ia32-msvc": "npm:14.0.4" - "@next/swc-win32-x64-msvc": "npm:14.0.4" +"next@npm:^14.1.3": + version: 14.1.3 + resolution: "next@npm:14.1.3" + dependencies: + "@next/env": "npm:14.1.3" + "@next/swc-darwin-arm64": "npm:14.1.3" + "@next/swc-darwin-x64": "npm:14.1.3" + "@next/swc-linux-arm64-gnu": "npm:14.1.3" + "@next/swc-linux-arm64-musl": "npm:14.1.3" + "@next/swc-linux-x64-gnu": "npm:14.1.3" + "@next/swc-linux-x64-musl": "npm:14.1.3" + "@next/swc-win32-arm64-msvc": "npm:14.1.3" + "@next/swc-win32-ia32-msvc": "npm:14.1.3" + "@next/swc-win32-x64-msvc": "npm:14.1.3" "@swc/helpers": "npm:0.5.2" busboy: "npm:1.6.0" - caniuse-lite: "npm:^1.0.30001406" + caniuse-lite: "npm:^1.0.30001579" graceful-fs: "npm:^4.2.11" postcss: "npm:8.4.31" styled-jsx: "npm:5.1.1" - watchpack: "npm:2.4.0" peerDependencies: "@opentelemetry/api": ^1.1.0 react: ^18.2.0 @@ -6744,7 +8200,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: f119dfed59ba14972759bbc354fd2e99793c5a31689465a5e7cacd329977ed3d259eb756142bef31e96f28a80a00997e76314425faeda4c6fcf4e4ad6c5fa960 + checksum: 6e77d5d35161b1e9337112ce7864527029f5c8e25ddd29e07e8a730c83f7241dd241e7eb0bdcd006e813b610274e960e0af18c167ab8a2deeeb7776121a7d770 languageName: node linkType: hard @@ -7657,12 +9113,12 @@ __metadata: linkType: hard "postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.15": - version: 6.0.16 - resolution: "postcss-selector-parser@npm:6.0.16" + version: 6.0.15 + resolution: "postcss-selector-parser@npm:6.0.15" dependencies: cssesc: "npm:^3.0.0" util-deprecate: "npm:^1.0.2" - checksum: 9324f63992c6564d392f9f6b16c56c05f157256e3be2d55d1234f7728252257dfd6b870a65a5d04ee3ceb9d9e7b78c043f630a58c9869b4b0481d6e064edc2cf + checksum: cea591e1d9bce60eea724428863187228e27ddaebd98e5ecb4ee6d4c9a4b68e8157fd44c916b3fef1691d19ad16aa416bb7279b5eab260c32340ae630a34e200 languageName: node linkType: hard @@ -7941,51 +9397,63 @@ __metadata: "@types/use-sync-external-store": "npm:^0.0.6" jest: "npm:^29.7.0" npm-run-all: "npm:^4.1.5" - react-dnd: "npm:^14.0.3" - react-dnd-html5-backend: "npm:^14.0.3" + react-aria: "npm:^3.32.1" react-window: "npm:^1.8.10" - redux: "npm:^5.0.0" rimraf: "npm:^5.0.5" ts-jest: "npm:^29.1.1" typescript: "npm:^5.3.3" - use-sync-external-store: "npm:^1.2.0" + when-clause: "npm:0.0.3" peerDependencies: react: ">= 16.14" react-dom: ">= 16.14" languageName: unknown linkType: soft -"react-dnd-html5-backend@npm:^14.0.3": - version: 14.1.0 - resolution: "react-dnd-html5-backend@npm:14.1.0" - dependencies: - dnd-core: "npm:14.0.1" - checksum: 9249d538bdd6395d5148d4690568853754397786fb2356372d809f5e709b98ec0c1d40f71560511f562a47c5522aeb78d028f4c99b59f1d6ab3a01436ab70567 - languageName: node - linkType: hard - -"react-dnd@npm:^14.0.3": - version: 14.0.5 - resolution: "react-dnd@npm:14.0.5" - dependencies: - "@react-dnd/invariant": "npm:^2.0.0" - "@react-dnd/shallowequal": "npm:^2.0.0" - dnd-core: "npm:14.0.1" - fast-deep-equal: "npm:^3.1.3" - hoist-non-react-statics: "npm:^3.3.2" +"react-aria@npm:^3.32.1": + version: 3.32.1 + resolution: "react-aria@npm:3.32.1" + dependencies: + "@internationalized/string": "npm:^3.2.1" + "@react-aria/breadcrumbs": "npm:^3.5.11" + "@react-aria/button": "npm:^3.9.3" + "@react-aria/calendar": "npm:^3.5.6" + "@react-aria/checkbox": "npm:^3.14.1" + "@react-aria/combobox": "npm:^3.8.4" + "@react-aria/datepicker": "npm:^3.9.3" + "@react-aria/dialog": "npm:^3.5.12" + "@react-aria/dnd": "npm:^3.5.3" + "@react-aria/focus": "npm:^3.16.2" + "@react-aria/gridlist": "npm:^3.7.5" + "@react-aria/i18n": "npm:^3.10.2" + "@react-aria/interactions": "npm:^3.21.1" + "@react-aria/label": "npm:^3.7.6" + "@react-aria/link": "npm:^3.6.5" + "@react-aria/listbox": "npm:^3.11.5" + "@react-aria/menu": "npm:^3.13.1" + "@react-aria/meter": "npm:^3.4.11" + "@react-aria/numberfield": "npm:^3.11.1" + "@react-aria/overlays": "npm:^3.21.1" + "@react-aria/progress": "npm:^3.4.11" + "@react-aria/radio": "npm:^3.10.2" + "@react-aria/searchfield": "npm:^3.7.3" + "@react-aria/select": "npm:^3.14.3" + "@react-aria/selection": "npm:^3.17.5" + "@react-aria/separator": "npm:^3.3.11" + "@react-aria/slider": "npm:^3.7.6" + "@react-aria/ssr": "npm:^3.9.2" + "@react-aria/switch": "npm:^3.6.2" + "@react-aria/table": "npm:^3.13.5" + "@react-aria/tabs": "npm:^3.8.5" + "@react-aria/tag": "npm:^3.3.3" + "@react-aria/textfield": "npm:^3.14.3" + "@react-aria/tooltip": "npm:^3.7.2" + "@react-aria/utils": "npm:^3.23.2" + "@react-aria/visually-hidden": "npm:^3.8.10" + "@react-types/shared": "npm:^3.22.1" peerDependencies: - "@types/hoist-non-react-statics": ">= 3.3.1" - "@types/node": ">= 12" - "@types/react": ">= 16" - react: ">= 16.14" - peerDependenciesMeta: - "@types/hoist-non-react-statics": - optional: true - "@types/node": - optional: true - "@types/react": - optional: true - checksum: 027d23c1e6c3e54fbb791c11cf8b42700d0eb3ff5eeaca64088e49c162e29fcd1f5ec07807c989e9c7253e3204c425f6510c0d92f0eb0325b3def400da0ac1a7 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 8ef6d071ef8de6102a2c7123e4dcde9137f01af40993e9e8e129795d6ffdb94cafaa583d8d309cacb9563c79ee1533fa35fbbd478ba671b49ba41bdba4d47ec4 languageName: node linkType: hard @@ -8010,7 +9478,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.7.0": +"react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: 5aa564a1cde7d391ac980bedee21202fc90bdea3b399952117f54fb71a932af1e5902020144fb354b4690b2414a0c7aafe798eb617b76a3d441d956db7726fdf @@ -8086,22 +9554,6 @@ __metadata: languageName: node linkType: hard -"redux@npm:^4.1.1": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": "npm:^7.9.2" - checksum: 371e4833b671193303a7dea7803c8fdc8e0d566740c78f580e0a3b77b4161da25037626900a2205a5d616117fa6ad09a4232e5a110bd437186b5c6355a041750 - languageName: node - linkType: hard - -"redux@npm:^5.0.0": - version: 5.0.0 - resolution: "redux@npm:5.0.0" - checksum: a316de394977a5a0b6aa36c439efb6773710f9a8b3642590b17ec12987825b3aca9e21d64ee0200e35551e0ac4226a228e917f6911949bd7db6a314458c9cedc - languageName: node - linkType: hard - "reflect.getprototypeof@npm:^1.0.4": version: 1.0.4 resolution: "reflect.getprototypeof@npm:1.0.4" @@ -8622,7 +10074,7 @@ __metadata: eslint: "npm:^8.55.0" eslint-config-next: "npm:^14.0.4" nanoid: "npm:^5.0.4" - next: "npm:^14.0.4" + next: "npm:^14.1.3" npm-run-all: "npm:^4.1.5" react: "npm:^18.2.0" react-arborist: "workspace:*" @@ -9681,15 +11133,6 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:^1.2.0": - version: 1.2.0 - resolution: "use-sync-external-store@npm:1.2.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: a676216affc203876bd47981103f201f28c2731361bb186367e12d287a7566763213a8816910c6eb88265eccd4c230426eb783d64c373c4a180905be8820ed8e - languageName: node - linkType: hard - "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -9776,13 +11219,10 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:2.4.0": - version: 2.4.0 - resolution: "watchpack@npm:2.4.0" - dependencies: - glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.1.2" - checksum: 4280b45bc4b5d45d5579113f2a4af93b67ae1b9607cc3d86ae41cdd53ead10db5d9dc3237f24256d05ef88b28c69a02712f78e434cb7ecc8edaca134a56e8cab +"when-clause@npm:0.0.3": + version: 0.0.3 + resolution: "when-clause@npm:0.0.3" + checksum: c379193fe3cc80439b995781830b010a938c9f57852b40f21f6eb02c2ea80e79f4308b975d33e8c6094ad1955ac58264d91b06303c707b1448f2e7f190c02935 languageName: node linkType: hard