UNPKG

@headless-tree/core

Version:

The definitive tree component for the Web

1,422 lines (1,407 loc) 84.4 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/utils.ts var memo = (deps, fn) => { let value; let oldDeps = null; return (...a) => { const newDeps = deps(...a); if (!value) { value = fn(...newDeps); oldDeps = newDeps; return value; } const match = oldDeps && oldDeps.length === newDeps.length && !oldDeps.some((dep, i) => dep !== newDeps[i]); if (match) { return value; } value = fn(...newDeps); oldDeps = newDeps; return value; }; }; function functionalUpdate(updater, input) { return typeof updater === "function" ? updater(input) : updater; } function makeStateUpdater(key, instance) { return (updater) => { instance.setState((old) => { return __spreadProps(__spreadValues({}, old), { [key]: functionalUpdate(updater, old[key]) }); }); }; } var poll = (fn, interval = 100, timeout = 1e3) => new Promise((resolve) => { let clear; const i = setInterval(() => { if (fn()) { resolve(); clearInterval(i); clearTimeout(clear); } }, interval); clear = setTimeout(() => { clearInterval(i); }, timeout); }); // src/utilities/errors.ts var prefix = "Headless Tree: "; var throwError = (message) => Error(prefix + message); var logWarning = (message) => console.warn(prefix + message); // src/features/tree/feature.ts var treeFeature = { key: "tree", getInitialState: (initialState) => __spreadValues({ expandedItems: [], focusedItem: null }, initialState), getDefaultConfig: (defaultConfig, tree) => __spreadValues({ setExpandedItems: makeStateUpdater("expandedItems", tree), setFocusedItem: makeStateUpdater("focusedItem", tree) }, defaultConfig), stateHandlerNames: { expandedItems: "setExpandedItems", focusedItem: "setFocusedItem" }, treeInstance: { getItemsMeta: ({ tree }) => { const { rootItemId } = tree.getConfig(); const { expandedItems } = tree.getState(); const flatItems = []; const expandedItemsSet = new Set(expandedItems); const recursiveAdd = (itemId, path, level, setSize, posInSet) => { var _a; if (path.includes(itemId)) { logWarning(`Circular reference for ${path.join(".")}`); return; } flatItems.push({ itemId, level, index: flatItems.length, parentId: path.at(-1), setSize, posInSet }); if (expandedItemsSet.has(itemId)) { const children2 = (_a = tree.retrieveChildrenIds(itemId)) != null ? _a : []; let i2 = 0; for (const childId of children2) { recursiveAdd( childId, path.concat(itemId), level + 1, children2.length, i2++ ); } } }; const children = tree.retrieveChildrenIds(rootItemId); let i = 0; for (const itemId of children) { recursiveAdd(itemId, [rootItemId], 0, children.length, i++); } return flatItems; }, getFocusedItem: ({ tree }) => { var _a; const focusedItemId = tree.getState().focusedItem; return (_a = focusedItemId !== null ? tree.getItemInstance(focusedItemId) : null) != null ? _a : tree.getItems()[0]; }, getRootItem: ({ tree }) => { const { rootItemId } = tree.getConfig(); return tree.getItemInstance(rootItemId); }, focusNextItem: ({ tree }) => { var _a; const focused = tree.getFocusedItem().getItemMeta(); if (!focused) return; const nextIndex = Math.min(focused.index + 1, tree.getItems().length - 1); (_a = tree.getItems()[nextIndex]) == null ? void 0 : _a.setFocused(); }, focusPreviousItem: ({ tree }) => { var _a; const focused = tree.getFocusedItem().getItemMeta(); if (!focused) return; const nextIndex = Math.max(focused.index - 1, 0); (_a = tree.getItems()[nextIndex]) == null ? void 0 : _a.setFocused(); }, updateDomFocus: ({ tree }) => { setTimeout(() => __async(null, null, function* () { var _a, _b; const focusedItem = tree.getFocusedItem(); (_b = (_a = tree.getConfig()).scrollToItem) == null ? void 0 : _b.call(_a, focusedItem); yield poll(() => focusedItem.getElement() !== null, 20); const focusedElement = focusedItem.getElement(); if (!focusedElement) return; focusedElement.focus(); })); }, getContainerProps: ({ prev, tree }, treeLabel) => __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), { role: "tree", "aria-label": treeLabel != null ? treeLabel : "", ref: tree.registerElement }), // relevant for hotkeys of this feature isSearchOpen: () => false }, itemInstance: { scrollTo: (_0, _1) => __async(null, [_0, _1], function* ({ tree, item }, scrollIntoViewArg) { var _a, _b, _c; (_b = (_a = tree.getConfig()).scrollToItem) == null ? void 0 : _b.call(_a, item); yield poll(() => item.getElement() !== null, 20); (_c = item.getElement()) == null ? void 0 : _c.scrollIntoView(scrollIntoViewArg); }), getId: ({ itemId }) => itemId, getKey: ({ itemId }) => itemId, // TODO apply to all stories to use getProps: ({ item, prev }) => { const itemMeta = item.getItemMeta(); return __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), { ref: item.registerElement, role: "treeitem", "aria-setsize": itemMeta.setSize, "aria-posinset": itemMeta.posInSet + 1, "aria-selected": "false", "aria-label": item.getItemName(), "aria-level": itemMeta.level + 1, tabIndex: item.isFocused() ? 0 : -1, onClick: (e) => { item.setFocused(); item.primaryAction(); if (e.ctrlKey || e.shiftKey || e.metaKey) { return; } if (!item.isFolder()) { return; } if (item.isExpanded()) { item.collapse(); } else { item.expand(); } } }); }, expand: ({ tree, item, itemId }) => { var _a; if (!item.isFolder()) { return; } if ((_a = tree.getState().loadingItemChildrens) == null ? void 0 : _a.includes(itemId)) { return; } tree.applySubStateUpdate("expandedItems", (expandedItems) => [ ...expandedItems, itemId ]); tree.rebuildTree(); }, collapse: ({ tree, item, itemId }) => { if (!item.isFolder()) { return; } tree.applySubStateUpdate( "expandedItems", (expandedItems) => expandedItems.filter((id) => id !== itemId) ); tree.rebuildTree(); }, getItemData: ({ tree, itemId }) => tree.retrieveItemData(itemId), equals: ({ item }, other) => item.getId() === (other == null ? void 0 : other.getId()), isExpanded: ({ tree, itemId }) => tree.getState().expandedItems.includes(itemId), isDescendentOf: ({ item }, parentId) => { const parent = item.getParent(); return Boolean( (parent == null ? void 0 : parent.getId()) === parentId || (parent == null ? void 0 : parent.isDescendentOf(parentId)) ); }, isFocused: ({ tree, item, itemId }) => tree.getState().focusedItem === itemId || tree.getState().focusedItem === null && item.getItemMeta().index === 0, isFolder: ({ tree, item }) => item.getItemMeta().level === -1 || tree.getConfig().isItemFolder(item), getItemName: ({ tree, item }) => { const config = tree.getConfig(); return config.getItemName(item); }, setFocused: ({ tree, itemId }) => { tree.applySubStateUpdate("focusedItem", itemId); }, primaryAction: ({ tree, item }) => { var _a, _b; return (_b = (_a = tree.getConfig()).onPrimaryAction) == null ? void 0 : _b.call(_a, item); }, getParent: ({ tree, item }) => item.getItemMeta().parentId ? tree.getItemInstance(item.getItemMeta().parentId) : void 0, getIndexInParent: ({ item }) => item.getItemMeta().posInSet, getChildren: ({ tree, itemId }) => tree.retrieveChildrenIds(itemId).map((id) => tree.getItemInstance(id)), getTree: ({ tree }) => tree, getItemAbove: ({ tree, item }) => tree.getItems()[item.getItemMeta().index - 1], getItemBelow: ({ tree, item }) => tree.getItems()[item.getItemMeta().index + 1] }, hotkeys: { focusNextItem: { hotkey: "ArrowDown", canRepeat: true, preventDefault: true, isEnabled: (tree) => { var _a, _b; return !((_b = (_a = tree.isSearchOpen) == null ? void 0 : _a.call(tree)) != null ? _b : false) && !tree.getState().dnd; }, // TODO what happens when the feature doesnt exist? proxy method still claims to exist handler: (e, tree) => { tree.focusNextItem(); tree.updateDomFocus(); } }, focusPreviousItem: { hotkey: "ArrowUp", canRepeat: true, preventDefault: true, isEnabled: (tree) => { var _a, _b; return !((_b = (_a = tree.isSearchOpen) == null ? void 0 : _a.call(tree)) != null ? _b : false) && !tree.getState().dnd; }, handler: (e, tree) => { tree.focusPreviousItem(); tree.updateDomFocus(); } }, expandOrDown: { hotkey: "ArrowRight", canRepeat: true, handler: (e, tree) => { const item = tree.getFocusedItem(); if (item.isExpanded() || !item.isFolder()) { tree.focusNextItem(); tree.updateDomFocus(); } else { item.expand(); } } }, collapseOrUp: { hotkey: "ArrowLeft", canRepeat: true, handler: (e, tree) => { var _a; const item = tree.getFocusedItem(); if ((!item.isExpanded() || !item.isFolder()) && item.getItemMeta().level !== 0) { (_a = item.getParent()) == null ? void 0 : _a.setFocused(); tree.updateDomFocus(); } else { item.collapse(); } } }, focusFirstItem: { hotkey: "Home", handler: (e, tree) => { var _a; (_a = tree.getItems()[0]) == null ? void 0 : _a.setFocused(); tree.updateDomFocus(); } }, focusLastItem: { hotkey: "End", handler: (e, tree) => { var _a; (_a = tree.getItems()[tree.getItems().length - 1]) == null ? void 0 : _a.setFocused(); tree.updateDomFocus(); } } } }; // src/core/build-static-instance.ts var buildStaticInstance = (features, instanceType, buildOpts) => { const instance = {}; const finalize = () => { const opts = buildOpts(instance); featureLoop: for (let i = 0; i < features.length; i++) { const definition = features[i][instanceType]; if (!definition) continue featureLoop; methodLoop: for (const [key, method] of Object.entries(definition)) { if (!method) continue methodLoop; const prev = instance[key]; instance[key] = (...args) => { return method(__spreadProps(__spreadValues({}, opts), { prev }), ...args); }; } } }; return [instance, finalize]; }; // src/core/create-tree.ts var verifyFeatures = (features) => { var _a; const loadedFeatures = features == null ? void 0 : features.map((feature) => feature.key); for (const feature of features != null ? features : []) { const missingDependency = (_a = feature.deps) == null ? void 0 : _a.find( (dep) => !(loadedFeatures == null ? void 0 : loadedFeatures.includes(dep)) ); if (missingDependency) { throw throwError(`${feature.key} needs ${missingDependency}`); } } }; var exhaustiveSort = (arr, compareFn) => { const n = arr.length; for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (compareFn(arr[j], arr[i]) < 0) { [arr[i], arr[j]] = [arr[j], arr[i]]; } } } return arr; }; var compareFeatures = (originalOrder) => (feature1, feature2) => { var _a, _b; if (feature2.key && ((_a = feature1.overwrites) == null ? void 0 : _a.includes(feature2.key))) { return 1; } if (feature1.key && ((_b = feature2.overwrites) == null ? void 0 : _b.includes(feature1.key))) { return -1; } return originalOrder.indexOf(feature1) - originalOrder.indexOf(feature2); }; var sortFeatures = (features = []) => exhaustiveSort(features, compareFeatures(features)); var createTree = (initialConfig) => { var _a, _b, _c, _d; const buildInstance = (_a = initialConfig.instanceBuilder) != null ? _a : buildStaticInstance; const additionalFeatures = [ treeFeature, ...sortFeatures(initialConfig.features) ]; verifyFeatures(additionalFeatures); const features = [...additionalFeatures]; const [treeInstance, finalizeTree] = buildInstance( features, "treeInstance", (tree) => ({ tree }) ); let state = additionalFeatures.reduce( (acc, feature) => { var _a2, _b2; return (_b2 = (_a2 = feature.getInitialState) == null ? void 0 : _a2.call(feature, acc, treeInstance)) != null ? _b2 : acc; }, (_c = (_b = initialConfig.initialState) != null ? _b : initialConfig.state) != null ? _c : {} ); let config = additionalFeatures.reduce( (acc, feature) => { var _a2, _b2; return (_b2 = (_a2 = feature.getDefaultConfig) == null ? void 0 : _a2.call(feature, acc, treeInstance)) != null ? _b2 : acc; }, initialConfig ); const stateHandlerNames = additionalFeatures.reduce( (acc, feature) => __spreadValues(__spreadValues({}, acc), feature.stateHandlerNames), {} ); let treeElement; const treeDataRef = { current: {} }; let rebuildScheduled = false; const itemInstancesMap = {}; let itemInstances = []; const itemElementsMap = {}; const itemDataRefs = {}; let itemMetaMap = {}; const hotkeyPresets = {}; const rebuildItemMeta = () => { itemInstances = []; itemMetaMap = {}; const [rootInstance, finalizeRootInstance] = buildInstance( features, "itemInstance", (item) => ({ item, tree: treeInstance, itemId: config.rootItemId }) ); finalizeRootInstance(); itemInstancesMap[config.rootItemId] = rootInstance; itemMetaMap[config.rootItemId] = { itemId: config.rootItemId, index: -1, parentId: null, level: -1, posInSet: 0, setSize: 1 }; for (const item of treeInstance.getItemsMeta()) { itemMetaMap[item.itemId] = item; if (!itemInstancesMap[item.itemId]) { const [instance, finalizeInstance] = buildInstance( features, "itemInstance", (instance2) => ({ item: instance2, tree: treeInstance, itemId: item.itemId }) ); finalizeInstance(); itemInstancesMap[item.itemId] = instance; itemInstances.push(instance); } else { itemInstances.push(itemInstancesMap[item.itemId]); } } rebuildScheduled = false; }; const eachFeature = (fn) => { for (const feature of additionalFeatures) { fn(feature); } }; const mainFeature = { key: "main", treeInstance: { getState: () => state, setState: ({}, updater) => { var _a2; (_a2 = config.setState) == null ? void 0 : _a2.call(config, state); }, setMounted: ({}, isMounted) => { var _a2; const ref = treeDataRef.current; ref.isMounted = isMounted; if (isMounted) { (_a2 = ref.waitingForMount) == null ? void 0 : _a2.forEach((cb) => cb()); ref.waitingForMount = []; } }, applySubStateUpdate: ({}, stateName, updater) => { var _a2; const apply = () => { state[stateName] = typeof updater === "function" ? updater(state[stateName]) : updater; const externalStateSetter = config[stateHandlerNames[stateName]]; externalStateSetter == null ? void 0 : externalStateSetter(state[stateName]); }; const ref = treeDataRef.current; if (ref.isMounted) { apply(); } else { (_a2 = ref.waitingForMount) != null ? _a2 : ref.waitingForMount = []; ref.waitingForMount.push(apply); } }, // TODO rebuildSubTree: (itemId: string) => void; rebuildTree: () => { var _a2, _b2; const ref = treeDataRef.current; if (ref.isMounted) { rebuildItemMeta(); (_a2 = config.setState) == null ? void 0 : _a2.call(config, state); } else { (_b2 = ref.waitingForMount) != null ? _b2 : ref.waitingForMount = []; ref.waitingForMount.push(() => { var _a3; rebuildItemMeta(); (_a3 = config.setState) == null ? void 0 : _a3.call(config, state); }); } }, scheduleRebuildTree: () => { rebuildScheduled = true; }, getConfig: () => config, setConfig: (_, updater) => { var _a2, _b2, _c2; const newConfig = typeof updater === "function" ? updater(config) : updater; const hasChangedExpandedItems = ((_a2 = newConfig.state) == null ? void 0 : _a2.expandedItems) && ((_b2 = newConfig.state) == null ? void 0 : _b2.expandedItems) !== state.expandedItems; config = newConfig; if (newConfig.state) { state = __spreadValues(__spreadValues({}, state), newConfig.state); } if (hasChangedExpandedItems) { rebuildItemMeta(); (_c2 = config.setState) == null ? void 0 : _c2.call(config, state); } }, getItemInstance: ({}, itemId) => { const existingInstance = itemInstancesMap[itemId]; if (!existingInstance) { const [instance, finalizeInstance] = buildInstance( features, "itemInstance", (instance2) => ({ item: instance2, tree: treeInstance, itemId }) ); finalizeInstance(); return instance; } return existingInstance; }, getItems: () => { if (rebuildScheduled) rebuildItemMeta(); return itemInstances; }, registerElement: ({}, element) => { if (treeElement === element) { return; } if (treeElement && !element) { eachFeature( (feature) => { var _a2; return (_a2 = feature.onTreeUnmount) == null ? void 0 : _a2.call(feature, treeInstance, treeElement); } ); } else if (!treeElement && element) { eachFeature( (feature) => { var _a2; return (_a2 = feature.onTreeMount) == null ? void 0 : _a2.call(feature, treeInstance, element); } ); } treeElement = element; }, getElement: () => treeElement, getDataRef: () => treeDataRef, getHotkeyPresets: () => hotkeyPresets }, itemInstance: { registerElement: ({ itemId, item }, element) => { if (itemElementsMap[itemId] === element) { return; } const oldElement = itemElementsMap[itemId]; if (oldElement && !element) { eachFeature( (feature) => { var _a2; return (_a2 = feature.onItemUnmount) == null ? void 0 : _a2.call(feature, item, oldElement, treeInstance); } ); } else if (!oldElement && element) { eachFeature( (feature) => { var _a2; return (_a2 = feature.onItemMount) == null ? void 0 : _a2.call(feature, item, element, treeInstance); } ); } itemElementsMap[itemId] = element; }, getElement: ({ itemId }) => itemElementsMap[itemId], // eslint-disable-next-line no-return-assign getDataRef: ({ itemId }) => { var _a2; return (_a2 = itemDataRefs[itemId]) != null ? _a2 : itemDataRefs[itemId] = { current: {} }; }, getItemMeta: ({ itemId }) => { var _a2; return (_a2 = itemMetaMap[itemId]) != null ? _a2 : { itemId, parentId: null, level: -1, index: -1, posInSet: 0, setSize: 1 }; } } }; features.unshift(mainFeature); for (const feature of features) { Object.assign(hotkeyPresets, (_d = feature.hotkeys) != null ? _d : {}); } finalizeTree(); return treeInstance; }; // src/features/drag-and-drop/types.ts var DragTargetPosition = /* @__PURE__ */ ((DragTargetPosition2) => { DragTargetPosition2["Top"] = "top"; DragTargetPosition2["Bottom"] = "bottom"; DragTargetPosition2["Item"] = "item"; return DragTargetPosition2; })(DragTargetPosition || {}); // src/features/keyboard-drag-and-drop/types.ts var AssistiveDndState = /* @__PURE__ */ ((AssistiveDndState2) => { AssistiveDndState2[AssistiveDndState2["None"] = 0] = "None"; AssistiveDndState2[AssistiveDndState2["Started"] = 1] = "Started"; AssistiveDndState2[AssistiveDndState2["Dragging"] = 2] = "Dragging"; AssistiveDndState2[AssistiveDndState2["Completed"] = 3] = "Completed"; AssistiveDndState2[AssistiveDndState2["Aborted"] = 4] = "Aborted"; return AssistiveDndState2; })(AssistiveDndState || {}); // src/features/checkboxes/types.ts var CheckedState = /* @__PURE__ */ ((CheckedState2) => { CheckedState2["Checked"] = "checked"; CheckedState2["Unchecked"] = "unchecked"; CheckedState2["Indeterminate"] = "indeterminate"; return CheckedState2; })(CheckedState || {}); // src/features/selection/feature.ts var selectionFeature = { key: "selection", getInitialState: (initialState) => __spreadValues({ selectedItems: [] }, initialState), getDefaultConfig: (defaultConfig, tree) => __spreadValues({ setSelectedItems: makeStateUpdater("selectedItems", tree) }, defaultConfig), stateHandlerNames: { selectedItems: "setSelectedItems" }, treeInstance: { setSelectedItems: ({ tree }, selectedItems) => { tree.applySubStateUpdate("selectedItems", selectedItems); }, getSelectedItems: ({ tree }) => { return tree.getState().selectedItems.map(tree.getItemInstance); } }, itemInstance: { select: ({ tree, itemId }) => { const { selectedItems } = tree.getState(); tree.setSelectedItems( selectedItems.includes(itemId) ? selectedItems : [...selectedItems, itemId] ); }, deselect: ({ tree, itemId }) => { const { selectedItems } = tree.getState(); tree.setSelectedItems(selectedItems.filter((id) => id !== itemId)); }, isSelected: ({ tree, itemId }) => { const { selectedItems } = tree.getState(); return selectedItems.includes(itemId); }, selectUpTo: ({ tree, item }, ctrl) => { const indexA = item.getItemMeta().index; const indexB = tree.getFocusedItem().getItemMeta().index; const [a, b] = indexA < indexB ? [indexA, indexB] : [indexB, indexA]; const newSelectedItems = tree.getItems().slice(a, b + 1).map((treeItem) => treeItem.getItemMeta().itemId); if (!ctrl) { tree.setSelectedItems(newSelectedItems); return; } const { selectedItems } = tree.getState(); const uniqueSelectedItems = [ .../* @__PURE__ */ new Set([...selectedItems, ...newSelectedItems]) ]; tree.setSelectedItems(uniqueSelectedItems); }, toggleSelect: ({ item }) => { if (item.isSelected()) { item.deselect(); } else { item.select(); } }, getProps: ({ tree, item, prev }) => __spreadProps(__spreadValues({}, prev == null ? void 0 : prev()), { "aria-selected": item.isSelected() ? "true" : "false", onClick: (e) => { var _a, _b; if (e.shiftKey) { item.selectUpTo(e.ctrlKey || e.metaKey); } else if (e.ctrlKey || e.metaKey) { item.toggleSelect(); } else { tree.setSelectedItems([item.getItemMeta().itemId]); } (_b = (_a = prev == null ? void 0 : prev()) == null ? void 0 : _a.onClick) == null ? void 0 : _b.call(_a, e); } }) }, hotkeys: { // setSelectedItem: { // hotkey: "space", // handler: (e, tree) => { // tree.setSelectedItems([tree.getFocusedItem().getId()]); // }, // }, toggleSelectedItem: { hotkey: "Control+Space", preventDefault: true, handler: (_, tree) => { tree.getFocusedItem().toggleSelect(); } }, selectUpwards: { hotkey: "Shift+ArrowUp", handler: (e, tree) => { const focused = tree.getFocusedItem(); const above = focused.getItemAbove(); if (!above) return; if (focused.isSelected() && above.isSelected()) { focused.deselect(); } else { above.select(); } above.setFocused(); tree.updateDomFocus(); } }, selectDownwards: { hotkey: "Shift+ArrowDown", handler: (e, tree) => { const focused = tree.getFocusedItem(); const below = focused.getItemBelow(); if (!below) return; if (focused.isSelected() && below.isSelected()) { focused.deselect(); } else { below.select(); } below.setFocused(); tree.updateDomFocus(); } }, selectAll: { hotkey: "Control+KeyA", preventDefault: true, handler: (e, tree) => { tree.setSelectedItems(tree.getItems().map((item) => item.getId())); } } } }; // src/features/checkboxes/feature.ts var getAllLoadedDescendants = (tree, itemId, includeFolders = false) => { if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) { return [itemId]; } const descendants = tree.retrieveChildrenIds(itemId, true).map((child) => getAllLoadedDescendants(tree, child, includeFolders)).flat(); return includeFolders ? [itemId, ...descendants] : descendants; }; var getAllDescendants = (tree, itemId, includeFolders = false) => __async(null, null, function* () { yield tree.loadItemData(itemId); if (!tree.getConfig().isItemFolder(tree.getItemInstance(itemId))) { return [itemId]; } const childrenIds = yield tree.loadChildrenIds(itemId); const descendants = (yield Promise.all( childrenIds.map( (child) => getAllDescendants(tree, child, includeFolders) ) )).flat(); return includeFolders ? [itemId, ...descendants] : descendants; }); var withLoadingState = (tree, itemId, callback) => __async(null, null, function* () { tree.applySubStateUpdate("loadingCheckPropagationItems", (items) => [ ...items, itemId ]); try { yield callback(); } finally { tree.applySubStateUpdate( "loadingCheckPropagationItems", (items) => items.filter((id) => id !== itemId) ); } }); var checkboxesFeature = { key: "checkboxes", overwrites: ["selection"], getInitialState: (initialState) => __spreadValues({ checkedItems: [], loadingCheckPropagationItems: [] }, initialState), getDefaultConfig: (defaultConfig, tree) => { var _a, _b; const propagateCheckedState = (_a = defaultConfig.propagateCheckedState) != null ? _a : true; const canCheckFolders = (_b = defaultConfig.canCheckFolders) != null ? _b : !propagateCheckedState; return __spreadValues({ setCheckedItems: makeStateUpdater("checkedItems", tree), setLoadingCheckPropagationItems: makeStateUpdater( "loadingCheckPropagationItems", tree ), propagateCheckedState, canCheckFolders }, defaultConfig); }, stateHandlerNames: { checkedItems: "setCheckedItems", loadingCheckPropagationItems: "setLoadingCheckPropagationItems" }, treeInstance: { setCheckedItems: ({ tree }, checkedItems) => { tree.applySubStateUpdate("checkedItems", checkedItems); } }, itemInstance: { getCheckboxProps: ({ item }) => { const checkedState = item.getCheckedState(); return { onChange: item.toggleCheckedState, checked: checkedState === "checked" /* Checked */, ref: (r) => { if (r) { r.indeterminate = checkedState === "indeterminate" /* Indeterminate */; } } }; }, toggleCheckedState: (_0) => __async(null, [_0], function* ({ item }) { if (item.getCheckedState() === "checked" /* Checked */) { yield item.setUnchecked(); } else { yield item.setChecked(); } }), getCheckedState: ({ item, tree }) => { const { checkedItems } = tree.getState(); const { propagateCheckedState } = tree.getConfig(); const itemId = item.getId(); if (checkedItems.includes(itemId)) { return "checked" /* Checked */; } if (item.isFolder() && propagateCheckedState) { const descendants = getAllLoadedDescendants(tree, itemId); if (descendants.length === 0) return "unchecked" /* Unchecked */; if (descendants.every((d) => checkedItems.includes(d))) { return "checked" /* Checked */; } if (descendants.some((d) => checkedItems.includes(d))) { return "indeterminate" /* Indeterminate */; } } return "unchecked" /* Unchecked */; }, setChecked: (_0) => __async(null, [_0], function* ({ item, tree, itemId }) { yield withLoadingState(tree, itemId, () => __async(null, null, function* () { const { propagateCheckedState, canCheckFolders } = tree.getConfig(); if (item.isFolder() && propagateCheckedState) { const descendants = yield getAllDescendants( tree, itemId, canCheckFolders ); tree.applySubStateUpdate("checkedItems", (items) => [ ...items, ...descendants ]); } else if (!item.isFolder() || canCheckFolders) { tree.applySubStateUpdate("checkedItems", (items) => [ ...items, itemId ]); } })); }), setUnchecked: (_0) => __async(null, [_0], function* ({ item, tree, itemId }) { yield withLoadingState(tree, itemId, () => __async(null, null, function* () { const { propagateCheckedState, canCheckFolders } = tree.getConfig(); if (item.isFolder() && propagateCheckedState) { const descendants = yield getAllDescendants( tree, itemId, canCheckFolders ); tree.applySubStateUpdate( "checkedItems", (items) => items.filter((id) => !descendants.includes(id) && id !== itemId) ); } else { tree.applySubStateUpdate( "checkedItems", (items) => items.filter((id) => id !== itemId) ); } })); }) } }; // src/features/hotkeys-core/feature.ts var specialKeys = { // TODO:breaking deprecate auto-lowercase letter: /^Key[A-Z]$/, letterornumber: /^(Key[A-Z]|Digit[0-9])$/, plus: /^(NumpadAdd|Plus)$/, minus: /^(NumpadSubtract|Minus)$/, control: /^(ControlLeft|ControlRight)$/, shift: /^(ShiftLeft|ShiftRight)$/, metaorcontrol: /^(MetaLeft|MetaRight|ControlLeft|ControlRight)$/ }; var testHotkeyMatch = (pressedKeys, tree, hotkey) => { const supposedKeys = hotkey.hotkey.toLowerCase().split("+"); const doKeysMatch = supposedKeys.every((key) => { if (key in specialKeys) { return [...pressedKeys].some( (pressedKey) => specialKeys[key].test(pressedKey) ); } const pressedKeysLowerCase = [...pressedKeys].map((k) => k.toLowerCase()); if (pressedKeysLowerCase.includes(key.toLowerCase())) { return true; } if (pressedKeysLowerCase.includes(`key${key.toLowerCase()}`)) { return true; } return false; }); const isEnabled = !hotkey.isEnabled || hotkey.isEnabled(tree); const equalCounts = pressedKeys.size === supposedKeys.length; return doKeysMatch && isEnabled && equalCounts; }; var findHotkeyMatch = (pressedKeys, tree, config1, config2) => { var _a; return (_a = Object.entries(__spreadValues(__spreadValues({}, config1), config2)).find( ([, hotkey]) => testHotkeyMatch(pressedKeys, tree, hotkey) )) == null ? void 0 : _a[0]; }; var hotkeysCoreFeature = { key: "hotkeys-core", onTreeMount: (tree, element) => { const data = tree.getDataRef(); const keydown = (e) => { var _a, _b; const { ignoreHotkeysOnInputs, onTreeHotkey, hotkeys } = tree.getConfig(); if (e.target instanceof HTMLInputElement && ignoreHotkeysOnInputs) { return; } (_b = (_a = data.current).pressedKeys) != null ? _b : _a.pressedKeys = /* @__PURE__ */ new Set(); const newMatch = !data.current.pressedKeys.has(e.code); data.current.pressedKeys.add(e.code); const hotkeyName = findHotkeyMatch( data.current.pressedKeys, tree, tree.getHotkeyPresets(), hotkeys ); if (e.target instanceof HTMLInputElement) { data.current.pressedKeys.delete(e.code); } if (!hotkeyName) return; const hotkeyConfig = __spreadValues(__spreadValues({}, tree.getHotkeyPresets()[hotkeyName]), hotkeys == null ? void 0 : hotkeys[hotkeyName]); if (!hotkeyConfig) return; if (!hotkeyConfig.allowWhenInputFocused && e.target instanceof HTMLInputElement) return; if (!hotkeyConfig.canRepeat && !newMatch) return; if (hotkeyConfig.preventDefault) e.preventDefault(); hotkeyConfig.handler(e, tree); onTreeHotkey == null ? void 0 : onTreeHotkey(hotkeyName, e); }; const keyup = (e) => { var _a, _b; (_b = (_a = data.current).pressedKeys) != null ? _b : _a.pressedKeys = /* @__PURE__ */ new Set(); data.current.pressedKeys.delete(e.code); }; const reset = () => { data.current.pressedKeys = /* @__PURE__ */ new Set(); }; element.addEventListener("keydown", keydown); document.addEventListener("keyup", keyup); window.addEventListener("focus", reset); data.current.keydownHandler = keydown; data.current.keyupHandler = keyup; data.current.resetHandler = reset; }, onTreeUnmount: (tree, element) => { const data = tree.getDataRef(); if (data.current.keyupHandler) { document.removeEventListener("keyup", data.current.keyupHandler); delete data.current.keyupHandler; } if (data.current.keydownHandler) { element.removeEventListener("keydown", data.current.keydownHandler); delete data.current.keydownHandler; } if (data.current.resetHandler) { window.removeEventListener("focus", data.current.resetHandler); delete data.current.resetHandler; } } }; // src/features/async-data-loader/feature.ts var getDataRef = (tree) => { var _a, _b, _c, _d; const dataRef = tree.getDataRef(); (_b = (_a = dataRef.current).itemData) != null ? _b : _a.itemData = {}; (_d = (_c = dataRef.current).childrenIds) != null ? _d : _c.childrenIds = {}; return dataRef; }; var loadItemData = (tree, itemId) => __async(null, null, function* () { var _a; const config = tree.getConfig(); const dataRef = getDataRef(tree); if (!dataRef.current.itemData[itemId]) { tree.applySubStateUpdate("loadingItemData", (loadingItemData) => [ ...loadingItemData, itemId ]); } const item = yield config.dataLoader.getItem(itemId); dataRef.current.itemData[itemId] = item; (_a = config.onLoadedItem) == null ? void 0 : _a.call(config, itemId, item); tree.applySubStateUpdate( "loadingItemData", (loadingItemData) => loadingItemData.filter((id) => id !== itemId) ); return item; }); var loadChildrenIds = (tree, itemId) => __async(null, null, function* () { var _a, _b; const config = tree.getConfig(); const dataRef = getDataRef(tree); let childrenIds; if (!dataRef.current.childrenIds[itemId]) { tree.applySubStateUpdate("loadingItemChildrens", (loadingItemChildrens) => [ ...loadingItemChildrens, itemId ]); } if ("getChildrenWithData" in config.dataLoader) { const children = yield config.dataLoader.getChildrenWithData(itemId); childrenIds = children.map((c) => c.id); dataRef.current.childrenIds[itemId] = childrenIds; children.forEach(({ id, data }) => { var _a2; dataRef.current.itemData[id] = data; (_a2 = config.onLoadedItem) == null ? void 0 : _a2.call(config, id, data); }); (_a = config.onLoadedChildren) == null ? void 0 : _a.call(config, itemId, childrenIds); tree.rebuildTree(); tree.applySubStateUpdate( "loadingItemData", (loadingItemData) => loadingItemData.filter((id) => !childrenIds.includes(id)) ); } else { childrenIds = yield config.dataLoader.getChildren(itemId); dataRef.current.childrenIds[itemId] = childrenIds; (_b = config.onLoadedChildren) == null ? void 0 : _b.call(config, itemId, childrenIds); tree.rebuildTree(); } tree.applySubStateUpdate( "loadingItemChildrens", (loadingItemChildrens) => loadingItemChildrens.filter((id) => id !== itemId) ); return childrenIds; }); var asyncDataLoaderFeature = { key: "async-data-loader", getInitialState: (initialState) => __spreadValues({ loadingItemData: [], loadingItemChildrens: [] }, initialState), getDefaultConfig: (defaultConfig, tree) => __spreadValues({ setLoadingItemData: makeStateUpdater("loadingItemData", tree), setLoadingItemChildrens: makeStateUpdater("loadingItemChildrens", tree) }, defaultConfig), stateHandlerNames: { loadingItemData: "setLoadingItemData", loadingItemChildrens: "setLoadingItemChildrens" }, treeInstance: { waitForItemDataLoaded: ({ tree }, itemId) => tree.loadItemData(itemId), waitForItemChildrenLoaded: ({ tree }, itemId) => tree.loadChildrenIds(itemId), loadItemData: (_0, _1) => __async(null, [_0, _1], function* ({ tree }, itemId) { var _a; return (_a = getDataRef(tree).current.itemData[itemId]) != null ? _a : yield loadItemData(tree, itemId); }), loadChildrenIds: (_0, _1) => __async(null, [_0, _1], function* ({ tree }, itemId) { var _a; return (_a = getDataRef(tree).current.childrenIds[itemId]) != null ? _a : yield loadChildrenIds(tree, itemId); }), retrieveItemData: ({ tree }, itemId, skipFetch = false) => { var _a, _b; const config = tree.getConfig(); const dataRef = getDataRef(tree); if (dataRef.current.itemData[itemId]) { return dataRef.current.itemData[itemId]; } if (!tree.getState().loadingItemData.includes(itemId) && !skipFetch) { setTimeout(() => loadItemData(tree, itemId)); } return (_b = (_a = config.createLoadingItemData) == null ? void 0 : _a.call(config)) != null ? _b : null; }, retrieveChildrenIds: ({ tree }, itemId, skipFetch = false) => { const dataRef = getDataRef(tree); if (dataRef.current.childrenIds[itemId]) { return dataRef.current.childrenIds[itemId]; } if (tree.getState().loadingItemChildrens.includes(itemId) || skipFetch) { return []; } setTimeout(() => loadChildrenIds(tree, itemId)); return []; } }, itemInstance: { isLoading: ({ tree, item }) => tree.getState().loadingItemData.includes(item.getItemMeta().itemId) || tree.getState().loadingItemChildrens.includes(item.getItemMeta().itemId), invalidateItemData: (_0, _1) => __async(null, [_0, _1], function* ({ tree, itemId }, optimistic) { var _a; if (!optimistic) { (_a = getDataRef(tree).current.itemData) == null ? true : delete _a[itemId]; } yield loadItemData(tree, itemId); }), invalidateChildrenIds: (_0, _1) => __async(null, [_0, _1], function* ({ tree, itemId }, optimistic) { var _a; if (!optimistic) { (_a = getDataRef(tree).current.childrenIds) == null ? true : delete _a[itemId]; } yield loadChildrenIds(tree, itemId); }), updateCachedChildrenIds: ({ tree, itemId }, childrenIds) => { const dataRef = tree.getDataRef(); dataRef.current.childrenIds[itemId] = childrenIds; tree.rebuildTree(); }, updateCachedData: ({ tree, itemId }, data) => { const dataRef = tree.getDataRef(); dataRef.current.itemData[itemId] = data; tree.rebuildTree(); } } }; // src/features/sync-data-loader/feature.ts var undefErrorMessage = "sync dataLoader returned undefined"; var promiseErrorMessage = "sync dataLoader returned promise"; var unpromise = (data) => { if (!data) { throw throwError(undefErrorMessage); } if (typeof data === "object" && "then" in data) { throw throwError(promiseErrorMessage); } return data; }; var syncDataLoaderFeature = { key: "sync-data-loader", getInitialState: (initialState) => __spreadValues({ loadingItemData: [], loadingItemChildrens: [] }, initialState), getDefaultConfig: (defaultConfig, tree) => __spreadValues({ setLoadingItemData: makeStateUpdater("loadingItemData", tree), setLoadingItemChildrens: makeStateUpdater("loadingItemChildrens", tree) }, defaultConfig), stateHandlerNames: { loadingItemData: "setLoadingItemData", loadingItemChildrens: "setLoadingItemChildrens" }, treeInstance: { waitForItemDataLoaded: () => __async(null, null, function* () { }), waitForItemChildrenLoaded: () => __async(null, null, function* () { }), retrieveItemData: ({ tree }, itemId) => { return unpromise(tree.getConfig().dataLoader.getItem(itemId)); }, retrieveChildrenIds: ({ tree }, itemId) => { const { dataLoader } = tree.getConfig(); if ("getChildren" in dataLoader) { return unpromise(dataLoader.getChildren(itemId)); } return unpromise(dataLoader.getChildrenWithData(itemId)).map( (c) => c.data ); }, loadItemData: ({ tree }, itemId) => tree.retrieveItemData(itemId), loadChildrenIds: ({ tree }, itemId) => tree.retrieveChildrenIds(itemId) }, itemInstance: { isLoading: () => false } }; // src/features/drag-and-drop/utils.ts var isOrderedDragTarget = (dragTarget) => "childIndex" in dragTarget; var canDrop = (dataTransfer, target, tree) => { var _a, _b, _c; const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems; const config = tree.getConfig(); if (draggedItems && !((_c = (_b = config.canDrop) == null ? void 0 : _b.call(config, draggedItems, target)) != null ? _c : true)) { return false; } if (draggedItems && draggedItems.some( (draggedItem) => target.item.getId() === draggedItem.getId() || target.item.isDescendentOf(draggedItem.getId()) )) { return false; } if (!draggedItems && dataTransfer && config.canDropForeignDragObject && !config.canDropForeignDragObject(dataTransfer, target)) { return false; } return true; }; var getItemDropCategory = (item) => { if (item.isExpanded()) { return 1 /* ExpandedFolder */; } const parent = item.getParent(); if (parent && item.getIndexInParent() === item.getItemMeta().setSize - 1) { return 2 /* LastInGroup */; } return 0 /* Item */; }; var getInsertionIndex = (children, childIndex, draggedItems) => { var _a; const numberOfDragItemsBeforeTarget = (_a = children.slice(0, childIndex).reduce( (counter, child) => child && (draggedItems == null ? void 0 : draggedItems.some((i) => i.getId() === child.getId())) ? ++counter : counter, 0 )) != null ? _a : 0; return childIndex - numberOfDragItemsBeforeTarget; }; var getTargetPlacement = (e, item, tree, canMakeChild) => { var _a, _b, _c, _d, _e; const config = tree.getConfig(); if (!config.canReorder) { return canMakeChild ? { type: 2 /* MakeChild */ } : { type: 1 /* ReorderBelow */ }; } const bb = (_a = item.getElement()) == null ? void 0 : _a.getBoundingClientRect(); const topPercent = bb ? (e.clientY - bb.top) / bb.height : 0.5; const leftPixels = bb ? e.clientX - bb.left : 0; const targetDropCategory = getItemDropCategory(item); const reorderAreaPercentage = !canMakeChild ? 0.5 : (_b = config.reorderAreaPercentage) != null ? _b : 0.3; const indent = (_c = config.indent) != null ? _c : 20; const makeChildType = canMakeChild ? 2 /* MakeChild */ : 1 /* ReorderBelow */; if (targetDropCategory === 1 /* ExpandedFolder */) { if (topPercent < reorderAreaPercentage) { return { type: 0 /* ReorderAbove */ }; } return { type: makeChildType }; } if (targetDropCategory === 2 /* LastInGroup */) { if (leftPixels < item.getItemMeta().level * indent) { if (topPercent < 0.5) { return { type: 0 /* ReorderAbove */ }; } const minLevel = (_e = (_d = item.getItemBelow()) == null ? void 0 : _d.getItemMeta().level) != null ? _e : 0; return { type: 3 /* Reparent */, reparentLevel: Math.max(minLevel, Math.floor(leftPixels / indent)) }; } } if (topPercent < reorderAreaPercentage) { return { type: 0 /* ReorderAbove */ }; } if (topPercent > 1 - reorderAreaPercentage) { return { type: 1 /* ReorderBelow */ }; } return { type: makeChildType }; }; var getDragCode = (item, placement) => { return [ item.getId(), placement.type, placement.type === 3 /* Reparent */ ? placement.reparentLevel : 0 ].join("__"); }; var getNthParent = (item, n) => { if (n === item.getItemMeta().level) { return item; } return getNthParent(item.getParent(), n); }; var getReparentTarget = (item, reparentLevel, draggedItems) => { const itemMeta = item.getItemMeta(); const reparentedTarget = getNthParent(item, reparentLevel - 1); const targetItemAbove = getNthParent(item, reparentLevel); const targetIndex = targetItemAbove.getIndexInParent() + 1; return { item: reparentedTarget, childIndex: targetIndex, insertionIndex: getInsertionIndex( reparentedTarget.getChildren(), targetIndex, draggedItems ), dragLineIndex: itemMeta.index + 1, dragLineLevel: reparentLevel }; }; var getDragTarget = (e, item, tree, canReorder = tree.getConfig().canReorder) => { var _a; const draggedItems = (_a = tree.getState().dnd) == null ? void 0 : _a.draggedItems; const itemMeta = item.getItemMeta(); const parent = item.getParent(); const itemTarget = { item }; const parentTarget = parent ? { item: parent } : null; const canBecomeSibling = parentTarget && canDrop(e.dataTransfer, parentTarget, tree); const canMakeChild = canDrop(e.dataTransfer, itemTarget, tree); const placement = getTargetPlacement(e, item, tree, canMakeChild); if (!canReorder && parent && canBecomeSibling && placement.type !== 2 /* MakeChild */) { if (draggedItems == null ? void 0 : draggedItems.some((item2) => item2.isDescendentOf(parent.getId()))) { return itemTarget; } return parentTarget; } if (!canReorder && parent && !canBecomeSibling) { return getDragTarget(e, parent, tree, false); } if (!parent) { return itemTarget; } if (placement.type === 2 /* MakeChild */) { return itemTarget; } if (!canBecomeSibling) { return getDragTarget(e, parent, tree, false); } if (placement.type === 3 /* Reparent */) { return getReparentTarget(item, placement.reparentLevel, draggedItems); } const maybeAddOneForBelow = placement.type === 0 /* ReorderAbove */ ? 0 : 1; const childIndex = item.getIndexInParent() + maybeAddOneForBelow; return { item: parent, dragLineIndex: itemMeta.index + maybeAddOneForBelow, dragLineLevel: itemMeta.level, childIndex, // TODO performance could be improved by computing this only when dragcode changed insertionIndex: getInsertionIndex( parent.getChildren(), childIndex, draggedItems ) }; }; // src/features/drag-and-drop/feature.ts var handleAutoOpenFolder = (dataRef, tree, item, placement) => { const { openOnDropDelay } = tree.getConfig(); const dragCode = dataRef.current.lastDragCode; if (!openOnDropDelay || !item.isFolder() || item.isExpanded() || placement.type !== 2 /* MakeChild */) { return; } clearTimeout(dataRef.current.autoExpandTimeout); dataRef.current.autoExpandTimeout = setTimeout(() => { if (dragCode !== dataRef.current.lastDragCode || !dataRef.current.lastAllowDrop) return; item.expand(); }, openOnDropDelay); }; var defaultCanDropForeignDragObject = () => false; var dragAndDropFeature = { key: "drag-and