From 45be51cfc829063d58fa7ef815dd5b01a0786419 Mon Sep 17 00:00:00 2001 From: Laura Silva <91160746+silvalaura@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:23:20 -0400 Subject: [PATCH] fix(TreeView): Fix undefined error (#1519) --- .changeset/treeview-undefined.md | 5 + .../src/components/TreeView/TreeItem.test.js | 7 +- .../src/components/TreeView/TreeItem.tsx | 8 +- .../components/TreeView/TreeView.stories.tsx | 109 ++- .../src/components/TreeView/TreeView.tsx | 5 +- .../components/TreeView/TreeViewContext.ts | 10 +- .../src/components/TreeView/useTreeItem.ts | 87 ++- .../src/components/TreeView/useTreeView.ts | 230 +++++-- .../src/components/TreeView/utils.ts | 630 +++++++++++++----- 9 files changed, 800 insertions(+), 291 deletions(-) create mode 100644 .changeset/treeview-undefined.md diff --git a/.changeset/treeview-undefined.md b/.changeset/treeview-undefined.md new file mode 100644 index 0000000000..f0fb94916c --- /dev/null +++ b/.changeset/treeview-undefined.md @@ -0,0 +1,5 @@ +--- +'react-magma-dom': patch +--- + +fix(TreeView): Fix undefined problem diff --git a/packages/react-magma-dom/src/components/TreeView/TreeItem.test.js b/packages/react-magma-dom/src/components/TreeView/TreeItem.test.js index 03bd5ec9b6..c3620ae73e 100644 --- a/packages/react-magma-dom/src/components/TreeView/TreeItem.test.js +++ b/packages/react-magma-dom/src/components/TreeView/TreeItem.test.js @@ -94,7 +94,12 @@ describe('TreeItem', () => { it('the ability to expand the item is disabled', () => { const { getByTestId } = render( - + ( props, forwardedRef ); - - const { isDisabled} = contextValue; + + const { isDisabled } = contextValue; const { checkboxChangeHandler, @@ -398,7 +398,9 @@ export const TreeItem = React.forwardRef( })} - ) : child; + ) : ( + child + ); } )} diff --git a/packages/react-magma-dom/src/components/TreeView/TreeView.stories.tsx b/packages/react-magma-dom/src/components/TreeView/TreeView.stories.tsx index d4ae5c4342..ebc76c95c2 100644 --- a/packages/react-magma-dom/src/components/TreeView/TreeView.stories.tsx +++ b/packages/react-magma-dom/src/components/TreeView/TreeView.stories.tsx @@ -98,12 +98,24 @@ function createTags(items: TreeItemSelectedInterface[]) { }; } - -function createControlledTags(items: TreeItemSelectedInterface[] = [], api?: TreeViewApi) { +function createControlledTags( + items: TreeItemSelectedInterface[] = [], + api?: TreeViewApi +) { const selected = items ?.filter(i => i.checkedStatus === IndeterminateCheckboxStatus.checked) .map((i, key) => ( - api?.selectItem({ itemId: i.itemId, checkedStatus: IndeterminateCheckboxStatus.unchecked })}> + + api?.selectItem({ + itemId: i.itemId, + checkedStatus: IndeterminateCheckboxStatus.unchecked, + }) + } + > {i.itemId} )); @@ -111,7 +123,17 @@ function createControlledTags(items: TreeItemSelectedInterface[] = [], api?: Tre const indeterminate = items ?.filter(i => i.checkedStatus === IndeterminateCheckboxStatus.indeterminate) .map((i, key) => ( - api?.selectItem({ itemId: i.itemId, checkedStatus: IndeterminateCheckboxStatus.unchecked })}> + + api?.selectItem({ + itemId: i.itemId, + checkedStatus: IndeterminateCheckboxStatus.unchecked, + }) + } + > {i.itemId} )); @@ -123,8 +145,12 @@ function createControlledTags(items: TreeItemSelectedInterface[] = [], api?: Tre } export const Simple = (args: Partial) => { - const [selectedItems, setSelectedItems] = React.useState(null); - const [indeterminateItems, setIndeterminateItems] = React.useState(null); + const [selectedItems, setSelectedItems] = React.useState< + React.ReactNode[] | null + >(null); + const [indeterminateItems, setIndeterminateItems] = React.useState< + React.ReactNode[] | null + >(null); const [total, setTotal] = React.useState(selectedItems?.length || 0); function onSelection(items: TreeItemSelectedInterface[]) { @@ -172,17 +198,25 @@ export const Simple = (args: Partial) => { Simple.parameters = { controls: { exclude: ['isInverse'] } }; export const Complex = (args: Partial) => { - const [selectedItems, setSelectedItems] = React.useState(); + const [selectedItems, setSelectedItems] = + React.useState(); const apiRef = React.useRef(); - const { selected, indeterminate } = createControlledTags(selectedItems, apiRef?.current); + const { selected, indeterminate } = createControlledTags( + selectedItems, + apiRef?.current + ); const total = selectedItems?.length || 0; return ( <> - + Part 1: Introduction} itemId="pt1" testId="pt1"> } @@ -417,7 +451,10 @@ Complex.args = { { itemId: 'pt1ch1', checkedStatus: IndeterminateCheckboxStatus.checked }, { itemId: 'pt1', checkedStatus: IndeterminateCheckboxStatus.indeterminate }, { itemId: 'pt2ch4', checkedStatus: IndeterminateCheckboxStatus.checked }, - { itemId: 'pt2ch5.1.1', checkedStatus: IndeterminateCheckboxStatus.checked }, + { + itemId: 'pt2ch5.1.1', + checkedStatus: IndeterminateCheckboxStatus.checked, + }, { itemId: 'pt2ch5.1.2', checkedStatus: IndeterminateCheckboxStatus.unchecked, @@ -433,8 +470,12 @@ Complex.args = { }; export const NoIcons = (args: Partial) => { - const [selectedItems, setSelectedItems] = React.useState(null); - const [indeterminateItems, setIndeterminateItems] = React.useState(null); + const [selectedItems, setSelectedItems] = React.useState< + React.ReactNode[] | null + >(null); + const [indeterminateItems, setIndeterminateItems] = React.useState< + React.ReactNode[] | null + >(null); function onSelection(items: TreeItemSelectedInterface[]) { const selected = createTags(items).selected; @@ -504,8 +545,12 @@ NoIcons.args = { }; export const Textbook = (args: Partial) => { - const [selectedItems, setSelectedItems] = React.useState(null); - const [indeterminateItems, setIndeterminateItems] = React.useState(null); + const [selectedItems, setSelectedItems] = React.useState< + React.ReactNode[] | null + >(null); + const [indeterminateItems, setIndeterminateItems] = React.useState< + React.ReactNode[] | null + >(null); const [total, setTotal] = React.useState(selectedItems?.length || 0); function onSelection(items: TreeItemSelectedInterface[]) { @@ -603,8 +648,12 @@ Textbook.args = { }; export const DefaultIcon = (args: Partial) => { - const [selectedItems, setSelectedItems] = React.useState(null); - const [indeterminateItems, setIndeterminateItems] = React.useState(null); + const [selectedItems, setSelectedItems] = React.useState< + React.ReactNode[] | null + >(null); + const [indeterminateItems, setIndeterminateItems] = React.useState< + React.ReactNode[] | null + >(null); function onSelection(items: TreeItemSelectedInterface[]) { const selected = createTags(items).selected; @@ -665,8 +714,12 @@ export const DefaultIcon = (args: Partial) => { DefaultIcon.parameters = { controls: { exclude: ['isInverse'] } }; export const FirstItemLeaf = (args: Partial) => { - const [selectedItems, setSelectedItems] = React.useState(null); - const [indeterminateItems, setIndeterminateItems] = React.useState(null); + const [selectedItems, setSelectedItems] = React.useState< + React.ReactNode[] | null + >(null); + const [indeterminateItems, setIndeterminateItems] = React.useState< + React.ReactNode[] | null + >(null); function onSelection(items: TreeItemSelectedInterface[]) { const selected = createTags(items).selected; @@ -723,8 +776,12 @@ FirstItemLeaf.args = { FirstItemLeaf.parameters = { controls: { exclude: ['isInverse'] } }; export const FirstItemBranch = (args: Partial) => { - const [selectedItems, setSelectedItems] = React.useState(null); - const [indeterminateItems, setIndeterminateItems] = React.useState(null); + const [selectedItems, setSelectedItems] = React.useState< + React.ReactNode[] | null + >(null); + const [indeterminateItems, setIndeterminateItems] = React.useState< + React.ReactNode[] | null + >(null); const [total, setTotal] = React.useState(selectedItems?.length || 0); function onSelection(items: TreeItemSelectedInterface[]) { @@ -854,9 +911,15 @@ FlatTree.args = { FlatTree.parameters = { controls: { exclude: ['isInverse'] } }; -export const ParentsAndChildrenNotAutoChecked = (args: Partial) => { - const [selectedItems, setSelectedItems] = React.useState(null); - const [indeterminateItems, setIndeterminateItems] = React.useState(null); +export const ParentsAndChildrenNotAutoChecked = ( + args: Partial +) => { + const [selectedItems, setSelectedItems] = React.useState< + React.ReactNode[] | null + >(null); + const [indeterminateItems, setIndeterminateItems] = React.useState< + React.ReactNode[] | null + >(null); function onSelection(items: TreeItemSelectedInterface[]) { const selected = createTags(items).selected; diff --git a/packages/react-magma-dom/src/components/TreeView/TreeView.tsx b/packages/react-magma-dom/src/components/TreeView/TreeView.tsx index de5d8154a1..745789bd0c 100644 --- a/packages/react-magma-dom/src/components/TreeView/TreeView.tsx +++ b/packages/react-magma-dom/src/components/TreeView/TreeView.tsx @@ -1,8 +1,5 @@ import * as React from 'react'; -import { - UseTreeViewProps, - useTreeView, -} from './useTreeView'; +import { UseTreeViewProps, useTreeView } from './useTreeView'; import { TreeViewSelectable } from './types'; import { TreeItem } from './TreeItem'; import { ThemeContext } from '../../theme/ThemeContext'; diff --git a/packages/react-magma-dom/src/components/TreeView/TreeViewContext.ts b/packages/react-magma-dom/src/components/TreeView/TreeViewContext.ts index 0540590391..248bce272c 100644 --- a/packages/react-magma-dom/src/components/TreeView/TreeViewContext.ts +++ b/packages/react-magma-dom/src/components/TreeView/TreeViewContext.ts @@ -13,8 +13,8 @@ export interface TreeViewItemInterface { parentId?: string | null; icon?: React.ReactNode; checkedStatus: IndeterminateCheckboxStatus; - hasOwnTreeItems: boolean - isDisabled?: boolean + hasOwnTreeItems: boolean; + isDisabled?: boolean; } export interface TreeViewContextInterface { @@ -38,7 +38,9 @@ export interface TreeViewContextInterface { checkParents: boolean; checkChildren: boolean; items: TreeViewItemInterface[]; - selectItem: (data: Pick) => void + selectItem: ( + data: Pick + ) => void; } export const TreeViewContext = React.createContext({ @@ -52,5 +54,5 @@ export const TreeViewContext = React.createContext({ checkParents: true, checkChildren: true, items: [], - selectItem: () => undefined + selectItem: () => undefined, }); diff --git a/packages/react-magma-dom/src/components/TreeView/useTreeItem.ts b/packages/react-magma-dom/src/components/TreeView/useTreeItem.ts index 438f64b0a5..41768dc999 100644 --- a/packages/react-magma-dom/src/components/TreeView/useTreeItem.ts +++ b/packages/react-magma-dom/src/components/TreeView/useTreeItem.ts @@ -62,14 +62,7 @@ export const checkedStatusToBoolean = ( ): boolean => status === IndeterminateCheckboxStatus.checked; export function useTreeItem(props: UseTreeItemProps, forwardedRef) { - const { - children, - itemDepth, - itemId, - onClick, - parentDepth, - topLevel, - } = props; + const { children, itemDepth, itemId, onClick, parentDepth, topLevel } = props; const { initialExpandedItems, @@ -81,20 +74,22 @@ export function useTreeItem(props: UseTreeItemProps, forwardedRef) { items, selectItem, } = React.useContext(TreeViewContext); - + const treeViewItemData = React.useMemo(() => { - return items.find((item) => item.itemId === itemId) - }, [itemId, items]) + return items.find(item => item.itemId === itemId); + }, [itemId, items]); const isDisabled = treeViewItemData?.isDisabled; - + const checkedStatus = React.useMemo(() => { - return treeViewItemData?.checkedStatus ?? IndeterminateCheckboxStatus.unchecked - }, [treeViewItemData]) + return ( + treeViewItemData?.checkedStatus ?? IndeterminateCheckboxStatus.unchecked + ); + }, [treeViewItemData]); const hasOwnTreeItems = React.useMemo(() => { - return treeViewItemData?.hasOwnTreeItems - }, [treeViewItemData]) + return treeViewItemData?.hasOwnTreeItems; + }, [treeViewItemData]); const [expanded, setExpanded] = React.useState(false); @@ -150,7 +145,12 @@ export function useTreeItem(props: UseTreeItemProps, forwardedRef) { } if (selectable !== TreeViewSelectable.off) { - selectItem({ itemId, checkedStatus: isChecked ? IndeterminateCheckboxStatus.unchecked : IndeterminateCheckboxStatus.checked }) + selectItem({ + itemId, + checkedStatus: isChecked + ? IndeterminateCheckboxStatus.unchecked + : IndeterminateCheckboxStatus.checked, + }); onClick && typeof onClick === 'function' && onClick(); } }; @@ -181,7 +181,9 @@ export function useTreeItem(props: UseTreeItemProps, forwardedRef) { const filteredRefArray = filterNullEntries(treeItemRefArray); const curr = filteredRefArray['current']; - (curr?.[0].current as HTMLDivElement).closest('[role=treeitem]').focus(); + (curr?.[0].current as HTMLDivElement) + .closest('[role=treeitem]') + .focus(); }; const focusNext = () => { @@ -233,9 +235,9 @@ export function useTreeItem(props: UseTreeItemProps, forwardedRef) { const filteredRefArray = filterNullEntries(treeItemRefArray); const arrLength = filteredRefArray['current'].length; - ( - filteredRefArray['current']?.[arrLength - 1].current as HTMLDivElement - ).closest('[role=treeitem]').focus(); + (filteredRefArray['current']?.[arrLength - 1].current as HTMLDivElement) + .closest('[role=treeitem]') + .focus(); }; const focusSelf = () => { @@ -243,7 +245,9 @@ export function useTreeItem(props: UseTreeItemProps, forwardedRef) { const curr = filteredRefArray['current']; focusIndex = getFocusIndex(curr); - (curr?.[focusIndex].current as HTMLDivElement).closest('[role=treeitem]').focus(); + (curr?.[focusIndex].current as HTMLDivElement) + .closest('[role=treeitem]') + .focus(); }; const expandFocusedNode = () => { @@ -275,7 +279,18 @@ export function useTreeItem(props: UseTreeItemProps, forwardedRef) { const curr = filteredRefArray['current']; const arrLength = curr.length; - if (['ArrowDown', 'ArrowUp', 'ArrowRight', 'ArrowLeft', 'Home', 'End', 'Enter', ' '].includes(event.key)) { + if ( + [ + 'ArrowDown', + 'ArrowUp', + 'ArrowRight', + 'ArrowLeft', + 'Home', + 'End', + 'Enter', + ' ', + ].includes(event.key) + ) { event.preventDefault(); event.stopPropagation(); } @@ -322,10 +337,19 @@ export function useTreeItem(props: UseTreeItemProps, forwardedRef) { return; } // In single-select it selects the focused node. - selectItem({ itemId, checkedStatus: IndeterminateCheckboxStatus.checked }) + selectItem({ + itemId, + checkedStatus: IndeterminateCheckboxStatus.checked, + }); } else if (selectable === TreeViewSelectable.multi) { // In multi-select, it toggles the selection state of the focused node. - selectItem({ itemId, checkedStatus: checkedStatus === IndeterminateCheckboxStatus.checked ? IndeterminateCheckboxStatus.unchecked : IndeterminateCheckboxStatus.checked }) + selectItem({ + itemId, + checkedStatus: + checkedStatus === IndeterminateCheckboxStatus.checked + ? IndeterminateCheckboxStatus.unchecked + : IndeterminateCheckboxStatus.checked, + }); } break; } @@ -340,10 +364,19 @@ export function useTreeItem(props: UseTreeItemProps, forwardedRef) { if (isChecked) { return; } - selectItem({ itemId, checkedStatus: IndeterminateCheckboxStatus.checked }); + selectItem({ + itemId, + checkedStatus: IndeterminateCheckboxStatus.checked, + }); } } else if (selectable === TreeViewSelectable.multi) { - selectItem({ itemId, checkedStatus: checkedStatus === IndeterminateCheckboxStatus.checked ? IndeterminateCheckboxStatus.unchecked : IndeterminateCheckboxStatus.checked }); + selectItem({ + itemId, + checkedStatus: + checkedStatus === IndeterminateCheckboxStatus.checked + ? IndeterminateCheckboxStatus.unchecked + : IndeterminateCheckboxStatus.checked, + }); } break; } diff --git a/packages/react-magma-dom/src/components/TreeView/useTreeView.ts b/packages/react-magma-dom/src/components/TreeView/useTreeView.ts index 1062625278..61d976b873 100644 --- a/packages/react-magma-dom/src/components/TreeView/useTreeView.ts +++ b/packages/react-magma-dom/src/components/TreeView/useTreeView.ts @@ -1,6 +1,9 @@ import * as React from 'react'; import { useDescendants } from '../../hooks/useDescendants'; -import { TreeItemSelectedInterface, TreeViewItemInterface } from './TreeViewContext'; +import { + TreeItemSelectedInterface, + TreeViewItemInterface, +} from './TreeViewContext'; import { getInitialExpandedIds, getInitialItems, @@ -16,7 +19,10 @@ import { IndeterminateCheckboxStatus } from '../IndeterminateCheckbox'; export { TreeItemSelectedInterface }; export interface TreeViewApi { - selectItem({ itemId, checkedStatus }: Pick): void; + selectItem({ + itemId, + checkedStatus, + }: Pick): void; selectAll(): void; clearAll(): void; } @@ -80,15 +86,15 @@ export interface UseTreeViewProps { * @default true */ checkChildren?: boolean; - children?: React.ReactNode[]; + children?: React.ReactNode[] | React.ReactNode; /** * The ref object that allows TreeView manipulation. * Actions available: - * selectItem({ itemId, checkedStatus }: Pick): void - action that allows to change item selection, - * selectAll(): void - action that allows to select all items, + * selectItem({ itemId, checkedStatus }: Pick): void - action that allows to change item selection, + * selectAll(): void - action that allows to select all items, * clearAll(): void - action that allows to unselect all items. */ - apiRef?: React.MutableRefObject, + apiRef?: React.MutableRefObject; /** * If true, every item is disabled * @default false @@ -112,34 +118,61 @@ export function useTreeView(props: UseTreeViewProps) { const hasPreselectedItems = Boolean(preselectedItems); - const [items, setItems] = React.useState( - () => getInitialItems({ children, preselectedItems, checkParents, checkChildren, selectable, isDisabled }) + const [items, setItems] = React.useState(() => + getInitialItems({ + children, + preselectedItems, + checkParents, + checkChildren, + selectable, + isDisabled, + }) ); - const [hasIcons] = React.useState(() => { - const initialItems = getInitialItems({ children, preselectedItems, checkParents, checkChildren, selectable, isDisabled }) + const [hasIcons] = React.useState(() => { + const initialItems = getInitialItems({ + children, + preselectedItems, + checkParents, + checkChildren, + selectable, + isDisabled, + }); - return initialItems.some((item) => item.icon); + return initialItems.some(item => item.icon); }); - + const selectedItems = React.useMemo(() => { - return items.filter((item) => item.checkedStatus === IndeterminateCheckboxStatus.checked) + return items.filter( + item => item.checkedStatus === IndeterminateCheckboxStatus.checked + ); }, [items]); const initialExpandedItems = React.useMemo(() => { - return getInitialExpandedIds({ items, initialExpandedItems: rawInitialExpandedItems }) - }, [items, rawInitialExpandedItems]); + return getInitialExpandedIds({ + items, + initialExpandedItems: rawInitialExpandedItems, + }); + }, [items, rawInitialExpandedItems]); const itemToFocus = React.useMemo(() => { - const enabledItems = items.filter((item) => !item.isDisabled); + const enabledItems = items.filter(item => !item.isDisabled); const [firstItem] = enabledItems; if (selectable === TreeViewSelectable.off) { - const firstExpandableItem = enabledItems.find((item) => item.hasOwnTreeItems) + const firstExpandableItem = enabledItems.find( + item => item.hasOwnTreeItems + ); - return firstExpandableItem ? firstExpandableItem.itemId : firstItem?.itemId; + return firstExpandableItem + ? firstExpandableItem.itemId + : firstItem?.itemId; } - const firstNonUncheckedItem = enabledItems.find((item) => item.checkedStatus && item.checkedStatus !== IndeterminateCheckboxStatus.unchecked) + const firstNonUncheckedItem = enabledItems.find( + item => + item.checkedStatus && + item.checkedStatus !== IndeterminateCheckboxStatus.unchecked + ); if (firstNonUncheckedItem) { return firstNonUncheckedItem.itemId; @@ -147,7 +180,7 @@ export function useTreeView(props: UseTreeViewProps) { return firstItem?.itemId; }, [items]); - + const prevSelectedItemsRef = React.useRef(null); const prevPreselectedItemsRef = React.useRef(preselectedItems); @@ -158,105 +191,164 @@ export function useTreeView(props: UseTreeViewProps) { return; } - setItems(getInitialItems({ children, preselectedItems, checkParents, checkChildren, selectable, isDisabled })); + setItems( + getInitialItems({ + children, + preselectedItems, + checkParents, + checkChildren, + selectable, + isDisabled, + }) + ); prevPreselectedItemsRef.current = preselectedItems; - }, [preselectedItems, checkParents, checkChildren, selectable, isDisabled]) - + }, [preselectedItems, checkParents, checkChildren, selectable, isDisabled]); + React.useEffect(() => { if (initializationRef.current) { return; } - const itemsWithUpdatedDisabledState = getInitialItems({ children, preselectedItems, checkParents, checkChildren, selectable, isDisabled }); + const itemsWithUpdatedDisabledState = getInitialItems({ + children, + preselectedItems, + checkParents, + checkChildren, + selectable, + isDisabled, + }); - setItems((prevItems) => { + setItems(prevItems => { return prevItems.map(prevItem => { - const itemWithUpdatedDisabledState = itemsWithUpdatedDisabledState.find((item) => item.itemId === prevItem.itemId); + const itemWithUpdatedDisabledState = itemsWithUpdatedDisabledState.find( + item => item.itemId === prevItem.itemId + ); if (itemWithUpdatedDisabledState?.isDisabled === prevItem.isDisabled) { return prevItem; } - return { ...prevItem, isDisabled: itemWithUpdatedDisabledState.isDisabled }; - }) - }) - }, [isDisabled]) + return { + ...prevItem, + isDisabled: itemWithUpdatedDisabledState.isDisabled, + }; + }); + }); + }, [isDisabled]); React.useEffect(() => { const isInitialization = initializationRef.current; - + initializationRef.current = false; if (isInitialization && !hasPreselectedItems) { return; } - + if (selectable === TreeViewSelectable.off) { - return + return; } const nextSelectedItems = items - .filter(({ checkedStatus }) => checkedStatus && checkedStatus !== IndeterminateCheckboxStatus.unchecked) - .map(({ itemId, checkedStatus }) => ({ itemId, checkedStatus })) - - if (!isSelectedItemsChanged(prevSelectedItemsRef.current, nextSelectedItems)) { + .filter( + ({ checkedStatus }) => + checkedStatus && + checkedStatus !== IndeterminateCheckboxStatus.unchecked + ) + .map(({ itemId, checkedStatus }) => ({ itemId, checkedStatus })); + + if ( + !isSelectedItemsChanged(prevSelectedItemsRef.current, nextSelectedItems) + ) { return; } prevSelectedItemsRef.current = nextSelectedItems; - onSelectedItemChange && onSelectedItemChange(nextSelectedItems) - }, [items, selectable, hasPreselectedItems]) - - const selectItem = React.useCallback(({ itemId, checkedStatus }: Pick & Partial>) => { - if(selectable === TreeViewSelectable.off) { - return; - } - - const item = items.find((item) => item.itemId === itemId); + onSelectedItemChange && onSelectedItemChange(nextSelectedItems); + }, [items, selectable, hasPreselectedItems]); + + const selectItem = React.useCallback( + ({ + itemId, + checkedStatus, + }: Pick & + Partial>) => { + if (selectable === TreeViewSelectable.off) { + return; + } - if (item?.isDisabled) { - return; - } + const item = items.find(item => item.itemId === itemId); - setItems(prevItems => { - if(selectable === TreeViewSelectable.single) { - return selectSingle({ items: prevItems, itemId, checkedStatus: checkedStatus ?? IndeterminateCheckboxStatus.checked }); - } - - if(selectable === TreeViewSelectable.multi) { - return toggleMulti({ items: prevItems, itemId, checkedStatus, checkChildren, checkParents }); + if (item?.isDisabled) { + return; } - - return prevItems; - }); - }, [selectable, checkChildren, checkParents, items]) + + setItems(prevItems => { + if (selectable === TreeViewSelectable.single) { + return selectSingle({ + items: prevItems, + itemId, + checkedStatus: checkedStatus ?? IndeterminateCheckboxStatus.checked, + }); + } + + if (selectable === TreeViewSelectable.multi) { + return toggleMulti({ + items: prevItems, + itemId, + checkedStatus, + checkChildren, + checkParents, + }); + } + + return prevItems; + }); + }, + [selectable, checkChildren, checkParents, items] + ); React.useEffect(() => { if (apiRef) { apiRef.current = { selectItem, selectAll() { - if ([TreeViewSelectable.single, TreeViewSelectable.single].includes(selectable) || isDisabled) { + if ( + [TreeViewSelectable.single, TreeViewSelectable.single].includes( + selectable + ) || + isDisabled + ) { return; } setItems(prevItems => { - return toggleAllMulti({ items, checkedStatus: IndeterminateCheckboxStatus.checked, checkChildren, checkParents }); - }) + return toggleAllMulti({ + items, + checkedStatus: IndeterminateCheckboxStatus.checked, + checkChildren, + checkParents, + }); + }); }, - + clearAll() { if (isDisabled) { return; } setItems(prevItems => { - return toggleAllMulti({ items, checkedStatus: IndeterminateCheckboxStatus.unchecked, checkChildren, checkParents }) - }) + return toggleAllMulti({ + items, + checkedStatus: IndeterminateCheckboxStatus.unchecked, + checkChildren, + checkParents, + }); + }); }, }; - } - }, [selectItem, isDisabled]) - + } + }, [selectItem, isDisabled]); + const [initialExpandedItemsNeedUpdate, setInitialExpandedItemsNeedUpdate] = React.useState(false); diff --git a/packages/react-magma-dom/src/components/TreeView/utils.ts b/packages/react-magma-dom/src/components/TreeView/utils.ts index c10618dba3..35f123b60c 100644 --- a/packages/react-magma-dom/src/components/TreeView/utils.ts +++ b/packages/react-magma-dom/src/components/TreeView/utils.ts @@ -4,7 +4,10 @@ import { UseTreeViewProps } from './useTreeView'; import { TreeViewSelectable } from './types'; import React from 'react'; import { IndeterminateCheckboxStatus } from '../IndeterminateCheckbox'; -import { TreeItemSelectedInterface, TreeViewItemInterface } from './TreeViewContext'; +import { + TreeItemSelectedInterface, + TreeViewItemInterface, +} from './TreeViewContext'; import { TreeItem } from './TreeItem'; export enum TreeNodeType { @@ -176,165 +179,378 @@ export function filterNullEntries(obj) { return {}; } -const getIsDisabled = ({ selectable, props, preselectedItems, isTreeViewDisabled, isParentDisabled, checkChildren }: { props: TreeViewItemInterface; isParentDisabled?: TreeViewItemInterface['isDisabled'], isTreeViewDisabled: UseTreeViewProps['isDisabled'] } & Pick) => { +const getIsDisabled = ({ + selectable, + props, + preselectedItems, + isTreeViewDisabled, + isParentDisabled, + checkChildren, +}: { + props: TreeViewItemInterface; + isParentDisabled?: TreeViewItemInterface['isDisabled']; + isTreeViewDisabled: UseTreeViewProps['isDisabled']; +} & Pick< + UseTreeViewProps, + 'checkChildren' | 'selectable' | 'preselectedItems' +>) => { if (isTreeViewDisabled) { return true; } - const preselectedItem = preselectedItems?.find((item) => item.itemId === props.itemId); - const isDisabled = preselectedItem?.isDisabled !== undefined ? preselectedItem?.isDisabled : props.isDisabled; - + const preselectedItem = preselectedItems?.find( + item => item.itemId === props.itemId + ); + const isDisabled = + preselectedItem?.isDisabled !== undefined + ? preselectedItem?.isDisabled + : props.isDisabled; + if (selectable === TreeViewSelectable.multi && !checkChildren) { - return isDisabled + return isDisabled; } - - return isParentDisabled || isDisabled; -} -const getTreeViewData = ({ children, selectable, checkChildren, parentId = null, isParentDisabled, preselectedItems, isTreeViewDisabled }: { isParentDisabled?: TreeViewItemInterface['isDisabled'], isTreeViewDisabled: UseTreeViewProps['isDisabled'] } & Pick & Pick) => { + return isParentDisabled || isDisabled; +}; + +const getTreeViewData = ({ + children, + selectable, + checkChildren, + parentId = null, + isParentDisabled, + preselectedItems, + isTreeViewDisabled, +}: { + isParentDisabled?: TreeViewItemInterface['isDisabled']; + isTreeViewDisabled: UseTreeViewProps['isDisabled']; +} & Pick & + Pick< + UseTreeViewProps, + 'children' | 'checkChildren' | 'selectable' | 'preselectedItems' + >) => { const treeItemChildren = React.Children.toArray(children).filter( (child: React.ReactElement) => child.type === TreeItem ) as React.ReactElement[]; - return treeItemChildren.map(({ props }) => { - const isDisabled = getIsDisabled({ selectable, props, preselectedItems, isTreeViewDisabled, isParentDisabled, checkChildren }); - - return [ - { - itemId: props.itemId, - parentId, - icon: props.icon, - hasOwnTreeItems: Boolean(props.children), - isDisabled - }, - ...(props.children ? getTreeViewData({ - children: props.children, - parentId: props.itemId, + return treeItemChildren + .map(({ props }) => { + const isDisabled = getIsDisabled({ selectable, - checkChildren, - isParentDisabled: isDisabled, + props, preselectedItems, - isTreeViewDisabled - }) : []) - ] - }).flat(); -} + isTreeViewDisabled, + isParentDisabled, + checkChildren, + }); + + return [ + { + itemId: props.itemId, + parentId, + icon: props.icon, + hasOwnTreeItems: Boolean(props.children), + isDisabled, + }, + ...(props.children + ? getTreeViewData({ + children: props.children, + parentId: props.itemId, + selectable, + checkChildren, + isParentDisabled: isDisabled, + preselectedItems, + isTreeViewDisabled, + }) + : []), + ]; + }) + .flat(); +}; + +const processItemCheckedStatus = ({ + items, + itemId, + checkedStatus, + forceCheckedStatusForDisabled, +}: { + items: TreeViewItemInterface[]; + itemId: TreeViewItemInterface['itemId']; + checkedStatus: TreeViewItemInterface['checkedStatus']; + forceCheckedStatusForDisabled?: boolean; +}) => { + const item = items.find(item => item.itemId === itemId); -const processItemCheckedStatus = ({ items, itemId, checkedStatus, forceCheckedStatusForDisabled }: { items: TreeViewItemInterface[]; itemId: TreeViewItemInterface['itemId']; checkedStatus: TreeViewItemInterface['checkedStatus']; forceCheckedStatusForDisabled?: boolean }) => { - const item = items.find((item) => item.itemId === itemId); - if (item.isDisabled && !forceCheckedStatusForDisabled) { return items; } - - return items.map((item) => item.itemId === itemId ? { ...item, checkedStatus } : item); -} -const processChildrenSelection = ({ items, itemId, checkedStatus, forceCheckedStatusForDisabled }: { items: TreeViewItemInterface[]; itemId: TreeViewItemInterface['itemId']; checkedStatus: TreeViewItemInterface['checkedStatus']; forceCheckedStatusForDisabled?: boolean }) => { - const item = items.find((item) => item.itemId === itemId); - - const itemsWithProcessedItemCheckedStatus = processItemCheckedStatus({ items, itemId, checkedStatus, forceCheckedStatusForDisabled }); - + return items.map(item => + item.itemId === itemId ? { ...item, checkedStatus } : item + ); +}; + +const processChildrenSelection = ({ + items, + itemId, + checkedStatus, + forceCheckedStatusForDisabled, +}: { + items: TreeViewItemInterface[]; + itemId: TreeViewItemInterface['itemId']; + checkedStatus: TreeViewItemInterface['checkedStatus']; + forceCheckedStatusForDisabled?: boolean; +}) => { + const item = items.find(item => item.itemId === itemId); + + const itemsWithProcessedItemCheckedStatus = processItemCheckedStatus({ + items, + itemId, + checkedStatus, + forceCheckedStatusForDisabled, + }); + if (!item.hasOwnTreeItems) { return itemsWithProcessedItemCheckedStatus; } - - const directChildren = itemsWithProcessedItemCheckedStatus.filter((item) => item.parentId === itemId); - - const itemsWithProcessedChildren = directChildren.reduce((result, directChild) => { - return processChildrenSelection({ items: result, itemId: directChild.itemId, checkedStatus, forceCheckedStatusForDisabled }) - }, itemsWithProcessedItemCheckedStatus); - - const childrenIds = getChildrenIds({ items: itemsWithProcessedChildren, itemId }); - const children = itemsWithProcessedChildren.filter((item) => childrenIds.includes(item.itemId)); - - const uniqueChildrenCheckedStatus = Array.from(new Set(children.map((children) => children.checkedStatus === IndeterminateCheckboxStatus.checked))); - const isAllChildrenWithTheSameCheckedStatus = uniqueChildrenCheckedStatus.length === 1; - const itemCheckedStatus = isAllChildrenWithTheSameCheckedStatus ? checkedStatus : IndeterminateCheckboxStatus.indeterminate; - - return processItemCheckedStatus({ items: itemsWithProcessedChildren, itemId, checkedStatus: itemCheckedStatus, forceCheckedStatusForDisabled }); -} -const getChildrenIds = ({ items, itemId }: { items: TreeViewItemInterface[]; itemId: TreeViewItemInterface['itemId']; }) => { - return items.reduce((result, item) => { - if (item.parentId !== itemId) { - return result; - } + const directChildren = itemsWithProcessedItemCheckedStatus.filter( + item => item?.parentId === itemId + ); + + const itemsWithProcessedChildren = directChildren.reduce( + (result, directChild) => { + return processChildrenSelection({ + items: result, + itemId: directChild.itemId, + checkedStatus, + forceCheckedStatusForDisabled, + }); + }, + itemsWithProcessedItemCheckedStatus + ); - if (item.hasOwnTreeItems) { - return [...result, ...getChildrenIds({ items, itemId: item.itemId })]; - } + const childrenIds = getChildrenIds({ + items: itemsWithProcessedChildren, + itemId, + }); + const children = itemsWithProcessedChildren.filter(item => + childrenIds.includes(item.itemId) + ); + + const uniqueChildrenCheckedStatus = Array.from( + new Set( + children.map( + children => + children.checkedStatus === IndeterminateCheckboxStatus.checked + ) + ) + ); + const isAllChildrenWithTheSameCheckedStatus = + uniqueChildrenCheckedStatus.length === 1; + const itemCheckedStatus = isAllChildrenWithTheSameCheckedStatus + ? checkedStatus + : IndeterminateCheckboxStatus.indeterminate; + + return processItemCheckedStatus({ + items: itemsWithProcessedChildren, + itemId, + checkedStatus: itemCheckedStatus, + forceCheckedStatusForDisabled, + }); +}; + +const getChildrenIds = ({ + items, + itemId, +}: { + items: TreeViewItemInterface[]; + itemId: TreeViewItemInterface['itemId']; +}) => { + return items.reduce( + (result, item) => { + if (item?.parentId !== itemId) { + return result; + } - return [...result, item.itemId]; - }, [itemId]) -} + if (item.hasOwnTreeItems) { + return [...result, ...getChildrenIds({ items, itemId: item.itemId })]; + } -const getChildren = ({ items, itemId }: { items: TreeViewItemInterface[]; itemId: TreeViewItemInterface['itemId']; }) => { + return [...result, item.itemId]; + }, + [itemId] + ); +}; + +const getChildren = ({ + items, + itemId, +}: { + items: TreeViewItemInterface[]; + itemId: TreeViewItemInterface['itemId']; +}) => { const childrenIds = getChildrenIds({ items, itemId }); - return items.filter((item) => childrenIds.includes(item.itemId)); -} - -const getChildrenUniqueStatuses = ({ items, itemId }: { items: TreeViewItemInterface[]; itemId: TreeViewItemInterface['itemId']; }) => { + return items.filter(item => childrenIds.includes(item.itemId)); +}; + +const getChildrenUniqueStatuses = ({ + items, + itemId, +}: { + items: TreeViewItemInterface[]; + itemId: TreeViewItemInterface['itemId']; +}) => { const childrenAndItemIds = getChildrenIds({ items, itemId }); - const leaves = items.filter((item) => { + const leaves = items.filter(item => { return !item.hasOwnTreeItems && childrenAndItemIds.includes(item.itemId); - }) - const uniqueStatuses = Array.from(new Set(leaves.map(item => item.checkedStatus ?? IndeterminateCheckboxStatus.unchecked))); - - return uniqueStatuses.filter(checkedStatus => checkedStatus && checkedStatus !== IndeterminateCheckboxStatus.indeterminate) -} - -const processInitialParentStatuses = ({ items }: { items: TreeViewItemInterface[]; }) => { + }); + const uniqueStatuses = Array.from( + new Set( + leaves.map( + item => item.checkedStatus ?? IndeterminateCheckboxStatus.unchecked + ) + ) + ); + + return uniqueStatuses.filter( + checkedStatus => + checkedStatus && + checkedStatus !== IndeterminateCheckboxStatus.indeterminate + ); +}; + +const processInitialParentStatuses = ({ + items, +}: { + items: TreeViewItemInterface[]; +}) => { const itemsWithSelectedChildren = items.reduce((result, item) => { - if (!item.hasOwnTreeItems || item.checkedStatus !== IndeterminateCheckboxStatus.checked) { + if ( + !item.hasOwnTreeItems || + item.checkedStatus !== IndeterminateCheckboxStatus.checked + ) { return result; } - return processChildrenSelection({items: result, itemId: item.itemId, checkedStatus: IndeterminateCheckboxStatus.checked, forceCheckedStatusForDisabled: true }); + return processChildrenSelection({ + items: result, + itemId: item.itemId, + checkedStatus: IndeterminateCheckboxStatus.checked, + forceCheckedStatusForDisabled: true, + }); }, items); - return itemsWithSelectedChildren.map((item) => { + return itemsWithSelectedChildren.map(item => { if (!item.hasOwnTreeItems) { return item; } - const childrenUniqueStatuses = getChildrenUniqueStatuses({ items: itemsWithSelectedChildren, itemId: item.itemId }); - - const parentStatus = childrenUniqueStatuses.length > 1 ? IndeterminateCheckboxStatus.indeterminate : childrenUniqueStatuses[0] - - return parentStatus ? { ...item, checkedStatus: parentStatus } : item - }) -} + const childrenUniqueStatuses = getChildrenUniqueStatuses({ + items: itemsWithSelectedChildren, + itemId: item.itemId, + }); -export const getInitialItems = ({ children, preselectedItems: rawPreselectedItems, checkParents, checkChildren, selectable, isDisabled: isTreeViewDisabled }: Pick) => { - const treeViewData = getTreeViewData({ children, checkChildren, selectable, preselectedItems: rawPreselectedItems, isTreeViewDisabled }); - const preselectedItems = rawPreselectedItems?.length && selectable === TreeViewSelectable.single ? [rawPreselectedItems[0]] : rawPreselectedItems; + const parentStatus = + childrenUniqueStatuses.length > 1 + ? IndeterminateCheckboxStatus.indeterminate + : childrenUniqueStatuses[0]; - const enhancedWithPreselectedItems = preselectedItems ? treeViewData.map((treeViewDataItem) => { - const preselectedItem = preselectedItems.find(({itemId}) => treeViewDataItem.itemId === itemId); + return parentStatus ? { ...item, checkedStatus: parentStatus } : item; + }); +}; + +export const getInitialItems = ({ + children, + preselectedItems: rawPreselectedItems, + checkParents, + checkChildren, + selectable, + isDisabled: isTreeViewDisabled, +}: Pick< + UseTreeViewProps, + | 'children' + | 'preselectedItems' + | 'checkParents' + | 'checkChildren' + | 'selectable' + | 'isDisabled' +>) => { + const treeViewData = getTreeViewData({ + children, + checkChildren, + selectable, + preselectedItems: rawPreselectedItems, + isTreeViewDisabled, + }); + const preselectedItems = + rawPreselectedItems?.length && selectable === TreeViewSelectable.single + ? [rawPreselectedItems[0]] + : rawPreselectedItems; + + const enhancedWithPreselectedItems = preselectedItems + ? treeViewData.map(treeViewDataItem => { + const preselectedItem = preselectedItems.find( + ({ itemId }) => treeViewDataItem.itemId === itemId + ); - return preselectedItem ? { - ...treeViewDataItem, - checkedStatus: preselectedItem.checkedStatus, - } : treeViewDataItem - }) : treeViewData + return preselectedItem + ? { + ...treeViewDataItem, + checkedStatus: preselectedItem.checkedStatus, + } + : treeViewDataItem; + }) + : treeViewData; const itemsWithProcessedChildrenSelection = checkChildren ? enhancedWithPreselectedItems.reduce((result, item) => { - return item.checkedStatus === IndeterminateCheckboxStatus.checked - ? processChildrenSelection({ items: result, itemId: item.itemId, checkedStatus: item.checkedStatus }) - : result; - }, enhancedWithPreselectedItems) - : enhancedWithPreselectedItems - - return selectable === TreeViewSelectable.multi && checkParents && preselectedItems ? processInitialParentStatuses({ items: itemsWithProcessedChildrenSelection }) : itemsWithProcessedChildrenSelection -} - -export const selectSingle = ({items, itemId, checkedStatus}: { items: TreeViewItemInterface[]; itemId: TreeViewItemInterface['itemId']; checkedStatus: TreeViewItemInterface['checkedStatus'] }) => { - return items.map((item) => ({ ...item, checkedStatus: item.itemId === itemId ? checkedStatus : IndeterminateCheckboxStatus.unchecked })) -} - -const processParentsSelection = ({items, itemId, checkedStatus}: { items: TreeViewItemInterface[]; itemId: TreeViewItemInterface['itemId']; checkedStatus: TreeViewItemInterface['checkedStatus'] }) => { + return item.checkedStatus === IndeterminateCheckboxStatus.checked + ? processChildrenSelection({ + items: result, + itemId: item.itemId, + checkedStatus: item.checkedStatus, + }) + : result; + }, enhancedWithPreselectedItems) + : enhancedWithPreselectedItems; + + return selectable === TreeViewSelectable.multi && + checkParents && + preselectedItems + ? processInitialParentStatuses({ + items: itemsWithProcessedChildrenSelection, + }) + : itemsWithProcessedChildrenSelection; +}; + +export const selectSingle = ({ + items, + itemId, + checkedStatus, +}: { + items: TreeViewItemInterface[]; + itemId: TreeViewItemInterface['itemId']; + checkedStatus: TreeViewItemInterface['checkedStatus']; +}) => { + return items.map(item => ({ + ...item, + checkedStatus: + item.itemId === itemId + ? checkedStatus + : IndeterminateCheckboxStatus.unchecked, + })); +}; + +const processParentsSelection = ({ + items, + itemId, + checkedStatus, +}: { + items: TreeViewItemInterface[]; + itemId: TreeViewItemInterface['itemId']; + checkedStatus: TreeViewItemInterface['checkedStatus']; +}) => { const item = items.find(item => item.itemId === itemId); const { parentId } = item; @@ -342,86 +558,180 @@ const processParentsSelection = ({items, itemId, checkedStatus}: { items: TreeVi return items; } - const siblings = items.filter(item => item.parentId === parentId); - const isAllSiblingsHasTheSameStatus = siblings.every(item => (item.checkedStatus || IndeterminateCheckboxStatus.unchecked) === checkedStatus); - const parentStatus = isAllSiblingsHasTheSameStatus ? checkedStatus : IndeterminateCheckboxStatus.indeterminate; - - const parent = items.find(item => item.itemId === parentId) - - const nextItems = items.map(item => item.itemId === parent.itemId ? { ...item, checkedStatus: parentStatus } : item) - - return processParentsSelection({items: nextItems, itemId: parent.itemId, checkedStatus: parentStatus }) -} + const siblings = items.filter(item => item?.parentId === parentId); + const isAllSiblingsHasTheSameStatus = siblings.every( + item => + (item.checkedStatus || IndeterminateCheckboxStatus.unchecked) === + checkedStatus + ); + const parentStatus = isAllSiblingsHasTheSameStatus + ? checkedStatus + : IndeterminateCheckboxStatus.indeterminate; + + const parent = items.find(item => item.itemId === parentId); + + const nextItems = items.map(item => + item.itemId === parent.itemId + ? { ...item, checkedStatus: parentStatus } + : item + ); + + return processParentsSelection({ + items: nextItems, + itemId: parent.itemId, + checkedStatus: parentStatus, + }); +}; const getMultiToggledStatus = ({ items, itemId }) => { const children = getChildren({ items, itemId }); const enabledChildren = children.filter(item => !item.isDisabled); - - if (enabledChildren.some(item => !item.checkedStatus || item.checkedStatus === IndeterminateCheckboxStatus.unchecked)) { + + if ( + enabledChildren.some( + item => + !item.checkedStatus || + item.checkedStatus === IndeterminateCheckboxStatus.unchecked + ) + ) { return IndeterminateCheckboxStatus.checked; } return IndeterminateCheckboxStatus.unchecked; -} - -export const toggleMulti = ({ items, itemId, checkedStatus: rawCheckedStatus, forceCheckedStatus, checkChildren, checkParents }: { items: TreeViewItemInterface[]; itemId: TreeViewItemInterface['itemId']; checkedStatus: TreeViewItemInterface['checkedStatus']; forceCheckedStatus?: boolean } & Pick) => { - const checkedStatus = checkChildren && !forceCheckedStatus ? getMultiToggledStatus({ items, itemId }) : rawCheckedStatus - - const itemsWithProcessedItemSelection = items.map((item) => item.itemId === itemId ? { ...item, checkedStatus } : item) - const itemsWithProcessedChildrenSelection = checkChildren ? processChildrenSelection({ items: itemsWithProcessedItemSelection, itemId, checkedStatus }) : itemsWithProcessedItemSelection - return checkParents ? processParentsSelection({ items: itemsWithProcessedChildrenSelection, itemId, checkedStatus }) : itemsWithProcessedChildrenSelection; -} - -const getParentIds = ({ items, itemId, prevParentIds = []}: { items: TreeViewItemInterface[]; itemId: TreeViewItemInterface['itemId']; prevParentIds?: TreeViewItemInterface['itemId'][] }) => { +}; + +export const toggleMulti = ({ + items, + itemId, + checkedStatus: rawCheckedStatus, + forceCheckedStatus, + checkChildren, + checkParents, +}: { + items: TreeViewItemInterface[]; + itemId: TreeViewItemInterface['itemId']; + checkedStatus: TreeViewItemInterface['checkedStatus']; + forceCheckedStatus?: boolean; +} & Pick) => { + const checkedStatus = + checkChildren && !forceCheckedStatus + ? getMultiToggledStatus({ items, itemId }) + : rawCheckedStatus; + + const itemsWithProcessedItemSelection = items.map(item => + item.itemId === itemId ? { ...item, checkedStatus } : item + ); + const itemsWithProcessedChildrenSelection = checkChildren + ? processChildrenSelection({ + items: itemsWithProcessedItemSelection, + itemId, + checkedStatus, + }) + : itemsWithProcessedItemSelection; + return checkParents + ? processParentsSelection({ + items: itemsWithProcessedChildrenSelection, + itemId, + checkedStatus, + }) + : itemsWithProcessedChildrenSelection; +}; + +const getParentIds = ({ + items, + itemId, + prevParentIds = [], +}: { + items: TreeViewItemInterface[]; + itemId: TreeViewItemInterface['itemId']; + prevParentIds?: TreeViewItemInterface['itemId'][]; +}) => { const item = items.find(item => item.itemId === itemId); - + if (!item) { return prevParentIds; } const { parentId } = item; - return parentId ? getParentIds({ itemId: parentId, items, prevParentIds: [...prevParentIds, parentId]}) : prevParentIds; -} + return parentId + ? getParentIds({ + itemId: parentId, + items, + prevParentIds: [...prevParentIds, parentId], + }) + : prevParentIds; +}; const getEnabledRootParentIds = (items: TreeViewItemInterface[]) => { - const rootParents = items.filter(({ parentId, isDisabled }) => !parentId && !isDisabled); + const rootParents = items.filter( + ({ parentId, isDisabled }) => !parentId && !isDisabled + ); return rootParents.map(({ itemId }) => itemId); -} - -export const toggleAllMulti = ({ items, checkedStatus, checkChildren, checkParents }: { items: TreeViewItemInterface[]; checkedStatus: TreeViewItemInterface['checkedStatus'] } & Pick) => { +}; + +export const toggleAllMulti = ({ + items, + checkedStatus, + checkChildren, + checkParents, +}: { + items: TreeViewItemInterface[]; + checkedStatus: TreeViewItemInterface['checkedStatus']; +} & Pick) => { if (!checkChildren) { return items.map(item => { if (item.isDisabled) { return item; } - return { ...item, ...(checkedStatus === IndeterminateCheckboxStatus.unchecked ? {} :{ checkedStatus }) } - }) + return { + ...item, + ...(checkedStatus === IndeterminateCheckboxStatus.unchecked + ? {} + : { checkedStatus }), + }; + }); } - + const rootParentIds = getEnabledRootParentIds(items); - - return rootParentIds.reduce((result, rootParentId) => { - return toggleMulti({ items: result, itemId: rootParentId, checkedStatus, forceCheckedStatus: true, checkChildren, checkParents }) - }, items) -} -export const getInitialExpandedIds = ({ items, initialExpandedItems }: { items: TreeViewItemInterface[]; } & Pick) => { + return rootParentIds.reduce((result, rootParentId) => { + return toggleMulti({ + items: result, + itemId: rootParentId, + checkedStatus, + forceCheckedStatus: true, + checkChildren, + checkParents, + }); + }, items); +}; + +export const getInitialExpandedIds = ({ + items, + initialExpandedItems, +}: { items: TreeViewItemInterface[] } & Pick< + UseTreeViewProps, + 'initialExpandedItems' +>) => { if (!initialExpandedItems) { return initialExpandedItems; } - + return initialExpandedItems.reduce((result, itemId) => { return [...result, itemId, ...getParentIds({ itemId, items })]; }, []); -} +}; export const isEqualArrays = (arrayA: T[], arrayB: T[]) => { return JSON.stringify(arrayA) === JSON.stringify(arrayB); -} +}; -export const isSelectedItemsChanged = (prevSelectedItems: TreeItemSelectedInterface[] | null, selectedItems: TreeItemSelectedInterface[]) => { +export const isSelectedItemsChanged = ( + prevSelectedItems: TreeItemSelectedInterface[] | null, + selectedItems: TreeItemSelectedInterface[] +) => { return !isEqualArrays(prevSelectedItems ?? [], selectedItems); -} \ No newline at end of file +};