@ariakit/react-core
Version:
Ariakit React core
656 lines (623 loc) • 22.2 kB
JavaScript
"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;