UNPKG

box-overflow-core

Version:

Headless UI for automatically collapsing boxes when overflow.

315 lines (314 loc) 11.1 kB
"use strict"; 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); return value; }; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const observer = require("./observer.cjs"); const utils = require("./utils.cjs"); const internalFixedKeys = ["rest", "prefix", "suffix"]; class BoxOverflow { constructor(options) { __publicField(this, "options"); __publicField(this, "displayCount", 0); __publicField(this, "isRestReady", false); __publicField(this, "hasRest", false); __publicField(this, "containerElement", null); __publicField(this, "sizeChanged", false); __publicField(this, "sizeCache", /* @__PURE__ */ new Map()); __publicField(this, "measurementsCache", []); __publicField(this, "measureElementCache", /* @__PURE__ */ new Map()); __publicField(this, "itemsObserver", observer.Observer.createResizeObserver(this.itemMeasurer.bind(this))); __publicField(this, "containerObserver", observer.Observer.createResizeObserver(this.containerMeasurer.bind(this))); __publicField(this, "childrenObserver", observer.Observer.createMutationObserver(this.childrenChange.bind(this))); __publicField(this, "triggerChange", utils.memo(() => [this.getMeasurements(), this.sizeCache], (measurements) => { var _a, _b; (_b = (_a = this.options).onDisplayChange) == null ? void 0 : _b.call(_a, measurements); })); __publicField(this, "getRests", utils.memo(() => [this.displayCount, this.getItemCounts()], (start, end) => { var _a, _b; if (start >= end) return []; const rests = []; for (; start < end; start++) { const key = (_b = (_a = this.options).getIdByIndex) == null ? void 0 : _b.call(_a, start); if (!key) continue; rests.push(key); } return rests; })); this.setOptions(options); } itemMeasurer(element) { this.measureElement(element); if (this.sizeChanged) this.triggerChange(); } containerMeasurer(element) { this.measureContainer(element); this.triggerChange(); } childrenChange(mutation) { mutation.removedNodes.forEach((node) => { if (utils.isElementNode(node)) { const key = this.idOfElement(node); if (!key) return; this.measureElementCache.delete(key); } }); mutation.addedNodes.forEach((node) => { if (utils.isElementNode(node)) { const key = this.idOfElement(node); if (!key) return; this.measureElement(node); } }); } setOptions(options) { Object.entries(options).forEach( ([key, value]) => typeof value === "undefined" && delete options[key] ); this.options = { idAttribute: "data-id", ...options }; if (this.containerElement) this.triggerChange(); } onMount() { const container = this.options.getContainer(); if (!container) return () => { }; this.containerElement = container; this.containerObserver.observe(container); this.childrenObserver.observe(container); this.measureContainer(container); this.measureElements(); this.triggerChange(); return () => { this.destroy(); }; } onUpdate() { const container = this.options.getContainer(); if (container !== this.containerElement) { this.destroy(); this.containerElement = container; this.containerObserver.observe(container); this.childrenObserver.observe(container); this.measureContainer(container); this.measureElements(); this.triggerChange(); } } destroy() { this.itemsObserver.disconnect(); this.containerObserver.disconnect(); this.childrenObserver.disconnect(); this.containerElement = null; this.measureElementCache.clear(); } idOfElement(node) { const attributeName = this.options.idAttribute; const keyStr = node.getAttribute(attributeName); if (!keyStr) { console.warn( `Missing attribute name '${attributeName}={index}' on element.` ); return ""; } return String(keyStr); } measureElements() { const container = this.containerElement; if (!container) return; const children = Array.from(container.children); children.forEach((child) => { if (utils.isElementNode(child)) { const id = this.idOfElement(child); if (!id) return; this.measureElement(child); } }); } measureElement(node) { const id = this.idOfElement(node); const prevNode = this.measureElementCache.get(id); if (node !== prevNode) { if (prevNode) this.itemsObserver.unobserve(prevNode); this.itemsObserver.observe(node); this.measureElementCache.set(id, node); } const rect = node.getBoundingClientRect(); const prevSize = this.sizeCache.get(id); if (!prevSize || prevSize.width !== rect.width || prevSize.height !== rect.height) { this.sizeChanged = true; this.sizeCache.set(id, { width: rect.width, height: rect.height }); } } measureContainer(element) { const rect = element.getBoundingClientRect(); const style = window.getComputedStyle(element); const width = rect.width - Number.parseFloat(style.paddingLeft) - Number.parseFloat(style.paddingRight) - Number.parseFloat(style.borderLeftWidth) - Number.parseFloat(style.borderRightWidth); const height = rect.height - Number.parseFloat(style.paddingTop) - Number.parseFloat(style.paddingBottom) - Number.parseFloat(style.borderTopWidth) - Number.parseFloat(style.borderBottomWidth); this.sizeCache.set("container", { width, height }); } getItemCounts() { return Array.from(this.measureElementCache.values()).filter((item) => { const key = this.idOfElement(item); return !internalFixedKeys.includes(key); }).length; } getMaxCount() { const maxCount = this.options.maxCount; if (maxCount && maxCount < 1) console.error("maxCount can not be less than 1"); const elementLength = this.getItemCounts(); return maxCount ? Math.min(maxCount, elementLength) : elementLength; } getMaxLine() { const maxLine = this.options.maxLine; if (maxLine === void 0) return void 0; return Math.max(maxLine - 1, 0); } getMeasurements() { var _a, _b; const maxLine = this.getMaxLine(); const maxCount = this.getMaxCount(); const restSize = this.sizeCache.get("rest"); const prefixSize = this.sizeCache.get("prefix"); const suffixSize = this.sizeCache.get("suffix"); const containerSize = this.sizeCache.get("container"); let suffixSingleLine = false; let top = 0; let currentLineMaxHeight = 0; let hasRest = maxCount < this.getItemCounts(); let prevOverflow = null; const overflows = []; if (prefixSize) { const prefixSize2 = this.sizeCache.get("prefix"); overflows.push(prevOverflow = { key: "prefix", index: 0, line: 0, top: 0, left: 0, width: prefixSize2.width || 0, height: prefixSize2.height || 0 }); } if (suffixSize && suffixSize.width > containerSize.width) suffixSingleLine = true; const genNextItem = (key, size, nextLine) => { const prevIndex = (prevOverflow == null ? void 0 : prevOverflow.index) ?? -1; const prevLine = (prevOverflow == null ? void 0 : prevOverflow.line) || 0; const left = nextLine ? 0 : ((prevOverflow == null ? void 0 : prevOverflow.left) || 0) + ((prevOverflow == null ? void 0 : prevOverflow.width) || 0); return { key, index: prevIndex + 1, line: nextLine ? prevLine + 1 : prevLine, top, left, width: (size == null ? void 0 : size.width) || 0, height: (size == null ? void 0 : size.height) || 0 }; }; for (let i = 0; i < maxCount; i++) { const key = (_b = (_a = this.options).getIdByIndex) == null ? void 0 : _b.call(_a, i); const size = this.sizeCache.get(key); if (!size && size !== null) break; let nextLine = false; const isLastOne = i === maxCount - 1; const restWidth = isLastOne ? 0 : (restSize == null ? void 0 : restSize.width) || 0; const maybeMaxLine = utils.notNil(maxLine) ? suffixSingleLine ? maxLine - 1 : maxLine : void 0; let validWidth = containerSize.width; if (prevOverflow) validWidth -= prevOverflow.left + prevOverflow.width; if (utils.notNil(maybeMaxLine) && (prevOverflow == null ? void 0 : prevOverflow.line) === maybeMaxLine) { validWidth -= restWidth; if (!suffixSingleLine) validWidth -= (suffixSize == null ? void 0 : suffixSize.width) || 0; if (validWidth < size.width || 0) { hasRest = true; break; } } else { if (validWidth < size.width || 0) { nextLine = true; top += currentLineMaxHeight; currentLineMaxHeight = 0; } } overflows.push(prevOverflow = genNextItem(key, size, nextLine)); currentLineMaxHeight = Math.max(currentLineMaxHeight, size.height); } if (hasRest) overflows.push(prevOverflow = genNextItem("rest", restSize, false)); if (suffixSize) overflows.push(prevOverflow = genNextItem("suffix", suffixSize, suffixSingleLine)); return this.updateMeasurementsCache(overflows); } updateMeasurementsCache(measurements) { const isSame = measurements.length === this.measurementsCache.length && measurements.every((item, index) => { var _a; return item.key === ((_a = this.measurementsCache[index]) == null ? void 0 : _a.key); }); this.sizeChanged = false; if (!isSame) { this.measurementsCache = measurements; const displayCount = measurements.filter((item) => !internalFixedKeys.includes(item.key)).length; this.displayCount = displayCount; } return this.measurementsCache; } getContainerStyle() { const { direction } = this.options; const style = { display: "flex", flexWrap: "wrap", position: "relative" }; direction && (style.direction = direction); return style; } getItemStyle(id) { const item = this.measurementsCache.find((item2) => item2.key === id); if (!item) { return { opacity: "0", position: "absolute", top: "-9999px", left: "-9999px" }; } return { flexShrink: "0", margin: "0" }; } getRestStyle() { const rest = this.measurementsCache.find((item) => item.key === "rest"); if (!rest) { return { opacity: "0", position: "absolute", top: "-9999px", left: "-9999px" }; } return {}; } } exports.BoxOverflow = BoxOverflow; //# sourceMappingURL=index.cjs.map