@legendapp/list
Version:
Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.
1,462 lines (1,450 loc) • 90.6 kB
JavaScript
import * as React2 from 'react';
import React2__default, { useReducer, useEffect, createContext, useMemo, useRef, useCallback, useLayoutEffect, useImperativeHandle, useContext, useState, forwardRef, memo } from 'react';
import { View, Text, Platform, Animated, ScrollView, StyleSheet, Dimensions, RefreshControl } from 'react-native';
import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { LegendList as LegendList$1 } from '@legendapp/list';
// src/LegendList.tsx
var ContextState = React2.createContext(null);
function StateProvider({ children }) {
const [value] = React2.useState(() => ({
listeners: /* @__PURE__ */ new Map(),
values: /* @__PURE__ */ new Map([
["paddingTop", 0],
["alignItemsPaddingTop", 0],
["stylePaddingTop", 0],
["headerSize", 0]
]),
mapViewabilityCallbacks: /* @__PURE__ */ new Map(),
mapViewabilityValues: /* @__PURE__ */ new Map(),
mapViewabilityAmountCallbacks: /* @__PURE__ */ new Map(),
mapViewabilityAmountValues: /* @__PURE__ */ new Map(),
columnWrapperStyle: void 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 {
subscribe: (cb) => {
const listeners = [];
for (const signalName of signalNames) {
listeners.push(listen$(ctx, signalName, cb));
}
return () => {
for (const listener of listeners) {
listener();
}
};
},
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;
}
};
}
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 getContentSize(ctx) {
const { values } = ctx;
const stylePaddingTop = values.get("stylePaddingTop") || 0;
const headerSize = values.get("headerSize") || 0;
const footerSize = values.get("footerSize") || 0;
const totalSize = values.get("totalSize") || 0;
return headerSize + footerSize + totalSize + stylePaddingTop;
}
function useArr$(signalNames) {
const ctx = React2.useContext(ContextState);
const { subscribe, get } = React2.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
const value = useSyncExternalStore(subscribe, get);
return value;
}
function useSelector$(signalName, selector) {
const ctx = React2.useContext(ContextState);
const { subscribe, get } = React2.useMemo(() => createSelectorFunctionsArr(ctx, [signalName]), [ctx, signalName]);
const value = useSyncExternalStore(subscribe, () => selector(get()[0]));
return value;
}
// src/DebugView.tsx
var DebugRow = ({ children }) => {
return /* @__PURE__ */ React2.createElement(View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" } }, children);
};
var DebugView = React2.memo(function DebugView2({ state }) {
const ctx = useStateContext();
const [
totalSize = 0,
totalSizeWithScrollAdjust = 0,
scrollAdjust = 0,
rawScroll = 0,
scroll = 0,
numContainers = 0,
numContainersPooled = 0
] = useArr$([
"totalSize",
"totalSizeWithScrollAdjust",
"scrollAdjust",
"debugRawScroll",
"debugComputedScroll",
"numContainers",
"numContainersPooled"
]);
const contentSize = getContentSize(ctx);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
useInterval(() => {
forceUpdate();
}, 100);
return /* @__PURE__ */ React2.createElement(
View,
{
style: {
position: "absolute",
top: 0,
right: 0,
paddingLeft: 4,
paddingBottom: 4,
// height: 100,
backgroundColor: "#FFFFFFCC",
padding: 4,
borderRadius: 4
},
pointerEvents: "none"
},
/* @__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(state.isAtEnd))),
/* @__PURE__ */ React2.createElement(Text, null),
/* @__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, "TotalSizeReal: "), /* @__PURE__ */ React2.createElement(Text, null, totalSizeWithScrollAdjust.toFixed(2))),
/* @__PURE__ */ React2.createElement(Text, null),
/* @__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/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 (__DEV__ && !warned.has(id)) {
warned.add(id);
console.warn(`[legend-list] ${text}`);
}
}
function isNullOrUndefined(value) {
return value === null || value === void 0;
}
function comparatorByDistance(a, b) {
return b.distance - a.distance;
}
function comparatorDefault(a, b) {
return a - b;
}
function getPadding(s, type) {
var _a, _b, _c;
return (_c = (_b = (_a = s[`padding${type}`]) != null ? _a : s.paddingVertical) != null ? _b : s.padding) != null ? _c : 0;
}
function extractPadding(style, contentContainerStyle, type) {
return getPadding(style, type) + getPadding(contentContainerStyle, type);
}
var symbolFirst = Symbol();
function useInit(cb) {
const refValue = useRef(symbolFirst);
if (refValue.current === symbolFirst) {
refValue.current = cb();
}
return refValue.current;
}
// src/ContextContainer.ts
var ContextContainer = createContext(null);
function useViewability(callback, configId) {
const ctx = useStateContext();
const { containerId } = useContext(ContextContainer);
const key = containerId + (configId != null ? configId : "");
useInit(() => {
const value = ctx.mapViewabilityValues.get(key);
if (value) {
callback(value);
}
});
ctx.mapViewabilityCallbacks.set(key, callback);
useEffect(
() => () => {
ctx.mapViewabilityCallbacks.delete(key);
},
[]
);
}
function useViewabilityAmount(callback) {
const ctx = useStateContext();
const { containerId } = useContext(ContextContainer);
useInit(() => {
const value = ctx.mapViewabilityAmountValues.get(containerId);
if (value) {
callback(value);
}
});
ctx.mapViewabilityAmountCallbacks.set(containerId, callback);
useEffect(
() => () => {
ctx.mapViewabilityAmountCallbacks.delete(containerId);
},
[]
);
}
function useRecyclingEffect(effect) {
const { index, value } = useContext(ContextContainer);
const prevValues = useRef({
prevIndex: void 0,
prevItem: void 0
});
useEffect(() => {
let ret = void 0;
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;
}, [index, value]);
}
function useRecyclingState(valueOrFun) {
const { index, value, itemKey, triggerLayout } = useContext(ContextContainer);
const refState = useRef({
itemKey: null,
value: null
});
const [_, setRenderNum] = useState(0);
if (refState.current.itemKey !== itemKey) {
refState.current.itemKey = itemKey;
refState.current.value = isFunction(valueOrFun) ? valueOrFun({
index,
item: value,
prevIndex: void 0,
prevItem: void 0
}) : valueOrFun;
}
const setState = useCallback(
(newState) => {
refState.current.value = isFunction(newState) ? newState(refState.current.value) : newState;
setRenderNum((v) => v + 1);
triggerLayout();
},
[triggerLayout]
);
return [refState.current.value, setState];
}
function useIsLastItem() {
const { itemKey } = useContext(ContextContainer);
const isLast = useSelector$("lastItemKeys", (lastItemKeys) => (lastItemKeys == null ? void 0 : lastItemKeys.includes(itemKey)) || false);
return isLast;
}
function useListScrollSize() {
const [scrollSize] = useArr$(["scrollSize"]);
return scrollSize;
}
var LeanViewComponent = React2.forwardRef((props, ref) => {
return React2.createElement("RCTView", { ...props, ref });
});
LeanViewComponent.displayName = "RCTView";
var LeanView = Platform.OS === "android" || Platform.OS === "ios" ? LeanViewComponent : View;
// src/constants.ts
var POSITION_OUT_OF_VIEW = -1e7;
var ANCHORED_POSITION_OUT_OF_VIEW = {
type: "top",
relativeCoordinate: POSITION_OUT_OF_VIEW,
top: POSITION_OUT_OF_VIEW
};
var ENABLE_DEVMODE = __DEV__ && false;
var ENABLE_DEBUG_VIEW = __DEV__ && false;
var IsNewArchitecture = global.nativeFabricUIManager != null;
// src/Container.tsx
var Container = ({
id,
recycleItems,
horizontal,
getRenderedItem,
updateItemSize,
ItemSeparatorComponent
}) => {
const ctx = useStateContext();
const columnWrapperStyle = ctx.columnWrapperStyle;
const [
maintainVisibleContentPosition,
position = ANCHORED_POSITION_OUT_OF_VIEW,
column = 0,
numColumns,
lastItemKeys,
itemKey,
data,
extraData
] = useArr$([
"maintainVisibleContentPosition",
`containerPosition${id}`,
`containerColumn${id}`,
"numColumns",
"lastItemKeys",
`containerItemKey${id}`,
`containerItemData${id}`,
"extraData"
]);
const refLastSize = useRef();
const ref = useRef(null);
const [layoutRenderCount, forceLayoutRender] = useState(0);
const otherAxisPos = numColumns > 1 ? `${(column - 1) / numColumns * 100}%` : 0;
const otherAxisSize = numColumns > 1 ? `${1 / numColumns * 100}%` : void 0;
const isALastItem = lastItemKeys.includes(itemKey);
let paddingStyles;
if (columnWrapperStyle) {
const { columnGap, rowGap, gap } = columnWrapperStyle;
if (horizontal) {
paddingStyles = {
paddingRight: !isALastItem ? columnGap || gap || void 0 : void 0,
paddingVertical: numColumns > 1 ? (rowGap || gap || 0) / 2 : void 0
};
} else {
paddingStyles = {
paddingBottom: !isALastItem ? rowGap || gap || void 0 : void 0,
paddingHorizontal: numColumns > 1 ? (columnGap || gap || 0) / 2 : void 0
};
}
}
const style = horizontal ? {
flexDirection: ItemSeparatorComponent ? "row" : void 0,
position: "absolute",
top: otherAxisPos,
height: otherAxisSize,
left: position.relativeCoordinate,
...paddingStyles || {}
} : {
position: "absolute",
left: otherAxisPos,
right: numColumns > 1 ? null : 0,
width: otherAxisSize,
top: position.relativeCoordinate,
...paddingStyles || {}
};
const renderedItemInfo = useMemo(
() => itemKey !== void 0 ? getRenderedItem(itemKey) : null,
[itemKey, data, extraData]
);
const { index, renderedItem } = renderedItemInfo || {};
const triggerLayout = useCallback(() => {
forceLayoutRender((v) => v + 1);
}, []);
const onLayout = (event) => {
var _a, _b;
if (!isNullOrUndefined(itemKey)) {
let layout = event.nativeEvent.layout;
const size = layout[horizontal ? "width" : "height"];
const doUpdate = () => {
refLastSize.current = { width: layout.width, height: layout.height };
updateItemSize(itemKey, layout);
};
if (IsNewArchitecture || size > 0) {
doUpdate();
} else {
(_b = (_a = ref.current) == null ? void 0 : _a.measure) == null ? void 0 : _b.call(_a, (x, y, width, height) => {
layout = { width, height };
doUpdate();
});
}
}
};
if (IsNewArchitecture) {
useLayoutEffect(() => {
var _a, _b;
if (!isNullOrUndefined(itemKey)) {
const measured = (_b = (_a = ref.current) == null ? void 0 : _a.unstable_getBoundingClientRect) == null ? void 0 : _b.call(_a);
if (measured) {
const size = Math.floor(measured[horizontal ? "width" : "height"] * 8) / 8;
if (size) {
updateItemSize(itemKey, measured);
}
}
}
}, [itemKey, layoutRenderCount, isALastItem]);
} else {
useEffect(() => {
if (!isNullOrUndefined(itemKey)) {
const timeout = setTimeout(() => {
if (refLastSize.current) {
updateItemSize(itemKey, refLastSize.current);
}
}, 16);
return () => {
clearTimeout(timeout);
};
}
}, [itemKey]);
}
const contextValue = useMemo(() => {
ctx.viewRefs.set(id, ref);
return { containerId: id, itemKey, index, value: data, triggerLayout };
}, [id, itemKey, index, data]);
const contentFragment = /* @__PURE__ */ React2__default.createElement(React2__default.Fragment, { key: recycleItems ? void 0 : itemKey }, /* @__PURE__ */ React2__default.createElement(ContextContainer.Provider, { value: contextValue }, renderedItem, renderedItemInfo && ItemSeparatorComponent && !isALastItem && /* @__PURE__ */ React2__default.createElement(ItemSeparatorComponent, { leadingItem: renderedItemInfo.item })));
if (maintainVisibleContentPosition) {
const anchorStyle = horizontal ? position.type === "top" ? { position: "absolute", left: 0, top: 0, bottom: 0, flexDirection: "row", alignItems: "stretch" } : { position: "absolute", right: 0, top: 0, bottom: 0, flexDirection: "row", alignItems: "stretch" } : position.type === "top" ? { position: "absolute", top: 0, left: 0, right: 0 } : { position: "absolute", bottom: 0, left: 0, right: 0 };
if (__DEV__ && ENABLE_DEVMODE) {
anchorStyle.borderColor = position.type === "top" ? "red" : "blue";
anchorStyle.borderWidth = 1;
}
return /* @__PURE__ */ React2__default.createElement(LeanView, { style }, /* @__PURE__ */ React2__default.createElement(LeanView, { style: [anchorStyle, paddingStyles], onLayout, ref }, contentFragment, __DEV__ && ENABLE_DEVMODE && /* @__PURE__ */ React2__default.createElement(Text, { style: { position: "absolute", top: 0, left: 0, zIndex: 1e3 } }, position.top)));
}
return /* @__PURE__ */ React2__default.createElement(LeanView, { style, onLayout, ref }, contentFragment);
};
var typedForwardRef = forwardRef;
var typedMemo = memo;
var useAnimatedValue = (initialValue) => {
return useRef(new Animated.Value(initialValue)).current;
};
// src/useValue$.ts
function useValue$(key, getValue, useMicrotask) {
var _a;
const ctx = useStateContext();
const animValue = useAnimatedValue((_a = getValue ? getValue(peek$(ctx, key)) : peek$(ctx, key)) != null ? _a : 0);
useMemo(() => {
let newValue = void 0;
listen$(ctx, key, (v) => {
if (useMicrotask && newValue === void 0) {
queueMicrotask(() => {
animValue.setValue(newValue);
newValue = void 0;
});
}
newValue = getValue ? getValue(v) : v;
if (!useMicrotask) {
animValue.setValue(newValue);
}
});
}, []);
return animValue;
}
// src/Containers.tsx
var Containers = typedMemo(function Containers2({
horizontal,
recycleItems,
ItemSeparatorComponent,
waitForInitialLayout,
updateItemSize,
getRenderedItem
}) {
const ctx = useStateContext();
const columnWrapperStyle = ctx.columnWrapperStyle;
const [numContainers, numColumns] = useArr$(["numContainersPooled", "numColumns"]);
const animSize = useValue$(
"totalSizeWithScrollAdjust",
void 0,
/*useMicrotask*/
true
);
const animOpacity = waitForInitialLayout ? useValue$("containersDidLayout", (value) => value ? 1 : 0) : void 0;
const otherAxisSize = useValue$(
"otherAxisSize",
void 0,
/*useMicrotask*/
true
);
const containers = [];
for (let i = 0; i < numContainers; i++) {
containers.push(
/* @__PURE__ */ React2.createElement(
Container,
{
id: i,
key: i,
recycleItems,
horizontal,
getRenderedItem,
updateItemSize,
ItemSeparatorComponent
}
)
);
}
const style = horizontal ? { width: animSize, opacity: animOpacity, minHeight: otherAxisSize } : { height: animSize, opacity: animOpacity, minWidth: otherAxisSize };
if (columnWrapperStyle && numColumns > 1) {
const { columnGap, rowGap, gap } = columnWrapperStyle;
if (horizontal) {
const my = (rowGap || gap || 0) / 2;
if (my) {
style.marginVertical = -my;
}
} else {
const mx = (columnGap || gap || 0) / 2;
if (mx) {
style.marginHorizontal = -mx;
}
}
}
return /* @__PURE__ */ React2.createElement(Animated.View, { style }, containers);
});
function ListHeaderComponentContainer({
children,
style,
ctx,
horizontal,
waitForInitialLayout
}) {
var _a;
const hasData = ((_a = peek$(ctx, "lastItemKeys")) == null ? void 0 : _a.length) > 0;
const scrollAdjust = useValue$("scrollAdjust", (v) => v != null ? v : 0, true);
const animOpacity = waitForInitialLayout ? useValue$("containersDidLayout", (value) => value ? 1 : 0) : void 0;
const additionalSize = {
transform: [{ translateY: Animated.multiply(scrollAdjust, -1) }],
// Header should show if there's no data yet, but containersDidLayout will be false until it has some data
opacity: hasData ? animOpacity : 1
};
return /* @__PURE__ */ React2.createElement(
Animated.View,
{
style: [style, additionalSize],
onLayout: (event) => {
const size = event.nativeEvent.layout[horizontal ? "width" : "height"];
set$(ctx, "headerSize", size);
}
},
children
);
}
// src/ListComponent.tsx
var getComponent = (Component) => {
if (React2.isValidElement(Component)) {
return Component;
}
if (Component) {
return /* @__PURE__ */ React2.createElement(Component, null);
}
return null;
};
var PaddingAndAdjust = () => {
const animPaddingTop = useValue$("paddingTop", (v) => v, true);
const animScrollAdjust = useValue$("scrollAdjust", (v) => v, true);
const additionalSize = { marginTop: animScrollAdjust, paddingTop: animPaddingTop };
return /* @__PURE__ */ React2.createElement(Animated.View, { style: additionalSize });
};
var PaddingAndAdjustDevMode = () => {
const animPaddingTop = useValue$("paddingTop", (v) => v, true);
const animScrollAdjust = useValue$("scrollAdjust", (v) => v, true);
return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Animated.View, { style: { marginTop: animScrollAdjust } }), /* @__PURE__ */ React2.createElement(Animated.View, { style: { paddingTop: animPaddingTop } }), /* @__PURE__ */ React2.createElement(
Animated.View,
{
style: {
position: "absolute",
top: Animated.add(animScrollAdjust, Animated.multiply(animScrollAdjust, -1)),
height: animPaddingTop,
left: 0,
right: 0,
backgroundColor: "green"
}
}
), /* @__PURE__ */ React2.createElement(
Animated.View,
{
style: {
position: "absolute",
top: animPaddingTop,
height: animScrollAdjust,
left: -16,
right: -16,
backgroundColor: "lightblue"
}
}
), /* @__PURE__ */ React2.createElement(
Animated.View,
{
style: {
position: "absolute",
top: animPaddingTop,
height: Animated.multiply(animScrollAdjust, -1),
width: 8,
right: 4,
borderStyle: "dashed",
borderColor: "blue",
borderWidth: 1,
backgroundColor: "lightblue"
//backgroundColor: "blue",
}
}
));
};
var ListComponent = typedMemo(function ListComponent2({
style,
contentContainerStyle,
horizontal,
initialContentOffset,
recycleItems,
ItemSeparatorComponent,
alignItemsAtEnd,
waitForInitialLayout,
handleScroll,
onLayout,
ListHeaderComponent,
ListHeaderComponentStyle,
ListFooterComponent,
ListFooterComponentStyle,
ListEmptyComponent,
getRenderedItem,
updateItemSize,
refScrollView,
maintainVisibleContentPosition,
renderScrollComponent,
...rest
}) {
const ctx = useStateContext();
const ScrollComponent = renderScrollComponent ? useMemo(
() => React2.forwardRef((props, ref) => renderScrollComponent({ ...props, ref })),
[renderScrollComponent]
) : ScrollView;
return /* @__PURE__ */ React2.createElement(
ScrollComponent,
{
...rest,
style,
maintainVisibleContentPosition: maintainVisibleContentPosition && !ListEmptyComponent ? { minIndexForVisible: 0 } : void 0,
contentContainerStyle: [
contentContainerStyle,
horizontal ? {
height: "100%"
} : {}
],
onScroll: handleScroll,
onLayout,
horizontal,
contentOffset: initialContentOffset ? horizontal ? { x: initialContentOffset, y: 0 } : { x: 0, y: initialContentOffset } : void 0,
ref: refScrollView
},
ENABLE_DEVMODE ? /* @__PURE__ */ React2.createElement(PaddingAndAdjustDevMode, null) : /* @__PURE__ */ React2.createElement(PaddingAndAdjust, null),
ListHeaderComponent && /* @__PURE__ */ React2.createElement(
ListHeaderComponentContainer,
{
style: ListHeaderComponentStyle,
ctx,
horizontal,
waitForInitialLayout
},
getComponent(ListHeaderComponent)
),
ListEmptyComponent && getComponent(ListEmptyComponent),
/* @__PURE__ */ React2.createElement(
Containers,
{
horizontal,
recycleItems,
waitForInitialLayout,
getRenderedItem,
ItemSeparatorComponent,
updateItemSize
}
),
ListFooterComponent && /* @__PURE__ */ React2.createElement(
View,
{
style: ListFooterComponentStyle,
onLayout: (event) => {
const size = event.nativeEvent.layout[horizontal ? "width" : "height"];
set$(ctx, "footerSize", size);
}
},
getComponent(ListFooterComponent)
)
);
});
// src/ScrollAdjustHandler.ts
var ScrollAdjustHandler = class {
constructor(ctx) {
this.ctx = ctx;
this.appliedAdjust = 0;
this.busy = false;
this.isPaused = false;
this.isDisabled = false;
this.context = ctx;
}
doAjdust() {
set$(this.context, "scrollAdjust", this.appliedAdjust);
this.busy = false;
}
requestAdjust(adjust, onAdjusted) {
if (this.isDisabled) {
return;
}
const oldAdjustTop = peek$(this.context, "scrollAdjust");
if (oldAdjustTop === adjust) {
return;
}
this.appliedAdjust = adjust;
if (!this.busy && !this.isPaused) {
this.busy = true;
this.doAjdust();
onAdjusted(oldAdjustTop - adjust);
}
}
getAppliedAdjust() {
return this.appliedAdjust;
}
pauseAdjust() {
this.isPaused = true;
}
setDisableAdjust(disable) {
this.isDisabled = disable;
}
// return true if it was paused
unPauseAdjust() {
if (this.isPaused) {
this.isPaused = false;
this.doAjdust();
return true;
}
return false;
}
};
var useCombinedRef = (...refs) => {
const callback = useCallback((element) => {
for (const ref of refs) {
if (!ref) {
continue;
}
if (isFunction(ref)) {
ref(element);
} else {
ref.current = element;
}
}
}, refs);
return callback;
};
// src/viewability.ts
var mapViewabilityConfigCallbackPairs = /* @__PURE__ */ new Map();
function setupViewability(props) {
let { viewabilityConfig, viewabilityConfigCallbackPairs, onViewableItemsChanged } = props;
if (viewabilityConfig || onViewableItemsChanged) {
viewabilityConfigCallbackPairs = [
...viewabilityConfigCallbackPairs || [],
{
viewabilityConfig: viewabilityConfig || {
viewAreaCoveragePercentThreshold: 0
},
onViewableItemsChanged
}
];
}
if (viewabilityConfigCallbackPairs) {
for (const pair of viewabilityConfigCallbackPairs) {
mapViewabilityConfigCallbackPairs.set(pair.viewabilityConfig.id, {
viewableItems: [],
start: -1,
end: -1,
previousStart: -1,
previousEnd: -1
});
}
}
return viewabilityConfigCallbackPairs;
}
function updateViewableItems(state, ctx, viewabilityConfigCallbackPairs, getId, scrollSize, start, end) {
for (const viewabilityConfigCallbackPair of viewabilityConfigCallbackPairs) {
const viewabilityState = mapViewabilityConfigCallbackPairs.get(
viewabilityConfigCallbackPair.viewabilityConfig.id
);
viewabilityState.start = start;
viewabilityState.end = end;
if (viewabilityConfigCallbackPair.viewabilityConfig.minimumViewTime) {
const timer = setTimeout(() => {
state.timeouts.delete(timer);
updateViewableItemsWithConfig(state.data, viewabilityConfigCallbackPair, getId, state, ctx, scrollSize);
}, viewabilityConfigCallbackPair.viewabilityConfig.minimumViewTime);
state.timeouts.add(timer);
} else {
updateViewableItemsWithConfig(state.data, viewabilityConfigCallbackPair, getId, state, ctx, scrollSize);
}
}
}
function updateViewableItemsWithConfig(data, viewabilityConfigCallbackPair, getId, state, ctx, scrollSize) {
const { viewabilityConfig, onViewableItemsChanged } = viewabilityConfigCallbackPair;
const configId = viewabilityConfig.id;
const viewabilityState = mapViewabilityConfigCallbackPairs.get(configId);
const { viewableItems: previousViewableItems, start, end } = viewabilityState;
const viewabilityTokens = /* @__PURE__ */ new Map();
for (const [containerId, value] of ctx.mapViewabilityAmountValues) {
viewabilityTokens.set(
containerId,
computeViewability(
state,
ctx,
viewabilityConfig,
containerId,
value.key,
scrollSize,
value.item,
value.index
)
);
}
const changed = [];
if (previousViewableItems) {
for (const viewToken of previousViewableItems) {
const containerId = findContainerId(ctx, viewToken.key);
if (!isViewable(
state,
ctx,
viewabilityConfig,
containerId,
viewToken.key,
scrollSize,
viewToken.item,
viewToken.index
)) {
viewToken.isViewable = false;
changed.push(viewToken);
}
}
}
const viewableItems = [];
for (let i = start; i <= end; i++) {
const item = data[i];
if (item) {
const key = getId(i);
const containerId = findContainerId(ctx, key);
if (isViewable(state, ctx, viewabilityConfig, containerId, key, scrollSize, item, i)) {
const viewToken = {
item,
key,
index: i,
isViewable: true,
containerId
};
viewableItems.push(viewToken);
if (!(previousViewableItems == null ? void 0 : previousViewableItems.find((v) => v.key === viewToken.key))) {
changed.push(viewToken);
}
}
}
}
Object.assign(viewabilityState, {
viewableItems,
previousStart: start,
previousEnd: end
});
if (changed.length > 0) {
viewabilityState.viewableItems = viewableItems;
for (let i = 0; i < changed.length; i++) {
const change = changed[i];
maybeUpdateViewabilityCallback(ctx, configId, change.containerId, change);
}
if (onViewableItemsChanged) {
onViewableItemsChanged({ viewableItems, changed });
}
}
for (const [containerId, value] of ctx.mapViewabilityAmountValues) {
if (value.sizeVisible < 0) {
ctx.mapViewabilityAmountValues.delete(containerId);
}
}
}
function computeViewability(state, ctx, viewabilityConfig, containerId, key, scrollSize, item, index) {
const { sizes, positions, scroll: scrollState, scrollAdjustHandler } = state;
const topPad = (peek$(ctx, "stylePaddingTop") || 0) + (peek$(ctx, "headerSize") || 0);
const { itemVisiblePercentThreshold, viewAreaCoveragePercentThreshold } = viewabilityConfig;
const viewAreaMode = viewAreaCoveragePercentThreshold != null;
const viewablePercentThreshold = viewAreaMode ? viewAreaCoveragePercentThreshold : itemVisiblePercentThreshold;
const previousScrollAdjust = scrollAdjustHandler.getAppliedAdjust();
const scroll = scrollState - previousScrollAdjust - topPad;
const top = positions.get(key) - scroll;
const size = sizes.get(key) || 0;
const bottom = top + size;
const isEntirelyVisible = top >= 0 && bottom <= scrollSize && bottom > top;
const sizeVisible = isEntirelyVisible ? size : Math.min(bottom, scrollSize) - Math.max(top, 0);
const percentVisible = size ? isEntirelyVisible ? 100 : 100 * (sizeVisible / size) : 0;
const percentOfScroller = size ? 100 * (sizeVisible / scrollSize) : 0;
const percent = isEntirelyVisible ? 100 : viewAreaMode ? percentOfScroller : percentVisible;
const isViewable2 = percent >= viewablePercentThreshold;
const value = {
index,
isViewable: isViewable2,
item,
key,
percentVisible,
percentOfScroller,
sizeVisible,
size,
scrollSize,
containerId
};
if (JSON.stringify(value) !== JSON.stringify(ctx.mapViewabilityAmountValues.get(containerId))) {
ctx.mapViewabilityAmountValues.set(containerId, value);
const cb = ctx.mapViewabilityAmountCallbacks.get(containerId);
if (cb) {
cb(value);
}
}
return value;
}
function isViewable(state, ctx, viewabilityConfig, containerId, key, scrollSize, item, index) {
const value = ctx.mapViewabilityAmountValues.get(containerId) || computeViewability(state, ctx, viewabilityConfig, containerId, key, scrollSize, item, index);
return value.isViewable;
}
function findContainerId(ctx, key) {
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;
}
function maybeUpdateViewabilityCallback(ctx, configId, containerId, viewToken) {
const key = containerId + configId;
ctx.mapViewabilityValues.set(key, viewToken);
const cb = ctx.mapViewabilityCallbacks.get(key);
cb == null ? void 0 : cb(viewToken);
}
// src/LegendList.tsx
var DEFAULT_DRAW_DISTANCE = 250;
var DEFAULT_ITEM_SIZE = 100;
function createColumnWrapperStyle(contentContainerStyle) {
const { gap, columnGap, rowGap } = contentContainerStyle;
if (gap || columnGap || rowGap) {
contentContainerStyle.gap = void 0;
contentContainerStyle.columnGap = void 0;
contentContainerStyle.rowGap = void 0;
return {
gap,
columnGap,
rowGap
};
}
}
var LegendList = typedForwardRef(function LegendList2(props, forwardedRef) {
return /* @__PURE__ */ React2.createElement(StateProvider, null, /* @__PURE__ */ React2.createElement(LegendListInner, { ...props, ref: forwardedRef }));
});
var LegendListInner = typedForwardRef(function LegendListInner2(props, forwardedRef) {
const {
data: dataProp = [],
initialScrollIndex: initialScrollIndexProp,
initialScrollOffset,
horizontal,
drawDistance = 250,
recycleItems = false,
onEndReachedThreshold = 0.5,
onStartReachedThreshold = 0.5,
maintainScrollAtEnd = false,
maintainScrollAtEndThreshold = 0.1,
alignItemsAtEnd = false,
maintainVisibleContentPosition = false,
onScroll: onScrollProp,
onMomentumScrollEnd,
numColumns: numColumnsProp = 1,
columnWrapperStyle,
keyExtractor: keyExtractorProp,
renderItem: renderItem2,
estimatedListSize,
estimatedItemSize: estimatedItemSizeProp,
getEstimatedItemSize,
suggestEstimatedItemSize,
ListHeaderComponent,
ListEmptyComponent,
onItemSizeChanged,
refScrollView,
waitForInitialLayout = true,
extraData,
contentContainerStyle: contentContainerStyleProp,
style: styleProp,
onLayout: onLayoutProp,
onRefresh,
refreshing,
progressViewOffset,
refreshControl,
initialContainerPoolRatio = 2,
viewabilityConfig,
viewabilityConfigCallbackPairs,
onViewableItemsChanged,
...rest
} = props;
const initialScroll = typeof initialScrollIndexProp === "number" ? { index: initialScrollIndexProp } : initialScrollIndexProp;
const initialScrollIndex = initialScroll == null ? void 0 : initialScroll.index;
const refLoadStartTime = useRef(Date.now());
const callbacks = useRef({
onStartReached: rest.onStartReached,
onEndReached: rest.onEndReached
});
callbacks.current.onStartReached = rest.onStartReached;
callbacks.current.onEndReached = rest.onEndReached;
const contentContainerStyle = { ...StyleSheet.flatten(contentContainerStyleProp) };
const style = { ...StyleSheet.flatten(styleProp) };
const stylePaddingTopState = extractPadding(style, contentContainerStyle, "Top");
const stylePaddingBottomState = extractPadding(style, contentContainerStyle, "Bottom");
if (style == null ? void 0 : style.paddingTop) {
style.paddingTop = void 0;
}
if (contentContainerStyle == null ? void 0 : contentContainerStyle.paddingTop) {
contentContainerStyle.paddingTop = void 0;
}
const ctx = useStateContext();
ctx.columnWrapperStyle = columnWrapperStyle || (contentContainerStyle ? createColumnWrapperStyle(contentContainerStyle) : void 0);
const refScroller = useRef(null);
const combinedRef = useCombinedRef(refScroller, refScrollView);
const estimatedItemSize = estimatedItemSizeProp != null ? estimatedItemSizeProp : DEFAULT_ITEM_SIZE;
const scrollBuffer = (drawDistance != null ? drawDistance : DEFAULT_DRAW_DISTANCE) || 1;
const keyExtractor = keyExtractorProp != null ? keyExtractorProp : (item, index) => index.toString();
const refState = useRef();
const getId = (index) => {
var _a;
const data = (_a = refState.current) == null ? void 0 : _a.data;
if (!data) {
return "";
}
const ret = index < data.length ? keyExtractor ? keyExtractor(data[index], index) : index : null;
return `${ret}`;
};
const getItemSize = (key, index, data, useAverageSize = false) => {
const state = refState.current;
state.sizesKnown.get(key);
const sizePrevious = state.sizes.get(key);
let size;
peek$(ctx, "numColumns");
if (size === void 0 && sizePrevious !== void 0) {
return sizePrevious;
}
if (size === void 0) {
size = getEstimatedItemSize ? getEstimatedItemSize(index, data) : estimatedItemSize;
}
state.sizes.set(key, size);
return size;
};
const calculateOffsetForIndex = (indexParam) => {
var _a;
const isFromInit = indexParam === void 0;
const index = isFromInit ? initialScrollIndex : indexParam;
const data = dataProp;
if (index !== void 0) {
let offset = 0;
const canGetSize = !!refState.current;
if (canGetSize || getEstimatedItemSize) {
const sizeFn = (index2) => {
if (canGetSize) {
return getItemSize(getId(index2), index2, data[index2], true);
}
return getEstimatedItemSize(index2, data[index2]);
};
for (let i = 0; i < index; i++) {
offset += sizeFn(i);
}
} else {
offset = index * estimatedItemSize;
}
const adjust = peek$(ctx, "containersDidLayout") ? ((_a = refState.current) == null ? void 0 : _a.scrollAdjustHandler.getAppliedAdjust()) || 0 : 0;
const stylePaddingTop = isFromInit ? stylePaddingTopState : peek$(ctx, "stylePaddingTop");
const topPad = (stylePaddingTop != null ? stylePaddingTop : 0) + peek$(ctx, "headerSize");
return offset / numColumnsProp - adjust + topPad;
}
return 0;
};
const calculateOffsetWithOffsetPosition = (offsetParam, params) => {
const { index, viewOffset, viewPosition } = params;
let offset = offsetParam;
const state = refState.current;
if (viewOffset) {
offset -= viewOffset;
}
if (viewPosition !== void 0 && index !== void 0) {
offset -= viewPosition * (state.scrollLength - getItemSize(getId(index), index, state.data[index]));
}
return offset;
};
const initialContentOffset = initialScrollOffset != null ? initialScrollOffset : useMemo(() => calculateOffsetForIndex(void 0), []);
if (!refState.current) {
const initialScrollLength = (estimatedListSize != null ? estimatedListSize : Dimensions.get("window"))[horizontal ? "width" : "height"];
refState.current = {
sizes: /* @__PURE__ */ new Map(),
positions: /* @__PURE__ */ new Map(),
columns: /* @__PURE__ */ new Map(),
pendingAdjust: 0,
isStartReached: initialContentOffset < initialScrollLength * onStartReachedThreshold,
isEndReached: false,
isAtEnd: false,
isAtStart: false,
data: dataProp,
scrollLength: initialScrollLength,
startBuffered: -1,
startNoBuffer: -1,
endBuffered: -1,
endNoBuffer: -1,
scroll: initialContentOffset || 0,
totalSize: 0,
totalSizeBelowAnchor: 0,
timeouts: /* @__PURE__ */ new Set(),
viewabilityConfigCallbackPairs: void 0,
renderItem: void 0,
scrollAdjustHandler: new ScrollAdjustHandler(ctx),
nativeMarginTop: 0,
scrollPrev: 0,
scrollPrevTime: 0,
scrollTime: 0,
scrollPending: 0,
indexByKey: /* @__PURE__ */ new Map(),
scrollHistory: [],
scrollVelocity: 0,
sizesKnown: /* @__PURE__ */ new Map(),
timeoutSizeMessage: 0,
scrollTimer: void 0,
belowAnchorElementPositions: void 0,
rowHeights: /* @__PURE__ */ new Map(),
startReachedBlockedByTimer: false,
endReachedBlockedByTimer: false,
scrollForNextCalculateItemsInView: void 0,
enableScrollForNextCalculateItemsInView: true,
minIndexSizeChanged: 0,
queuedCalculateItemsInView: 0,
lastBatchingAction: Date.now(),
averageSizes: {},
onScroll: onScrollProp
};
const dataLength = dataProp.length;
if (maintainVisibleContentPosition && dataLength > 0) {
if (initialScrollIndex && initialScrollIndex < dataLength) {
refState.current.anchorElement = {
coordinate: initialContentOffset,
id: getId(initialScrollIndex)
};
} else if (dataLength > 0) {
refState.current.anchorElement = {
coordinate: initialContentOffset,
id: getId(0)
};
} else {
__DEV__ && warnDevOnce(
"maintainVisibleContentPosition",
"[legend-list] maintainVisibleContentPosition was not able to find an anchor element"
);
}
}
set$(ctx, "scrollAdjust", 0);
set$(ctx, "maintainVisibleContentPosition", maintainVisibleContentPosition);
set$(ctx, "extraData", extraData);
}
const didDataChange = refState.current.data !== dataProp;
refState.current.data = dataProp;
refState.current.onScroll = onScrollProp;
const getAnchorElementIndex = () => {
const state = refState.current;
if (state.anchorElement) {
const el = state.indexByKey.get(state.anchorElement.id);
return el;
}
return void 0;
};
const scrollToIndex = ({
index,
viewOffset = 0,
animated = true,
viewPosition
}) => {
var _a;
const state = refState.current;
if (index >= state.data.length) {
index = state.data.length - 1;
} else if (index < 0) {
index = 0;
}
const firstIndexOffset = calculateOffsetForIndex(index);
const isLast = index === state.data.length - 1;
if (isLast && viewPosition !== void 0) {
viewPosition = 1;
}
let firstIndexScrollPostion = firstIndexOffset - viewOffset;
const diff = Math.abs(state.scroll - firstIndexScrollPostion);
const topPad = peek$(ctx, "stylePaddingTop") + peek$(ctx, "headerSize");
const needsReanchoring = maintainVisibleContentPosition && diff > 100;
state.scrollForNextCalculateItemsInView = void 0;
if (needsReanchoring) {
const id = getId(index);
state.anchorElement = { id, coordinate: firstIndexOffset - topPad };
(_a = state.belowAnchorElementPositions) == null ? void 0 : _a.clear();
state.positions.clear();
calcTotalSizesAndPositions({ forgetPositions: true });
state.startBufferedId = id;
state.minIndexSizeChanged = index;
firstIndexScrollPostion = firstIndexOffset - viewOffset + state.scrollAdjustHandler.getAppliedAdjust();
}
scrollTo({ offset: firstIndexScrollPostion, animated, index, viewPosition: viewPosition != null ? viewPosition : 0, viewOffset });
};
const setDidLayout = () => {
refState.current.queuedInitialLayout = true;
checkAtBottom();
const setIt = () => {
set$(ctx, "containersDidLayout", true);
if (props.onLoad) {
props.onLoad({ elapsedTimeInMs: Date.now() - refLoadStartTime.current });
}
};
if (initialScroll) {
queueMicrotask(() => {
scrollToIndex({ ...initialScroll, animated: false });
requestAnimationFrame(() => {
if (!IsNewArchitecture) {
scrollToIndex({ ...initialScroll, animated: false });
}
setIt();
});
});
} else {
queueMicrotask(setIt);
}
};
const addTotalSize = useCallback((key, add, totalSizeBelowAnchor) => {
const state = refState.current;
const { indexByKey, anchorElement } = state;
const index = key === null ? 0 : indexByKey.get(key);
let isAboveAnchor = false;
if (maintainVisibleContentPosition) {
if (anchorElement && index < getAnchorElementIndex()) {
isAboveAnchor = true;
}
}
if (key === null) {
state.totalSize = add;
state.totalSizeBelowAnchor = totalSizeBelowAnchor;
} else {
state.totalSize += add;
if (isAboveAnchor) {
state.totalSizeBelowAnchor += add;
}
}
let applyAdjustValue = 0;
let resultSize = state.totalSize;
if (maintainVisibleContentPosition && anchorElement !== void 0) {
const newAdjust = anchorElement.coordinate - state.totalSizeBelowAnchor;
applyAdjustValue = -newAdjust;
state.belowAnchorElementPositions = buildElementPositionsBelowAnchor();
state.rowHeights.clear();
if (applyAdjustValue !== void 0) {
resultSize -= applyAdjustValue;
state.scrollAdjustHandler.requestAdjust(applyAdjustValue, (diff) => {
state.scroll -= diff;
});
}
}
set$(ctx, "totalSize", state.totalSize);
set$(ctx, "totalSizeWithScrollAdjust", resultSize);
if (alignItemsAtEnd) {
updateAlignItemsPaddingTop();
}
}, []);
const getRowHeight = (n) => {
const { rowHeights, data } = refState.current;
const numColumns = peek$(ctx, "numColumns");
if (numColumns === 1) {
const id = getId(n);
return getItemSize(id, n, data[n]);
}
if (rowHeights.has(n)) {
return rowHeights.get(n) || 0;
}
let rowHeight = 0;
const startEl = n * numColumns;
for (let i = startEl; i < startEl + numColumns && i < data.length; i++) {
const id = getId(i);
const size = getItemSize(id, i, data[i]);
rowHeight = Math.max(rowHeight, size);
}
rowHeights.set(n, rowHeight);
return rowHeight;
};
const buildElementPositionsBelowAnchor = () => {
const state = refState.current;
if (!state.anchorElement) {
return /* @__PURE__ */ new Map();
}
const anchorIndex = state.indexByKey.get(state.anchorElement.id);
if (anchorIndex === 0) {
return /* @__PURE__ */ new Map();
}
const map = state.belowAnchorElementPositions || /* @__PURE__ */ new Map();
const numColumns = peek$(ctx, "numColumns");
let top = state.anchorElement.coordinate;
for (let i = anchorIndex - 1; i >= 0; i--) {
const id = getId(i);
const rowNumber = Math.floor(i / numColumns);
if (i % numColumns === 0) {
top -= getRowHeight(rowNumber);
}
map.set(id, top);
}
return map;
};
const disableScrollJumps = (timeout) => {
const state = refState.current;
if (state.scrollingTo === void 0) {
state.disableScrollJumpsFrom = state.scroll - state.scrollAdjustHandler.getAppliedAdjust();
state.scrollHistory.length = 0;
setTimeout(() => {
state.disableScrollJumpsFrom = void 0;
if (state.scrollPending !== void 0 && state.scrollPending !== state.scroll) {
updateScroll(state.scrollPending);
}
}, timeout);
}
};
const getElementPositionBelowAchor = (id) => {
var _a;
const state = refState.current;
if (!refState.current.belowAnchorElementPositions) {
state.belowAnchorElementPositions = buildElementPositionsBelowAnchor();
}
const res = state.belowAnchorElementPositions.get(id);
if (res === void 0) {
console.warn(`Undefined position below anchor ${id} ${(_a = state.anchorElement) == null ? void 0 : _a.id}`);
return 0;
}
return res;
};
const fixGaps = useCallback(() => {
var _a;
const state = refState.current;
const { data, scrollLength, positions, startBuffered, endBuffered } = state;
const numColumns = peek$(ctx, "numColumns");
if (!data || scrollLength === 0 || numColumns > 1) {
return;
}
const numContainers = ctx.values.get("numContainers");
let numMeasurements = 0;
for (let i = 0; i < numContainers; i++) {
const itemKey = peek$(ctx, `containerItemKey${i}`);
const isSizeKnown = state.sizesKnown.get(itemKey);
if (itemKey && !isSizeKnown) {
const containerRef = ctx.viewRefs.get(i);
if (containerRef) {
let measured;
(_a = containerRef.current) == null ? void 0 : _a.measure((x, y, width, height) => {
measured = { width, height };
});
numMeasurements++;
if (measured) {
updateItemSize(
itemKey,
measured,
/*fromFixGaps*/
true
);
}
}
}
}
if (numMeasurements > 0) {
let top;
const diffs = /* @__PURE__ */ new Map();
for (let i = startBuffered; i <= endBuffered; i++) {
const id = getId(i);
if (top === void 0) {
top = positions.get(id);
}
if (positions.get(id) !== top) {
diffs.set(id, top - positions.get(id));
positions.set(id, top);
}
const size = getItemSize(id, i, data[i]);
const bottom = top + size;
top = bottom;
}
for (let i = 0; i < numContainers; i++) {
const itemKey = peek$(ctx, `containerItemKey${i}`);
const diff = diffs.get(itemKey);
if (diff) {
const prevPos = peek$(ctx, `containerPosition${i}`);
const newPos = prevPos.top + diff;
if (prevPos.top !== newPos) {
const pos = { ...prevPos };
pos.relativeCoordinate += diff;
pos.top += diff;
set$(ctx, `containerPosition${i}`, pos);
}
}
}
}
}, []);
const checkAllSizesKnown = useCallback(() => {
const { startBuffered, endBuffered, sizesKnown } = refState.current;
if (endBuffered !== null) {
let areAllKnown = true;
for (let i = startBuffered; areAllKnown && i <= endBuffered; i++) {
const key = getId(i);
areAllKnown && (areAllKnown = sizesKnown.has(key));
}
return areAllKnown;
}
return false;
}, []);
const calculateItemsInView = useCallback((isReset) => {
var _a;
const state = refState.current;
const {
data,
scrollLength,
startBufferedId: startBufferedIdOrig,
positions,
columns,
scrollAdjustHandler,
scrollVelocity: speed
} = state;
if (!data || scrollLength === 0) {
return;
}
const totalSize = peek$(ctx, "totalSizeWithScrollAdjust");
const t