UNPKG

@ariakit/react-core

Version:

Ariakit React core

656 lines (623 loc) 22.2 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _25BPIGZHcjs = require('./25BPIGZH.cjs'); var _6HKL3JR2cjs = require('./6HKL3JR2.cjs'); var _WULEED4Qcjs = require('./WULEED4Q.cjs'); var _OZM4QA2Vcjs = require('./OZM4QA2V.cjs'); var _7EQBAZ46cjs = require('./7EQBAZ46.cjs'); // src/collection/collection-renderer.tsx var _dom = require('@ariakit/core/utils/dom'); var _misc = require('@ariakit/core/utils/misc'); var _react = require('react'); var _reactdom = require('react-dom'); var _jsxruntime = require('react/jsx-runtime'); var TagName = "div"; var CollectionRendererContext = _react.createContext.call(void 0, null); function createTask() { let raf = 0; const run = (cb) => { if (raf) return; raf = requestAnimationFrame(() => { raf = 0; cb(); }); }; const cancel = () => { cancelAnimationFrame(raf); raf = 0; }; return { run, cancel }; } function findNearestIndex(items, target, getValue) { let left = 0; let right = getItemsLength(items) - 1; while (left <= right) { const index = (left + right) / 2 | 0; const value = getValue(index); if (value === target) return index; else if (value < target) left = index + 1; else right = index - 1; } if (left > 0) return left - 1; return 0; } function getItemsLength(items) { return typeof items === "number" ? items : items.length; } function getItemObject(item) { if (!item || typeof item !== "object") { return { value: item }; } return item; } function getItemId(item, index, baseId) { var _a; _misc.invariant.call(void 0, baseId, "CollectionRenderer must be given an `id` prop."); const defaultId = `${baseId}/${index}`; return (_a = getItemObject(item).id) != null ? _a : defaultId; } function getItem(items, index) { if (typeof items === "number") { if (index >= items) return null; return {}; } const item = items[index]; if (!item) return null; if (typeof item === "object") return item; return { value: item }; } function getItemSize(item, horizontal, fallbackElement) { var _a, _b, _c, _d, _e; const itemObject = getItemObject(item); horizontal = itemObject.orientation === "horizontal" || horizontal; const prop = horizontal ? "width" : "height"; const style = itemObject.style; if (style) { const size = style[prop]; if (typeof size === "number") return size; } const items = itemObject.items; if (items == null ? void 0 : items.length) { const hasSameOrientation = !itemObject.orientation || horizontal && itemObject.orientation === "horizontal" || !horizontal && itemObject.orientation === "vertical"; const paddingStart = (_b = (_a = itemObject.paddingStart) != null ? _a : itemObject.padding) != null ? _b : 0; const paddingEnd = (_d = (_c = itemObject.paddingEnd) != null ? _c : itemObject.padding) != null ? _d : 0; const padding = hasSameOrientation ? paddingStart + paddingEnd : 0; const initialSize = ((_e = itemObject.gap) != null ? _e : 0) * (items.length - 1) + padding; if (hasSameOrientation && itemObject.itemSize) { return initialSize + itemObject.itemSize * items.length; } const totalSize = items.reduce( (sum, item2) => sum + getItemSize(item2, horizontal), initialSize ); if (totalSize !== initialSize) return totalSize; } const element = fallbackElement !== false ? itemObject.element || fallbackElement : null; if (element == null ? void 0 : element.isConnected) { return element.getBoundingClientRect()[prop]; } return 0; } function getAverageSize(props) { const length = getItemsLength(props.items); let currentIndex = 0; let averageSize = props.estimatedItemSize; const setAverageSize = (size) => { const prevIndex = currentIndex; currentIndex = currentIndex + 1; averageSize = (averageSize * prevIndex + size) / currentIndex; }; for (let index = 0; index < length; index += 1) { const item = getItem(props.items, index); const itemId = getItemId(item, index, props.baseId); const itemData = props.data.get(itemId); const fallbackElement = props.elements.get(itemId); const size = getItemSize(item, props.horizontal, fallbackElement); if (size) { setAverageSize(size); } else if (itemData == null ? void 0 : itemData.rendered) { setAverageSize(itemData.end - itemData.start); } } return averageSize; } function getScrollOffset(scroller, horizontal) { if ("scrollX" in scroller) { return horizontal ? scroller.scrollX : scroller.scrollY; } return horizontal ? scroller.scrollLeft : scroller.scrollTop; } function getViewport(scroller) { const { defaultView, documentElement } = scroller.ownerDocument; if (scroller === documentElement) return defaultView; return scroller; } function useScroller(rendererRef) { const [scroller, setScroller] = _react.useState.call(void 0, null); _react.useEffect.call(void 0, () => { const renderer = rendererRef == null ? void 0 : rendererRef.current; if (!renderer) return; const scroller2 = _dom.getScrollingElement.call(void 0, renderer); if (!scroller2) return; setScroller(scroller2); }, [rendererRef]); return scroller; } function getRendererOffset(renderer, scroller, horizontal) { const win = _dom.getWindow.call(void 0, renderer); const htmlElement = win == null ? void 0 : win.document.documentElement; const rendererRect = renderer.getBoundingClientRect(); const rendererOffset = horizontal ? rendererRect.left : rendererRect.top; if (scroller === htmlElement) { const scrollOffset2 = getScrollOffset(win, horizontal); return scrollOffset2 + rendererOffset; } const scrollerRect = scroller.getBoundingClientRect(); const scrollerOffset = horizontal ? scrollerRect.left : scrollerRect.top; const scrollOffset = getScrollOffset(scroller, horizontal); return rendererOffset - scrollerOffset + scrollOffset; } function getOffsets(renderer, scroller, horizontal) { const scrollOffset = getScrollOffset(scroller, horizontal); const rendererOffset = getRendererOffset(renderer, scroller, horizontal); const scrollSize = horizontal ? scroller.clientWidth : scroller.clientHeight; const start = scrollOffset - rendererOffset; const end = start + scrollSize; return { start, end }; } function getItemsEnd(props) { const length = getItemsLength(props.items); const totalPadding = props.paddingStart + props.paddingEnd; if (!length) return totalPadding; const lastIndex = length - 1; const totalGap = lastIndex * props.gap; if (props.itemSize != null) { return length * props.itemSize + totalGap + totalPadding; } const defaultEnd = length * props.estimatedItemSize + totalGap + totalPadding; if (!props.baseId) return defaultEnd; const lastItem = getItem(props.items, lastIndex); const lastItemId = getItemId(lastItem, lastIndex, props.baseId); const lastItemData = props.data.get(lastItemId); if (lastItemData == null ? void 0 : lastItemData.end) return lastItemData.end + props.paddingEnd; if (!Array.isArray(props.items)) return defaultEnd; const end = props.items.reduce( (sum, item) => sum + getItemSize(item, props.horizontal, false), 0 ); if (!end) return defaultEnd; return end + totalGap + totalPadding; } function getData(props) { var _a; const length = getItemsLength(props.items); let nextData; let start = props.paddingStart; const avgSize = getAverageSize(props); for (let index = 0; index < length; index += 1) { const item = getItem(props.items, index); const itemId = getItemId(item, index, props.baseId); const itemData = props.data.get(itemId); const prevRendered = (_a = itemData == null ? void 0 : itemData.rendered) != null ? _a : false; const setSize = (size2, rendered = prevRendered) => { start = start ? start + props.gap : start; const end = start + size2; const nextItemData = { index, rendered, start, end }; if (!_misc.shallowEqual.call(void 0, itemData, nextItemData)) { if (!nextData) { nextData = new Map(props.data); } nextData.set(itemId, { index, rendered, start, end }); } start = end; }; const size = getItemSize( item, props.horizontal, props.elements.get(itemId) ); if (size) { setSize(size, true); } else if (itemData == null ? void 0 : itemData.rendered) { setSize(itemData.end - itemData.start, true); } else { setSize(avgSize); } } return nextData; } function useCollectionRenderer(_a) { var _b = _a, { store, items: itemsProp, initialItems = 0, gap = 0, itemSize, estimatedItemSize = 40, overscan: overscanProp, orientation: orientationProp, padding = 0, paddingStart = padding, paddingEnd = padding, persistentIndices, renderOnScroll = true, renderOnResize = !!renderOnScroll, children: renderItem } = _b, props = _7EQBAZ46cjs.__objRest.call(void 0, _b, [ "store", "items", "initialItems", "gap", "itemSize", "estimatedItemSize", "overscan", "orientation", "padding", "paddingStart", "paddingEnd", "persistentIndices", "renderOnScroll", "renderOnResize", "children" ]); var _a2, _b2; const context = _6HKL3JR2cjs.useCollectionContext.call(void 0, ); store = store || context; const items = _25BPIGZHcjs.useStoreState.call(void 0, store, (state) => itemsProp != null ? itemsProp : state == null ? void 0 : state.items ); _misc.invariant.call(void 0, items != null, process.env.NODE_ENV !== "production" && "CollectionRenderer must be either wrapped in a Collection component or be given an `items` prop." ); let parent = _react.useContext.call(void 0, CollectionRendererContext); if (store && (parent == null ? void 0 : parent.store) !== store) { parent = null; } const parentData = parent == null ? void 0 : parent.childrenData; const orientation = (_a2 = orientationProp != null ? orientationProp : parent == null ? void 0 : parent.orientation) != null ? _a2 : "vertical"; const overscan = (_b2 = overscanProp != null ? overscanProp : parent == null ? void 0 : parent.overscan) != null ? _b2 : 1; const ref = _react.useRef.call(void 0, null); const baseId = _OZM4QA2Vcjs.useId.call(void 0, props.id); const horizontal = orientation === "horizontal"; const elements = _react.useMemo.call(void 0, () => /* @__PURE__ */ new Map(), []); const [elementsUpdated, updateElements] = _OZM4QA2Vcjs.useForceUpdate.call(void 0, ); const [defaultVisibleIndices, setVisibleIndices] = _react.useState.call(void 0, () => { if (!initialItems) return []; const length = getItemsLength(items); const initialLength = Math.min(length, Math.abs(initialItems)); return Array.from({ length: initialLength }, (_, index) => { if (initialItems < 0) return length - index - 1; return index; }); }); const visibleIndices = _react.useMemo.call(void 0, () => { if (!persistentIndices) return defaultVisibleIndices; const nextIndices = defaultVisibleIndices.slice(); for (const index of persistentIndices) { if (index < 0) continue; if (nextIndices.includes(index)) continue; nextIndices.push(index); } nextIndices.sort((a, b) => a - b); if (_misc.shallowEqual.call(void 0, defaultVisibleIndices, nextIndices)) { return defaultVisibleIndices; } return nextIndices; }, [defaultVisibleIndices, persistentIndices]); const [data, setData] = _react.useState.call(void 0, () => { if (!baseId) return /* @__PURE__ */ new Map(); const data2 = (parentData == null ? void 0 : parentData.get(baseId)) || /* @__PURE__ */ new Map(); if (itemSize != null) return data2; if (!items) return data2; const nextData = getData({ baseId, items, data: data2, gap, elements, horizontal, paddingStart, itemSize, estimatedItemSize }); return nextData || data2; }); const totalSize = _react.useMemo.call(void 0, () => { return getItemsEnd({ baseId, items, data, gap, horizontal, itemSize, estimatedItemSize, paddingStart, paddingEnd }); }, [ baseId, items, data, gap, horizontal, itemSize, estimatedItemSize, paddingStart, paddingEnd ]); _react.useEffect.call(void 0, () => { if (!baseId) return; parentData == null ? void 0 : parentData.set(baseId, data); }, [baseId, parentData, data]); _react.useEffect.call(void 0, () => { if (itemSize != null) return; if (!baseId) return; if (!items) return; const nextData = getData({ baseId, items, data, gap, elements, horizontal, paddingStart, itemSize, estimatedItemSize }); if (nextData) { setData(nextData); } }, [ elementsUpdated, itemSize, baseId, items, data, gap, elements, horizontal, paddingStart, estimatedItemSize ]); const scroller = useScroller(items ? ref : null); const offsetsRef = _react.useRef.call(void 0, { start: 0, end: 0 }); const processVisibleIndices = _react.useCallback.call(void 0, () => { const offsets = offsetsRef.current; if (!items) return; if (!baseId) return; if (!offsets.end) return; if (!data.size && !itemSize) return; const length = getItemsLength(items); const getItemOffset = (index, prop = "start") => { var _a3; if (itemSize) { const start2 = itemSize * index + gap * index + paddingStart; if (prop === "start") return start2; return start2 + itemSize; } const item = getItem(items, index); const itemId = getItemId(item, index, baseId); const itemData = data.get(itemId); return (_a3 = itemData == null ? void 0 : itemData[prop]) != null ? _a3 : 0; }; const initialStart = findNearestIndex(items, offsets.start, getItemOffset); let initialEnd = initialStart; while (initialEnd < length && getItemOffset(initialEnd) < offsets.end) { initialEnd += 1; } const finalOverscan = initialEnd - initialStart ? overscan : 0; const start = Math.max(initialStart - finalOverscan, 0); const end = Math.min(initialEnd + finalOverscan, length); const indices = Array.from( { length: end - start }, (_, index) => index + start ); setVisibleIndices((prevIndices) => { if (_misc.shallowEqual.call(void 0, prevIndices, indices)) return prevIndices; return indices; }); }, [ elementsUpdated, items, baseId, data, itemSize, gap, paddingStart, overscan ]); _react.useEffect.call(void 0, processVisibleIndices, [processVisibleIndices]); const processVisibleIndicesEvent = _OZM4QA2Vcjs.useEvent.call(void 0, processVisibleIndices); _react.useEffect.call(void 0, () => { const renderer = ref.current; if (!renderer) return; if (!scroller) return; offsetsRef.current = getOffsets(renderer, scroller, horizontal); processVisibleIndicesEvent(); }, [scroller, horizontal, processVisibleIndicesEvent]); const mayRenderOnScroll = !!renderOnScroll; const renderOnScrollProp = _OZM4QA2Vcjs.useBooleanEvent.call(void 0, renderOnScroll); _react.useEffect.call(void 0, () => { if (!mayRenderOnScroll) return; const renderer = ref.current; if (!renderer) return; if (!scroller) return; const viewport = getViewport(scroller); if (!viewport) return; const task = createTask(); const onScroll = (event) => { task.run(() => { if (!renderOnScrollProp(event)) return; offsetsRef.current = getOffsets(renderer, scroller, horizontal); processVisibleIndicesEvent(); }); }; viewport.addEventListener("scroll", onScroll, { passive: true }); return () => { task.cancel(); viewport.removeEventListener("scroll", onScroll); }; }, [ mayRenderOnScroll, scroller, renderOnScrollProp, horizontal, processVisibleIndicesEvent ]); const mayRenderOnResize = !!renderOnResize; const renderOnResizeProp = _OZM4QA2Vcjs.useBooleanEvent.call(void 0, renderOnResize); _react.useEffect.call(void 0, () => { if (!mayRenderOnResize) return; const renderer = ref.current; if (!renderer) return; if (!scroller) return; const viewport = getViewport(scroller); if (!viewport) return; const task = createTask(); if (viewport === scroller) { if (typeof ResizeObserver !== "function") return; let firstRun = true; const observer = new ResizeObserver(() => { if (firstRun) { firstRun = false; return; } task.run(() => { if (!renderOnResizeProp(scroller)) return; offsetsRef.current = getOffsets(renderer, scroller, horizontal); processVisibleIndicesEvent(); }); }); observer.observe(scroller); return () => { task.cancel(); observer.disconnect(); }; } const onResize = () => { task.run(() => { if (!renderOnResizeProp(scroller)) return; offsetsRef.current = getOffsets(renderer, scroller, horizontal); processVisibleIndicesEvent(); }); }; viewport.addEventListener("resize", onResize, { passive: true }); return () => { task.cancel(); viewport.removeEventListener("resize", onResize); }; }, [ mayRenderOnResize, scroller, renderOnResizeProp, horizontal, processVisibleIndicesEvent ]); _react.useEffect.call(void 0, () => { if (typeof IntersectionObserver !== "function") return; const renderer = ref.current; if (!renderer) return; if (!scroller) return; const viewport = getViewport(scroller); if (!viewport) return; const observer = new IntersectionObserver( () => { offsetsRef.current = getOffsets(renderer, scroller, horizontal); processVisibleIndicesEvent(); }, { root: scroller === viewport ? scroller : null } ); observer.observe(renderer); return () => { observer.disconnect(); }; }, [scroller, processVisibleIndicesEvent]); const elementObserver = _react.useMemo.call(void 0, () => { if (typeof ResizeObserver !== "function") return; return new ResizeObserver(() => { _reactdom.flushSync.call(void 0, updateElements); }); }, [updateElements]); const itemRef = _react.useCallback.call(void 0, (element) => { if (!element) return; if (itemSize) return; updateElements(); elements.set(element.id, element); elementObserver == null ? void 0 : elementObserver.observe(element); }, [itemSize, elements, updateElements, elementObserver] ); const getItemProps = _react.useCallback.call(void 0, (item, index) => { var _a3, _b3; const itemId = getItemId(item, index, baseId); const offset = itemSize ? paddingStart + itemSize * index + gap * index : (_b3 = (_a3 = data.get(itemId)) == null ? void 0 : _a3.start) != null ? _b3 : 0; const baseItemProps = { id: itemId, ref: itemRef, index, style: { position: "absolute", left: horizontal ? offset : 0, top: horizontal ? 0 : offset } }; if (itemSize) { baseItemProps.style[horizontal ? "width" : "height"] = itemSize; } if (item == null) return baseItemProps; const itemProps = getItemObject(item); return _7EQBAZ46cjs.__spreadProps.call(void 0, _7EQBAZ46cjs.__spreadValues.call(void 0, _7EQBAZ46cjs.__spreadValues.call(void 0, {}, itemProps), baseItemProps), { style: _7EQBAZ46cjs.__spreadValues.call(void 0, _7EQBAZ46cjs.__spreadValues.call(void 0, {}, itemProps.style), baseItemProps.style) }); }, [baseId, data, itemSize, paddingStart, gap, horizontal, itemRef] ); const itemsProps = _react.useMemo.call(void 0, () => { return visibleIndices.map((index) => { if (index < 0) return; const item = getItem(items, index); if (!item) return; return getItemProps(item, index); }).filter((value) => value != null); }, [items, visibleIndices, getItemProps]); const children = itemsProps == null ? void 0 : itemsProps.map((itemProps) => { return renderItem == null ? void 0 : renderItem(itemProps); }); const styleProp = props.style; const sizeProperty = horizontal ? "width" : "height"; const style = _react.useMemo.call(void 0, () => _7EQBAZ46cjs.__spreadValues.call(void 0, { flex: "none", position: "relative", [sizeProperty]: totalSize }, styleProp), [styleProp, sizeProperty, totalSize] ); const childrenData = _react.useMemo.call(void 0, () => /* @__PURE__ */ new Map(), []); const providerValue = _react.useMemo.call(void 0, () => ({ store, orientation, overscan, childrenData }), [store, orientation, overscan, childrenData] ); props = _OZM4QA2Vcjs.useWrapElement.call(void 0, props, (element) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CollectionRendererContext.Provider, { value: providerValue, children: element }), [providerValue] ); props = _7EQBAZ46cjs.__spreadProps.call(void 0, _7EQBAZ46cjs.__spreadValues.call(void 0, { id: baseId }, props), { style, ref: _OZM4QA2Vcjs.useMergeRefs.call(void 0, ref, props.ref) }); return _7EQBAZ46cjs.__spreadProps.call(void 0, _7EQBAZ46cjs.__spreadValues.call(void 0, {}, props), { children }); } var CollectionRenderer = _WULEED4Qcjs.forwardRef.call(void 0, function CollectionRenderer2(props) { const htmlProps = useCollectionRenderer(props); return _WULEED4Qcjs.createElement.call(void 0, TagName, htmlProps); }); var getCollectionRendererItem = getItem; var getCollectionRendererItemId = getItemId; exports.useCollectionRenderer = useCollectionRenderer; exports.CollectionRenderer = CollectionRenderer; exports.getCollectionRendererItem = getCollectionRendererItem; exports.getCollectionRendererItemId = getCollectionRendererItemId;