@headless-tree/core
Version:
The definitive tree component for the Web
1,422 lines (1,407 loc) • 84.4 kB
JavaScript
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