box-overflow-core
Version:
Headless UI for automatically collapsing boxes when overflow.
315 lines (314 loc) • 11.1 kB
JavaScript
"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