@zag-js/collection
Version:
Utilities to manage a collection of items.
527 lines (525 loc) • 17.5 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// src/list-collection.ts
var list_collection_exports = {};
__export(list_collection_exports, {
ListCollection: () => ListCollection,
isListCollection: () => isListCollection
});
module.exports = __toCommonJS(list_collection_exports);
var import_utils = require("@zag-js/utils");
var fallback = {
itemToValue(item) {
if (typeof item === "string") return item;
if ((0, import_utils.isObject)(item) && (0, import_utils.hasProp)(item, "value")) return item.value;
return "";
},
itemToString(item) {
if (typeof item === "string") return item;
if ((0, import_utils.isObject)(item) && (0, import_utils.hasProp)(item, "label")) return item.label;
return fallback.itemToValue(item);
},
isItemDisabled(item) {
if ((0, import_utils.isObject)(item) && (0, import_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");
__publicField(this, "indexMap", null);
/**
* 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 (0, import_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) => {
const values = [];
for (const item of items) {
const value = this.getItemValue(item);
if (value != null) values.push(value);
}
return values;
});
/**
* Get the item based on its value
*/
__publicField(this, "find", (value) => {
if (value == null) return null;
const index = this.indexOf(value);
return index !== -1 ? this.at(index) : null;
});
/**
* Get the items based on its values
*/
__publicField(this, "findMany", (values) => {
const result = [];
for (const value of values) {
const item = this.find(value);
if (item != null) result.push(item);
}
return result;
});
/**
* 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 = ", ") => {
const strs = [];
for (const item of items) {
const str = this.stringifyItem(item);
if (str != null) strs.push(str);
}
return strs.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);
}
if (!this.indexMap) {
this.indexMap = /* @__PURE__ */ new Map();
let idx = 0;
const groups = this.group();
for (const [, items] of groups) {
for (const item of items) {
const itemValue = this.getItemValue(item);
if (itemValue != null) {
this.indexMap.set(itemValue, idx);
}
idx++;
}
}
}
return this.indexMap.get(value) ?? -1;
});
__publicField(this, "getByText", (text, current) => {
const currentIndex = current != null ? this.indexOf(current) : -1;
const isSingleKey = text.length === 1;
for (let i = 0; i < this.items.length; i++) {
const item = this.items[(currentIndex + i + 1) % this.items.length];
if (isSingleKey && this.getItemValue(item) === current) continue;
if (this.getItemDisabled(item)) continue;
if (match(this.stringifyItem(item), text)) return item;
}
return void 0;
});
/**
* 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());
};
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)];
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ListCollection,
isListCollection
});