@gravity-ui/uikit
Version:
Gravity UI base styling and components
84 lines (83 loc) • 3.51 kB
JavaScript
import * as React from 'react';
import isEqual from "lodash/isEqual.js";
import { useLayoutEffect } from "../useLayoutEffect/index.js";
import { useResizeObserver } from "../useResizeObserver/index.js";
export function useCollapseChildren({ enabled = true, containerRef, preservedRefs = [], minCount = 0, maxCount = Infinity, direction = 'end', gap = 0, childSelector = '*', getChildWidth = (child) => child.getBoundingClientRect().width, }) {
const exertedProps = [minCount, maxCount, direction, gap, childSelector];
const [exertedPropsState, setExertedPropsState] = React.useState(exertedProps);
const [calculated, setCalculated] = React.useState(false);
const isCalculatingRef = React.useRef(true);
const [visibleCount, setVisibleCount] = React.useState(maxCount);
const calculate = (desiredVisibleCount) => {
const container = containerRef.current;
if (!container)
return;
// Batch elements measurement to optimize performance
const containerChildren = Array.from(container.querySelectorAll(`:scope > :where(${childSelector})`));
const containerWidth = container.getBoundingClientRect().width;
const childWidthMap = new WeakMap(containerChildren.map((child) => [child, getChildWidth(child)]));
if (direction === 'start') {
containerChildren.reverse();
}
// Place preserved items at the beginning to calculate them first
const preservedItems = preservedRefs.flatMap((ref) => ref.current ?? []);
containerChildren.sort((itemA, itemB) => {
const valueA = preservedItems.includes(itemA) ? 1 : 0;
const valueB = preservedItems.includes(itemB) ? 1 : 0;
return valueB - valueA;
});
let availableWidth = containerWidth;
let newVisibleCount = 0;
for (const item of containerChildren) {
if (newVisibleCount >= maxCount) {
break;
}
availableWidth -= childWidthMap.get(item) ?? 0;
if (availableWidth < 0)
break;
availableWidth -= gap;
if (!preservedItems.includes(item)) {
newVisibleCount++;
}
}
const minNewVisibleCount = Math.max(newVisibleCount, minCount);
if (minNewVisibleCount === desiredVisibleCount) {
setCalculated(true);
}
else {
setVisibleCount(minNewVisibleCount);
}
};
const recalculate = React.useCallback(() => {
if (enabled && !isCalculatingRef.current) {
isCalculatingRef.current = true;
setVisibleCount(maxCount);
setCalculated(false);
}
}, [enabled, maxCount]);
// Recalculate on changing props
if (!isEqual(exertedProps, exertedPropsState)) {
recalculate();
setExertedPropsState(exertedProps);
}
useResizeObserver({
ref: containerRef,
onResize: recalculate,
});
useLayoutEffect(() => {
if (calculated) {
isCalculatingRef.current = false;
return;
}
if (enabled) {
calculate(visibleCount);
}
else {
setVisibleCount(Infinity);
setCalculated(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enabled, calculated, visibleCount]);
return { calculated, recalculate, visibleCount };
}
//# sourceMappingURL=useCollapseChildren.js.map