react-truncate-list
Version:
Truncate a list of elements with a symbol or component of your choice
124 lines (123 loc) • 5.22 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TruncatedList = void 0;
const react_1 = require("react");
const rectContainsRect = (parent, child) => {
return (child.top >= parent.top && child.bottom <= parent.bottom && child.left >= parent.left && child.right <= parent.right);
};
const TruncatedList = ({ renderTruncator, alwaysShowTruncator, onResize, className, style, children, }) => {
const containerRef = (0, react_1.useRef)(null);
const truncate = (0, react_1.useCallback)(() => {
if (!containerRef.current) {
return;
}
const childNodes = Array.from(containerRef.current.children);
//
// Put the list in its initial state.
//
// Change from a scrollable container to overflow: hidden during hydration
containerRef.current.style.overflow = "hidden";
// Show all items, hide all truncators.
for (let i = 0; i < childNodes.length; ++i) {
childNodes[i].hidden = i % 2 === 0;
}
// If there are no items (the last truncator is always included).
if (childNodes.length === 1) {
return;
}
//
// Test if truncation is necessary.
//
if (alwaysShowTruncator) {
// if the last truncator fits, exit
const truncatorEl = childNodes[childNodes.length - 1];
truncatorEl.hidden = false;
if (rectContainsRect(containerRef.current.getBoundingClientRect(), truncatorEl.getBoundingClientRect())) {
return;
}
truncatorEl.hidden = true;
}
else {
// if the last item fits, exit
const itemEl = childNodes[childNodes.length - 2];
if (rectContainsRect(containerRef.current.getBoundingClientRect(), itemEl.getBoundingClientRect())) {
return;
}
}
//
// Truncation is necessary - binary search to find the last truncator that can fit.
//
const numTruncators = Math.floor((childNodes.length - 1) / 2);
let left = 0;
let right = numTruncators - 1;
let truncatorIndex = null;
while (left <= right) {
const middle = Math.floor((left + right) / 2);
// show all items before the truncator
for (let i = 0; i < middle; i += 1) {
childNodes[i * 2 + 1].hidden = false;
}
// hide all items after the truncator
for (let i = middle; i < numTruncators; i += 1) {
childNodes[i * 2 + 1].hidden = true;
}
const truncatorEl = childNodes[middle * 2];
truncatorEl.hidden = false;
// check if this truncator fits
if (rectContainsRect(containerRef.current.getBoundingClientRect(), truncatorEl.getBoundingClientRect())) {
truncatorIndex = middle;
left = middle + 1;
}
else {
right = middle - 1;
}
truncatorEl.hidden = true;
}
// If we didn't find a truncator that fits, everything will be hidden at this point and we can exit early
if (truncatorIndex === null) {
return;
}
//
// Now we have found the last truncator that fits, show it.
//
// show all items before the truncator
for (let i = 0; i < truncatorIndex; i += 1) {
childNodes[i * 2 + 1].hidden = false;
}
// hide all items after truncator
for (let i = truncatorIndex; i < numTruncators; i += 1) {
childNodes[i * 2 + 1].hidden = true;
}
const truncatorEl = childNodes[truncatorIndex * 2];
truncatorEl.hidden = false;
}, [alwaysShowTruncator]);
// Set up a resize observer
(0, react_1.useLayoutEffect)(() => {
const resizeObserver = new ResizeObserver(() => {
if (onResize) {
onResize({ truncate });
}
else {
truncate();
}
});
const containerEl = containerRef.current;
if (!containerEl) {
throw new Error("assert: container is mounted");
}
resizeObserver.observe(containerEl);
truncate();
return () => {
resizeObserver.unobserve(containerEl);
};
// trigger if children change also
}, [truncate, onResize, children]);
const childArray = react_1.default.Children.toArray(children);
const items = childArray.map((item, i) => (react_1.default.createElement(react_1.default.Fragment, { key: i },
react_1.default.createElement("li", { hidden: true }, renderTruncator({ hiddenItemsCount: childArray.length - i })),
react_1.default.createElement("li", null, item))));
return (react_1.default.createElement("ul", { ref: containerRef, className: `react-truncate-list ${className !== null && className !== void 0 ? className : ""}`, style: style },
items,
react_1.default.createElement("li", { hidden: true }, renderTruncator({ hiddenItemsCount: 0 }))));
};
exports.TruncatedList = TruncatedList;