diff --git a/change/@fluentui-react-tree-b474b0cc-81a2-48d4-b63b-268445c11d76.json b/change/@fluentui-react-tree-b474b0cc-81a2-48d4-b63b-268445c11d76.json new file mode 100644 index 00000000000000..4dff240a00eb69 --- /dev/null +++ b/change/@fluentui-react-tree-b474b0cc-81a2-48d4-b63b-268445c11d76.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "feat: adds openItems and checkedItems to tree callback data", + "packageName": "@fluentui/react-tree", + "email": "bernardo.sunderhus@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-tree/etc/react-tree.api.md b/packages/react-components/react-tree/etc/react-tree.api.md index 9d65dc26c4a87b..312b199f750541 100644 --- a/packages/react-components/react-tree/etc/react-tree.api.md +++ b/packages/react-components/react-tree/etc/react-tree.api.md @@ -101,6 +101,7 @@ export const Tree: ForwardRefComponent; // @public (undocumented) export type TreeCheckedChangeData = { value: TreeItemValue; + checkedItems: Map; target: HTMLElement; event: React_2.ChangeEvent; type: 'Change'; @@ -152,6 +153,7 @@ export type TreeItemContextValue = { itemType: TreeItemType; value: TreeItemValue; open: boolean; + checked?: TreeSelectionValue; }; // @public @@ -269,6 +271,7 @@ export type TreeNavigationEvent_unstable = TreeNavigationData_unstable['event']; // @public (undocumented) export type TreeOpenChangeData = { open: boolean; + openItems: Set; value: TreeItemValue; target: HTMLElement; } & ({ diff --git a/packages/react-components/react-tree/src/components/FlatTree/__snapshots__/FlatTree.test.tsx.snap b/packages/react-components/react-tree/src/components/FlatTree/__snapshots__/FlatTree.test.tsx.snap index 3c359b50b261a9..0121eef37e9f1e 100644 --- a/packages/react-components/react-tree/src/components/FlatTree/__snapshots__/FlatTree.test.tsx.snap +++ b/packages/react-components/react-tree/src/components/FlatTree/__snapshots__/FlatTree.test.tsx.snap @@ -4,7 +4,7 @@ exports[`FlatTree renders a default state 1`] = `
Default FlatTree
diff --git a/packages/react-components/react-tree/src/components/FlatTree/useFlatTreeNavigation.ts b/packages/react-components/react-tree/src/components/FlatTree/useFlatTreeNavigation.ts index 79e655c0f78b85..3c7b7133cba635 100644 --- a/packages/react-components/react-tree/src/components/FlatTree/useFlatTreeNavigation.ts +++ b/packages/react-components/react-tree/src/components/FlatTree/useFlatTreeNavigation.ts @@ -1,56 +1,54 @@ import { useFluent_unstable } from '@fluentui/react-shared-contexts'; -import { useEventCallback, useMergedRefs } from '@fluentui/react-utilities'; +import { useEventCallback } from '@fluentui/react-utilities'; import { TreeNavigationData_unstable } from '../../Tree'; import { HeadlessTree, HeadlessTreeItemProps } from '../../utils/createHeadlessTree'; import { nextTypeAheadElement } from '../../utils/nextTypeAheadElement'; import { treeDataTypes } from '../../utils/tokens'; import { treeItemFilter } from '../../utils/treeItemFilter'; -import { HTMLElementWalker, useHTMLElementWalkerRef } from '../../hooks/useHTMLElementWalker'; import { useRovingTabIndex } from '../../hooks/useRovingTabIndexes'; import { dataTreeItemValueAttrName, getTreeItemValueFromElement } from '../../utils/getTreeItemValueFromElement'; +import { HTMLElementWalker } from '../../utils/createHTMLElementWalker'; export function useFlatTreeNavigation(virtualTree: HeadlessTree) { const { targetDocument } = useFluent_unstable(); - const [treeItemWalkerRef, treeItemWalkerRootRef] = useHTMLElementWalkerRef(treeItemFilter); - const [{ rove }, rovingRootRef] = useRovingTabIndex(treeItemFilter); + const { rove, initialize } = useRovingTabIndex(treeItemFilter); - function getNextElement(data: TreeNavigationData_unstable) { - if (!targetDocument || !treeItemWalkerRef.current) { + function getNextElement(data: TreeNavigationData_unstable, walker: HTMLElementWalker) { + if (!targetDocument) { return null; } - const treeItemWalker = treeItemWalkerRef.current; switch (data.type) { case treeDataTypes.Click: return data.target; case treeDataTypes.TypeAhead: - treeItemWalker.currentElement = data.target; - return nextTypeAheadElement(treeItemWalker, data.event.key); + walker.currentElement = data.target; + return nextTypeAheadElement(walker, data.event.key); case treeDataTypes.ArrowLeft: - return parentElement(virtualTree, data.target, treeItemWalker); + return parentElement(virtualTree, data.target, walker); case treeDataTypes.ArrowRight: - treeItemWalker.currentElement = data.target; - return firstChild(data.target, treeItemWalker); + walker.currentElement = data.target; + return firstChild(data.target, walker); case treeDataTypes.End: - treeItemWalker.currentElement = treeItemWalker.root; - return treeItemWalker.lastChild(); + walker.currentElement = walker.root; + return walker.lastChild(); case treeDataTypes.Home: - treeItemWalker.currentElement = treeItemWalker.root; - return treeItemWalker.firstChild(); + walker.currentElement = walker.root; + return walker.firstChild(); case treeDataTypes.ArrowDown: - treeItemWalker.currentElement = data.target; - return treeItemWalker.nextElement(); + walker.currentElement = data.target; + return walker.nextElement(); case treeDataTypes.ArrowUp: - treeItemWalker.currentElement = data.target; - return treeItemWalker.previousElement(); + walker.currentElement = data.target; + return walker.previousElement(); } } - const navigate = useEventCallback((data: TreeNavigationData_unstable) => { - const nextElement = getNextElement(data); + const navigate = useEventCallback((data: TreeNavigationData_unstable, walker: HTMLElementWalker) => { + const nextElement = getNextElement(data, walker); if (nextElement) { rove(nextElement); } }); - return [navigate, useMergedRefs(treeItemWalkerRootRef, rovingRootRef)] as const; + return { navigate, initialize } as const; } function firstChild(target: HTMLElement, treeWalker: HTMLElementWalker): HTMLElement | null { diff --git a/packages/react-components/react-tree/src/components/FlatTree/useHeadlessFlatTree.ts b/packages/react-components/react-tree/src/components/FlatTree/useHeadlessFlatTree.ts index 1cd0f2bbae210f..51b3fdecf9b993 100644 --- a/packages/react-components/react-tree/src/components/FlatTree/useHeadlessFlatTree.ts +++ b/packages/react-components/react-tree/src/components/FlatTree/useHeadlessFlatTree.ts @@ -18,6 +18,8 @@ import { TreeOpenChangeEvent, TreeProps, } from '../Tree/Tree.types'; +import { HTMLElementWalker, createHTMLElementWalker } from '../../utils/createHTMLElementWalker'; +import { treeItemFilter } from '../../utils/treeItemFilter'; export type HeadlessFlatTreeItemProps = HeadlessTreeItemProps; export type HeadlessFlatTreeItem = HeadlessTreeItem; @@ -114,7 +116,18 @@ export function useHeadlessFlatTree_unstable createHeadlessTree(props), [props]); const [openItems, setOpenItems] = useControllableOpenItems(options); const [checkedItems, setCheckedItems] = useFlatControllableCheckedItems(options); - const [navigate, navigationRef] = useFlatTreeNavigation(headlessTree); + const { initialize, navigate } = useFlatTreeNavigation(headlessTree); + const walkerRef = React.useRef(); + const initializeWalker = React.useCallback( + (root: HTMLElement | null) => { + if (root) { + walkerRef.current = createHTMLElementWalker(root, treeItemFilter); + initialize(walkerRef.current); + } + }, + [initialize], + ); + const treeRef = React.useRef(null); const handleOpenChange = useEventCallback((event: TreeOpenChangeEvent, data: TreeOpenChangeData) => { options.onOpenChange?.(event, data); @@ -129,7 +142,9 @@ export function useHeadlessFlatTree_unstable { options.onNavigation_unstable?.(event, data); - navigate(data); + if (walkerRef.current) { + navigate(data, walkerRef.current); + } }, ); @@ -161,7 +176,7 @@ export function useHeadlessFlatTree_unstable(treeRef, navigationRef as React.Ref); + const ref = useMergedRefs(treeRef, initializeWalker); const getTreeProps = React.useCallback( () => ({ @@ -181,7 +196,17 @@ export function useHeadlessFlatTree_unstable headlessTree.visibleItems(openItems), [openItems, headlessTree]); return React.useMemo>( - () => ({ navigate, getTreeProps, getNextNavigableItem, getElementFromItem, items }), + () => ({ + navigate: data => { + if (walkerRef.current) { + navigate(data, walkerRef.current); + } + }, + getTreeProps, + getNextNavigableItem, + getElementFromItem, + items, + }), [navigate, getTreeProps, getNextNavigableItem, getElementFromItem, items], ); } diff --git a/packages/react-components/react-tree/src/components/Tree/Tree.types.ts b/packages/react-components/react-tree/src/components/Tree/Tree.types.ts index 19ca60bab2cd88..8783a4bdeb6b47 100644 --- a/packages/react-components/react-tree/src/components/Tree/Tree.types.ts +++ b/packages/react-components/react-tree/src/components/Tree/Tree.types.ts @@ -31,6 +31,7 @@ export type TreeNavigationEvent_unstable = TreeNavigationData_unstable['event']; export type TreeOpenChangeData = { open: boolean; + openItems: Set; value: TreeItemValue; target: HTMLElement; } & ( @@ -45,6 +46,7 @@ export type TreeOpenChangeEvent = TreeOpenChangeData['event']; export type TreeCheckedChangeData = { value: TreeItemValue; + checkedItems: Map; target: HTMLElement; event: React.ChangeEvent; type: 'Change'; diff --git a/packages/react-components/react-tree/src/components/Tree/__snapshots__/Tree.test.tsx.snap b/packages/react-components/react-tree/src/components/Tree/__snapshots__/Tree.test.tsx.snap index 0a325e6d2f3767..09d301c5717540 100644 --- a/packages/react-components/react-tree/src/components/Tree/__snapshots__/Tree.test.tsx.snap +++ b/packages/react-components/react-tree/src/components/Tree/__snapshots__/Tree.test.tsx.snap @@ -4,7 +4,7 @@ exports[`Tree renders a default state 1`] = `
Default Tree
diff --git a/packages/react-components/react-tree/src/components/Tree/useTree.ts b/packages/react-components/react-tree/src/components/Tree/useTree.ts index be62cac02e6782..6c9906331e7e90 100644 --- a/packages/react-components/react-tree/src/components/Tree/useTree.ts +++ b/packages/react-components/react-tree/src/components/Tree/useTree.ts @@ -16,11 +16,23 @@ import { useControllableCheckedItems } from './useControllableCheckedItems'; import { useTreeContext_unstable } from '../../contexts/treeContext'; import { useRootTree } from '../../hooks/useRootTree'; import { useSubtree } from '../../hooks/useSubtree'; +import { HTMLElementWalker, createHTMLElementWalker } from '../../utils/createHTMLElementWalker'; +import { treeItemFilter } from '../../utils/treeItemFilter'; export const useTree_unstable = (props: TreeProps, ref: React.Ref): TreeState => { const [openItems, setOpenItems] = useControllableOpenItems(props); const [checkedItems] = useControllableCheckedItems(props); - const [navigate, navigationRef] = useTreeNavigation(); + const { navigate, initialize } = useTreeNavigation(); + const walkerRef = React.useRef(); + const initializeWalker = React.useCallback( + (root: HTMLElement | null) => { + if (root) { + walkerRef.current = createHTMLElementWalker(root, treeItemFilter); + initialize(walkerRef.current); + } + }, + [initialize], + ); const handleOpenChange = useEventCallback((event: TreeOpenChangeEvent, data: TreeOpenChangeData) => { props.onOpenChange?.(event, data); @@ -33,7 +45,9 @@ export const useTree_unstable = (props: TreeProps, ref: React.Ref): const handleNavigation = useEventCallback( (event: TreeNavigationEvent_unstable, data: TreeNavigationData_unstable) => { props.onNavigation_unstable?.(event, data); - navigate(data); + if (walkerRef.current) { + navigate(data, walkerRef.current); + } }, ); @@ -47,7 +61,7 @@ export const useTree_unstable = (props: TreeProps, ref: React.Ref): onCheckedChange: handleCheckedChange, }; - const baseRef = useMergedRefs(ref, navigationRef); + const baseRef = useMergedRefs(ref, initializeWalker); const isSubtree = useTreeContext_unstable(ctx => ctx.level > 0); // as isSubTree is static, this doesn't break rule of hooks diff --git a/packages/react-components/react-tree/src/components/Tree/useTreeNavigation.ts b/packages/react-components/react-tree/src/components/Tree/useTreeNavigation.ts index 317755ea6cac41..6436e2a4d668a3 100644 --- a/packages/react-components/react-tree/src/components/Tree/useTreeNavigation.ts +++ b/packages/react-components/react-tree/src/components/Tree/useTreeNavigation.ts @@ -1,20 +1,14 @@ -import { useMergedRefs } from '@fluentui/react-utilities'; import { TreeNavigationData_unstable } from './Tree.types'; import { nextTypeAheadElement } from '../../utils/nextTypeAheadElement'; import { treeDataTypes } from '../../utils/tokens'; import { treeItemFilter } from '../../utils/treeItemFilter'; import { useRovingTabIndex } from '../../hooks/useRovingTabIndexes'; -import { HTMLElementWalker, useHTMLElementWalkerRef } from '../../hooks/useHTMLElementWalker'; +import { HTMLElementWalker } from '../../utils/createHTMLElementWalker'; export function useTreeNavigation() { - const [{ rove }, rovingRootRef] = useRovingTabIndex(treeItemFilter); - const [walkerRef, rootRef] = useHTMLElementWalkerRef(treeItemFilter); + const { rove, initialize } = useRovingTabIndex(treeItemFilter); - const getNextElement = (data: TreeNavigationData_unstable) => { - if (!walkerRef.current) { - return; - } - const treeItemWalker = walkerRef.current; + const getNextElement = (data: TreeNavigationData_unstable, treeItemWalker: HTMLElementWalker) => { switch (data.type) { case treeDataTypes.Click: return data.target; @@ -41,13 +35,13 @@ export function useTreeNavigation() { return treeItemWalker.previousElement(); } }; - function navigate(data: TreeNavigationData_unstable) { - const nextElement = getNextElement(data); + function navigate(data: TreeNavigationData_unstable, walker: HTMLElementWalker) { + const nextElement = getNextElement(data, walker); if (nextElement) { rove(nextElement); } } - return [navigate, useMergedRefs(rootRef, rovingRootRef)] as const; + return { navigate, initialize } as const; } function lastChildRecursive(walker: HTMLElementWalker) { diff --git a/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx b/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx index 8eff75465b65aa..af3b56d89a7140 100644 --- a/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx +++ b/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx @@ -3,7 +3,7 @@ import { getNativeElementProps, useId, useMergedRefs } from '@fluentui/react-uti import { useEventCallback } from '@fluentui/react-utilities'; import { elementContains } from '@fluentui/react-portal'; import type { TreeItemProps, TreeItemState } from './TreeItem.types'; -import { useTreeContext_unstable } from '../../contexts/index'; +import { useTreeContext_unstable, useTreeItemContext_unstable } from '../../contexts/index'; import { dataTreeItemValueAttrName } from '../../utils/getTreeItemValueFromElement'; import { Space } from '@fluentui/keyboard-keys'; import { treeDataTypes } from '../../utils/tokens'; @@ -42,8 +42,14 @@ export function useTreeItem_unstable(props: TreeItemProps, ref: React.Ref(null); const open = useTreeContext_unstable(ctx => ctx.openItems.has(value)); - const checked = useTreeContext_unstable(ctx => ctx.checkedItems.get(value) ?? false); const selectionMode = useTreeContext_unstable(ctx => ctx.selectionMode); + const parentChecked = useTreeItemContext_unstable(ctx => ctx.checked); + const checked = useTreeContext_unstable(ctx => { + if (selectionMode === 'multiselect' && typeof parentChecked === 'boolean') { + return parentChecked; + } + return ctx.checkedItems.get(value); + }); const handleClick = useEventCallback((event: React.MouseEvent) => { onClick?.(event); @@ -133,13 +139,21 @@ export function useTreeItem_unstable(props: TreeItemProps, ref: React.Ref ctx.selectionRef); const expandIconRef = useTreeItemContext_unstable(ctx => ctx.expandIconRef); const actionsRef = useTreeItemContext_unstable(ctx => ctx.actionsRef); - const value = useTreeItemContext_unstable(ctx => ctx.value); - const checked = useTreeContext_unstable(ctx => ctx.checkedItems.get(value) ?? false); + const checked = useTreeItemContext_unstable(ctx => ctx.checked ?? false); const isBranch = useTreeItemContext_unstable(ctx => ctx.itemType === 'branch'); const expandIcon = resolveShorthand(props.expandIcon, { diff --git a/packages/react-components/react-tree/src/contexts/treeContext.ts b/packages/react-components/react-tree/src/contexts/treeContext.ts index d8a3bca4b35fd1..923a603210beb6 100644 --- a/packages/react-components/react-tree/src/contexts/treeContext.ts +++ b/packages/react-components/react-tree/src/contexts/treeContext.ts @@ -19,9 +19,9 @@ export type TreeContextValue = { }; export type TreeItemRequest = { itemType: TreeItemType } & ( - | OmitWithoutExpanding + | OmitWithoutExpanding | TreeNavigationData_unstable - | OmitWithoutExpanding + | OmitWithoutExpanding ); // helper type that avoids the expansion of unions while inferring it, should work exactly the same as Omit diff --git a/packages/react-components/react-tree/src/contexts/treeItemContext.ts b/packages/react-components/react-tree/src/contexts/treeItemContext.ts index 9e9aa07143a9eb..8dc467e07980ca 100644 --- a/packages/react-components/react-tree/src/contexts/treeItemContext.ts +++ b/packages/react-components/react-tree/src/contexts/treeItemContext.ts @@ -1,7 +1,8 @@ import * as React from 'react'; import { Context, ContextSelector, createContext, useContextSelector } from '@fluentui/react-context-selector'; import type { TreeItemType, TreeItemValue } from '../TreeItem'; -import { virtualTreeRootId } from '../utils/createHeadlessTree'; +import { headlessTreeRootId } from '../utils/createHeadlessTree'; +import { TreeSelectionValue } from '../Tree'; export type TreeItemContextValue = { isActionsVisible: boolean; @@ -14,10 +15,11 @@ export type TreeItemContextValue = { itemType: TreeItemType; value: TreeItemValue; open: boolean; + checked?: TreeSelectionValue; }; const defaultContextValue: TreeItemContextValue = { - value: virtualTreeRootId, + value: headlessTreeRootId, selectionRef: React.createRef(), layoutRef: React.createRef(), subtreeRef: React.createRef(), @@ -27,6 +29,7 @@ const defaultContextValue: TreeItemContextValue = { isAsideVisible: false, itemType: 'leaf', open: false, + checked: undefined, }; export const TreeItemContext: Context = createContext< diff --git a/packages/react-components/react-tree/src/hooks/useRootTree.ts b/packages/react-components/react-tree/src/hooks/useRootTree.ts index d3ec2bfb4c28a1..a813e4f8c26b09 100644 --- a/packages/react-components/react-tree/src/hooks/useRootTree.ts +++ b/packages/react-components/react-tree/src/hooks/useRootTree.ts @@ -14,10 +14,10 @@ import { createCheckedItems } from '../utils/createCheckedItems'; import { treeDataTypes } from '../utils/tokens'; /** - * Create the state required to render the root level BaseTree. + * Create the state required to render the root level tree. * - * @param props - props from this instance of BaseTree - * @param ref - reference to root HTMLElement of BaseTree + * @param props - props from this instance of tree + * @param ref - reference to root HTMLElement of tree */ export function useRootTree( props: Pick< @@ -56,7 +56,11 @@ export function useRootTree( case treeDataTypes.Click: case treeDataTypes.ExpandIconClick: { return ReactDOM.unstable_batchedUpdates(() => { - requestOpenChange({ ...request, open: request.itemType === 'branch' && !openItems.has(request.value) }); + requestOpenChange({ + ...request, + open: request.itemType === 'branch' && !openItems.has(request.value), + openItems: openItems.dangerouslyGetInternalSet_unstable(), + }); requestNavigation({ ...request, type: treeDataTypes.Click }); }); } @@ -66,18 +70,31 @@ export function useRootTree( } const open = openItems.has(request.value); if (!open) { - return requestOpenChange({ ...request, open: true }); + return requestOpenChange({ + ...request, + open: true, + openItems: openItems.dangerouslyGetInternalSet_unstable(), + }); } return requestNavigation(request); } case treeDataTypes.Enter: { const open = openItems.has(request.value); - return requestOpenChange({ ...request, open: request.itemType === 'branch' && !open }); + return requestOpenChange({ + ...request, + open: request.itemType === 'branch' && !open, + openItems: openItems.dangerouslyGetInternalSet_unstable(), + }); } case treeDataTypes.ArrowLeft: { const open = openItems.has(request.value); if (open && request.itemType === 'branch') { - return requestOpenChange({ ...request, open: false, type: treeDataTypes.ArrowLeft }); + return requestOpenChange({ + ...request, + open: false, + type: treeDataTypes.ArrowLeft, + openItems: openItems.dangerouslyGetInternalSet_unstable(), + }); } return requestNavigation({ ...request, type: treeDataTypes.ArrowLeft }); } @@ -88,12 +105,11 @@ export function useRootTree( case treeDataTypes.TypeAhead: return requestNavigation({ ...request, target: request.event.currentTarget }); case treeDataTypes.Change: { - const previousCheckedValue = checkedItems.get(request.value); return requestCheckedChange({ ...request, selectionMode: selectionMode as SelectionMode, - checked: previousCheckedValue === 'mixed' ? true : !previousCheckedValue, - }); + checkedItems: checkedItems.dangerouslyGetInternalMap_unstable(), + } as TreeCheckedChangeData); } } }); @@ -112,7 +128,7 @@ export function useRootTree( requestTreeResponse, root: getNativeElementProps('div', { ref, - role: 'baseTree', + role: 'tree', 'aria-multiselectable': selectionMode === 'multiselect' ? true : undefined, ...props, }), @@ -123,7 +139,7 @@ function warnIfNoProperPropsRootTree(props: Pick(); - const [walkerRef, rootRef] = useHTMLElementWalkerRef(filter); - const rootRefCallback = (element?: HTMLElement) => { - if (!element) { - return; - } - reset(); - }; - - function reset() { - if (!walkerRef.current) { - return; - } - const walker = walkerRef.current; + const initialize = React.useCallback((walker: HTMLElementWalker) => { walker.currentElement = walker.root; let tabbableChild = walker.firstChild(element => element.tabIndex === 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP, @@ -36,8 +23,8 @@ export function useRovingTabIndex(filter?: HTMLElementFilter) { while ((nextElement = walker.nextElement()) && nextElement !== tabbableChild) { nextElement.tabIndex = -1; } - } - function rove(nextElement: HTMLElement) { + }, []); + const rove = React.useCallback((nextElement: HTMLElement) => { if (!currentElementRef.current) { return; } @@ -45,13 +32,10 @@ export function useRovingTabIndex(filter?: HTMLElementFilter) { nextElement.tabIndex = 0; nextElement.focus(); currentElementRef.current = nextElement; - } + }, []); - return [ - { - rove, - reset, - }, - useMergedRefs(rootRef, rootRefCallback) as React.Ref, - ] as const; + return { + rove, + initialize, + }; } diff --git a/packages/react-components/react-tree/src/hooks/useSubtree.ts b/packages/react-components/react-tree/src/hooks/useSubtree.ts index e6a947703d0580..3bda2ff5163017 100644 --- a/packages/react-components/react-tree/src/hooks/useSubtree.ts +++ b/packages/react-components/react-tree/src/hooks/useSubtree.ts @@ -4,10 +4,10 @@ import { useTreeContext_unstable, useTreeItemContext_unstable } from '../context import { getNativeElementProps, useMergedRefs } from '@fluentui/react-utilities'; /** - * Create the state required to render a sub-level BaseTree. + * Create the state required to render a sub-level tree. * - * @param props - props from this instance of BaseTree - * @param ref - reference to root HTMLElement of BaseTree + * @param props - props from this instance of tree + * @param ref - reference to root HTMLElement of tree */ export function useSubtree(props: Pick, ref: React.Ref): TreeState { const contextAppearance = useTreeContext_unstable(ctx => ctx.appearance); diff --git a/packages/react-components/react-tree/src/hooks/useHTMLElementWalker.ts b/packages/react-components/react-tree/src/utils/createHTMLElementWalker.ts similarity index 85% rename from packages/react-components/react-tree/src/hooks/useHTMLElementWalker.ts rename to packages/react-components/react-tree/src/utils/createHTMLElementWalker.ts index 8f42ea477a4f72..bbd3a2ff35b19a 100644 --- a/packages/react-components/react-tree/src/hooks/useHTMLElementWalker.ts +++ b/packages/react-components/react-tree/src/utils/createHTMLElementWalker.ts @@ -1,5 +1,4 @@ import { isHTMLElement } from '@fluentui/react-utilities'; -import * as React from 'react'; export interface HTMLElementWalker { readonly root: HTMLElement; @@ -83,16 +82,3 @@ export function createHTMLElementWalker( }, }; } - -export const useHTMLElementWalkerRef = (filter?: HTMLElementFilter) => { - const walkerRef = React.useRef(); - - const rootRefCallback = (element?: HTMLElement) => { - if (!element) { - walkerRef.current = undefined; - return; - } - walkerRef.current = createHTMLElementWalker(element, filter); - }; - return [walkerRef as React.RefObject, rootRefCallback as React.Ref] as const; -}; diff --git a/packages/react-components/react-tree/src/utils/createHeadlessTree.ts b/packages/react-components/react-tree/src/utils/createHeadlessTree.ts index e18ad358357f1a..bcf6405ee51f9b 100644 --- a/packages/react-components/react-tree/src/utils/createHeadlessTree.ts +++ b/packages/react-components/react-tree/src/utils/createHeadlessTree.ts @@ -104,7 +104,7 @@ export function createHeadlessTree( get: key => itemsPerValue.get(key), has: key => itemsPerValue.has(key), add(props) { - const { parentValue = virtualTreeRootId, ...propsWithoutParentValue } = props; + const { parentValue = headlessTreeRootId, ...propsWithoutParentValue } = props; const parentItem = itemsPerValue.get(parentValue); if (!parentItem) { if (process.env.NODE_ENV === 'development') { @@ -169,12 +169,12 @@ export function createHeadlessTree( return headlessTree as HeadlessTree; } -export const virtualTreeRootId = '__fuiHeadlessTreeRoot'; +export const headlessTreeRootId = '__fuiHeadlessTreeRoot'; function createHeadlessTreeRootItem(): HeadlessTreeItem { return { parentValue: undefined, - value: virtualTreeRootId, + value: headlessTreeRootId, itemType: 'branch', getTreeItemProps: () => { if (process.env.NODE_ENV !== 'production') { @@ -182,8 +182,8 @@ function createHeadlessTreeRootItem(): HeadlessTreeItem { console.error('HeadlessTree: internal error, trying to access treeitem props from invalid root element'); } return { - id: virtualTreeRootId, - value: virtualTreeRootId, + id: headlessTreeRootId, + value: headlessTreeRootId, 'aria-setsize': -1, 'aria-level': -1, 'aria-posinset': -1, @@ -223,11 +223,8 @@ function* HeadlessTreeSubtreeGenerator( return; } for (const childValue of item.childrenValues) { - const child = virtualTreeItems.get(childValue)!; - yield child; - if (child.childrenValues.length > 0) { - yield* HeadlessTreeSubtreeGenerator(childValue, virtualTreeItems); - } + yield virtualTreeItems.get(childValue)!; + yield* HeadlessTreeSubtreeGenerator(childValue, virtualTreeItems); } } diff --git a/packages/react-components/react-tree/src/utils/nextTypeAheadElement.ts b/packages/react-components/react-tree/src/utils/nextTypeAheadElement.ts index 695f5d7107432e..03f0b4f61a092f 100644 --- a/packages/react-components/react-tree/src/utils/nextTypeAheadElement.ts +++ b/packages/react-components/react-tree/src/utils/nextTypeAheadElement.ts @@ -1,4 +1,4 @@ -import { HTMLElementWalker } from '../hooks/useHTMLElementWalker'; +import { HTMLElementWalker } from './createHTMLElementWalker'; export function nextTypeAheadElement(treeWalker: HTMLElementWalker, key: string) { const keyToLowerCase = key.toLowerCase();