UNPKG

@gravity-ui/uikit

Version:

Gravity UI base styling and components

84 lines (83 loc) 3.51 kB
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