@zag-js/collection
Version:
Utilities to manage a collection of items.
1,451 lines (1,445 loc) • 51.3 kB
JavaScript
'use strict';
var utils = require('@zag-js/utils');
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var fallback = {
itemToValue(item) {
if (typeof item === "string") return item;
if (utils.isObject(item) && utils.hasProp(item, "value")) return item.value;
return "";
},
itemToString(item) {
if (typeof item === "string") return item;
if (utils.isObject(item) && utils.hasProp(item, "label")) return item.label;
return fallback.itemToValue(item);
},
isItemDisabled(item) {
if (utils.isObject(item) && utils.hasProp(item, "disabled")) return !!item.disabled;
return false;
}
};
var ListCollection = class _ListCollection {
constructor(options) {
this.options = options;
/**
* The items in the collection
*/
__publicField(this, "items");
/**
* Copy the collection
*/
__publicField(this, "copy", (items) => {
return new _ListCollection({ ...this.options, items: items ?? [...this.items] });
});
/**
* Check if the collection is equal to another collection
*/
__publicField(this, "isEqual", (other) => {
return utils.isEqual(this.items, other.items);
});
/**
* Function to update the collection items
*/
__publicField(this, "setItems", (items) => {
return this.copy(items);
});
/**
* Returns all the values in the collection
*/
__publicField(this, "getValues", (items = this.items) => {
return Array.from(items).map((item) => this.getItemValue(item)).filter(Boolean);
});
/**
* Get the item based on its value
*/
__publicField(this, "find", (value) => {
if (value == null) return null;
const index = this.indexOf(value);
return index != null ? this.at(index) : null;
});
/**
* Get the items based on its values
*/
__publicField(this, "findMany", (values) => {
return Array.from(values).map((value) => this.find(value)).filter((item) => item != null);
});
/**
* Get the item based on its index
*/
__publicField(this, "at", (index) => {
if (!this.options.groupBy && !this.options.groupSort) {
return this.items[index] ?? null;
}
let idx = 0;
const groups = this.group();
for (const [, items] of groups) {
for (const item of items) {
if (idx === index) return item;
idx++;
}
}
return null;
});
__publicField(this, "sortFn", (valueA, valueB) => {
const indexA = this.indexOf(valueA);
const indexB = this.indexOf(valueB);
return (indexA ?? 0) - (indexB ?? 0);
});
/**
* Sort the values based on their index
*/
__publicField(this, "sort", (values) => {
return [...values].sort(this.sortFn.bind(this));
});
/**
* Convert an item to a value
*/
__publicField(this, "getItemValue", (item) => {
if (item == null) return null;
return this.options.itemToValue?.(item) ?? fallback.itemToValue(item);
});
/**
* Whether an item is disabled
*/
__publicField(this, "getItemDisabled", (item) => {
if (item == null) return false;
return this.options.isItemDisabled?.(item) ?? fallback.isItemDisabled(item);
});
/**
* Convert an item to a string
*/
__publicField(this, "stringifyItem", (item) => {
if (item == null) return null;
return this.options.itemToString?.(item) ?? fallback.itemToString(item);
});
/**
* Convert a value to a string
*/
__publicField(this, "stringify", (value) => {
if (value == null) return null;
return this.stringifyItem(this.find(value));
});
/**
* Convert an array of items to a string
*/
__publicField(this, "stringifyItems", (items, separator = ", ") => {
return Array.from(items).map((item) => this.stringifyItem(item)).filter(Boolean).join(separator);
});
/**
* Convert an array of items to a string
*/
__publicField(this, "stringifyMany", (value, separator) => {
return this.stringifyItems(this.findMany(value), separator);
});
/**
* Whether the collection has a value
*/
__publicField(this, "has", (value) => {
return this.indexOf(value) !== -1;
});
/**
* Whether the collection has an item
*/
__publicField(this, "hasItem", (item) => {
if (item == null) return false;
return this.has(this.getItemValue(item));
});
/**
* Group items by the groupBy function provided in options
* Returns an array of [groupKey, items] tuples
*/
__publicField(this, "group", () => {
const { groupBy, groupSort } = this.options;
if (!groupBy) return [["", [...this.items]]];
const groups = /* @__PURE__ */ new Map();
this.items.forEach((item, index) => {
const groupKey = groupBy(item, index);
if (!groups.has(groupKey)) {
groups.set(groupKey, []);
}
groups.get(groupKey).push(item);
});
let entries = Array.from(groups.entries());
if (groupSort) {
entries.sort(([a], [b]) => {
if (typeof groupSort === "function") return groupSort(a, b);
if (Array.isArray(groupSort)) {
const indexA = groupSort.indexOf(a);
const indexB = groupSort.indexOf(b);
if (indexA === -1) return 1;
if (indexB === -1) return -1;
return indexA - indexB;
}
if (groupSort === "asc") return a.localeCompare(b);
if (groupSort === "desc") return b.localeCompare(a);
return 0;
});
}
return entries;
});
/**
* Returns the next value in the collection
*/
__publicField(this, "getNextValue", (value, step = 1, clamp = false) => {
let index = this.indexOf(value);
if (index === -1) return null;
index = clamp ? Math.min(index + step, this.size - 1) : index + step;
while (index <= this.size && this.getItemDisabled(this.at(index))) index++;
return this.getItemValue(this.at(index));
});
/**
* Returns the previous value in the collection
*/
__publicField(this, "getPreviousValue", (value, step = 1, clamp = false) => {
let index = this.indexOf(value);
if (index === -1) return null;
index = clamp ? Math.max(index - step, 0) : index - step;
while (index >= 0 && this.getItemDisabled(this.at(index))) index--;
return this.getItemValue(this.at(index));
});
/**
* Get the index of an item based on its key
*/
__publicField(this, "indexOf", (value) => {
if (value == null) return -1;
if (!this.options.groupBy && !this.options.groupSort) {
return this.items.findIndex((item) => this.getItemValue(item) === value);
}
let idx = 0;
const groups = this.group();
for (const [, items] of groups) {
for (const item of items) {
if (this.getItemValue(item) === value) return idx;
idx++;
}
}
return -1;
});
__publicField(this, "getByText", (text, current) => {
let items = current != null ? wrap(this.items, this.indexOf(current)) : this.items;
const isSingleKey = text.length === 1;
if (isSingleKey) items = items.filter((item) => this.getItemValue(item) !== current);
return items.find((item) => match(this.stringifyItem(item), text));
});
/**
* Search for a value based on a query
*/
__publicField(this, "search", (queryString, options) => {
const { state, currentValue, timeout = 350 } = options;
const search = state.keysSoFar + queryString;
const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]);
const query = isRepeated ? search[0] : search;
const item = this.getByText(query, currentValue);
const value = this.getItemValue(item);
function cleanup() {
clearTimeout(state.timer);
state.timer = -1;
}
function update(value2) {
state.keysSoFar = value2;
cleanup();
if (value2 !== "") {
state.timer = +setTimeout(() => {
update("");
cleanup();
}, timeout);
}
}
update(search);
return value;
});
/**
* Update an item in the collection
*/
__publicField(this, "update", (value, item) => {
let index = this.indexOf(value);
if (index === -1) return this;
return this.copy([...this.items.slice(0, index), item, ...this.items.slice(index + 1)]);
});
/**
* Update an item in the collection if it exists, otherwise append it
*/
__publicField(this, "upsert", (value, item, mode = "append") => {
let index = this.indexOf(value);
if (index === -1) {
const fn = mode === "append" ? this.append : this.prepend;
return fn(item);
}
return this.copy([...this.items.slice(0, index), item, ...this.items.slice(index + 1)]);
});
/**
* Insert items at a specific index
*/
__publicField(this, "insert", (index, ...items) => {
return this.copy(insert(this.items, index, ...items));
});
/**
* Insert items before a specific value
*/
__publicField(this, "insertBefore", (value, ...items) => {
let toIndex = this.indexOf(value);
if (toIndex === -1) {
if (this.items.length === 0) toIndex = 0;
else return this;
}
return this.copy(insert(this.items, toIndex, ...items));
});
/**
* Insert items after a specific value
*/
__publicField(this, "insertAfter", (value, ...items) => {
let toIndex = this.indexOf(value);
if (toIndex === -1) {
if (this.items.length === 0) toIndex = 0;
else return this;
}
return this.copy(insert(this.items, toIndex + 1, ...items));
});
/**
* Prepend items to the collection
*/
__publicField(this, "prepend", (...items) => {
return this.copy(insert(this.items, 0, ...items));
});
/**
* Append items to the collection
*/
__publicField(this, "append", (...items) => {
return this.copy(insert(this.items, this.items.length, ...items));
});
/**
* Filter the collection
*/
__publicField(this, "filter", (fn) => {
const filteredItems = this.items.filter((item, index) => fn(this.stringifyItem(item), index, item));
return this.copy(filteredItems);
});
/**
* Remove items from the collection
*/
__publicField(this, "remove", (...itemsOrValues) => {
const values = itemsOrValues.map(
(itemOrValue) => typeof itemOrValue === "string" ? itemOrValue : this.getItemValue(itemOrValue)
);
return this.copy(
this.items.filter((item) => {
const value = this.getItemValue(item);
if (value == null) return false;
return !values.includes(value);
})
);
});
/**
* Move an item to a specific index
*/
__publicField(this, "move", (value, toIndex) => {
const fromIndex = this.indexOf(value);
if (fromIndex === -1) return this;
return this.copy(move(this.items, [fromIndex], toIndex));
});
/**
* Move items before a specific value
*/
__publicField(this, "moveBefore", (value, ...values) => {
let toIndex = this.items.findIndex((item) => this.getItemValue(item) === value);
if (toIndex === -1) return this;
let indices = values.map((value2) => this.items.findIndex((item) => this.getItemValue(item) === value2)).sort((a, b) => a - b);
return this.copy(move(this.items, indices, toIndex));
});
/**
* Move items after a specific value
*/
__publicField(this, "moveAfter", (value, ...values) => {
let toIndex = this.items.findIndex((item) => this.getItemValue(item) === value);
if (toIndex === -1) return this;
let indices = values.map((value2) => this.items.findIndex((item) => this.getItemValue(item) === value2)).sort((a, b) => a - b);
return this.copy(move(this.items, indices, toIndex + 1));
});
/**
* Reorder items
*/
__publicField(this, "reorder", (fromIndex, toIndex) => {
return this.copy(move(this.items, [fromIndex], toIndex));
});
/**
* Compare two values
*/
__publicField(this, "compareValue", (a, b) => {
const indexA = this.indexOf(a);
const indexB = this.indexOf(b);
if (indexA < indexB) return -1;
if (indexA > indexB) return 1;
return 0;
});
/**
* Get the range of values between two values
*/
__publicField(this, "range", (from, to) => {
let keys = [];
let key = from;
while (key != null) {
let item = this.find(key);
if (item) keys.push(key);
if (key === to) return keys;
key = this.getNextValue(key);
}
return [];
});
/**
* Get the range of values between two values
*/
__publicField(this, "getValueRange", (from, to) => {
if (from && to) {
if (this.compareValue(from, to) <= 0) {
return this.range(from, to);
}
return this.range(to, from);
}
return [];
});
/**
* Convert the collection to a string
*/
__publicField(this, "toString", () => {
let result = "";
for (const item of this.items) {
const value = this.getItemValue(item);
const label = this.stringifyItem(item);
const disabled = this.getItemDisabled(item);
const itemString = [value, label, disabled].filter(Boolean).join(":");
result += itemString + ",";
}
return result;
});
/**
* Convert the collection to a JSON object
*/
__publicField(this, "toJSON", () => {
return {
size: this.size,
first: this.firstValue,
last: this.lastValue
};
});
this.items = [...options.items];
}
/**
* Returns the number of items in the collection
*/
get size() {
return this.items.length;
}
/**
* Returns the first value in the collection
*/
get firstValue() {
let index = 0;
while (this.getItemDisabled(this.at(index))) index++;
return this.getItemValue(this.at(index));
}
/**
* Returns the last value in the collection
*/
get lastValue() {
let index = this.size - 1;
while (this.getItemDisabled(this.at(index))) index--;
return this.getItemValue(this.at(index));
}
*[Symbol.iterator]() {
yield* this.items;
}
};
var match = (label, query) => {
return !!label?.toLowerCase().startsWith(query.toLowerCase());
};
var wrap = (v, idx) => {
return v.map((_, index) => v[(Math.max(idx, 0) + index) % v.length]);
};
function isListCollection(v) {
return v instanceof ListCollection;
}
function insert(items, index, ...values) {
return [...items.slice(0, index), ...values, ...items.slice(index)];
}
function move(items, indices, toIndex) {
indices = [...indices].sort((a, b) => a - b);
const itemsToMove = indices.map((i) => items[i]);
for (let i = indices.length - 1; i >= 0; i--) {
items = [...items.slice(0, indices[i]), ...items.slice(indices[i] + 1)];
}
toIndex = Math.max(0, toIndex - indices.filter((i) => i < toIndex).length);
return [...items.slice(0, toIndex), ...itemsToMove, ...items.slice(toIndex)];
}
// src/grid-collection.ts
var GridCollection = class extends ListCollection {
constructor(options) {
const { columnCount } = options;
super(options);
__publicField(this, "columnCount");
/**
* Returns the row data in the grid
*/
__publicField(this, "getRows", () => {
return utils.chunk([...this.items], this.columnCount);
});
/**
* Returns the number of rows in the grid
*/
__publicField(this, "getRowCount", () => {
return this.getRows().length;
});
/**
* Returns the index of the specified row and column in the grid
*/
__publicField(this, "getCellIndex", (row, column) => {
return row * this.columnCount + column;
});
/**
* Returns the item at the specified row and column in the grid
*/
__publicField(this, "getCell", (row, column) => {
return this.at(this.getCellIndex(row, column));
});
/**
* Returns the row and column index for a given value
*/
__publicField(this, "getValueCell", (value) => {
const index = this.indexOf(value);
if (index === -1) return null;
const row = Math.floor(index / this.columnCount);
const column = index % this.columnCount;
return { row, column };
});
/**
* Returns the value of the last enabled column in a row
*/
__publicField(this, "getLastEnabledColumnIndex", (row) => {
for (let col = this.columnCount - 1; col >= 0; col--) {
const cell = this.getCell(row, col);
if (cell && !this.getItemDisabled(cell)) {
return col;
}
}
return null;
});
/**
* Returns the index of the first enabled column in a row
*/
__publicField(this, "getFirstEnabledColumnIndex", (row) => {
for (let col = 0; col < this.columnCount; col++) {
const cell = this.getCell(row, col);
if (cell && !this.getItemDisabled(cell)) {
return col;
}
}
return null;
});
/**
* Returns the value of the previous row in the grid, based on the current value
*/
__publicField(this, "getPreviousRowValue", (value, loop = false) => {
const currentCell = this.getValueCell(value);
if (currentCell === null) return null;
const rows = this.getRows();
const rowCount = this.getRowCount();
let prevRowIndex = currentCell.row;
let prevColumnIndex = currentCell.column;
for (let i = 1; i <= rowCount; i++) {
prevRowIndex = utils.prevIndex(rows, prevRowIndex, { loop });
const prevRow = rows[prevRowIndex];
if (!prevRow) continue;
const prevCell = prevRow[prevColumnIndex];
if (!prevCell) {
const lastColumnIndex = this.getLastEnabledColumnIndex(prevRowIndex);
if (lastColumnIndex != null) {
prevColumnIndex = lastColumnIndex;
}
}
const cell = this.getCell(prevRowIndex, prevColumnIndex);
if (!this.getItemDisabled(cell)) {
return this.getItemValue(cell);
}
}
return this.firstValue;
});
/**
* Returns the value of the next row in the grid, based on the current value
*/
__publicField(this, "getNextRowValue", (value, loop = false) => {
const currentCell = this.getValueCell(value);
if (currentCell === null) return null;
const rows = this.getRows();
const rowCount = this.getRowCount();
let nextRowIndex = currentCell.row;
let nextColumnIndex = currentCell.column;
for (let i = 1; i <= rowCount; i++) {
nextRowIndex = utils.nextIndex(rows, nextRowIndex, { loop });
const nextRow = rows[nextRowIndex];
if (!nextRow) continue;
const nextCell = nextRow[nextColumnIndex];
if (!nextCell) {
const lastColumnIndex = this.getLastEnabledColumnIndex(nextRowIndex);
if (lastColumnIndex != null) {
nextColumnIndex = lastColumnIndex;
}
}
const cell = this.getCell(nextRowIndex, nextColumnIndex);
if (!this.getItemDisabled(cell)) {
return this.getItemValue(cell);
}
}
return this.lastValue;
});
this.columnCount = columnCount;
}
};
function isGridCollection(v) {
return v instanceof GridCollection;
}
var Selection = class _Selection extends Set {
constructor(values = []) {
super(values);
__publicField(this, "selectionMode", "single");
__publicField(this, "deselectable", true);
__publicField(this, "copy", () => {
const clone = new _Selection([...this]);
return this.sync(clone);
});
__publicField(this, "sync", (other) => {
other.selectionMode = this.selectionMode;
other.deselectable = this.deselectable;
return other;
});
__publicField(this, "isEmpty", () => {
return this.size === 0;
});
__publicField(this, "isSelected", (value) => {
if (this.selectionMode === "none" || value == null) {
return false;
}
return this.has(value);
});
__publicField(this, "canSelect", (collection, value) => {
return this.selectionMode !== "none" || !collection.getItemDisabled(collection.find(value));
});
__publicField(this, "firstSelectedValue", (collection) => {
let firstValue = null;
for (let value of this) {
if (!firstValue || collection.compareValue(value, firstValue) < 0) {
firstValue = value;
}
}
return firstValue;
});
__publicField(this, "lastSelectedValue", (collection) => {
let lastValue = null;
for (let value of this) {
if (!lastValue || collection.compareValue(value, lastValue) > 0) {
lastValue = value;
}
}
return lastValue;
});
__publicField(this, "extendSelection", (collection, anchorValue, targetValue) => {
if (this.selectionMode === "none") {
return this;
}
if (this.selectionMode === "single") {
return this.replaceSelection(collection, targetValue);
}
const selection = this.copy();
const lastSelected = Array.from(this).pop();
for (let key of collection.getValueRange(anchorValue, lastSelected ?? targetValue)) {
selection.delete(key);
}
for (let key of collection.getValueRange(targetValue, anchorValue)) {
if (this.canSelect(collection, key)) {
selection.add(key);
}
}
return selection;
});
__publicField(this, "toggleSelection", (collection, value) => {
if (this.selectionMode === "none") {
return this;
}
if (this.selectionMode === "single" && !this.isSelected(value)) {
return this.replaceSelection(collection, value);
}
const selection = this.copy();
if (selection.has(value)) {
selection.delete(value);
} else if (selection.canSelect(collection, value)) {
selection.add(value);
}
return selection;
});
__publicField(this, "replaceSelection", (collection, value) => {
if (this.selectionMode === "none") {
return this;
}
if (value == null) {
return this;
}
if (!this.canSelect(collection, value)) {
return this;
}
const selection = new _Selection([value]);
return this.sync(selection);
});
__publicField(this, "setSelection", (values) => {
if (this.selectionMode === "none") {
return this;
}
let selection = new _Selection();
for (let value of values) {
if (value != null) {
selection.add(value);
if (this.selectionMode === "single") {
break;
}
}
}
return this.sync(selection);
});
__publicField(this, "clearSelection", () => {
const selection = this.copy();
if (selection.deselectable && selection.size > 0) {
selection.clear();
}
return selection;
});
__publicField(this, "select", (collection, value, forceToggle) => {
if (this.selectionMode === "none") {
return this;
}
if (this.selectionMode === "single") {
if (this.isSelected(value) && this.deselectable) {
return this.toggleSelection(collection, value);
} else {
return this.replaceSelection(collection, value);
}
} else if (this.selectionMode === "multiple" || forceToggle) {
return this.toggleSelection(collection, value);
} else {
return this.replaceSelection(collection, value);
}
});
__publicField(this, "deselect", (value) => {
const selection = this.copy();
selection.delete(value);
return selection;
});
__publicField(this, "isEqual", (other) => {
return utils.isEqual(Array.from(this), Array.from(other));
});
}
};
// src/tree-visit.ts
function access(node, indexPath, options) {
for (let i = 0; i < indexPath.length; i++) node = options.getChildren(node, indexPath.slice(i + 1))[indexPath[i]];
return node;
}
function ancestorIndexPaths(indexPaths) {
const sortedPaths = sortIndexPaths(indexPaths);
const result = [];
const seen = /* @__PURE__ */ new Set();
for (const indexPath of sortedPaths) {
const key = indexPath.join();
if (!seen.has(key)) {
seen.add(key);
result.push(indexPath);
}
}
return result;
}
function compareIndexPaths(a, b) {
for (let i = 0; i < Math.min(a.length, b.length); i++) {
if (a[i] < b[i]) return -1;
if (a[i] > b[i]) return 1;
}
return a.length - b.length;
}
function sortIndexPaths(indexPaths) {
return indexPaths.sort(compareIndexPaths);
}
function find(node, options) {
let found;
visit(node, {
...options,
onEnter: (child, indexPath) => {
if (options.predicate(child, indexPath)) {
found = child;
return "stop";
}
}
});
return found;
}
function findAll(node, options) {
const found = [];
visit(node, {
onEnter: (child, indexPath) => {
if (options.predicate(child, indexPath)) found.push(child);
},
getChildren: options.getChildren
});
return found;
}
function findIndexPath(node, options) {
let found;
visit(node, {
onEnter: (child, indexPath) => {
if (options.predicate(child, indexPath)) {
found = [...indexPath];
return "stop";
}
},
getChildren: options.getChildren
});
return found;
}
function reduce(node, options) {
let result = options.initialResult;
visit(node, {
...options,
onEnter: (child, indexPath) => {
result = options.nextResult(result, child, indexPath);
}
});
return result;
}
function flatMap(node, options) {
return reduce(node, {
...options,
initialResult: [],
nextResult: (result, child, indexPath) => {
result.push(...options.transform(child, indexPath));
return result;
}
});
}
function filter(node, options) {
const { predicate, create, getChildren } = options;
const filterRecursive = (node2, indexPath) => {
const children = getChildren(node2, indexPath);
const filteredChildren = [];
children.forEach((child, index) => {
const childIndexPath = [...indexPath, index];
const filteredChild = filterRecursive(child, childIndexPath);
if (filteredChild) filteredChildren.push(filteredChild);
});
const isRoot = indexPath.length === 0;
const nodeMatches = predicate(node2, indexPath);
const hasFilteredChildren = filteredChildren.length > 0;
if (isRoot || nodeMatches || hasFilteredChildren) {
return create(node2, filteredChildren, indexPath);
}
return null;
};
return filterRecursive(node, []) || create(node, [], []);
}
function flatten(rootNode, options) {
const nodes = [];
let idx = 0;
const idxMap = /* @__PURE__ */ new Map();
const parentMap = /* @__PURE__ */ new Map();
visit(rootNode, {
getChildren: options.getChildren,
onEnter: (node, indexPath) => {
if (!idxMap.has(node)) {
idxMap.set(node, idx++);
}
const children = options.getChildren(node, indexPath);
children.forEach((child) => {
if (!parentMap.has(child)) {
parentMap.set(child, node);
}
if (!idxMap.has(child)) {
idxMap.set(child, idx++);
}
});
const _children = children.length > 0 ? children.map((child) => idxMap.get(child)) : void 0;
const parent = parentMap.get(node);
const _parent = parent ? idxMap.get(parent) : void 0;
const _index = idxMap.get(node);
nodes.push({ ...node, _children, _parent, _index });
}
});
return nodes;
}
function insertOperation(index, nodes) {
return { type: "insert", index, nodes };
}
function removeOperation(indexes) {
return { type: "remove", indexes };
}
function replaceOperation() {
return { type: "replace" };
}
function splitIndexPath(indexPath) {
return [indexPath.slice(0, -1), indexPath[indexPath.length - 1]];
}
function getInsertionOperations(indexPath, nodes, operations = /* @__PURE__ */ new Map()) {
const [parentIndexPath, index] = splitIndexPath(indexPath);
for (let i = parentIndexPath.length - 1; i >= 0; i--) {
const parentKey = parentIndexPath.slice(0, i).join();
switch (operations.get(parentKey)?.type) {
case "remove":
continue;
}
operations.set(parentKey, replaceOperation());
}
const operation = operations.get(parentIndexPath.join());
switch (operation?.type) {
case "remove":
operations.set(parentIndexPath.join(), {
type: "removeThenInsert",
removeIndexes: operation.indexes,
insertIndex: index,
insertNodes: nodes
});
break;
default:
operations.set(parentIndexPath.join(), insertOperation(index, nodes));
}
return operations;
}
function getRemovalOperations(indexPaths) {
const operations = /* @__PURE__ */ new Map();
const indexesToRemove = /* @__PURE__ */ new Map();
for (const indexPath of indexPaths) {
const parentKey = indexPath.slice(0, -1).join();
const value = indexesToRemove.get(parentKey) ?? [];
value.push(indexPath[indexPath.length - 1]);
indexesToRemove.set(
parentKey,
value.sort((a, b) => a - b)
);
}
for (const indexPath of indexPaths) {
for (let i = indexPath.length - 2; i >= 0; i--) {
const parentKey = indexPath.slice(0, i).join();
if (!operations.has(parentKey)) {
operations.set(parentKey, replaceOperation());
}
}
}
for (const [parentKey, indexes] of indexesToRemove) {
operations.set(parentKey, removeOperation(indexes));
}
return operations;
}
function getReplaceOperations(indexPath, node) {
const operations = /* @__PURE__ */ new Map();
const [parentIndexPath, index] = splitIndexPath(indexPath);
for (let i = parentIndexPath.length - 1; i >= 0; i--) {
const parentKey = parentIndexPath.slice(0, i).join();
operations.set(parentKey, replaceOperation());
}
operations.set(parentIndexPath.join(), {
type: "removeThenInsert",
removeIndexes: [index],
insertIndex: index,
insertNodes: [node]
});
return operations;
}
function mutate(node, operations, options) {
return map(node, {
...options,
getChildren: (node2, indexPath) => {
const key = indexPath.join();
const operation = operations.get(key);
switch (operation?.type) {
case "replace":
case "remove":
case "removeThenInsert":
case "insert":
return options.getChildren(node2, indexPath);
default:
return [];
}
},
transform: (node2, children, indexPath) => {
const key = indexPath.join();
const operation = operations.get(key);
switch (operation?.type) {
case "remove":
return options.create(
node2,
children.filter((_, index) => !operation.indexes.includes(index)),
indexPath
);
case "removeThenInsert":
const updatedChildren = children.filter((_, index) => !operation.removeIndexes.includes(index));
const adjustedIndex = operation.removeIndexes.reduce(
(index, removedIndex) => removedIndex < index ? index - 1 : index,
operation.insertIndex
);
return options.create(node2, splice(updatedChildren, adjustedIndex, 0, ...operation.insertNodes), indexPath);
case "insert":
return options.create(node2, splice(children, operation.index, 0, ...operation.nodes), indexPath);
case "replace":
return options.create(node2, children, indexPath);
default:
return node2;
}
}
});
}
function splice(array, start, deleteCount, ...items) {
return [...array.slice(0, start), ...items, ...array.slice(start + deleteCount)];
}
function map(node, options) {
const childrenMap = {};
visit(node, {
...options,
onLeave: (child, indexPath) => {
const keyIndexPath = [0, ...indexPath];
const key = keyIndexPath.join();
const transformed = options.transform(child, childrenMap[key] ?? [], indexPath);
const parentKey = keyIndexPath.slice(0, -1).join();
const parentChildren = childrenMap[parentKey] ?? [];
parentChildren.push(transformed);
childrenMap[parentKey] = parentChildren;
}
});
return childrenMap[""][0];
}
function insert2(node, options) {
const { nodes, at } = options;
if (at.length === 0) throw new Error(`Can't insert nodes at the root`);
const state = getInsertionOperations(at, nodes);
return mutate(node, state, options);
}
function replace(node, options) {
if (options.at.length === 0) return options.node;
const operations = getReplaceOperations(options.at, options.node);
return mutate(node, operations, options);
}
function remove(node, options) {
if (options.indexPaths.length === 0) return node;
for (const indexPath of options.indexPaths) {
if (indexPath.length === 0) throw new Error(`Can't remove the root node`);
}
const operations = getRemovalOperations(options.indexPaths);
return mutate(node, operations, options);
}
function move2(node, options) {
if (options.indexPaths.length === 0) return node;
for (const indexPath of options.indexPaths) {
if (indexPath.length === 0) throw new Error(`Can't move the root node`);
}
if (options.to.length === 0) throw new Error(`Can't move nodes to the root`);
const _ancestorIndexPaths = ancestorIndexPaths(options.indexPaths);
const nodesToInsert = _ancestorIndexPaths.map((indexPath) => access(node, indexPath, options));
const operations = getInsertionOperations(options.to, nodesToInsert, getRemovalOperations(_ancestorIndexPaths));
return mutate(node, operations, options);
}
function visit(node, options) {
const { onEnter, onLeave, getChildren } = options;
let indexPath = [];
let stack = [{ node }];
const getIndexPath = options.reuseIndexPath ? () => indexPath : () => indexPath.slice();
while (stack.length > 0) {
let wrapper = stack[stack.length - 1];
if (wrapper.state === void 0) {
const enterResult = onEnter?.(wrapper.node, getIndexPath());
if (enterResult === "stop") return;
wrapper.state = enterResult === "skip" ? -1 : 0;
}
const children = wrapper.children || getChildren(wrapper.node, getIndexPath());
wrapper.children || (wrapper.children = children);
if (wrapper.state !== -1) {
if (wrapper.state < children.length) {
let currentIndex = wrapper.state;
indexPath.push(currentIndex);
stack.push({ node: children[currentIndex] });
wrapper.state = currentIndex + 1;
continue;
}
const leaveResult = onLeave?.(wrapper.node, getIndexPath());
if (leaveResult === "stop") return;
}
indexPath.pop();
stack.pop();
}
}
// src/tree-collection.ts
var TreeCollection = class _TreeCollection {
constructor(options) {
this.options = options;
__publicField(this, "rootNode");
__publicField(this, "isEqual", (other) => {
return utils.isEqual(this.rootNode, other.rootNode);
});
__publicField(this, "getNodeChildren", (node) => {
return this.options.nodeToChildren?.(node) ?? fallbackMethods.nodeToChildren(node) ?? [];
});
__publicField(this, "resolveIndexPath", (valueOrIndexPath) => {
return typeof valueOrIndexPath === "string" ? this.getIndexPath(valueOrIndexPath) : valueOrIndexPath;
});
__publicField(this, "resolveNode", (valueOrIndexPath) => {
const indexPath = this.resolveIndexPath(valueOrIndexPath);
return indexPath ? this.at(indexPath) : void 0;
});
__publicField(this, "getNodeChildrenCount", (node) => {
return this.options.nodeToChildrenCount?.(node) ?? fallbackMethods.nodeToChildrenCount(node);
});
__publicField(this, "getNodeValue", (node) => {
return this.options.nodeToValue?.(node) ?? fallbackMethods.nodeToValue(node);
});
__publicField(this, "getNodeDisabled", (node) => {
return this.options.isNodeDisabled?.(node) ?? fallbackMethods.isNodeDisabled(node);
});
__publicField(this, "stringify", (value) => {
const node = this.findNode(value);
if (!node) return null;
return this.stringifyNode(node);
});
__publicField(this, "stringifyNode", (node) => {
return this.options.nodeToString?.(node) ?? fallbackMethods.nodeToString(node);
});
__publicField(this, "getFirstNode", (rootNode = this.rootNode) => {
let firstChild;
visit(rootNode, {
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (!firstChild && indexPath.length > 0 && !this.getNodeDisabled(node)) {
firstChild = node;
return "stop";
}
}
});
return firstChild;
});
__publicField(this, "getLastNode", (rootNode = this.rootNode, opts = {}) => {
let lastChild;
visit(rootNode, {
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (this.isSameNode(node, rootNode)) return;
if (opts.skip?.({ value: this.getNodeValue(node), node, indexPath })) return "skip";
if (indexPath.length > 0 && !this.getNodeDisabled(node)) {
lastChild = node;
}
}
});
return lastChild;
});
__publicField(this, "at", (indexPath) => {
return access(this.rootNode, indexPath, {
getChildren: this.getNodeChildren
});
});
__publicField(this, "findNode", (value, rootNode = this.rootNode) => {
return find(rootNode, {
getChildren: this.getNodeChildren,
predicate: (node) => this.getNodeValue(node) === value
});
});
__publicField(this, "findNodes", (values, rootNode = this.rootNode) => {
const v = new Set(values.filter((v2) => v2 != null));
return findAll(rootNode, {
getChildren: this.getNodeChildren,
predicate: (node) => v.has(this.getNodeValue(node))
});
});
__publicField(this, "sort", (values) => {
return values.reduce((acc, value) => {
const indexPath = this.getIndexPath(value);
if (indexPath) acc.push({ value, indexPath });
return acc;
}, []).sort((a, b) => compareIndexPaths(a.indexPath, b.indexPath)).map(({ value }) => value);
});
__publicField(this, "getIndexPath", (value) => {
return findIndexPath(this.rootNode, {
getChildren: this.getNodeChildren,
predicate: (node) => this.getNodeValue(node) === value
});
});
__publicField(this, "getValue", (indexPath) => {
const node = this.at(indexPath);
return node ? this.getNodeValue(node) : void 0;
});
__publicField(this, "getValuePath", (indexPath) => {
if (!indexPath) return [];
const valuePath = [];
let currentPath = [...indexPath];
while (currentPath.length > 0) {
const node = this.at(currentPath);
if (node) valuePath.unshift(this.getNodeValue(node));
currentPath.pop();
}
return valuePath;
});
__publicField(this, "getDepth", (value) => {
const indexPath = findIndexPath(this.rootNode, {
getChildren: this.getNodeChildren,
predicate: (node) => this.getNodeValue(node) === value
});
return indexPath?.length ?? 0;
});
__publicField(this, "isSameNode", (node, other) => {
return this.getNodeValue(node) === this.getNodeValue(other);
});
__publicField(this, "isRootNode", (node) => {
return this.isSameNode(node, this.rootNode);
});
__publicField(this, "contains", (parentIndexPath, valueIndexPath) => {
if (!parentIndexPath || !valueIndexPath) return false;
return valueIndexPath.slice(0, parentIndexPath.length).every((_, i) => parentIndexPath[i] === valueIndexPath[i]);
});
__publicField(this, "getNextNode", (value, opts = {}) => {
let found = false;
let nextNode;
visit(this.rootNode, {
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (this.isRootNode(node)) return;
const nodeValue = this.getNodeValue(node);
if (opts.skip?.({ value: nodeValue, node, indexPath })) {
if (nodeValue === value) {
found = true;
}
return "skip";
}
if (found && !this.getNodeDisabled(node)) {
nextNode = node;
return "stop";
}
if (nodeValue === value) {
found = true;
}
}
});
return nextNode;
});
__publicField(this, "getPreviousNode", (value, opts = {}) => {
let previousNode;
let found = false;
visit(this.rootNode, {
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (this.isRootNode(node)) return;
const nodeValue = this.getNodeValue(node);
if (opts.skip?.({ value: nodeValue, node, indexPath })) {
return "skip";
}
if (nodeValue === value) {
found = true;
return "stop";
}
if (!this.getNodeDisabled(node)) {
previousNode = node;
}
}
});
return found ? previousNode : void 0;
});
__publicField(this, "getParentNodes", (valueOrIndexPath) => {
const indexPath = this.resolveIndexPath(valueOrIndexPath)?.slice();
if (!indexPath) return [];
const result = [];
while (indexPath.length > 0) {
indexPath.pop();
const parentNode = this.at(indexPath);
if (parentNode && !this.isRootNode(parentNode)) {
result.unshift(parentNode);
}
}
return result;
});
__publicField(this, "getDescendantNodes", (valueOrIndexPath, options) => {
const parentNode = this.resolveNode(valueOrIndexPath);
if (!parentNode) return [];
const result = [];
visit(parentNode, {
getChildren: this.getNodeChildren,
onEnter: (node, nodeIndexPath) => {
if (nodeIndexPath.length === 0) return;
if (!options?.withBranch && this.isBranchNode(node)) return;
result.push(node);
}
});
return result;
});
__publicField(this, "getDescendantValues", (valueOrIndexPath, options) => {
const children = this.getDescendantNodes(valueOrIndexPath, options);
return children.map((child) => this.getNodeValue(child));
});
__publicField(this, "getParentIndexPath", (indexPath) => {
return indexPath.slice(0, -1);
});
__publicField(this, "getParentNode", (valueOrIndexPath) => {
const indexPath = this.resolveIndexPath(valueOrIndexPath);
return indexPath ? this.at(this.getParentIndexPath(indexPath)) : void 0;
});
__publicField(this, "visit", (opts) => {
const { skip, ...rest } = opts;
visit(this.rootNode, {
...rest,
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (this.isRootNode(node)) return;
if (skip?.({ value: this.getNodeValue(node), node, indexPath })) return "skip";
return rest.onEnter?.(node, indexPath);
}
});
});
__publicField(this, "getPreviousSibling", (indexPath) => {
const parentNode = this.getParentNode(indexPath);
if (!parentNode) return;
const siblings = this.getNodeChildren(parentNode);
let idx = indexPath[indexPath.length - 1];
while (--idx >= 0) {
const sibling = siblings[idx];
if (!this.getNodeDisabled(sibling)) return sibling;
}
return;
});
__publicField(this, "getNextSibling", (indexPath) => {
const parentNode = this.getParentNode(indexPath);
if (!parentNode) return;
const siblings = this.getNodeChildren(parentNode);
let idx = indexPath[indexPath.length - 1];
while (++idx < siblings.length) {
const sibling = siblings[idx];
if (!this.getNodeDisabled(sibling)) return sibling;
}
return;
});
__publicField(this, "getSiblingNodes", (indexPath) => {
const parentNode = this.getParentNode(indexPath);
return parentNode ? this.getNodeChildren(parentNode) : [];
});
__publicField(this, "getValues", (rootNode = this.rootNode) => {
const values = flatMap(rootNode, {
getChildren: this.getNodeChildren,
transform: (node) => [this.getNodeValue(node)]
});
return values.slice(1);
});
__publicField(this, "isSameDepth", (indexPath, depth) => {
if (depth == null) return true;
return indexPath.length === depth;
});
__publicField(this, "isBranchNode", (node) => {
return this.getNodeChildren(node).length > 0 || this.getNodeChildrenCount(node) != null;
});
__publicField(this, "getBranchValues", (rootNode = this.rootNode, opts = {}) => {
let values = [];
visit(rootNode, {
getChildren: this.getNodeChildren,
onEnter: (node, indexPath) => {
if (indexPath.length === 0) return;
const nodeValue = this.getNodeValue(node);
if (opts.skip?.({ value: nodeValue, node, indexPath })) return "skip";
if (this.isBranchNode(node) && this.isSameDepth(indexPath, opts.depth)) {
values.push(this.getNodeValue(node));
}
}
});
return values;
});
__publicField(this, "flatten", (rootNode = this.rootNode) => {
return flatten(rootNode, { getChildren: this.getNodeChildren });
});
__publicField(this, "_create", (node, children) => {
return utils.compact({ ...node, children: children.length > 0 ? children : void 0 });
});
__publicField(this, "_insert", (rootNode, indexPath, nodes) => {
return this.copy(
insert2(rootNode, { at: indexPath, nodes, getChildren: this.getNodeChildren, create: this._create })
);
});
__publicField(this, "copy", (rootNode) => {
return new _TreeCollection({ ...this.options, rootNode });
});
__publicField(this, "_replace", (rootNode, indexPath, node) => {
return this.copy(
replace(rootNode, { at: indexPath, node, getChildren: this.getNodeChildren, create: this._create })
);
});
__publicField(this, "_move", (rootNode, indexPaths, to) => {
return this.copy(move2(rootNode, { indexPaths, to, getChildren: this.getNodeChildren, create: this._create }));
});
__publicField(this, "_remove", (rootNode, indexPaths) => {
return this.copy(remove(rootNode, { indexPaths, getChildren: this.getNodeChildren, create: this._create }));
});
__publicField(this, "replace", (indexPath, node) => {
return this._replace(this.rootNode, indexPath, node);
});
__publicField(this, "remove", (indexPaths) => {
return this._remove(this.rootNode, indexPaths);
});
__publicField(this, "insertBefore", (indexPath, nodes) => {
const parentNode = this.getParentNode(indexPath);
return parentNode ? this._insert(this.rootNode, indexPath, nodes) : void 0;
});
__publicField(this, "insertAfter", (indexPath, nodes) => {
const parentNode = this.getParentNode(indexPath);
if (!parentNode) return;
const nextIndex2 = [...indexPath.slice(0, -1), indexPath[indexPath.length - 1] + 1];
return this._insert(this.rootNode, nextIndex2, nodes);
});
__publicField(this, "move", (fromIndexPaths, toIndexPath) => {
return this._move(this.rootNode, fromIndexPaths, toIndexPath);
});
__publicField(this, "filter", (predicate) => {
const filteredRoot = filter(this.rootNode, {
predicate,
getChildren: this.getNodeChildren,
create: this._create
});
return this.copy(filteredRoot);
});
__publicField(this, "toJSON", () => {
return this.getValues(this.rootNode);
});
this.rootNode = options.rootNode;
}
};
function flattenedToTree(nodes, options = fallbackMethods) {
if (nodes.length === 0) {
throw new Error("[zag-js/tree] Cannot create tree from empty flattened array");
}
const rootFlatNode = nodes.find((node) => node._parent === void 0);
if (!rootFlatNode) {
throw new Error("[zag-js/tree] No root node found in flattened data");
}
const nodeMap = /* @__PURE__ */ new Map();
nodes.forEach((node) => {
nodeMap.set(node._index, node);
});
const buildNode = (idx) => {
const flatNode = nodeMap.get(idx);
if (!flatNode) return {};
const { _children, _parent, _index, ...cleanNode } = flatNode;
const children = [];
_children?.forEach((childIndex) => {
children.push(buildNode(childIndex));
});
return {
...cleanNode,
...children.length > 0 && { children }
};
};
const rootNode = buildNode(rootFlatNode._index);
return new TreeCollection({ ...options, rootNode });
}
function filePathToTree(paths) {
const rootNode = {
label: "",
value: "ROOT",
children: []
};
paths.forEach((path) => {
const parts = path.split("/");
let currentNode = rootNode;
parts.forEach((part, index) => {
let childNode = currentNode.children?.find((child) => child.label === part);
if (!childNode) {
childNode = {