diff --git a/src/components/TreeSelect/TreeSelect.tsx b/src/components/TreeSelect/TreeSelect.tsx index 3f48177786..96995a9170 100644 --- a/src/components/TreeSelect/TreeSelect.tsx +++ b/src/components/TreeSelect/TreeSelect.tsx @@ -18,7 +18,7 @@ import { import {block} from '../utils/cn'; import {TreeListContainer} from './components/TreeListContainer/TreeListContainer'; -import {useTreeSelectSelection} from './hooks/useTreeSelectSelection'; +import {useTreeSelectSelection, useValue} from './hooks/useTreeSelectSelection'; import type {RenderControlProps, TreeSelectProps} from './types'; import './TreeSelect.scss'; @@ -65,32 +65,11 @@ export const TreeSelect = React.forwardRef(function TreeSelect( const containerRef = React.useRef(null); const handleControlRef = useForkRef(ref, controlRef); - const { - value, - open, - toggleOpen, - handleClearValue, - handleMultipleSelection, - handleSingleSelection, - } = useTreeSelectSelection({ - onUpdate, + const {value, setInnerValue, selected} = useValue({ value: propsValue, defaultValue, - defaultOpen, - open: propsOpen, - onClose, - onOpenChange, }); - const selected = React.useMemo( - () => - value.reduce>((acc, value) => { - acc[value] = true; - return acc; - }, {}), - [value], - ); - const listState = useListState({ expandedById, disabledById, @@ -104,6 +83,26 @@ export const TreeSelect = React.forwardRef(function TreeSelect( getId, }); + const wrappedOnUpdate = React.useCallback( + (ids: ListItemId[]) => + onUpdate?.( + ids, + ids.map((id) => listParsedState.byId[id]), + ), + [listParsedState.byId, onUpdate], + ); + + const {open, toggleOpen, handleClearValue, handleMultipleSelection, handleSingleSelection} = + useTreeSelectSelection({ + setInnerValue, + value, + onUpdate: wrappedOnUpdate, + defaultOpen, + open: propsOpen, + onClose, + onOpenChange, + }); + const handleItemClick = React.useCallback( (id: ListItemId) => { // onItemClick = disabled - switch off default click behavior diff --git a/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx b/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx index d0e5f55bce..4675070d55 100644 --- a/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx +++ b/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx @@ -40,16 +40,16 @@ const DefaultTemplate: StoryFn< } > = ({itemsCount = 5, ...props}) => { const items = React.useMemo(() => createRandomizedData({num: itemsCount}), [itemsCount]); - const [value, setValue] = React.useState([]); return ( + console.log('Uncontrolled `TreeSelect onUpdate args: `', ...args) + } /> ); diff --git a/src/components/TreeSelect/hooks/useTreeSelectSelection.ts b/src/components/TreeSelect/hooks/useTreeSelectSelection.ts index ebb06a8383..a6742927f7 100644 --- a/src/components/TreeSelect/hooks/useTreeSelectSelection.ts +++ b/src/components/TreeSelect/hooks/useTreeSelectSelection.ts @@ -5,25 +5,50 @@ import {useOpenState} from '../../../hooks/useSelect/useOpenState'; import type {ListItemId} from '../../useList/types'; type UseTreeSelectSelectionProps = { - value?: ListItemId[]; - defaultValue?: ListItemId[]; + value: ListItemId[]; + setInnerValue?(ids: ListItemId[]): void; onUpdate?: (value: ListItemId[]) => void; } & UseOpenProps; +type UseValueProps = { + value?: ListItemId[]; + defaultValue?: ListItemId[]; +}; + +export const useValue = ({defaultValue, value: valueProps}: UseValueProps) => { + const [innerValue, setInnerValue] = React.useState(defaultValue || []); + + const value = valueProps || innerValue; + const uncontrolled = !valueProps; + + const selected = React.useMemo( + () => + value.reduce>((acc, value) => { + acc[value] = true; + return acc; + }, {}), + [value], + ); + + return { + selected, + value, + /** + * Available only if `uncontrolled` component valiant + */ + setInnerValue: uncontrolled ? setInnerValue : undefined, + }; +}; + export const useTreeSelectSelection = ({ + value, + setInnerValue, defaultOpen, onClose, onOpenChange, open: openProps, - value: valueProps, - defaultValue = [], onUpdate, }: UseTreeSelectSelectionProps) => { - const [innerValue, setInnerValue] = React.useState(defaultValue); - - const value = valueProps || innerValue; - const uncontrolled = !valueProps; - const {toggleOpen, open} = useOpenState({ defaultOpen, onClose, @@ -37,14 +62,12 @@ export const useTreeSelectSelection = ({ const nextValue = [id]; onUpdate?.(nextValue); - if (uncontrolled) { - setInnerValue(nextValue); - } + setInnerValue?.(nextValue); } toggleOpen(false); }, - [value, uncontrolled, onUpdate, toggleOpen], + [value, toggleOpen, onUpdate, setInnerValue], ); const handleMultipleSelection = React.useCallback( @@ -56,21 +79,18 @@ export const useTreeSelectSelection = ({ onUpdate?.(nextValue); - if (uncontrolled) { - setInnerValue(nextValue); - } + setInnerValue?.(nextValue); }, - [value, uncontrolled, onUpdate], + [value, onUpdate, setInnerValue], ); const handleClearValue = React.useCallback(() => { onUpdate?.([]); - setInnerValue([]); - }, [onUpdate]); + setInnerValue?.([]); + }, [onUpdate, setInnerValue]); return { open, - value, toggleOpen, handleSingleSelection, handleMultipleSelection, diff --git a/src/components/TreeSelect/types.ts b/src/components/TreeSelect/types.ts index dccaa3641d..09cb3dc185 100644 --- a/src/components/TreeSelect/types.ts +++ b/src/components/TreeSelect/types.ts @@ -86,7 +86,7 @@ export interface TreeSelectProps extends QAProps, Partial; renderControlContent(item: T): KnownItemStructure; onClose?(): void; - onUpdate?(value: string[]): void; + onUpdate?(value: ListItemId[], selectedItems: T[]): void; onOpenChange?(open: boolean): void; renderContainer?(props: RenderContainerProps): React.JSX.Element; /**