TreeMultiSelect API
API docs for the TreeMultiSelect component.
Types
Core
/**
* Enum representing the different types of the component.
*/
export enum Type {
/** Component behaves as a normal tree structure. */
TREE_SELECT = 'TREE_SELECT',
/** Component behaves as a flat tree structure (selecting a node has no effect on its descendants or ancestors). */
TREE_SELECT_FLAT = 'TREE_SELECT_FLAT',
/** Component behaves as a multi-select. */
MULTI_SELECT = 'MULTI_SELECT',
/** Component behaves as a single-select. */
SINGLE_SELECT = 'SINGLE_SELECT'
}
/**
* Enum representing the aggregate selection state of all nodes.
*/
export enum SelectionAggregateState {
/** All nodes are selected. */
ALL = 'ALL',
/** All selectable (non-disabled) nodes are selected. */
EFFECTIVE_ALL = 'EFFECTIVE_ALL',
/** Some, but not all, nodes are selected. */
PARTIAL = 'PARTIAL',
/** No nodes are selected. */
NONE = 'NONE'
}
TreeNode
/**
* Interface representing a node.
*/
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export interface TreeNode<T extends TreeNode<T> = any> {
/**
* Unique identifier for the node.
*
* - The ID MUST be unique across the entire tree.
* - The ID is used internally for tracking, selection, expansion, and virtual focus management.
*
* ### Important
* Node IDs **MUST NOT conflict** with any predefined virtual focus
* identifier suffixes (e.g., `INPUT_SUFFIX`, `CLEAR_ALL_SUFFIX`, `SELECT_ALL_SUFFIX`, `FOOTER_SUFFIX` constants),
* as these are reserved for internal components and may cause unexpected behavior.
* See `VirtualFocusId` type for more details.
*/
id: string;
/**
* The display label of the node.
*/
label: string;
/**
* Optional child nodes, enabling a nested tree structure.
*/
children?: T[];
/**
* Whether the node is disabled.
*/
disabled?: boolean;
/**
* Indicates whether the node has child nodes.
*
* When set to `true`, the node is treated as expandable, even if its `children`
* array is not yet populated. This is commonly used with lazy loading, where
* the actual children are loaded on demand (via `onLoadChildren`).
*
* If omitted or set to `false`, the node is treated as a leaf node unless
* child nodes are explicitly provided.
*/
hasChildren?: boolean;
}
Configs
import {VirtualFocusId} from './virtualFocus';
/**
* Controls when the Footer component is rendered in the dropdown.
*/
export type FooterConfig = {
/**
* Renders the Footer when the component is in the search mode (when the input contains value).
*
* @default false
*/
showWhenSearching?: boolean;
/**
* Renders the Footer when no items are available in the dropdown
* (takes precedence over `showWhenSearching` if both apply).
*
* @default false
*/
showWhenNoItems?: boolean;
};
/**
* Configuration options for keyboard behavior in the Field component.
*/
export type FieldKeyboardOptions = {
/**
* Enables looping when navigating left with the ArrowLeft key.
* If `true`, pressing ArrowLeft on the first item will move focus to the last item.
*
* @default false
*/
loopLeft?: boolean;
/**
* Enables looping when navigating right with the ArrowRight key.
* If `true`, pressing ArrowRight on the last item will move focus to the first item.
*
* @default false
*/
loopRight?: boolean;
};
/**
* Configuration options for keyboard behavior in the Dropdown component.
*/
export type DropdownKeyboardOptions = {
/**
* Enables looping when navigating upward with the ArrowUp key.
* If `true`, pressing ArrowUp on the first item will move focus to the last item.
*
* @default true
*/
loopUp?: boolean;
/**
* Enables looping when navigating downward with the ArrowDown key.
* If `true`, pressing ArrowDown on the last item will move focus to the first item.
*
* @default true
*/
loopDown?: boolean;
};
/**
* Controls keyboard navigation behavior for the component.
*/
export type KeyboardConfig = {
/**
* Configuration for the Field component.
*/
field?: FieldKeyboardOptions;
/**
* Configuration for the Dropdown component.
*/
dropdown?: DropdownKeyboardOptions;
};
/**
* Controls virtual focus behavior for the component.
*/
export type VirtualFocusConfig = {
/**
* Virtual focus IDs excluded from the virtual focus system.
*
* Items listed here:
* - Cannot receive focus via keyboard navigation
* - Cannot receive focus via mouse or pointer interaction
* - Can still respond to actions such as click, select, or expand
*/
excludedVirtualFocusIds?: VirtualFocusId[];
};
Virtual focus
/**
* String prefix used to identify virtual focus elements within the field area.
* Combined with `VIRTUAL_FOCUS_ID_DELIMITER` and an element-specific suffix
* to form a unique virtual focus identifier.
*/
export const FIELD_PREFIX = 'field';
/**
* String prefix used to identify virtual focus elements within the dropdown area.
* Combined with `VIRTUAL_FOCUS_ID_DELIMITER` and an element-specific suffix
* to form a unique virtual focus identifier.
*/
export const DROPDOWN_PREFIX = 'dropdown';
/**
* Delimiter used to join the region prefix and element-specific suffix
* when forming a virtual focus identifier.
*/
export const VIRTUAL_FOCUS_ID_DELIMITER = ':';
/**
* String suffix used to identify the virtual focus element
* associated with the input field.
*/
export const INPUT_SUFFIX = 'rtms-input';
/**
* String suffix used to identify the virtual focus element
* associated with the `SelectAllContainer` component.
*/
export const SELECT_ALL_SUFFIX = 'rtms-select-all';
/**
* String suffix used to identify the virtual focus element
* associated with the `FieldClear` component.
*/
export const CLEAR_ALL_SUFFIX = 'rtms-clear-all';
/**
* String suffix used to identify the virtual focus element
* associated with the `Footer` component.
*/
export const FOOTER_SUFFIX = 'rtms-footer';
/**
* Represents the identifier of a virtually focusable element within the component.
*
* The value is a string prefixed with either the `FIELD_PREFIX` or `DROPDOWN_PREFIX` constant,
* followed by the `VIRTUAL_FOCUS_ID_DELIMITER` and an element-specific suffix.
*
* ### Format
* A `virtualFocusId` for a node follows the format:
*
* ```
* <region-prefix><delimiter><node-id>
* ```
*
* **Examples:**
*
* ```
* field:1
* dropdown:123
* ```
*
* ### Predefined Virtual Focus IDs:
* Some virtually focusable elements use predefined `virtualFocusId` values:
*
* - `${FIELD_PREFIX}${VIRTUAL_FOCUS_ID_DELIMITER}${INPUT_SUFFIX}` — **Input** component in a **Field**
* - `${FIELD_PREFIX}${VIRTUAL_FOCUS_ID_DELIMITER}${CLEAR_ALL_SUFFIX}` — **FieldClear** component in a **Field**
* - `${DROPDOWN_PREFIX}${VIRTUAL_FOCUS_ID_DELIMITER}${SELECT_ALL_SUFFIX}` — **SelectAll** component in a **Dropdown**
* - `${DROPDOWN_PREFIX}${VIRTUAL_FOCUS_ID_DELIMITER}${FOOTER_SUFFIX}` — **Footer** component in a **Dropdown**
*/
export type VirtualFocusId =
| `${typeof FIELD_PREFIX}${typeof VIRTUAL_FOCUS_ID_DELIMITER}${string}`
| `${typeof DROPDOWN_PREFIX}${typeof VIRTUAL_FOCUS_ID_DELIMITER}${string}`;
Components
import React, {JSX} from 'react';
import {SelectionAggregateState, Type} from './core';
export interface FieldOwnProps {
type: Type;
isDropdownOpen: boolean;
withClearAll: boolean;
componentDisabled: boolean;
}
export interface ChipContainerOwnProps {
label: string;
focused: boolean;
disabled: boolean;
componentDisabled: boolean;
withChipClear: boolean;
}
export interface ChipLabelOwnProps {
label: string;
componentDisabled: boolean;
}
export interface ChipClearOwnProps {
componentDisabled: boolean;
}
export interface InputOwnProps {
placeholder: string;
value: string;
disabled: boolean;
}
export interface FieldClearOwnProps {
focused: boolean;
componentDisabled: boolean;
}
export interface FieldToggleOwnProps {
expanded: boolean;
componentDisabled: boolean;
}
export interface DropdownOwnProps {
componentDisabled: boolean;
}
export interface SelectAllContainerOwnProps {
label: string;
selectionAggregateState: SelectionAggregateState;
focused: boolean;
}
export interface SelectAllCheckboxOwnProps {
selectionAggregateState: SelectionAggregateState;
}
export interface SelectAllLabelOwnProps {
label: string;
}
export interface NodeContainerOwnProps {
label: string;
disabled: boolean;
selected: boolean;
partial: boolean;
expanded: boolean;
focused: boolean;
matched: boolean;
}
export interface NodeToggleOwnProps {
expanded: boolean;
}
export interface NodeCheckboxOwnProps {
checked: boolean;
partial: boolean;
disabled: boolean;
}
export interface NodeLabelOwnProps {
label: string;
}
export interface FooterOwnProps {
focused: boolean;
}
export interface NoDataOwnProps {
label: string;
}
/* eslint-disable-next-line @typescript-eslint/no-empty-object-type */
export interface SpinnerOwnProps {
}
export type Attributes<Tag extends keyof JSX.IntrinsicElements> = JSX.IntrinsicElements[Tag] & {
'data-rtms-virtual-focus-id'?: string;
};
export interface ComponentProps<Tag extends keyof JSX.IntrinsicElements, OwnProps, CustomProps = unknown> {
attributes: Attributes<Tag>;
ownProps: OwnProps;
customProps: CustomProps;
children?: React.ReactNode;
}
export interface Component<ComponentProps, CustomProps = unknown> {
component: React.ComponentType<ComponentProps>;
props?: CustomProps;
}
export type FieldProps<CustomProps = unknown> = ComponentProps<'div', FieldOwnProps, CustomProps>;
export type ChipContainerProps<CustomProps = unknown> = ComponentProps<'div', ChipContainerOwnProps, CustomProps>;
export type ChipLabelProps<CustomProps = unknown> = ComponentProps<'div', ChipLabelOwnProps, CustomProps>;
export type ChipClearProps<CustomProps = unknown> = ComponentProps<'div', ChipClearOwnProps, CustomProps>;
export type InputProps<CustomProps = unknown> = ComponentProps<'input', InputOwnProps, CustomProps>;
export type FieldClearProps<CustomProps = unknown> = ComponentProps<'div', FieldClearOwnProps, CustomProps>;
export type FieldToggleProps<CustomProps = unknown> = ComponentProps<'div', FieldToggleOwnProps, CustomProps>;
export type DropdownProps<CustomProps = unknown> = ComponentProps<'div', DropdownOwnProps, CustomProps>;
export type SelectAllContainerProps<CustomProps = unknown> = ComponentProps<'div', SelectAllContainerOwnProps, CustomProps>;
export type SelectAllCheckboxProps<CustomProps = unknown> = ComponentProps<'div', SelectAllCheckboxOwnProps, CustomProps>;
export type SelectAllLabelProps<CustomProps = unknown> = ComponentProps<'div', SelectAllLabelOwnProps, CustomProps>;
export type NodeContainerProps<CustomProps = unknown> = ComponentProps<'div', NodeContainerOwnProps, CustomProps>;
export type NodeToggleProps<CustomProps = unknown> = ComponentProps<'div', NodeToggleOwnProps, CustomProps>;
export type NodeCheckboxProps<CustomProps = unknown> = ComponentProps<'div', NodeCheckboxOwnProps, CustomProps>;
export type NodeLabelProps<CustomProps = unknown> = ComponentProps<'div', NodeLabelOwnProps, CustomProps>;
export type FooterProps<CustomProps = unknown> = ComponentProps<'div', FooterOwnProps, CustomProps>;
export type NoDataProps<CustomProps = unknown> = ComponentProps<'div', NoDataOwnProps, CustomProps>;
export type SpinnerProps<CustomProps = unknown> = ComponentProps<'div', SpinnerOwnProps, CustomProps>;
export type FieldType<CustomProps = unknown> = Component<FieldProps<CustomProps>, CustomProps>;
export type ChipContainerType<CustomProps = unknown> = Component<ChipContainerProps<CustomProps>, CustomProps>;
export type ChipLabelType<CustomProps = unknown> = Component<ChipLabelProps<CustomProps>, CustomProps>;
export type ChipClearType<CustomProps = unknown> = Component<ChipClearProps<CustomProps>, CustomProps>;
export type InputType<CustomProps = unknown> = Component<InputProps<CustomProps>, CustomProps>;
export type FieldClearType<CustomProps = unknown> = Component<FieldClearProps<CustomProps>, CustomProps>;
export type FieldToggleType<CustomProps = unknown> = Component<FieldToggleProps<CustomProps>, CustomProps>;
export type DropdownType<CustomProps = unknown> = Component<DropdownProps<CustomProps>, CustomProps>;
export type SelectAllContainerType<CustomProps = unknown> = Component<SelectAllContainerProps<CustomProps>, CustomProps>;
export type SelectAllCheckboxType<CustomProps = unknown> = Component<SelectAllCheckboxProps<CustomProps>, CustomProps>;
export type SelectAllLabelType<CustomProps = unknown> = Component<SelectAllLabelProps<CustomProps>, CustomProps>;
export type NodeContainerType<CustomProps = unknown> = Component<NodeContainerProps<CustomProps>, CustomProps>;
export type NodeToggleType<CustomProps = unknown> = Component<NodeToggleProps<CustomProps>, CustomProps>;
export type NodeCheckboxType<CustomProps = unknown> = Component<NodeCheckboxProps<CustomProps>, CustomProps>;
export type NodeLabelType<CustomProps = unknown> = Component<NodeLabelProps<CustomProps>, CustomProps>;
export type FooterType<CustomProps = unknown> = Component<FooterProps<CustomProps>, CustomProps>;
export type NoDataType<CustomProps = unknown> = Component<NoDataProps<CustomProps>, CustomProps>;
export type SpinnerType<CustomProps = unknown> = Component<SpinnerProps<CustomProps>, CustomProps>;
/* eslint-disable @typescript-eslint/no-explicit-any */
export type ComponentTypes = {
Field: FieldType<any>;
ChipContainer: ChipContainerType<any>;
ChipLabel: ChipLabelType<any>;
ChipClear: ChipClearType<any>;
Input: InputType<any>;
FieldClear: FieldClearType<any>;
FieldToggle: FieldToggleType<any>;
Dropdown: DropdownType<any>;
SelectAllContainer: SelectAllContainerType<any>;
SelectAllCheckbox: SelectAllCheckboxType<any>;
SelectAllLabel: SelectAllLabelType<any>;
NodeContainer: NodeContainerType<any>;
NodeToggle: NodeToggleType<any>;
NodeCheckbox: NodeCheckboxType<any>;
NodeLabel: NodeLabelType<any>;
Footer: FooterType<any>;
NoData: NoDataType<any>;
Spinner: SpinnerType<any>;
};
export type ComponentNames = keyof ComponentTypes;
export type Components<
ComponentsMap extends Partial<ComponentTypes>
& Record<Exclude<keyof ComponentsMap, ComponentNames>, never> = any
> = {
[K in ComponentNames]?: Component<
ComponentTypes[K] extends Component<infer ComponentProps, any> ? ComponentProps : never,
K extends keyof ComponentsMap
? ComponentsMap[K] extends Component<any, infer CustomProps>
? CustomProps
: unknown
: unknown
>;
};
Internal state
import {SelectionAggregateState} from './core';
import {VirtualFocusId} from './virtualFocus';
/**
* Represents the internal state of the component.
*/
export interface State {
/**
* The IDs of the nodes that are currently selected.
*
* - In **controlled mode**, this value mirrors the `selectedIds` prop.
* - In **uncontrolled mode**, this value is managed internally by the component.
* - For `Type.SELECT`, this array contains at most **one ID**.
*/
selectedIds: string[];
/**
* The IDs of the nodes that are currently expanded.
*
* - In **controlled mode**, this value mirrors the `expandedIds` prop.
* - In **uncontrolled mode**, this value is managed internally by the component.
* - For component types other than `Type.TREE_SELECT` and `Type.TREE_SELECT_FLAT`,
* this value is always an **empty array**.
*/
expandedIds: string[];
/**
* Represents the current overall selection state of all nodes.
*/
selectionAggregateState: SelectionAggregateState;
/**
* The current search input value.
*/
inputValue: string;
/**
* Indicates whether the dropdown is currently open (rendered).
*/
isDropdownOpen: boolean;
/**
* The identifier of the currently virtually focused element,
* or `null` if no element is virtually focused.
*/
virtualFocusId: VirtualFocusId | null;
}
Props
import React from 'react';
import {Components} from './components';
import {SelectionAggregateState, Type} from './core';
import {FooterConfig, KeyboardConfig, VirtualFocusConfig} from './configs';
import {TreeNode} from './nodes';
/**
* Props for the `TreeMultiSelect` component.
*
* Defines all configuration options, event callbacks, and customization points
* for controlling the behavior, appearance, and data handling of the component.
*/
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export interface TreeMultiSelectProps<T extends TreeNode<T> = any> {
/**
* The data to be rendered in the component.
*/
data: T[];
/**
* Specifies the type of the component, determining its behavior and rendering.
*
* @default Type.TREE_SELECT
*/
type?: Type;
/**
* The IDs of the nodes that should be selected.
*
* - For `Type.SELECT` type, exactly **one ID** should be passed;
* if more than one ID is provided, only the **first ID** will be used.
* - For other types it can contain multiple IDs.
*
* - The component treats this as a **controlled prop**.
*/
selectedIds?: string[];
/**
* The IDs of the nodes that should be selected initially (uncontrolled mode).
*
* - For `Type.SELECT` type, exactly **one ID** should be passed;
* if more than one ID is provided, only the **first ID** will be used.
* - For other types it can contain multiple IDs.
* - Used **only when `selectedIds` is not provided**.
* - The component will initialize its internal selection state using this value
* and will manage selection internally afterward.
* - Changes to this prop after the initial render are ignored.
*/
defaultSelectedIds?: string[];
/**
* The IDs of the nodes that should be expanded.
*
* - Used only when `type` is `Type.TREE_SELECT` or `Type.TREE_SELECT_FLAT`.
* For all other types, this prop is ignored.
*
* - The component treats this as a **controlled prop**.
*/
expandedIds?: string[];
/**
* The IDs of the nodes that should be expanded initially (uncontrolled mode).
*
* - Used only when `type` is `Type.TREE_SELECT` or `Type.TREE_SELECT_FLAT`.
* For all other types, this prop is ignored.
* - Used **only when `expandedIds` is not provided**.
* - The component will initialize its internal expansion state using this value
* and will manage expansion internally afterward.
* - Changes to this prop after the initial render are ignored.
*/
defaultExpandedIds?: string[];
/**
* The `id` attribute to apply to the root `<div>` of the component.
*/
id?: string;
/**
* The `className` to apply to the root `<div>` of the component.
*/
className?: string;
/**
* Placeholder text displayed in the search input field.
*
* @default "search..."
*/
inputPlaceholder?: string;
/**
* Text displayed when there is no data to show in the dropdown.
*
* @default "No data"
*/
noDataText?: string;
/**
* Text displayed when no matching results are found during a search.
*
* @default "No matches"
*/
noMatchesText?: string;
/**
* Disables the entire component, preventing user interaction.
*
* @default false
*/
isDisabled?: boolean;
/**
* Controls whether the search input is rendered.
* When `true`, a search input is shown either in the field
* or in the dropdown (if `withDropdownInput` is also `true`).
*
* @default true
*/
isSearchable?: boolean;
/**
* Controls whether the chip-level clear button (`ChipClear`) is displayed for each selected item.
*
* @default true
*/
withChipClear?: boolean;
/**
* Controls whether the field-level clear button (`FieldClear`) is displayed to clear all selected items at once.
*
* @default true
*/
withClearAll?: boolean;
/**
* Controls whether a sticky "SelectAll" component is rendered at the top of the dropdown.
*
* This option is automatically hidden when:
* - `type` is `Type.SELECT`
* - the search input has a value (search mode)
* - there is no available data
*
* @default false
*/
withSelectAll?: boolean;
/**
* Controls whether a sticky search input is rendered at the top of the dropdown.
* A hidden input is rendered in the field to preserve focus behavior.
*
* @default false
*/
withDropdownInput?: boolean;
/**
* Closes the dropdown automatically after a node is changed (selected/deselected in dropdown).
* Useful when `type` is `Type.SELECT`.
*
* @default false
*/
closeDropdownOnNodeChange?: boolean;
/**
* Controls whether the dropdown is rendered (open) or hidden (closed).
* This enables external control over the dropdown's rendering state.
*
* When set to `true`, the dropdown is rendered (opened).
* When set to `false`, the dropdown is hidden (closed).
*
* The component treats this as a **controlled prop**.
*
* If omitted, the component manages the dropdown state internally.
* For full control, use this prop in conjunction with the `onDropdownToggle` callback.
*/
isDropdownOpen?: boolean;
/**
* Controls whether the dropdown should be open initially (uncontrolled mode).
*
* - Used only when `isDropdownOpen` is not provided.
* - Initializes the internal open/close state on first render.
* - The component manages the dropdown’s visibility internally afterward.
* - Changes to this prop after the initial render are ignored.
*/
defaultIsDropdownOpen?: boolean;
/**
* Dropdown height in pixels. If the content height is smaller than this value,
* the dropdown height is automatically reduced to fit the content.
*
* @default 300
*/
dropdownHeight?: number;
/**
* The number of items to render outside the visible viewport (above and below)
* to improve scroll performance and reduce flickering during fast scrolling.
*
* @default 1
*/
overscan?: number;
/**
* Determines whether the dropdown list is rendered using virtualization.
* When enabled, only the visible portion of the list (plus overscan items)
* is rendered to improve performance with large datasets.
*
* @default true
*/
isVirtualized?: boolean;
/**
* Controls when the Footer component is rendered in the dropdown.
*/
footerConfig?: FooterConfig;
/**
* Controls keyboard navigation behavior for the component.
*/
keyboardConfig?: KeyboardConfig;
/**
* Controls virtual focus behavior for the component.
*/
virtualFocusConfig?: VirtualFocusConfig;
/**
* Custom components used to override the default UI elements of the TreeMultiSelect.
*
* Allows you to replace built-in components with your own implementations
* to match your design and behavior requirements.
*/
components?: Components;
/**
* Callback triggered when the dropdown is opened or closed.
*
* @param isOpen - `true` if the dropdown was opened, `false` if it was closed.
*/
onDropdownToggle?: (isOpen: boolean) => void;
/**
* Callback triggered when a node is selected or deselected.
* This includes interactions from the dropdown as well as chip removal in the field.
*
* @param node - The node that was changed.
* @param selectedIds - The list of currently selected nodes IDs.
*/
onNodeChange?: (node: T, selectedIds: string[]) => void;
/**
* Callback triggered when a node is toggled (expanded or collapsed).
*
* @param node - The node that was toggled.
* @param expandedIds - The list of currently expanded nodes IDs.
*/
onNodeToggle?: (node: T, expandedIds: string[]) => void;
/**
* Callback triggered when the `FieldClear` component is activated by user interaction,
* such as a mouse click or pressing the Backspace key.
*
* This is used to clear all selected nodes, except for nodes that are disabled.
*
* @param selectedIds - The list of currently selected nodes Ids.
* @param selectionAggregateState - The current overall selection state of all nodes.
*/
onClearAll?: (selectedIds: string[], selectionAggregateState: SelectionAggregateState) => void;
/**
* Callback triggered when the `SelectAll` component is activated by user interaction,
* such as a mouse click or pressing the Enter key.
*
* This is used to select or deselect all nodes, except for nodes that are disabled.
*
* @param selectedIds - The list of currently selected nodes IDs.
* @param selectionAggregateState - The current overall selection state of all nodes.
*/
onSelectAllChange?: (selectedIds: string[], selectionAggregateState: SelectionAggregateState) => void;
/**
* Callback triggered when the component receives focus.
*
* @param event - The React focus event.
*/
onFocus?: (event: React.FocusEvent) => void;
/**
* Callback triggered when the component loses focus.
*
* @param event - The React blur event.
*/
onBlur?: (event: React.FocusEvent) => void;
/**
* Callback triggered on keyboard interaction within the component.
*
* This allows interception or customization of the built-in keyboard behavior.
*
* - Returning `true` prevents the component’s default keyboard handling for the event.
* - Returning `false` or `undefined` allows the component’s default handling to continue.
*
* This means the user can simply omit a return statement if they do not want
* to block the default behavior.
*
* @param event - The original keyboard event.
* @returns `true` to stop the default keyboard handling; otherwise `false` or `undefined`.
*/
onKeyDown?: (event: React.KeyboardEvent) => boolean | undefined;
/**
* Callback triggered when the last item in the dropdown is rendered.
* This is useful for implementing infinite scrolling or lazy loading.
*
* Note: The callback is invoked when the last item (including overscan)
* is rendered, not based on actual scroll position.
*
* @param inputValue - The current search input value.
* @param displayedNodes - An array of TreeNode objects currently displayed in the dropdown.
*/
onDropdownLastItemReached?: (inputValue: string, displayedNodes: T[]) => void;
/**
* Callback for loading additional data to be appended to the end of the existing dataset.
*
* This is useful for implementing infinite scrolling, pagination, lazy loading, or incremental data fetching
* where new items extend the current list rather than replacing it.
*
* This function is *not* invoked automatically by the component. Instead, it is triggered manually by the consumer
* via the imperative `TreeMultiSelectHandle.loadData()` method.
*
* Note: The component automatically appends the nodes returned by this callback to the existing dataset.
* It does not remove duplicates, merge updates, or otherwise reconcile data conflicts.
* These responsibilities must be handled by the consumer.
*
* @returns A Promise resolving to an array of TreeNode objects to append.
*/
onLoadData?: () => Promise<T[]>;
/**
* Callback for loading children of a specific node on demand.
*
* This function is called automatically by the component when the user expands a node that has not yet loaded
* its children (i.e., the node’s `hasChildren` prop is `true`, and its children prop is not set or empty).
* It enables lazy loading of hierarchical data for large trees or server-driven datasets.
*
* The function should return a Promise resolving to an array of TreeNode objects,
* which will be set as the children of the specified node.
*
* Note: The component does not perform deduplication, merging, or conflict
* resolution, so ensure the data returned is complete and correct for that node.
*
* @param id - The unique identifier of the node whose children are being loaded.
* @returns A Promise resolving to an array of TreeNode objects to be set as the node’s children.
*/
onLoadChildren?: (id: string) => Promise<T[]>;
}
Imperative API
import {TreeNode} from './nodes';
import {State} from './state';
import {DROPDOWN_PREFIX, FIELD_PREFIX, VirtualFocusId} from './virtualFocus';
/**
* Imperative API for interacting with the TreeMultiSelect component.
*
* A `TreeMultiSelectHandle` instance can be obtained by passing a `ref`
* to the component (e.g., `useRef<TreeMultiSelectHandle>(null)`).
*
* All methods operate on the most recent internal state at the moment
* they are called.
*/
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export interface TreeMultiSelectHandle<T extends TreeNode<T> = any> {
/**
* Returns a snapshot of the current internal component state.
*
* The returned object is always up-to-date at the moment of the call.
*
* @returns The current internal state of the TreeMultiSelect component.
*/
getState: () => State;
/**
* Returns a node by its unique identifier.
*
* The identifier corresponds to the node `id`.
*
* @param id - The unique identifier of the node.
* @returns The matching `TreeNode` if found; otherwise `undefined`.
*/
getById: (id: string) => T | undefined;
/**
* Loads additional data and appends it to the current dataset.
*
* This method calls the `onLoadData` callback internally and merges the returned
* nodes with the existing data. It does nothing if `onLoadData` is not provided
* or returns an empty array.
*
* @returns A Promise that resolves once the data has been loaded and appended.
*/
loadData: () => Promise<void>;
/**
* Opens (renders) the dropdown.
*/
openDropdown: () => void;
/**
* Closes (hides) the dropdown.
*/
closeDropdown: () => void;
/**
* Toggles the dropdown's visibility.
*
* - If the dropdown is currently closed, it will be opened.
* - If the dropdown is currently open, it will be closed.
*/
toggleDropdown: () => void;
/**
* Selects all nodes, except for nodes that are disabled.
*/
selectAll: () => void;
/**
* Deselects all nodes, except for nodes that are disabled.
*/
deselectAll: () => void;
/**
* Toggles the selection state of all selectable nodes.
*
* - If all selectable nodes are currently selected, this will deselect all of them.
* - Otherwise, it will select all selectable nodes.
*/
toggleAllSelection: () => void;
/**
* Expands a node in the tree.
*
* Does nothing if the node is already expanded or not expandable.
*
* @param id - The unique identifier of the node to expand.
* If omitted, the currently virtually focused node in the dropdown will be expanded
* if it exists and is expandable.
*/
expandNode: (id?: string) => void;
/**
* Collapses a node in the tree.
*
* Does nothing if the node is already collapsed or not collapsible.
*
* @param id - The unique identifier of the node to collapse.
* If omitted, the currently virtually focused node in the dropdown will be collapsed
* if it exists and is collapsible.
*/
collapseNode: (id?: string) => void;
/**
* Toggles the expansion state of a node.
*
* - If the node is expanded, it will be collapsed.
* - If the node is collapsed and expandable, it will be expanded.
*
* @param id - The unique identifier of the node to toggle.
* If omitted, the currently virtually focused node in the dropdown will be toggled
* if it exists and is expandable/collapsible.
*/
toggleNodeExpansion: (id?: string) => void;
/**
* Selects a node explicitly.
*
* Does nothing if the node is already selected or not selectable.
*
* @param id - The unique identifier of the node to select.
* If omitted, the currently virtually focused node will be selected if it exists and is selectable.
*/
selectNode: (id?: string) => void;
/**
* Deselects a node explicitly.
*
* Does nothing if the node is already unselected or not selectable.
*
* @param id - The unique identifier of the node to deselect.
* If omitted, the currently virtually focused node will be deselected if it exists and is selectable.
*/
deselectNode: (id?: string) => void;
/**
* Toggles the selection of a node based on its current state.
*
* - If the node is fully selected or partially selected (with all non-disabled children selected),
* it will be deselected.
* - Otherwise, it will be selected.
*
* @param id - The unique identifier of the node whose selection state should be toggled.
* If omitted, the currently virtually focused node will toggle its selection state if it exists and is selectable.
*/
toggleNodeSelection: (id?: string) => void;
/**
* Moves virtual focus to the first virtually focusable element.
*
* - If `region` is provided, moves focus to the first element in that region (`FIELD` or `DROPDOWN`),
* ignoring the current virtual focus.
* - If `region` is omitted, moves focus within the same region as the currently focused element.
* Does nothing if there is no currently focused element (`virtualFocusId` is `null`).
*
* @param region - The focus region to target.
*/
focusFirstItem: (region?: typeof FIELD_PREFIX | typeof DROPDOWN_PREFIX) => void;
/**
* Moves virtual focus to the last virtually focusable element.
*
* - If `region` is provided, moves focus to the last element in that region (`FIELD` or `DROPDOWN`),
* ignoring the current virtual focus.
* - If `region` is omitted, moves focus within the same region as the currently focused element.
* Does nothing if there is no currently focused element (`virtualFocusId` is `null`).
*
* @param region - The focus region to target.
*/
focusLastItem: (region?: typeof FIELD_PREFIX | typeof DROPDOWN_PREFIX) => void;
/**
* Moves virtual focus to the previous virtually focusable element.
*
* - If `virtualFocusId` is provided, moves focus to the element immediately preceding
* the specified element, ignoring the currently focused element.
* - If `virtualFocusId` is omitted, moves focus to the element immediately preceding
* the currently virtually focused element.
* Does nothing if there is no currently focused element (`virtualFocusId` is `null`).
*
* @param virtualFocusId - The identifier of the virtually focusable element
* from which to move the virtual focus.
*/
focusPrevItem: (virtualFocusId?: VirtualFocusId) => void;
/**
* Moves virtual focus to the next virtually focusable element.
*
* - If `virtualFocusId` is provided, moves focus to the element immediately following
* the specified element, ignoring the currently focused element.
* - If `virtualFocusId` is omitted, moves focus to the element immediately following
* the currently virtually focused element.
* Does nothing if there is no currently focused element (`virtualFocusId` is `null`).
*
* @param virtualFocusId - The identifier of the virtually focusable element
* from which to move the virtual focus.
*/
focusNextItem: (virtualFocusId?: VirtualFocusId) => void;
}