UNPKG

@headless-tree/core

Version:

The definitive tree component for the Web

605 lines (571 loc) 24.8 kB
interface DndDataRef { lastDragCode?: string; lastAllowDrop?: boolean; lastDragEnter?: number; autoExpandTimeout?: any; windowDragEndListener?: () => void; } interface DndState<T> { draggedItems?: ItemInstance<T>[]; draggingOverItem?: ItemInstance<T>; dragTarget?: DragTarget<T>; } interface DragLineData { indent: number; top: number; left: number; width: number; } type DragTarget<T> = { item: ItemInstance<T>; childIndex: number; insertionIndex: number; dragLineIndex: number; dragLineLevel: number; } | { item: ItemInstance<T>; }; declare enum DragTargetPosition { Top = "top", Bottom = "bottom", Item = "item" } type DragAndDropFeatureDef<T> = { state: { dnd?: DndState<T> | null; }; config: { setDndState?: SetStateFn<DndState<T> | undefined | null>; /** Defines the size of the area at the top and bottom of an item where, when an item is dropped, the item willö * be placed above or below the item within the same parent, as opposed to being placed inside the item. * If `canReorder` is `false`, this is ignored. */ reorderAreaPercentage?: number; canReorder?: boolean; canDrag?: (items: ItemInstance<T>[]) => boolean; canDrop?: (items: ItemInstance<T>[], target: DragTarget<T>) => boolean; indent?: number; createForeignDragObject?: (items: ItemInstance<T>[]) => { format: string; data: any; dropEffect?: DataTransfer["dropEffect"]; effectAllowed?: DataTransfer["effectAllowed"]; }; setDragImage?: (items: ItemInstance<T>[]) => { imgElement: Element; xOffset?: number; yOffset?: number; }; /** Checks if a foreign drag object can be dropped on a target, validating that an actual drop can commence based on * the data in the DataTransfer object. */ canDropForeignDragObject?: (dataTransfer: DataTransfer, target: DragTarget<T>) => boolean; /** Checks if a droppable visualization should be displayed when dragging a foreign object over a target. Since this * is executed on a dragover event, `dataTransfer.getData()` is not available, so `dataTransfer.effectAllowed` or * `dataTransfer.types` should be used instead. Before actually completing the drag, @{link canDropForeignDragObject} * will be called by HT before applying the drop. */ canDragForeignDragObjectOver?: (dataTransfer: DataTransfer, target: DragTarget<T>) => boolean; onDrop?: (items: ItemInstance<T>[], target: DragTarget<T>) => void | Promise<void>; onDropForeignDragObject?: (dataTransfer: DataTransfer, target: DragTarget<T>) => void | Promise<void>; onCompleteForeignDrop?: (items: ItemInstance<T>[]) => void; /** When dragging for this many ms on a closed folder, the folder will automatically open. Set to zero to disable. */ openOnDropDelay?: number; }; treeInstance: { getDragTarget: () => DragTarget<T> | null; getDragLineData: () => DragLineData | null; getDragLineStyle: (topOffset?: number, leftOffset?: number) => Record<string, any>; }; itemInstance: { /** Checks if the user is dragging in a way which makes this the new parent of the dragged items, either by dragging on top of * this item, or by dragging inbetween children of this item. See @{isUnorderedDragTarget} if the latter is undesirable. */ isDragTarget: () => boolean; /** As opposed to @{isDragTarget}, this will not be true if the target is inbetween children of this item. This returns only true * if the user is dragging directly on top of this item. */ isUnorderedDragTarget: () => boolean; isDragTargetAbove: () => boolean; isDragTargetBelow: () => boolean; isDraggingOver: () => boolean; }; hotkeys: never; }; interface ItemMeta { itemId: string; parentId: string; level: number; index: number; setSize: number; posInSet: number; } interface TreeItemDataRef { memoizedValues: Record<string, any>; memoizedDeps: Record<string, any[] | undefined>; } type TreeFeatureDef<T> = { state: { expandedItems: string[]; focusedItem: string | null; }; config: { isItemFolder: (item: ItemInstance<T>) => boolean; getItemName: (item: ItemInstance<T>) => string; onPrimaryAction?: (item: ItemInstance<T>) => void; scrollToItem?: (item: ItemInstance<T>) => void; setExpandedItems?: SetStateFn<string[]>; setFocusedItem?: SetStateFn<string | null>; }; treeInstance: { /** @internal */ getItemsMeta: () => ItemMeta[]; getFocusedItem: () => ItemInstance<T>; getRootItem: () => ItemInstance<T>; focusNextItem: () => void; focusPreviousItem: () => void; updateDomFocus: () => void; /** Pass to the container rendering the tree children. The `treeLabel` parameter * will be passed as `aria-label` parameter, and is recommended to be set. */ getContainerProps: (treeLabel?: string) => Record<string, any>; }; itemInstance: { getId: () => string; getKey: () => string; getProps: () => Record<string, any>; getItemName: () => string; getItemData: () => T; equals: (other?: ItemInstance<any> | null) => boolean; expand: () => void; collapse: () => void; isExpanded: () => boolean; isDescendentOf: (parentId: string) => boolean; isFocused: () => boolean; isFolder: () => boolean; setFocused: () => void; getParent: () => ItemInstance<T> | undefined; getChildren: () => ItemInstance<T>[]; getIndexInParent: () => number; primaryAction: () => void; getTree: () => TreeInstance<T>; getItemAbove: () => ItemInstance<T> | undefined; getItemBelow: () => ItemInstance<T> | undefined; scrollTo: (scrollIntoViewArg?: boolean | ScrollIntoViewOptions) => Promise<void>; }; hotkeys: "focusNextItem" | "focusPreviousItem" | "expandOrDown" | "collapseOrUp" | "focusFirstItem" | "focusLastItem"; }; type InstanceTypeMap = { itemInstance: ItemInstance<any>; treeInstance: TreeInstance<any>; }; type InstanceBuilder = <T extends keyof InstanceTypeMap>(features: FeatureImplementation[], instanceType: T, buildOpts: (self: any) => any) => [instance: InstanceTypeMap[T], finalize: () => void]; type MainFeatureDef<T = any> = { state: {}; config: { features?: FeatureImplementation<any>[]; initialState?: Partial<TreeState<T>>; state?: Partial<TreeState<T>>; setState?: SetStateFn<Partial<TreeState<T>>>; instanceBuilder?: InstanceBuilder; }; treeInstance: { /** @internal */ applySubStateUpdate: <K extends keyof TreeState<any>>(stateName: K, updater: Updater<TreeState<T>[K]>) => void; setState: SetStateFn<TreeState<T>>; getState: () => TreeState<T>; setConfig: SetStateFn<TreeConfig<T>>; getConfig: () => TreeConfig<T>; getItemInstance: (itemId: string) => ItemInstance<T>; getItems: () => ItemInstance<T>[]; registerElement: (element: HTMLElement | null) => void; getElement: () => HTMLElement | undefined | null; /** @internal */ getDataRef: <D>() => { current: D; }; getHotkeyPresets: () => HotkeysConfig<T>; rebuildTree: () => void; /** @deprecated Experimental feature, might get removed or changed in the future. */ scheduleRebuildTree: () => void; /** @internal */ setMounted: (isMounted: boolean) => void; }; itemInstance: { registerElement: (element: HTMLElement | null) => void; getItemMeta: () => ItemMeta; getElement: () => HTMLElement | undefined | null; /** @internal */ getDataRef: <D>() => { current: D; }; }; hotkeys: never; }; type SelectionFeatureDef<T> = { state: { selectedItems: string[]; }; config: { setSelectedItems?: SetStateFn<string[]>; }; treeInstance: { setSelectedItems: (selectedItems: string[]) => void; getSelectedItems: () => ItemInstance<T>[]; }; itemInstance: { select: () => void; deselect: () => void; toggleSelect: () => void; isSelected: () => boolean; selectUpTo: (ctrl: boolean) => void; }; hotkeys: "toggleSelectedItem" | "selectUpwards" | "selectDownwards" | "selectAll"; }; interface HotkeyConfig<T> { hotkey: string; canRepeat?: boolean; allowWhenInputFocused?: boolean; isEnabled?: (tree: TreeInstance<T>) => boolean; preventDefault?: boolean; handler: (e: KeyboardEvent, tree: TreeInstance<T>) => void; } interface HotkeysCoreDataRef { keydownHandler?: (e: KeyboardEvent) => void; keyupHandler?: (e: KeyboardEvent) => void; resetHandler?: (e: FocusEvent) => void; pressedKeys: Set<string>; } type HotkeysCoreFeatureDef<T> = { state: {}; config: { hotkeys?: CustomHotkeysConfig<T>; onTreeHotkey?: (name: string, e: KeyboardEvent) => void; /** Do not handle key inputs while an HTML input element is focused */ ignoreHotkeysOnInputs?: boolean; }; treeInstance: {}; itemInstance: {}; hotkeys: never; }; type TreeDataLoader<T> = { getItem: (itemId: string) => T | Promise<T>; getChildren: (itemId: string) => string[] | Promise<string[]>; } | { getItem: (itemId: string) => T | Promise<T>; getChildrenWithData: (itemId: string) => { id: string; data: T; }[] | Promise<{ id: string; data: T; }[]>; }; type SyncDataLoaderFeatureDef<T> = { state: {}; config: { rootItemId: string; dataLoader: TreeDataLoader<T>; }; treeInstance: { retrieveItemData: (itemId: string) => T; /** Retrieve children Ids. If an async data loader is used, skipFetch is set to true, and children have not been retrieved * yet for this item, this will initiate fetching the children, and return an empty array. Once the children have loaded, * a rerender will be triggered. * @param skipFetch - Defaults to false. */ retrieveChildrenIds: (itemId: string, skipFetch?: boolean) => string[]; }; itemInstance: { isLoading: () => boolean; }; hotkeys: never; }; interface AsyncDataLoaderDataRef<T = any> { itemData: Record<string, T>; childrenIds: Record<string, string[]>; } /** * @category Async Data Loader/General * */ type AsyncDataLoaderFeatureDef<T> = { state: { loadingItemData: string[]; loadingItemChildrens: string[]; }; config: { rootItemId: string; /** Will be called when HT retrieves item data for an item whose item data is asynchronously being loaded. * Can be used to create placeholder data to use for rendering the tree item while it is loaded. If not defined, * the tree item data will be null. */ createLoadingItemData?: () => T; setLoadingItemData?: SetStateFn<string[]>; setLoadingItemChildrens?: SetStateFn<string[]>; onLoadedItem?: (itemId: string, item: T) => void; onLoadedChildren?: (itemId: string, childrenIds: string[]) => void; }; treeInstance: SyncDataLoaderFeatureDef<T>["treeInstance"] & { /** @deprecated use loadItemData instead */ waitForItemDataLoaded: (itemId: string) => Promise<void>; /** @deprecated use loadChildrenIds instead */ waitForItemChildrenLoaded: (itemId: string) => Promise<void>; loadItemData: (itemId: string) => Promise<T>; loadChildrenIds: (itemId: string) => Promise<string[]>; }; itemInstance: SyncDataLoaderFeatureDef<T>["itemInstance"] & { /** Invalidate fetched data for item, and triggers a refetch and subsequent rerender if the item is visible * @param optimistic If true, the item will not trigger a state update on `loadingItemData`, and * the tree will continue to display the old data until the new data has loaded. */ invalidateItemData: (optimistic?: boolean) => Promise<void>; /** Invalidate fetched children ids for item, and triggers a refetch and subsequent rerender if the item is visible * @param optimistic If true, the item will not trigger a state update on `loadingItemChildrens`, and * the tree will continue to display the old data until the new data has loaded. */ invalidateChildrenIds: (optimistic?: boolean) => Promise<void>; /** Set to undefined to clear cache without triggering automatic refetch. Use @invalidateItemData to clear and triggering refetch. */ updateCachedData: (data: T | undefined) => void; updateCachedChildrenIds: (childrenIds: string[]) => void; isLoading: () => boolean; }; hotkeys: SyncDataLoaderFeatureDef<T>["hotkeys"]; }; interface SearchFeatureDataRef<T = any> extends HotkeysCoreDataRef { matchingItems: ItemInstance<T>[]; searchInput: HTMLInputElement | null; } type SearchFeatureDef<T> = { state: { search: string | null; }; config: { setSearch?: SetStateFn<string | null>; onOpenSearch?: () => void; onCloseSearch?: () => void; isSearchMatchingItem?: (search: string, item: ItemInstance<T>) => boolean; }; treeInstance: { setSearch: (search: string | null) => void; openSearch: (initialValue?: string) => void; closeSearch: () => void; isSearchOpen: () => boolean; getSearchValue: () => string; registerSearchInputElement: (element: HTMLInputElement | null) => void; getSearchInputElement: () => HTMLInputElement | null; getSearchInputElementProps: () => any; getSearchMatchingItems: () => ItemInstance<T>[]; }; itemInstance: { isMatchingSearch: () => boolean; }; hotkeys: "openSearch" | "closeSearch" | "submitSearch" | "nextSearchItem" | "previousSearchItem"; }; type RenamingFeatureDef<T> = { state: { renamingItem?: string | null; renamingValue?: string; }; config: { setRenamingItem?: SetStateFn<string | null | undefined>; setRenamingValue?: SetStateFn<string | undefined>; canRename?: (item: ItemInstance<T>) => boolean; onRename?: (item: ItemInstance<T>, value: string) => void; }; treeInstance: { getRenamingItem: () => ItemInstance<T> | null; getRenamingValue: () => string; abortRenaming: () => void; completeRenaming: () => void; isRenamingItem: () => boolean; }; itemInstance: { getRenameInputProps: () => any; canRename: () => boolean; isRenaming: () => boolean; startRenaming: () => void; }; hotkeys: "renameItem" | "abortRenaming" | "completeRenaming"; }; interface ExpandAllDataRef { } type ExpandAllFeatureDef = { state: {}; config: {}; treeInstance: { expandAll: (cancelToken?: { current: boolean; }) => Promise<void>; collapseAll: () => void; }; itemInstance: { expandAll: (cancelToken?: { current: boolean; }) => Promise<void>; collapseAll: () => void; }; hotkeys: "expandSelected" | "collapseSelected"; }; interface PropMemoizationDataRef { memo?: { tree?: Record<string, any>; item?: Record<string, any>; search?: Record<string, any>; rename?: Record<string, any>; }; } type PropMemoizationFeatureDef = { state: {}; config: {}; treeInstance: {}; itemInstance: {}; hotkeys: never; }; interface KDndDataRef { kDndDataTransfer: DataTransfer | undefined; } declare enum AssistiveDndState { None = 0, Started = 1, Dragging = 2, Completed = 3, Aborted = 4 } type KeyboardDragAndDropFeatureDef<T> = { state: { assistiveDndState?: AssistiveDndState | null; }; config: { setAssistiveDndState?: SetStateFn<AssistiveDndState | undefined | null>; onStartKeyboardDrag?: (items: ItemInstance<T>[]) => void; }; treeInstance: { startKeyboardDrag: (items: ItemInstance<T>[]) => void; startKeyboardDragOnForeignObject: (dataTransfer: DataTransfer) => void; stopKeyboardDrag: () => void; }; itemInstance: {}; hotkeys: "startDrag" | "cancelDrag" | "completeDrag" | "dragUp" | "dragDown"; }; declare enum CheckedState { Checked = "checked", Unchecked = "unchecked", Indeterminate = "indeterminate" } type CheckboxesFeatureDef<T> = { state: { checkedItems: string[]; loadingCheckPropagationItems: string[]; }; config: { setCheckedItems?: SetStateFn<string[]>; setLoadingCheckPropagationItems?: SetStateFn<string[]>; canCheckFolders?: boolean; propagateCheckedState?: boolean; }; treeInstance: { setCheckedItems: (checkedItems: string[]) => void; }; itemInstance: { /** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not, * this will return immediately. */ setChecked: () => Promise<void>; /** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not, * this will return immediately. */ setUnchecked: () => Promise<void>; /** Will recursively load descendants if propagateCheckedState=true and async data loader is used. If not, * this will return immediately. */ toggleCheckedState: () => Promise<void>; getCheckedState: () => CheckedState; getCheckboxProps: () => Record<string, any>; isLoadingCheckPropagation: () => boolean; }; hotkeys: never; }; type Updater<T> = T | ((old: T) => T); type SetStateFn<T> = (updaterOrValue: Updater<T>) => void; type FeatureDef = { state: any; config: any; treeInstance: any; itemInstance: any; hotkeys: string; }; type EmptyFeatureDef = { state: {}; config: {}; treeInstance: {}; itemInstance: {}; hotkeys: never; }; type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never; type MergedFeatures<F extends FeatureDef> = { state: UnionToIntersection<F["state"]>; config: UnionToIntersection<F["config"]>; treeInstance: UnionToIntersection<F["treeInstance"]>; itemInstance: UnionToIntersection<F["itemInstance"]>; hotkeys: F["hotkeys"]; }; type RegisteredFeatures<T> = MainFeatureDef<T> | TreeFeatureDef<T> | SelectionFeatureDef<T> | CheckboxesFeatureDef<T> | DragAndDropFeatureDef<T> | KeyboardDragAndDropFeatureDef<T> | HotkeysCoreFeatureDef<T> | SyncDataLoaderFeatureDef<T> | AsyncDataLoaderFeatureDef<T> | SearchFeatureDef<T> | RenamingFeatureDef<T> | ExpandAllFeatureDef | PropMemoizationFeatureDef; type TreeStateType<T> = MergedFeatures<RegisteredFeatures<T>>["state"]; interface TreeState<T> extends TreeStateType<T> { } type TreeConfigType<T> = MergedFeatures<RegisteredFeatures<T>>["config"]; interface TreeConfig<T> extends TreeConfigType<T> { } type TreeInstanceType<T> = MergedFeatures<RegisteredFeatures<T>>["treeInstance"]; interface TreeInstance<T> extends TreeInstanceType<T> { } type ItemInstanceType<T> = MergedFeatures<RegisteredFeatures<T>>["itemInstance"]; interface ItemInstance<T> extends ItemInstanceType<T> { } type HotkeyName = MergedFeatures<RegisteredFeatures<any>>["hotkeys"]; type HotkeysConfig<T> = Record<HotkeyName, HotkeyConfig<T>>; type CustomHotkeysConfig<T> = Partial<Record<HotkeyName | `custom${string}`, Partial<HotkeyConfig<T>>>>; type MayReturnNull<T extends (...x: any[]) => any> = (...args: Parameters<T>) => ReturnType<T> | null; type ItemInstanceOpts<T, Key extends keyof ItemInstance<any>> = { item: ItemInstance<T>; tree: TreeInstance<T>; itemId: string; prev?: MayReturnNull<ItemInstance<T>[Key]>; }; type TreeInstanceOpts<Key extends keyof TreeInstance<any>> = { tree: TreeInstance<any>; prev?: MayReturnNull<TreeInstance<any>[Key]>; }; type FeatureImplementation<T = any> = { key?: string; deps?: string[]; overwrites?: string[]; stateHandlerNames?: Partial<Record<keyof TreeState<T>, keyof TreeConfig<T>>>; getInitialState?: (initialState: Partial<TreeState<T>>, tree: TreeInstance<T>) => Partial<TreeState<T>>; getDefaultConfig?: (defaultConfig: Partial<TreeConfig<T>>, tree: TreeInstance<T>) => Partial<TreeConfig<T>>; treeInstance?: { [key in keyof TreeInstance<T>]?: (opts: TreeInstanceOpts<key>, ...args: Parameters<TreeInstance<T>[key]>) => void; }; itemInstance?: { [key in keyof ItemInstance<T>]?: (opts: ItemInstanceOpts<T, key>, ...args: Parameters<ItemInstance<T>[key]>) => void; }; onTreeMount?: (instance: TreeInstance<T>, treeElement: HTMLElement) => void; onTreeUnmount?: (instance: TreeInstance<T>, treeElement: HTMLElement) => void; onItemMount?: (instance: ItemInstance<T>, itemElement: HTMLElement, tree: TreeInstance<T>) => void; onItemUnmount?: (instance: ItemInstance<T>, itemElement: HTMLElement, tree: TreeInstance<T>) => void; hotkeys?: Partial<HotkeysConfig<T>>; }; declare const createTree: <T>(initialConfig: TreeConfig<T>) => TreeInstance<T>; declare const selectionFeature: FeatureImplementation; declare const checkboxesFeature: FeatureImplementation; declare const hotkeysCoreFeature: FeatureImplementation; declare const asyncDataLoaderFeature: FeatureImplementation; declare const syncDataLoaderFeature: FeatureImplementation; declare const dragAndDropFeature: FeatureImplementation; declare const keyboardDragAndDropFeature: FeatureImplementation; declare const searchFeature: FeatureImplementation; declare const renamingFeature: FeatureImplementation; declare const expandAllFeature: FeatureImplementation; declare const propMemoizationFeature: FeatureImplementation; declare const createOnDropHandler: <T>(onChangeChildren: (item: ItemInstance<T>, newChildren: string[]) => void) => (items: ItemInstance<T>[], target: DragTarget<T>) => Promise<void>; declare const insertItemsAtTarget: <T>(itemIds: string[], target: DragTarget<T>, onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => Promise<void> | void) => Promise<void>; declare const removeItemsFromParents: <T>(movedItems: ItemInstance<T>[], onChangeChildren: (item: ItemInstance<T>, newChildrenIds: string[]) => void | Promise<void>) => Promise<void>; declare const buildProxiedInstance: InstanceBuilder; declare const buildStaticInstance: InstanceBuilder; declare function makeStateUpdater<K extends keyof TreeState<any>>(key: K, instance: unknown): SetStateFn<TreeState<any>[K]>; declare const isOrderedDragTarget: <T>(dragTarget: DragTarget<T>) => dragTarget is { item: ItemInstance<T>; childIndex: number; insertionIndex: number; dragLineIndex: number; dragLineLevel: number; }; export { AssistiveDndState, type AsyncDataLoaderDataRef, type AsyncDataLoaderFeatureDef, type CheckboxesFeatureDef, CheckedState, type CustomHotkeysConfig, type DndDataRef, type DndState, type DragAndDropFeatureDef, type DragLineData, type DragTarget, DragTargetPosition, type EmptyFeatureDef, type ExpandAllDataRef, type ExpandAllFeatureDef, type FeatureDef, type FeatureImplementation, type HotkeyConfig, type HotkeyName, type HotkeysConfig, type HotkeysCoreDataRef, type HotkeysCoreFeatureDef, type InstanceBuilder, type ItemInstance, type ItemInstanceOpts, type ItemMeta, type KDndDataRef, type KeyboardDragAndDropFeatureDef, type MainFeatureDef, type PropMemoizationDataRef, type PropMemoizationFeatureDef, type RegisteredFeatures, type RenamingFeatureDef, type SearchFeatureDataRef, type SearchFeatureDef, type SelectionFeatureDef, type SetStateFn, type SyncDataLoaderFeatureDef, type TreeConfig, type TreeDataLoader, type TreeFeatureDef, type TreeInstance, type TreeInstanceOpts, type TreeItemDataRef, type TreeState, type Updater, asyncDataLoaderFeature, buildProxiedInstance, buildStaticInstance, checkboxesFeature, createOnDropHandler, createTree, dragAndDropFeature, expandAllFeature, hotkeysCoreFeature, insertItemsAtTarget, isOrderedDragTarget, keyboardDragAndDropFeature, makeStateUpdater, propMemoizationFeature, removeItemsFromParents, renamingFeature, searchFeature, selectionFeature, syncDataLoaderFeature };