UNPKG

@legendapp/list

Version:

Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.

1,435 lines (1,411 loc) 247 kB
import * as React2 from 'react'; import { useReducer, useEffect, createContext, useRef, useState, useMemo, useCallback, useLayoutEffect, useImperativeHandle, useContext } from 'react'; import * as ReactNative from 'react-native'; import { Animated, Platform as Platform$1, View as View$1, Text as Text$1, StyleSheet as StyleSheet$1, RefreshControl, Dimensions, I18nManager } from 'react-native'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; // src/components/LegendList.tsx Animated.View; var View = View$1; var Text = Text$1; var Platform = Platform$1; var PlatformAdjustBreaksScroll = Platform.OS === "android"; // src/utils/rtl.ts function clampHorizontalOffset(offset, maxOffset) { if (maxOffset === void 0) { return offset; } return Math.max(0, Math.min(maxOffset, offset)); } function getHorizontalMaxOffset(state, contentWidth) { if (contentWidth === void 0 || !Number.isFinite(contentWidth) || !Number.isFinite(state.scrollLength) || contentWidth <= state.scrollLength) { return contentWidth !== void 0 && Number.isFinite(contentWidth) && Number.isFinite(state.scrollLength) ? 0 : void 0; } return Math.max(0, contentWidth - state.scrollLength); } function getDefaultHorizontalRTLScrollType() { return Platform.OS === "web" ? "normal" : "inverted"; } function getNativeHorizontalRTLScrollType(state) { var _a3; return (_a3 = state == null ? void 0 : state.horizontalRTLScrollType) != null ? _a3 : getDefaultHorizontalRTLScrollType(); } function isRTLProps(props) { var _a3; return (_a3 = props == null ? void 0 : props.rtl) != null ? _a3 : !!I18nManager.isRTL; } function isHorizontalRTL(state) { return isHorizontalRTLProps(state == null ? void 0 : state.props); } function isHorizontalRTLProps(props) { return !!(props == null ? void 0 : props.horizontal) && isRTLProps(props); } function getLogicalHorizontalMaxOffset(state, contentWidth) { var _a3; return (_a3 = getHorizontalMaxOffset(state, contentWidth)) != null ? _a3 : 0; } function getHorizontalInsetEnd(state, inset) { if (!inset) { return 0; } return (isHorizontalRTL(state) ? inset.left : inset.right) || 0; } function toPhysicalHorizontalItemPosition(state, logicalPosition, itemSize, listSize) { if (!isHorizontalRTL(state) || listSize === void 0 || !Number.isFinite(listSize)) { return logicalPosition; } return Math.max(0, listSize - logicalPosition - itemSize); } function toNativeHorizontalOffset(state, logicalOffset, contentWidth) { if (!state || !isHorizontalRTL(state)) { return logicalOffset; } const maxOffset = getHorizontalMaxOffset(state, contentWidth); const clampedLogicalOffset = clampHorizontalOffset(logicalOffset, maxOffset); const mode = getNativeHorizontalRTLScrollType(state); if (mode === "negative") { return clampedLogicalOffset === 0 ? 0 : -clampedLogicalOffset; } if (mode === "inverted") { if (maxOffset === void 0) { return clampedLogicalOffset; } return clampHorizontalOffset(maxOffset - clampedLogicalOffset, maxOffset); } return clampedLogicalOffset; } function toLogicalHorizontalOffset(state, rawOffset, contentWidth) { if (!isHorizontalRTL(state)) { state.horizontalRTLScrollType = void 0; return rawOffset; } const maxOffset = getHorizontalMaxOffset(state, contentWidth); if (rawOffset < 0) { state.horizontalRTLScrollType = "negative"; return clampHorizontalOffset(-rawOffset, maxOffset); } if (maxOffset === void 0) { return rawOffset; } const normalOffset = rawOffset; const invertedOffset = maxOffset - rawOffset; if (!Number.isFinite(invertedOffset)) { state.horizontalRTLScrollType = "normal"; return normalOffset; } const previousMode = state.horizontalRTLScrollType; if (previousMode === "inverted") { return clampHorizontalOffset(invertedOffset, maxOffset); } if (previousMode === "normal") { return clampHorizontalOffset(normalOffset, maxOffset); } if (!state.hasScrolled) { const defaultMode = getDefaultHorizontalRTLScrollType(); state.horizontalRTLScrollType = defaultMode; return clampHorizontalOffset(defaultMode === "inverted" ? invertedOffset : normalOffset, maxOffset); } const referenceScroll = state.scroll; const distanceNormal = Math.abs(normalOffset - referenceScroll); const distanceInverted = Math.abs(invertedOffset - referenceScroll); const useInverted = distanceInverted + 0.5 < distanceNormal; state.horizontalRTLScrollType = useInverted ? "inverted" : "normal"; return clampHorizontalOffset(useInverted ? invertedOffset : normalOffset, maxOffset); } var createAnimatedValue = (value) => new Animated.Value(value); // src/state/state.tsx var ContextState = React2.createContext(null); var contextNum = 0; function StateProvider({ children }) { const [value] = React2.useState(() => ({ animatedScrollY: createAnimatedValue(0), columnWrapperStyle: void 0, contextNum: contextNum++, listeners: /* @__PURE__ */ new Map(), mapViewabilityAmountCallbacks: /* @__PURE__ */ new Map(), mapViewabilityAmountValues: /* @__PURE__ */ new Map(), mapViewabilityCallbacks: /* @__PURE__ */ new Map(), mapViewabilityConfigStates: /* @__PURE__ */ new Map(), mapViewabilityValues: /* @__PURE__ */ new Map(), positionListeners: /* @__PURE__ */ new Map(), state: void 0, values: /* @__PURE__ */ new Map([ ["stylePaddingTop", 0], ["headerSize", 0], ["numContainers", 0], ["activeStickyIndex", -1], ["isAtEnd", false], ["isAtStart", false], ["isNearEnd", false], ["isNearStart", false], ["isWithinMaintainScrollAtEndThreshold", false], ["totalSize", 0], ["scrollAdjustPending", 0] ]), viewRefs: /* @__PURE__ */ new Map() })); return /* @__PURE__ */ React2.createElement(ContextState.Provider, { value }, children); } function useStateContext() { return React2.useContext(ContextState); } function createSelectorFunctionsArr(ctx, signalNames) { let lastValues = []; let lastSignalValues = []; return { get: () => { const currentValues = []; let hasChanged = false; for (let i = 0; i < signalNames.length; i++) { const value = peek$(ctx, signalNames[i]); currentValues.push(value); if (value !== lastSignalValues[i]) { hasChanged = true; } } lastSignalValues = currentValues; if (hasChanged) { lastValues = currentValues; } return lastValues; }, subscribe: (cb) => { const listeners = []; for (const signalName of signalNames) { listeners.push(listen$(ctx, signalName, cb)); } return () => { for (const listener of listeners) { listener(); } }; } }; } function listen$(ctx, signalName, cb) { const { listeners } = ctx; let setListeners = listeners.get(signalName); if (!setListeners) { setListeners = /* @__PURE__ */ new Set(); listeners.set(signalName, setListeners); } setListeners.add(cb); return () => setListeners.delete(cb); } function peek$(ctx, signalName) { const { values } = ctx; return values.get(signalName); } function set$(ctx, signalName, value) { const { listeners, values } = ctx; if (values.get(signalName) !== value) { values.set(signalName, value); const setListeners = listeners.get(signalName); if (setListeners) { for (const listener of setListeners) { listener(value); } } } } function listenPosition$(ctx, key, cb) { const { positionListeners } = ctx; let setListeners = positionListeners.get(key); if (!setListeners) { setListeners = /* @__PURE__ */ new Set(); positionListeners.set(key, setListeners); } setListeners.add(cb); return () => setListeners.delete(cb); } function notifyPosition$(ctx, key, value) { const { positionListeners } = ctx; const setListeners = positionListeners.get(key); if (setListeners) { for (const listener of setListeners) { listener(value); } } } function useArr$(signalNames) { const ctx = React2.useContext(ContextState); const { subscribe, get } = React2.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]); const value = useSyncExternalStore(subscribe, get, get); return value; } function useSelector$(signalName, selector) { const ctx = React2.useContext(ContextState); const { subscribe, get } = React2.useMemo(() => createSelectorFunctionsArr(ctx, [signalName]), [ctx, signalName]); const getSelectedValue = React2.useCallback(() => selector(get()[0]), [get, selector]); const value = useSyncExternalStore(subscribe, getSelectedValue, getSelectedValue); return value; } // src/state/getContentInsetEnd.ts function getContentInsetEndAdjustmentEnd(adjustment) { return Math.max(0, adjustment != null ? adjustment : 0); } function getContentInsetEnd(ctx, contentInsetEndAdjustmentOverride) { var _a3, _b; const state = ctx.state; const { props } = state; const horizontal = props.horizontal; const contentInset = props.contentInset; const baseInset = contentInset != null ? contentInset : state.nativeContentInset; const baseEndInset = (horizontal ? getHorizontalInsetEnd(state, baseInset) : baseInset == null ? void 0 : baseInset.bottom) || 0; const contentInsetEndAdjustment = getContentInsetEndAdjustmentEnd( contentInsetEndAdjustmentOverride != null ? contentInsetEndAdjustmentOverride : props.contentInsetEndAdjustment ); const anchoredEndSpaceSize = peek$(ctx, "anchoredEndSpaceSize"); const anchoredEndInset = ((_a3 = props.anchoredEndSpace) == null ? void 0 : _a3.includeInEndInset) && anchoredEndSpaceSize ? anchoredEndSpaceSize : 0; const overrideInset = (_b = state.contentInsetOverride) != null ? _b : void 0; const adjustedBaseEndInset = baseEndInset + contentInsetEndAdjustment; if (overrideInset) { const mergedInset = { bottom: 0, left: 0, right: 0, ...baseInset, ...overrideInset }; return Math.max( ((horizontal ? getHorizontalInsetEnd(state, mergedInset) : mergedInset.bottom) || 0) + contentInsetEndAdjustment, anchoredEndInset ); } return Math.max(adjustedBaseEndInset, anchoredEndInset); } // src/state/getContentSize.ts function getContentSize(ctx) { var _a3; const { values, state } = ctx; const stylePaddingTop = values.get("stylePaddingTop") || 0; const stylePaddingBottom = state.props.stylePaddingBottom || 0; const headerSize = values.get("headerSize") || 0; const footerSize = values.get("footerSize") || 0; const contentInsetBottom = getContentInsetEnd(ctx); const totalSize = (_a3 = state.pendingTotalSize) != null ? _a3 : values.get("totalSize"); return headerSize + footerSize + totalSize + stylePaddingTop + stylePaddingBottom + (contentInsetBottom || 0); } // src/components/DebugView.tsx var DebugRow = ({ children }) => { return /* @__PURE__ */ React2.createElement(View, { style: { alignItems: "center", flexDirection: "row", justifyContent: "space-between" } }, children); }; React2.memo(function DebugView2() { const ctx = useStateContext(); const [ totalSize = 0, scrollAdjust = 0, rawScroll = 0, scroll = 0, _numContainers = 0, _numContainersPooled = 0, isAtEnd = false ] = useArr$([ "totalSize", "scrollAdjust", "debugRawScroll", "debugComputedScroll", "numContainers", "numContainersPooled", "isAtEnd" ]); const contentSize = getContentSize(ctx); const [, forceUpdate] = useReducer((x) => x + 1, 0); useInterval(() => { forceUpdate(); }, 100); return /* @__PURE__ */ React2.createElement( View, { pointerEvents: "none", style: { // height: 100, backgroundColor: "#FFFFFFCC", borderRadius: 4, padding: 4, paddingBottom: 4, paddingLeft: 4, position: "absolute", right: 0, top: 0 } }, /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "TotalSize:"), /* @__PURE__ */ React2.createElement(Text, null, totalSize.toFixed(2))), /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "ContentSize:"), /* @__PURE__ */ React2.createElement(Text, null, contentSize.toFixed(2))), /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "At end:"), /* @__PURE__ */ React2.createElement(Text, null, String(isAtEnd))), /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "ScrollAdjust:"), /* @__PURE__ */ React2.createElement(Text, null, scrollAdjust.toFixed(2))), /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "RawScroll: "), /* @__PURE__ */ React2.createElement(Text, null, rawScroll.toFixed(2))), /* @__PURE__ */ React2.createElement(DebugRow, null, /* @__PURE__ */ React2.createElement(Text, null, "ComputedScroll: "), /* @__PURE__ */ React2.createElement(Text, null, scroll.toFixed(2))) ); }); function useInterval(callback, delay) { useEffect(() => { const interval = setInterval(callback, delay); return () => clearInterval(interval); }, [delay]); } // src/components/stickyPositionUtils.ts function getStickyPushLimit(state, index, itemKey) { if (!itemKey) { return void 0; } const currentSize = state.sizes.get(itemKey); if (!(currentSize && currentSize > 0)) { return void 0; } const stickyIndexInArray = state.props.stickyHeaderIndicesArr.indexOf(index); if (stickyIndexInArray === -1) { return void 0; } const nextStickyIndex = state.props.stickyHeaderIndicesArr[stickyIndexInArray + 1]; if (nextStickyIndex === void 0) { return void 0; } const nextStickyPosition = state.positions[nextStickyIndex]; if (nextStickyPosition === void 0) { return void 0; } return nextStickyPosition - currentSize; } // src/utils/devEnvironment.ts var metroDev = typeof __DEV__ !== "undefined" ? __DEV__ : void 0; var _a; var envMode = typeof process !== "undefined" && typeof process.env === "object" && process.env ? (_a = process.env.NODE_ENV) != null ? _a : process.env.MODE : void 0; var processDev = typeof envMode === "string" ? envMode.toLowerCase() !== "production" : void 0; var _a2; var IS_DEV = (_a2 = processDev != null ? processDev : metroDev) != null ? _a2 : false; // src/constants.ts var POSITION_OUT_OF_VIEW = -1e7; var EDGE_POSITION_EPSILON = 1; var ENABLE_DEVMODE = IS_DEV && false; var ENABLE_DEBUG_VIEW = IS_DEV && false; // src/constants-platform.native.ts var f = global.nativeFabricUIManager; var IsNewArchitecture = f !== void 0 && f != null; var useAnimatedValue = (initialValue) => { const [animAnimatedValue] = useState(() => new Animated.Value(initialValue)); return animAnimatedValue; }; // src/hooks/useValue$.ts function useValue$(key, params) { const { getValue } = params || {}; const ctx = useStateContext(); const getNewValue = () => { var _a3; return (_a3 = getValue ? getValue(peek$(ctx, key)) : peek$(ctx, key)) != null ? _a3 : 0; }; const animValue = useAnimatedValue(getNewValue()); useLayoutEffect(() => { const syncCurrentValue = () => { animValue.setValue(getNewValue()); }; const unsubscribe = listen$(ctx, key, syncCurrentValue); syncCurrentValue(); return unsubscribe; }, [animValue, ctx, key]); return animValue; } var typedForwardRef = React2.forwardRef; var typedMemo = React2.memo; var getComponent = (Component) => { if (React2.isValidElement(Component)) { return Component; } if (Component) { return /* @__PURE__ */ React2.createElement(Component, null); } return null; }; // src/components/PositionView.native.tsx var PositionViewState = typedMemo(function PositionViewState2({ id, horizontal, style, refView, ...rest }) { const [position = POSITION_OUT_OF_VIEW, _itemKey] = useArr$([`containerPosition${id}`, `containerItemKey${id}`]); return /* @__PURE__ */ React2.createElement(View$1, { ref: refView, style: [style, horizontal ? { left: position } : { top: position }], ...rest }); }); var PositionViewAnimated = typedMemo(function PositionViewAnimated2({ id, horizontal, style, refView, ...rest }) { const position$ = useValue$(`containerPosition${id}`, { getValue: (v) => v != null ? v : POSITION_OUT_OF_VIEW }); const position = horizontal ? { left: position$ } : { top: position$ }; return /* @__PURE__ */ React2.createElement(Animated.View, { ref: refView, style: [style, position], ...rest }); }); var PositionViewSticky = typedMemo(function PositionViewSticky2({ id, horizontal, style, refView, animatedScrollY, index, stickyHeaderConfig, children, ...rest }) { const ctx = useStateContext(); const [position = POSITION_OUT_OF_VIEW, headerSize = 0, stylePaddingTop = 0, itemKey, _totalSize = 0] = useArr$([ `containerPosition${id}`, "headerSize", "stylePaddingTop", `containerItemKey${id}`, "totalSize" ]); const pushLimit = React2.useMemo( () => getStickyPushLimit(ctx.state, index, itemKey), [ctx.state, index, itemKey, _totalSize] ); const transform = React2.useMemo(() => { var _a3; if (animatedScrollY) { const stickyConfigOffset = (_a3 = stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.offset) != null ? _a3 : 0; const stickyStart = position + headerSize + stylePaddingTop - stickyConfigOffset; let nextStickyPosition; if (pushLimit !== void 0) { if (pushLimit <= position) { nextStickyPosition = pushLimit; } else { nextStickyPosition = animatedScrollY.interpolate({ extrapolateLeft: "clamp", extrapolateRight: "clamp", inputRange: [stickyStart, stickyStart + (pushLimit - position)], outputRange: [position, pushLimit] }); } } else { nextStickyPosition = animatedScrollY.interpolate({ extrapolateLeft: "clamp", extrapolateRight: "extend", inputRange: [stickyStart, stickyStart + 5e3], outputRange: [position, position + 5e3] }); } return horizontal ? [{ translateX: nextStickyPosition }] : [{ translateY: nextStickyPosition }]; } }, [animatedScrollY, headerSize, position, pushLimit, stylePaddingTop, stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.offset]); const viewStyle = React2.useMemo(() => [style, { zIndex: index + 1e3 }, { transform }], [style, transform]); const renderStickyHeaderBackdrop = React2.useMemo(() => { if (!(stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.backdropComponent)) { return null; } return /* @__PURE__ */ React2.createElement( View$1, { style: { inset: 0, pointerEvents: "none", position: "absolute" } }, getComponent(stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.backdropComponent) ); }, [stickyHeaderConfig == null ? void 0 : stickyHeaderConfig.backdropComponent]); return /* @__PURE__ */ React2.createElement(Animated.View, { ref: refView, style: viewStyle, ...rest }, renderStickyHeaderBackdrop, children); }); var PositionView = IsNewArchitecture ? PositionViewState : PositionViewAnimated; function useInit(cb) { useState(() => cb()); } // src/utils/helpers.ts function isFunction(obj) { return typeof obj === "function"; } function isArray(obj) { return Array.isArray(obj); } var warned = /* @__PURE__ */ new Set(); function warnDevOnce(id, text) { if (IS_DEV && !warned.has(id)) { warned.add(id); console.warn(`[legend-list] ${text}`); } } function roundSize(size) { return Math.floor(size * 8) / 8; } function isNullOrUndefined(value) { return value === null || value === void 0; } function comparatorDefault(a, b) { return a - b; } function getPadding(s, type) { var _a3, _b, _c; const axisPadding = type === "Left" || type === "Right" ? s.paddingHorizontal : s.paddingVertical; return (_c = (_b = (_a3 = s[`padding${type}`]) != null ? _a3 : axisPadding) != null ? _b : s.padding) != null ? _c : 0; } function extractPadding(style, contentContainerStyle, type) { return getPadding(style, type) + getPadding(contentContainerStyle, type); } function findContainerId(ctx, key) { var _a3, _b; const directMatch = (_b = (_a3 = ctx.state) == null ? void 0 : _a3.containerItemKeys) == null ? void 0 : _b.get(key); if (directMatch !== void 0) { return directMatch; } const numContainers = peek$(ctx, "numContainers"); for (let i = 0; i < numContainers; i++) { const itemKey = peek$(ctx, `containerItemKey${i}`); if (itemKey === key) { return i; } } return -1; } // src/state/ContextContainer.ts var ContextContainer = createContext(null); function useContextContainer() { return useContext(ContextContainer); } function useViewability(callback, configId) { const ctx = useStateContext(); const containerContext = useContextContainer(); useInit(() => { if (!containerContext) { return; } const { containerId } = containerContext; const key = containerId + (configId != null ? configId : ""); const value = ctx.mapViewabilityValues.get(key); if (value) { callback(value); } }); useEffect(() => { if (!containerContext) { return; } const { containerId } = containerContext; const key = containerId + (configId != null ? configId : ""); ctx.mapViewabilityCallbacks.set(key, callback); return () => { ctx.mapViewabilityCallbacks.delete(key); }; }, [ctx, callback, configId, containerContext]); } function useViewabilityAmount(callback) { const ctx = useStateContext(); const containerContext = useContextContainer(); useInit(() => { if (!containerContext) { return; } const { containerId } = containerContext; const value = ctx.mapViewabilityAmountValues.get(containerId); if (value) { callback(value); } }); useEffect(() => { if (!containerContext) { return; } const { containerId } = containerContext; ctx.mapViewabilityAmountCallbacks.set(containerId, callback); return () => { ctx.mapViewabilityAmountCallbacks.delete(containerId); }; }, [ctx, callback, containerContext]); } function useRecyclingEffect(effect) { const containerContext = useContextContainer(); const prevValues = useRef({ prevIndex: void 0, prevItem: void 0 }); useEffect(() => { if (!containerContext) { return; } const { index, value } = containerContext; let ret; if (prevValues.current.prevIndex !== void 0 && prevValues.current.prevItem !== void 0) { ret = effect({ index, item: value, prevIndex: prevValues.current.prevIndex, prevItem: prevValues.current.prevItem }); } prevValues.current = { prevIndex: index, prevItem: value }; return ret; }, [effect, containerContext]); } function useRecyclingState(valueOrFun) { var _a3, _b; const containerContext = useContextContainer(); const computeValue = (ctx) => { if (isFunction(valueOrFun)) { const initializer = valueOrFun; return ctx ? initializer({ index: ctx.index, item: ctx.value, prevIndex: void 0, prevItem: void 0 }) : initializer(); } return valueOrFun; }; const [stateValue, setStateValue] = useState(() => { return computeValue(containerContext); }); const prevItemKeyRef = useRef((_a3 = containerContext == null ? void 0 : containerContext.itemKey) != null ? _a3 : null); const currentItemKey = (_b = containerContext == null ? void 0 : containerContext.itemKey) != null ? _b : null; if (currentItemKey !== null && prevItemKeyRef.current !== currentItemKey) { prevItemKeyRef.current = currentItemKey; setStateValue(computeValue(containerContext)); } const triggerLayout = containerContext == null ? void 0 : containerContext.triggerLayout; const setState = useCallback( (newState) => { if (!triggerLayout) { return; } setStateValue((prevValue) => { return isFunction(newState) ? newState(prevValue) : newState; }); triggerLayout(); }, [triggerLayout] ); return [stateValue, setState]; } function useIsLastItem() { const containerContext = useContextContainer(); const isLast = useSelector$("lastItemKeys", (lastItemKeys) => { if (containerContext) { const { itemKey } = containerContext; if (!isNullOrUndefined(itemKey)) { return (lastItemKeys == null ? void 0 : lastItemKeys.includes(itemKey)) || false; } } return false; }); return isLast; } function useListScrollSize() { const [scrollSize] = useArr$(["scrollSize"]); return scrollSize; } var noop = () => { }; function useSyncLayout() { const containerContext = useContextContainer(); if (IsNewArchitecture && containerContext) { const { triggerLayout: syncLayout } = containerContext; return syncLayout; } else { return noop; } } // src/components/Separator.tsx function Separator({ ItemSeparatorComponent, leadingItem }) { const isLastItem = useIsLastItem(); return isLastItem ? null : /* @__PURE__ */ React2.createElement(ItemSeparatorComponent, { leadingItem }); } function useOnLayoutSync({ ref, onLayoutProp, onLayoutChange }, deps = []) { const lastLayoutRef = useRef(null); const onLayout = useCallback( (event) => { var _a3, _b; const { layout } = event.nativeEvent; if (layout.height !== ((_a3 = lastLayoutRef.current) == null ? void 0 : _a3.height) || layout.width !== ((_b = lastLayoutRef.current) == null ? void 0 : _b.width)) { onLayoutChange(layout, false); lastLayoutRef.current = layout; } onLayoutProp == null ? void 0 : onLayoutProp(event); }, [onLayoutChange, onLayoutProp] ); if (IsNewArchitecture) { useLayoutEffect(() => { if (ref.current) { ref.current.measure((x, y, width, height) => { const layout = { height, width, x, y }; lastLayoutRef.current = layout; onLayoutChange(layout, true); }); } }, deps); } return { onLayout }; } // src/utils/isInMVCPActiveMode.native.ts function isInMVCPActiveMode(state) { return state.dataChangeNeedsScrollUpdate; } // src/components/Container.tsx function getContainerPositionStyle({ columnWrapperStyle, horizontal, hasItemSeparator, isHorizontalRTLList, numColumns, otherAxisPos, otherAxisSize }) { let paddingStyles; if (columnWrapperStyle) { const { columnGap, rowGap, gap } = columnWrapperStyle; if (horizontal) { paddingStyles = { paddingBottom: numColumns > 1 ? (rowGap || gap || 0) / 2 : void 0, paddingRight: columnGap || gap || void 0, paddingTop: numColumns > 1 ? (rowGap || gap || 0) / 2 : void 0 }; } else { paddingStyles = { paddingBottom: rowGap || gap || void 0, paddingLeft: numColumns > 1 ? (columnGap || gap || 0) / 2 : void 0, paddingRight: numColumns > 1 ? (columnGap || gap || 0) / 2 : void 0 }; } } return horizontal ? { boxSizing: paddingStyles ? "border-box" : void 0, direction: isHorizontalRTLList && Platform.OS === "web" ? "ltr" : void 0, flexDirection: hasItemSeparator ? "row" : void 0, height: otherAxisSize, left: 0, position: "absolute", top: otherAxisPos, ...paddingStyles || {} } : { boxSizing: paddingStyles ? "border-box" : void 0, left: otherAxisPos, position: "absolute", right: numColumns > 1 ? null : 0, top: 0, width: otherAxisSize, ...paddingStyles || {} }; } var Container = typedMemo(function Container2({ id, itemKey, recycleItems, horizontal, getRenderedItem: getRenderedItem2, updateItemSize: updateItemSize2, ItemSeparatorComponent, stickyHeaderConfig }) { const ctx = useStateContext(); const { columnWrapperStyle, animatedScrollY } = ctx; const isHorizontalRTLList = isHorizontalRTL(ctx.state); const positionComponentInternal = ctx.state.props.positionComponentInternal; const stickyPositionComponentInternal = ctx.state.props.stickyPositionComponentInternal; const [column = 0, span = 1, data, numColumns = 1, extraData, isSticky] = useArr$([ `containerColumn${id}`, `containerSpan${id}`, `containerItemData${id}`, "numColumns", "extraData", `containerSticky${id}` ]); const itemLayoutRef = useRef({ didLayout: false, horizontal, itemKey, pendingShrinkToken: 0, updateItemSize: updateItemSize2 }); itemLayoutRef.current.horizontal = horizontal; itemLayoutRef.current.itemKey = itemKey; itemLayoutRef.current.updateItemSize = updateItemSize2; const ref = useRef(null); const [layoutRenderCount, forceLayoutRender] = useState(0); const resolvedColumn = column > 0 ? column : 1; const resolvedSpan = Math.min(Math.max(span || 1, 1), numColumns); const otherAxisPos = numColumns > 1 ? `${(resolvedColumn - 1) / numColumns * 100}%` : 0; const otherAxisSize = numColumns > 1 ? `${resolvedSpan / numColumns * 100}%` : void 0; const style = useMemo( () => getContainerPositionStyle({ columnWrapperStyle, hasItemSeparator: !!ItemSeparatorComponent, horizontal, isHorizontalRTLList, numColumns, otherAxisPos, otherAxisSize }), [ horizontal, isHorizontalRTLList, otherAxisPos, otherAxisSize, columnWrapperStyle, numColumns, ItemSeparatorComponent ] ); const renderedItemInfo = useMemo( () => itemKey !== void 0 ? getRenderedItem2(itemKey) : null, [itemKey, data, extraData] ); const { index, renderedItem } = renderedItemInfo || {}; const contextValue = useMemo(() => { ctx.viewRefs.set(id, ref); return { containerId: id, index, itemKey, triggerLayout: () => { forceLayoutRender((v) => v + 1); }, value: data }; }, [id, itemKey, index, data]); const onLayoutChange = useCallback((rectangle) => { var _a3, _b; const { horizontal: currentHorizontal, itemKey: currentItemKey, updateItemSize: updateItemSizeFn, lastSize, pendingShrinkToken } = itemLayoutRef.current; if (isNullOrUndefined(currentItemKey)) { return; } itemLayoutRef.current.didLayout = true; let layout = rectangle; const axis = currentHorizontal ? "width" : "height"; const size = roundSize(rectangle[axis]); const prevSize = lastSize ? roundSize(lastSize[axis]) : void 0; const doUpdate = () => { itemLayoutRef.current.lastSize = layout; updateItemSizeFn(currentItemKey, layout); itemLayoutRef.current.didLayout = true; }; const shouldDeferWebShrinkLayoutUpdate = Platform.OS === "web" && !isInMVCPActiveMode(ctx.state) && prevSize !== void 0 && size + 1 < prevSize; if (shouldDeferWebShrinkLayoutUpdate) { const token = pendingShrinkToken + 1; itemLayoutRef.current.pendingShrinkToken = token; requestAnimationFrame(() => { var _a4; if (itemLayoutRef.current.pendingShrinkToken !== token) { return; } const element = ref.current; const rect = (_a4 = element == null ? void 0 : element.getBoundingClientRect) == null ? void 0 : _a4.call(element); if (rect) { layout = { height: rect.height, width: rect.width }; } doUpdate(); }); return; } if (IsNewArchitecture || size > 0) { doUpdate(); } else { (_b = (_a3 = ref.current) == null ? void 0 : _a3.measure) == null ? void 0 : _b.call(_a3, (_x, _y, width, height) => { layout = { height, width }; doUpdate(); }); } }, []); const { onLayout } = useOnLayoutSync( { onLayoutChange, ref}, [itemKey, layoutRenderCount] ); if (!IsNewArchitecture) { useEffect(() => { if (!isNullOrUndefined(itemKey)) { itemLayoutRef.current.didLayout = false; const timeout = setTimeout(() => { if (!itemLayoutRef.current.didLayout) { const { itemKey: currentItemKey, lastSize, updateItemSize: updateItemSizeFn } = itemLayoutRef.current; if (lastSize && !isNullOrUndefined(currentItemKey)) { updateItemSizeFn(currentItemKey, lastSize); itemLayoutRef.current.didLayout = true; } } }, 16); return () => { clearTimeout(timeout); }; } }, [itemKey]); } const PositionComponent = isSticky ? stickyPositionComponentInternal ? stickyPositionComponentInternal : PositionViewSticky : positionComponentInternal ? positionComponentInternal : PositionView; return /* @__PURE__ */ React2.createElement( PositionComponent, { animatedScrollY: isSticky ? animatedScrollY : void 0, horizontal, id, index, key: recycleItems ? void 0 : itemKey, onLayout, refView: ref, stickyHeaderConfig, style }, /* @__PURE__ */ React2.createElement(ContextContainer.Provider, { value: contextValue }, renderedItem, renderedItemInfo && ItemSeparatorComponent && /* @__PURE__ */ React2.createElement(Separator, { ItemSeparatorComponent, leadingItem: renderedItemInfo.item })) ); }); // src/components/ContainerSlot.tsx function ContainerSlotBase({ id, horizontal, recycleItems, ItemSeparatorComponent, updateItemSize: updateItemSize2, getRenderedItem: getRenderedItem2, stickyHeaderConfig, ContainerComponent = Container }) { const [itemKey] = useArr$([`containerItemKey${id}`]); if (itemKey === void 0) { return null; } return /* @__PURE__ */ React2.createElement( ContainerComponent, { getRenderedItem: getRenderedItem2, horizontal, ItemSeparatorComponent, id, itemKey, recycleItems, stickyHeaderConfig, updateItemSize: updateItemSize2 } ); } var ContainerSlot = typedMemo(function ContainerSlot2(props) { return /* @__PURE__ */ React2.createElement(ContainerSlotBase, { ...props }); }); // src/components/Containers.native.tsx var ContainersLayer = typedMemo(function ContainersLayer2({ children, horizontal }) { const ctx = useStateContext(); const columnWrapperStyle = ctx.columnWrapperStyle; const animSize = useValue$("totalSize"); const [readyToRender, numColumns, otherAxisSize = 0] = useArr$(["readyToRender", "numColumns", "otherAxisSize"]); const style = horizontal ? { height: otherAxisSize || "100%", minHeight: otherAxisSize, opacity: readyToRender ? 1 : 0, width: animSize } : { height: animSize, minWidth: otherAxisSize, opacity: readyToRender ? 1 : 0 }; if (columnWrapperStyle) { const { columnGap, rowGap, gap } = columnWrapperStyle; const gapX = columnGap || gap || 0; const gapY = rowGap || gap || 0; if (horizontal) { if (gapY && numColumns > 1) { style.marginVertical = -gapY / 2; } if (gapX) { style.marginRight = -gapX; } } else { if (gapX && numColumns > 1) { style.marginHorizontal = -gapX; } if (gapY) { style.marginBottom = -gapY; } } } return /* @__PURE__ */ React2.createElement(Animated.View, { style }, children); }); var Containers = typedMemo(function Containers2({ horizontal, recycleItems, ItemSeparatorComponent, stickyHeaderConfig, updateItemSize: updateItemSize2, getRenderedItem: getRenderedItem2 }) { const [numContainersPooled] = useArr$(["numContainersPooled"]); const containers = []; for (let i = 0; i < numContainersPooled; i++) { containers.push( /* @__PURE__ */ React2.createElement( ContainerSlot, { getRenderedItem: getRenderedItem2, horizontal, ItemSeparatorComponent, id: i, key: i, recycleItems, stickyHeaderConfig, updateItemSize: updateItemSize2 } ) ); } return /* @__PURE__ */ React2.createElement(ContainersLayer, { horizontal }, containers); }); var ListComponentScrollView = Animated.ScrollView; // src/components/listComponentStyles.ts function getAutoOtherAxisStyle({ horizontal, needsOtherAxisSize, otherAxisSize }) { if (!needsOtherAxisSize || !otherAxisSize || otherAxisSize <= 0) { return void 0; } return horizontal ? { height: otherAxisSize } : { width: otherAxisSize }; } function ScrollAdjust() { var _a3; const ctx = useStateContext(); const bias = 1e7; const [scrollAdjust, scrollAdjustUserOffset] = useArr$(["scrollAdjust", "scrollAdjustUserOffset"]); const scrollOffset = (scrollAdjust || 0) + (scrollAdjustUserOffset || 0) + bias; const horizontal = !!((_a3 = ctx.state) == null ? void 0 : _a3.props.horizontal); return /* @__PURE__ */ React2.createElement( View$1, { style: { height: 0, left: horizontal ? scrollOffset : 0, position: "absolute", top: horizontal ? 0 : scrollOffset, width: 0 } } ); } var SnapWrapper = React2.forwardRef(function SnapWrapperInner({ ScrollComponent, ...props }, ref) { const [snapToOffsets] = useArr$(["snapToOffsets"]); return /* @__PURE__ */ React2.createElement(ScrollComponent, { ...props, ref, snapToOffsets }); }); function WebAnchoredEndSpace({ horizontal }) { const ctx = useStateContext(); const [anchoredEndSpaceSize] = useArr$(["anchoredEndSpaceSize"]); const shouldRenderAnchoredEndSpace = !!ctx.state.props.anchoredEndSpace && (anchoredEndSpaceSize || 0) > 0; if (!shouldRenderAnchoredEndSpace) { return null; } const style = horizontal ? { height: "100%", width: anchoredEndSpaceSize || 0 } : { height: anchoredEndSpaceSize || 0 }; return /* @__PURE__ */ React2.createElement("div", { style }, null); } function useLatestRef(value) { const ref = React2.useRef(value); ref.current = value; return ref; } // src/hooks/useStableRenderComponent.tsx function useStableRenderComponent(renderComponent, mapProps) { const renderComponentRef = useLatestRef(renderComponent); const mapPropsRef = useLatestRef(mapProps); return React2.useMemo( () => React2.forwardRef( (props, ref) => { var _a3, _b; return (_b = (_a3 = renderComponentRef.current) == null ? void 0 : _a3.call(renderComponentRef, mapPropsRef.current(props, ref))) != null ? _b : null; } ), [mapPropsRef, renderComponentRef] ); } var LayoutView = ({ onLayoutChange, refView, ...rest }) => { const localRef = useRef(null); const ref = refView != null ? refView : localRef; const { onLayout } = useOnLayoutSync({ onLayoutChange, ref }); return /* @__PURE__ */ React2.createElement(View$1, { ...rest, onLayout, ref }); }; // src/components/ListComponent.tsx var ListComponent = typedMemo(function ListComponent2({ canRender, style, contentContainerStyle, horizontal, initialContentOffset, recycleItems, ItemSeparatorComponent, alignItemsAtEnd: _alignItemsAtEnd, onScroll: onScroll2, onLayout, ListHeaderComponent, ListHeaderComponentStyle, ListFooterComponent, ListFooterComponentStyle, ListEmptyComponent, getRenderedItem: getRenderedItem2, updateItemSize: updateItemSize2, refScrollView, renderScrollComponent, onLayoutFooter, scrollAdjustHandler, snapToIndices, stickyHeaderConfig, stickyHeaderIndices, useWindowScroll = false, ...rest }) { const ctx = useStateContext(); const maintainVisibleContentPosition = ctx.state.props.maintainVisibleContentPosition; const [otherAxisSize = 0] = useArr$(["otherAxisSize"]); const autoOtherAxisStyle = getAutoOtherAxisStyle({ horizontal, needsOtherAxisSize: ctx.state.needsOtherAxisSize, otherAxisSize }); const CustomScrollComponent = useStableRenderComponent( renderScrollComponent, (props, ref) => ({ ...props, ref }) ); const ScrollComponent = renderScrollComponent ? CustomScrollComponent : ListComponentScrollView; const SnapOrScroll = snapToIndices ? SnapWrapper : ScrollComponent; useLayoutEffect(() => { if (!ListHeaderComponent) { set$(ctx, "headerSize", 0); } if (!ListFooterComponent) { set$(ctx, "footerSize", 0); } }, [ListHeaderComponent, ListFooterComponent, ctx]); const onLayoutHeader = useCallback( (rect) => { const size = rect[horizontal ? "width" : "height"]; set$(ctx, "headerSize", size); }, [ctx, horizontal] ); const onLayoutFooterInternal = useCallback( (rect, fromLayoutEffect) => { const size = rect[horizontal ? "width" : "height"]; set$(ctx, "footerSize", size); onLayoutFooter == null ? void 0 : onLayoutFooter(rect, fromLayoutEffect); }, [ctx, horizontal, onLayoutFooter] ); return /* @__PURE__ */ React2.createElement( SnapOrScroll, { ...rest, ...ScrollComponent === ListComponentScrollView ? { useWindowScroll } : {}, contentContainerStyle: [ horizontal ? { height: "100%" } : {}, contentContainerStyle ], contentOffset: initialContentOffset !== void 0 ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0, horizontal, maintainVisibleContentPosition: maintainVisibleContentPosition.size || maintainVisibleContentPosition.data ? { minIndexForVisible: 0 } : void 0, onLayout, onScroll: onScroll2, ref: refScrollView, ScrollComponent: snapToIndices ? ScrollComponent : void 0, style: autoOtherAxisStyle ? [autoOtherAxisStyle, style] : style }, /* @__PURE__ */ React2.createElement(ScrollAdjust, null), ListHeaderComponent && /* @__PURE__ */ React2.createElement(LayoutView, { onLayoutChange: onLayoutHeader, style: ListHeaderComponentStyle }, getComponent(ListHeaderComponent)), ListEmptyComponent && getComponent(ListEmptyComponent), canRender && !ListEmptyComponent && /* @__PURE__ */ React2.createElement( Containers, { getRenderedItem: getRenderedItem2, horizontal, ItemSeparatorComponent, recycleItems, stickyHeaderConfig, updateItemSize: updateItemSize2 } ), ListFooterComponent && /* @__PURE__ */ React2.createElement(LayoutView, { onLayoutChange: onLayoutFooterInternal, style: ListFooterComponentStyle }, getComponent(ListFooterComponent)), Platform.OS === "web" && /* @__PURE__ */ React2.createElement(WebAnchoredEndSpace, { horizontal }), IS_DEV && ENABLE_DEVMODE ); }); var WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH = 100; var WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO = 0.9; var WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO = 0.9; function useDevChecksImpl(props) { const ctx = useStateContext(); const { childrenMode, keyExtractor, renderScrollComponent, useWindowScroll } = props; useEffect(() => { if (useWindowScroll && renderScrollComponent) { warnDevOnce( "useWindowScrollRenderScrollComponent", "useWindowScroll is not supported when renderScrollComponent is provided." ); } }, [renderScrollComponent, useWindowScroll]); useEffect(() => { if (!keyExtractor && !ctx.state.isFirst && ctx.state.didDataChange && !childrenMode) { warnDevOnce( "keyExtractor", "Changing data without a keyExtractor can cause slow performance and resetting scroll. If your list data can change you should use a keyExtractor with a unique id for best performance and behavior." ); } }, [childrenMode, ctx, keyExtractor]); useEffect(() => { const state = ctx.state; const dataLength = state.props.data.length; const useWindowScrollResolved = state.props.useWindowScroll; if (Platform.OS !== "web" || useWindowScrollResolved || dataLength < WEB_UNBOUNDED_HEIGHT_MIN_DATA_LENGTH) { return; } const warnIfUnboundedOuterSize = () => { const readyToRender = peek$(ctx, "readyToRender"); const numContainers = peek$(ctx, "numContainers") || 0; const totalSize = peek$(ctx, "totalSize") || 0; const scrollLength = ctx.state.scrollLength || 0; if (!readyToRender || totalSize <= 0 || scrollLength <= 0) { return; } const rendersAlmostEverything = numContainers >= Math.ceil(dataLength * WEB_UNBOUNDED_HEIGHT_CONTAINER_RATIO); const viewportMatchesContent = scrollLength >= totalSize * WEB_UNBOUNDED_HEIGHT_VIEWPORT_RATIO; if (rendersAlmostEverything && viewportMatchesContent) { warnDevOnce( "webUnboundedOuterSize", "LegendList appears to have an unbounded outer height on web, so virtualization is effectively disabled. Set a bounded height or flex: 1 on the list container, or use useWindowScroll." ); } }; warnIfUnboundedOuterSize(); const unsubscribe = [ listen$(ctx, "numContainers", warnIfUnboundedOuterSize), listen$(ctx, "readyToRender", warnIfUnboundedOuterSize), listen$(ctx, "totalSize", warnIfUnboundedOuterSize) ]; return () => { for (const unsub of unsubscribe) { unsub(); } }; }, [ctx]); } function useDevChecksNoop(_props) { } var useDevChecks = IS_DEV ? useDevChecksImpl : useDevChecksNoop; // src/core/deferredPublicOnScroll.ts function withResolvedContentOffset(state, event, resolvedOffset) { return { ...event, nativeEvent: { ...event.nativeEvent, contentOffset: state.props.horizontal ? { x: resolvedOffset, y: 0 } : { x: 0, y: resolvedOffset } } }; } function releaseDeferredPublicOnScroll(ctx, resolvedOffset) { var _a3, _b, _c, _d; const state = ctx.state; const deferredEvent = state.deferredPublicOnScrollEvent; state.deferredPublicOnScrollEvent = void 0; if (deferredEvent) { (_d = (_c = state.props).onScroll) == null ? void 0 : _d.call( _c, withResolvedContentOffset( state, deferredEvent, (_b = (_a3 = resolvedOffset != null ? resolvedOffset : state.scrollPending) != null ? _a3 : state.scroll) != null ? _b : 0 ) ); } } // src/core/initialScrollSession.ts var INITIAL_SCROLL_MIN_TARGET_OFFSET = 1; function hasInitialScrollSessionCompletion(completion) { return !!((completion == null ? void 0 : completion.didDispatchNativeScroll) || (completion == null ? void 0 : completion.didRetrySilentInitialScroll) || (completion == null ? void 0 : completion.watchdog)); } function clearInitialScrollSession(state) { state.initialScrollSession = void 0; return void 0; } function createInitialScrollSession(options) { const { bootstrap, completion, kind, previousDataLength } = options; return kind === "offset" ? { completion, kind, previousDataLength } : { bootstrap, completion, kind, previousDataLength }; } function ensureInitialScrollSessionCompletion(state, kind = ((_b) => (_b = ((_a3) => (_a3 = state.initialScrollSession) == null ? void 0 : _a3.kind)()) != null ? _b : "bootstrap")()) { var _a4, _b2; if (!state.initialScrollSession) { state.initialScrollSession = createInitialScrollSession({ completion: {}, kind, previousDataLength: 0 }); } else if (state.initialScrollSession.kind !== kind) { state.initialScrollSession = createInitialScrollSession({ bootstrap: state.initialScrollSession.kind === "bootstrap" ? state.initialScrollSession.bootstrap : void 0, completion: state.initialScrollSession.completion, kind, previousDataLength: state.initialScrollSession.previousDataLength }); } (_b2 = (_a4 = state.initialScrollSession).completion) != null ? _b2 : _a4.completion = {}; return state.initialScrollSession.completion; } var initialScrollCompletion = { didDispatchNativeScroll(state) { var _a3, _b; return !!((_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.completion) == null ? void 0 : _b.didDispatchNativeScroll); }, didRetrySilentInitialScroll(state) { var _a3, _b; return !!((_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.completion) == null ? void 0 : _b.didRetrySilentInitialScroll); }, markInitialScrollNativeDispatch(state) { ensureInitialScrollSessionCompletion(state).didDispatchNativeScroll = true; }, markSilentInitialScrollRetry(state) { ensureInitialScrollSessionCompletion(state).didRetrySilentInitialScroll = true; }, resetFlags(state) { if (!state.initialScrollSession) { return; } const completion = ensureInitialScrollSessionCompletion(state, state.initialScrollSession.kind); completion.didDispatchNativeScroll = void 0; completion.didRetrySilentInitialScroll = void 0; } }; var initialScrollWatchdog = { clear(state) { initialScrollWatchdog.set(state, void 0); }, didReachTarget(newScroll, watchdog) { const nextDistance = Math.abs(newScroll - watchdog.targetOffset); return nextDistance <= INITIAL_SCROLL_MIN_TARGET_OFFSET; }, get(state) { var _a3, _b; return (_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.completion) == null ? void 0 : _b.watchdog; }, hasNonZeroTargetOffset(targetOffset) { return targetOffset !== void 0 && targetOffset > INITIAL_SCROLL_MIN_TARGET_OFFSET; }, isAtZeroTargetOffset(targetOffset) { return targetOffset <= INITIAL_SCROLL_MIN_TARGET_OFFSET; }, set(state, watchdog) { var _a3, _b; if (!watchdog && !((_b = (_a3 = state.initialScrollSession) == null ? void 0 : _a3.completion) == null ? void 0 : _b.watchdog)) { return; } const completion = ensureInitialScrollSessionCompletion(state); completion.watchdog = watchdog ? { startScrol