New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@infinite-list/data-model

Package Overview
Dependencies
Maintainers
1
Versions
155
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@infinite-list/data-model - npm Package Compare versions

Comparing version 0.2.29-stillness.0 to 0.2.29-stillness.1

17

dist/ListDimensions.d.ts

@@ -123,2 +123,19 @@ import Batchinator from '@x-oasis/batchinator';

}) => number;
resolveSafeRange(props: {
visibleStartIndex: number;
visibleEndIndex: number;
}): {
startIndex: number;
endIndex: number;
};
updateIndices(targetIndices: Array<number>, props: {
safeRange: {
startIndex: number;
endIndex: number;
};
startIndex: number;
maxCount: number;
step: number;
}): number;
resolveRecycleRecycleState(state: ListState<ItemT>): any[];
resolveRecycleState(state: ListState<ItemT>): {

@@ -125,0 +142,0 @@ recycleState: any[];

@@ -6,3 +6,5 @@ export declare type OnEndReachedHelperProps = {

onEndReachedTimeoutThreshold?: number;
distanceFromEndThresholdValue?: number;
onEndReachedHandlerTimeoutThreshold?: number;
maxCountOfHandleOnEndReachedAfterStillness?: number;
};

@@ -14,1 +16,7 @@ export declare type OnEndReached = (props: {

}) => void;
export declare type SendOnEndReachedDistanceFromBottomStack = Array<{
distancesFromEnd: Array<number>;
ts: Array<number>;
hit: number;
resetCount: number;
}>;

24

dist/viewable/OnEndReachedHelper.d.ts
import Batchinator from '@x-oasis/batchinator';
import { OnEndReached, OnEndReachedHelperProps, ScrollMetrics } from '../types';
import { OnEndReached, OnEndReachedHelperProps, ScrollMetrics, SendOnEndReachedDistanceFromBottomStack } from '../types';
declare class OnEndReachedHelper {

@@ -9,6 +9,16 @@ readonly id: string;

readonly onEndReachedHandlerBatchinator: Batchinator;
readonly sendOnEndReachedDistanceFromEndStack: SendOnEndReachedDistanceFromBottomStack;
private onEndReached;
private _maxCountOfHandleOnEndReachedAfterStillness;
private _distanceFromEndThresholdValue;
private _waitingForDataChangedSinceEndReached;
private _onEndReachedTimeoutHandler;
readonly _consecutiveDistanceTimeoutThresholdValue = 800;
constructor(props: OnEndReachedHelperProps);
static createStackToken(distanceFromEnd: number): {
ts: number[];
hit: number;
distancesFromEnd: number[];
resetCount: number;
};
clear(): void;

@@ -22,2 +32,13 @@ setHandler(onEndReached: OnEndReached): void;

releaseHandlerMutex(): void;
timeoutReleaseHandlerMutex(): void;
get lastStack(): {
distancesFromEnd: number[];
ts: number[];
hit: number;
resetCount: number;
};
getStack(): SendOnEndReachedDistanceFromBottomStack;
reachCountLimitation(): boolean;
shouldResetCountLimitation(distanceFromEnd: number): boolean;
isConsecutiveDistance(distanceFromEnd: number): boolean;
performEndReached(info: {

@@ -27,2 +48,3 @@ isEndReached: boolean;

}): void;
updateStack(distanceFromEnd: number): void;
onEndReachedHandler(opts: {

@@ -29,0 +51,0 @@ distanceFromEnd: number;

2

package.json
{
"name": "@infinite-list/data-model",
"version": "0.2.29-stillness.0",
"version": "0.2.29-stillness.1",
"files": [

@@ -5,0 +5,0 @@ "dist",

@@ -137,6 +137,9 @@ import noop from '@x-oasis/noop';

persistanceIndices,
dispatchMetricsThreshold = DISPATCH_METRICS_THRESHOLD,
stillnessThreshold,
onEndReachedTimeoutThreshold,
distanceFromEndThresholdValue,
onEndReachedHandlerTimeoutThreshold,
dispatchMetricsThreshold = DISPATCH_METRICS_THRESHOLD,
maxCountOfHandleOnEndReachedAfterStillness,
} = props;

@@ -158,3 +161,5 @@ this._keyExtractor = keyExtractor;

onEndReachedTimeoutThreshold,
distanceFromEndThresholdValue,
onEndReachedHandlerTimeoutThreshold,
maxCountOfHandleOnEndReachedAfterStillness,
});

@@ -1101,153 +1106,162 @@ this._onBatchLayoutFinished = onBatchLayoutFinished;

resolveRecycleState(state: ListState<ItemT>) {
const {
visibleEndIndex,
bufferedEndIndex,
visibleStartIndex,
bufferedStartIndex,
data,
} = state;
resolveSafeRange(props: {
visibleStartIndex: number;
visibleEndIndex: number;
}) {
const { visibleStartIndex, visibleEndIndex } = props;
const velocity = this._scrollMetrics?.velocity || 0;
const _targetIndices = this._bufferSet.indices.map((i) => parseInt(i));
const targetIndices = new Array(_targetIndices.length).fill(null);
if (velocity < 0) {
return {
startIndex: Math.max(
visibleStartIndex - this.recycleBufferedCount * 2,
0
),
endIndex: visibleEndIndex,
};
} else if (velocity > 0) {
return {
startIndex: visibleStartIndex,
endIndex: Math.min(
visibleEndIndex + this.recycleBufferedCount * 2,
this._data.length
),
};
}
const recycleEnabled = this._recycleEnabled();
const recycleStateResult = [];
let spaceStateResult = [];
return {
startIndex: Math.max(visibleStartIndex - this.recycleBufferedCount, 0),
endIndex: Math.min(
visibleEndIndex + this.recycleBufferedCount,
this._data.length
),
};
}
// 只有当recycleEnabled为true的时候,才进行位置替换
if (recycleEnabled) {
if (visibleEndIndex >= 0) {
for (let index = visibleStartIndex; index <= visibleEndIndex; index++) {
const position = this.getPosition(
index,
visibleStartIndex - this.recycleBufferedCount,
visibleEndIndex + this.recycleBufferedCount
);
if (position !== null) targetIndices[position] = index;
}
}
updateIndices(
targetIndices: Array<number>,
props: {
safeRange: {
startIndex: number;
endIndex: number;
};
startIndex: number;
maxCount: number;
step: number;
}
) {
const { startIndex, safeRange, step, maxCount } = props;
let finalIndex = startIndex;
let count = 0;
for (
let index = startIndex;
step > 0 ? index <= this._data.length - 1 : index >= 0;
index += step
) {
const item = this._data[index];
if (!item) continue;
const itemMeta = this.getItemMeta(item, index);
const itemLayout = itemMeta?.getLayout();
const visibleSize = Math.max(visibleEndIndex - visibleStartIndex + 1, 0);
const beforeSize = Math.floor((this.recycleThreshold - visibleSize) / 2);
const afterSize = this.recycleThreshold - visibleSize - beforeSize;
if (count < maxCount || !itemLayout) {
const position = this.getPosition(
index,
safeRange.startIndex,
safeRange.endIndex
);
let _beforeCount = 0;
let topStartIndex = visibleStartIndex;
finalIndex = index;
if (position !== null) targetIndices[position] = index;
} else {
break;
}
for (
let index = visibleStartIndex, size = beforeSize;
size > 0 && index >= 0 && index >= bufferedStartIndex;
size--, index--
) {
const item = data[index];
if (!item) continue;
const itemMeta = this.getItemMeta(item, index);
const itemLayout = itemMeta?.getLayout();
if (_beforeCount < this.recycleBufferedCount || !itemLayout) {
const position = this.getPosition(
index,
visibleStartIndex - this.recycleBufferedCount,
visibleEndIndex + this.recycleBufferedCount
);
topStartIndex = index;
if (position !== null) targetIndices[position] = index;
} else {
break;
}
if (index >= this.initialNumToRender) {
_beforeCount++;
}
if (index >= this.initialNumToRender) {
count++;
}
}
return finalIndex;
}
let _afterCount = 0;
let bottomStartIndex = visibleEndIndex + 1;
resolveRecycleRecycleState(state: ListState<ItemT>) {
const { visibleEndIndex, visibleStartIndex: _visibleStartIndex } = state;
const _targetIndices = this._bufferSet.indices.map((i) => parseInt(i));
const targetIndices = new Array(_targetIndices.length).fill(null);
const recycleStateResult = [];
const velocity = this._scrollMetrics?.velocity || 0;
for (
let index = visibleEndIndex + 1, size = afterSize;
size > 0 && index <= bufferedEndIndex;
size--, index++
) {
const item = data[index];
if (!item) continue;
const itemMeta = this.getItemMeta(item, index);
const itemLayout = itemMeta?.getLayout();
const visibleStartIndex = Math.max(
_visibleStartIndex,
this.initialNumToRender
);
if (_afterCount < this.recycleBufferedCount || !itemLayout) {
const position = this.getPosition(
index,
visibleStartIndex - this.recycleBufferedCount,
visibleEndIndex + this.recycleBufferedCount
);
bottomStartIndex = index;
if (position !== null) targetIndices[position] = index;
} else {
break;
}
const safeRange = this.resolveSafeRange({
visibleStartIndex,
visibleEndIndex,
});
if (index >= this.initialNumToRender) {
_afterCount++;
}
if (visibleEndIndex >= 0) {
for (let index = visibleStartIndex; index <= visibleEndIndex; index++) {
const position = this.getPosition(
index,
safeRange.startIndex,
safeRange.endIndex
);
if (position !== null) targetIndices[position] = index;
}
}
const minValue = this._bufferSet.getMinValue();
const maxValue = this._bufferSet.getMaxValue();
const indexToOffsetMap = this.getIndexRangeOffsetMap(
minValue,
maxValue,
true
);
let topStartIndex = visibleStartIndex;
let bottomStartIndex = visibleEndIndex + 1;
const getOffset = this.getRecycleReuseOffsetBuilder({
topStartOffset: indexToOffsetMap[Math.max(topStartIndex, 0)] || 0,
bottomStartOffset: indexToOffsetMap[Math.max(bottomStartIndex, 0)] || 0,
minIndex: 0,
topStartIndex,
bottomStartIndex,
if (velocity > 0) {
bottomStartIndex = this.updateIndices(targetIndices, {
safeRange,
startIndex: visibleEndIndex + 1,
maxCount: this.recycleBufferedCount * 2,
step: 1,
});
} else if (velocity < 0) {
topStartIndex = this.updateIndices(targetIndices, {
safeRange,
startIndex: visibleStartIndex,
maxCount: this.recycleBufferedCount * 2,
step: -1,
});
} else {
topStartIndex = this.updateIndices(targetIndices, {
safeRange,
startIndex: visibleStartIndex,
maxCount: this.recycleBufferedCount,
step: -1,
});
bottomStartIndex = this.updateIndices(targetIndices, {
safeRange,
startIndex: visibleEndIndex + 1,
maxCount: this.recycleBufferedCount,
step: 1,
});
}
targetIndices.forEach((targetIndex, index) => {
if (targetIndex == null) {
targetIndex = _targetIndices[index];
const item = data[targetIndex];
if (!item) return;
const itemKey = this.getItemKey(item, targetIndex);
const itemMeta = this.getItemMeta(item, targetIndex);
const itemLayout = itemMeta?.getLayout();
const itemLength =
(itemLayout?.height || 0) + (itemMeta?.getSeparatorLength() || 0);
const minValue = this._bufferSet.getMinValue();
const maxValue = this._bufferSet.getMaxValue();
const indexToOffsetMap = this.getIndexRangeOffsetMap(
minValue,
maxValue,
true
);
let offset = 0;
const getOffset = this.getRecycleReuseOffsetBuilder({
topStartOffset: indexToOffsetMap[Math.max(topStartIndex, 0)] || 0,
bottomStartOffset: indexToOffsetMap[Math.max(bottomStartIndex, 0)] || 0,
minIndex: this.initialNumToRender,
topStartIndex,
bottomStartIndex,
});
if (this._scrollMetrics && itemLayout) {
const velocity = this._scrollMetrics.velocity;
offset = getOffset({
currentIndex: targetIndex,
length: itemLength,
velocity,
});
}
recycleStateResult.push({
key: `recycle_${index}`,
targetKey: itemKey,
targetIndex,
length: itemLength,
isSpace: false,
isSticky: false,
item,
itemMeta,
viewable: itemMeta.getState().viewable,
// 如果没有offset,说明item是新增的,那么它渲染就在最开始位置好了
offset: itemLayout ? offset : 0,
position: 'buffered',
});
return;
}
const item = data[targetIndex];
targetIndices.forEach((targetIndex, index) => {
if (targetIndex == null) {
targetIndex = _targetIndices[index];
const item = this._data[targetIndex];
if (!item) return;
const itemKey = this.getItemKey(item, targetIndex);

@@ -1259,16 +1273,12 @@ const itemMeta = this.getItemMeta(item, targetIndex);

const itemMetaState =
!this._scrollMetrics || !itemMeta?.getLayout()
? itemMeta
? itemMeta.getState()
: {}
: this._configTuple.resolveItemMetaState(
itemMeta,
this._scrollMetrics,
// should add container offset, because indexToOffsetMap containerOffset is
// exclusive.
() => indexToOffsetMap[targetIndex] + this.getContainerOffset()
);
let offset = 0;
itemMeta?.setItemMetaState(itemMetaState);
if (this._scrollMetrics && itemLayout) {
const velocity = this._scrollMetrics.velocity;
offset = getOffset({
currentIndex: targetIndex,
length: itemLength,
velocity,
});
}

@@ -1286,9 +1296,56 @@ recycleStateResult.push({

// 如果没有offset,说明item是新增的,那么它渲染就在最开始位置好了
offset: itemLength ? indexToOffsetMap[targetIndex] : 0,
offset: itemLayout ? offset : 0,
position: 'buffered',
});
return;
}
const item = this._data[targetIndex];
if (!item) return;
const itemKey = this.getItemKey(item, targetIndex);
const itemMeta = this.getItemMeta(item, targetIndex);
const itemLayout = itemMeta?.getLayout();
const itemLength =
(itemLayout?.height || 0) + (itemMeta?.getSeparatorLength() || 0);
const itemMetaState =
!this._scrollMetrics || !itemMeta?.getLayout()
? itemMeta
? itemMeta.getState()
: {}
: this._configTuple.resolveItemMetaState(
itemMeta,
this._scrollMetrics,
// should add container offset, because indexToOffsetMap containerOffset is
// exclusive.
() => indexToOffsetMap[targetIndex] + this.getContainerOffset()
);
itemMeta?.setItemMetaState(itemMetaState);
recycleStateResult.push({
key: `recycle_${index}`,
targetKey: itemKey,
targetIndex,
length: itemLength,
isSpace: false,
isSticky: false,
item,
itemMeta,
viewable: itemMeta.getState().viewable,
// 如果没有offset,说明item是新增的,那么它渲染就在最开始位置好了
offset: itemLength ? indexToOffsetMap[targetIndex] : 0,
position: 'buffered',
});
}
});
return recycleStateResult;
}
spaceStateResult = this.resolveRecycleSpaceState(state);
resolveRecycleState(state: ListState<ItemT>) {
const recycleEnabled = this._recycleEnabled();
// 只有当recycleEnabled为true的时候,才进行位置替换
const recycleStateResult = recycleEnabled
? this.resolveRecycleRecycleState(state)
: [];
const spaceStateResult = this.resolveRecycleSpaceState(state);

@@ -1310,11 +1367,10 @@ const stateResult = {

if (startIndex >= endIndex) return [];
const tokens = [
{
startIndex,
endIndex: startIndex + 1,
isSticky: false,
isReserved: false,
isSpace: true,
},
];
const createToken = (startIndex: number) => ({
startIndex,
endIndex: startIndex + 1,
isSticky: false,
isReserved: false,
isSpace: true,
});
const tokens = [createToken(startIndex)];

@@ -1329,11 +1385,3 @@ this.reservedIndices.forEach((index) => {

lastToken.isReserved = isReserved;
if (index + 1 !== endIndex) {
tokens.push({
startIndex: index + 1,
endIndex: index + 2,
isSticky: false,
isReserved: false,
isSpace: true,
});
}
if (index + 1 !== endIndex) tokens.push(createToken(index + 1));
} else {

@@ -1348,11 +1396,3 @@ lastToken.endIndex = index;

});
if (index + 1 !== endIndex) {
tokens.push({
startIndex: index + 1,
endIndex: index + 2,
isSticky: false,
isReserved: false,
isSpace: true,
});
}
if (index + 1 !== endIndex) tokens.push(createToken(index + 1));
}

@@ -1411,3 +1451,3 @@ }

if (isSticky || isReserved) {
const item = data[startIndex];
const item = this._data[startIndex];
spaceState.push({

@@ -1479,3 +1519,3 @@ item,

if (isSticky || isReserved) {
const item = data[startIndex];
const item = this._data[startIndex];
spaceState.push({

@@ -1482,0 +1522,0 @@ item,

@@ -118,3 +118,2 @@ import Batchinator from '@x-oasis/batchinator';

this.onEndReachedHelper = new OnEndReachedHelper({
id,
onEndReached,

@@ -121,0 +120,0 @@ onEndReachedThreshold,

@@ -6,3 +6,5 @@ export type OnEndReachedHelperProps = {

onEndReachedTimeoutThreshold?: number;
distanceFromEndThresholdValue?: number;
onEndReachedHandlerTimeoutThreshold?: number;
maxCountOfHandleOnEndReachedAfterStillness?: number;
};

@@ -15,1 +17,8 @@

}) => void;
export type SendOnEndReachedDistanceFromBottomStack = Array<{
distancesFromEnd: Array<number>;
ts: Array<number>;
hit: number;
resetCount: number;
}>;

@@ -7,3 +7,9 @@ import Batchinator from '@x-oasis/batchinator';

} from '../common';
import { OnEndReached, OnEndReachedHelperProps, ScrollMetrics } from '../types';
import {
OnEndReached,
OnEndReachedHelperProps,
ScrollMetrics,
SendOnEndReachedDistanceFromBottomStack,
} from '../types';
import isClamped from '@x-oasis/is-clamped';

@@ -16,7 +22,12 @@ class OnEndReachedHelper {

readonly onEndReachedHandlerBatchinator: Batchinator;
readonly sendOnEndReachedDistanceFromEndStack: SendOnEndReachedDistanceFromBottomStack;
private onEndReached: OnEndReached;
private _maxCountOfHandleOnEndReachedAfterStillness: number;
private _distanceFromEndThresholdValue: number;
private _waitingForDataChangedSinceEndReached = false;
private _onEndReachedTimeoutHandler: NodeJS.Timeout;
readonly _consecutiveDistanceTimeoutThresholdValue = 800;
constructor(props: OnEndReachedHelperProps) {

@@ -26,2 +37,4 @@ const {

onEndReached,
distanceFromEndThresholdValue = 100,
maxCountOfHandleOnEndReachedAfterStillness = 3,
onEndReachedThreshold = ON_END_REACHED_THRESHOLD,

@@ -38,2 +51,6 @@ onEndReachedTimeoutThreshold = ON_END_REACHED_TIMEOUT_THRESHOLD,

onEndReachedHandlerTimeoutThreshold;
this.sendOnEndReachedDistanceFromEndStack = [];
this._distanceFromEndThresholdValue = distanceFromEndThresholdValue;
this._maxCountOfHandleOnEndReachedAfterStillness =
maxCountOfHandleOnEndReachedAfterStillness;

@@ -47,2 +64,11 @@ this.releaseHandlerMutex = this.releaseHandlerMutex.bind(this);

static createStackToken(distanceFromEnd: number) {
return {
ts: [Date.now()],
hit: 1,
distancesFromEnd: [distanceFromEnd],
resetCount: 0,
};
}
clear() {

@@ -96,2 +122,67 @@ this.releaseHandlerMutex();

timeoutReleaseHandlerMutex() {
console.warn(
'OnEndReachedHelper ',
this.lastStack,
"' mutex is released due to timeout"
);
this.releaseHandlerMutex();
}
get lastStack() {
return this.sendOnEndReachedDistanceFromEndStack[
this.sendOnEndReachedDistanceFromEndStack.length - 1
];
}
getStack() {
return this.sendOnEndReachedDistanceFromEndStack;
}
reachCountLimitation() {
if (!this.lastStack) return false;
if (this.lastStack.hit >= this._maxCountOfHandleOnEndReachedAfterStillness)
return true;
return false;
}
shouldResetCountLimitation(distanceFromEnd: number) {
const { distancesFromEnd } = this.lastStack;
const distance = distancesFromEnd[distancesFromEnd.length - 1];
if (distanceFromEnd <= 0) return false;
if (distance !== distanceFromEnd) {
this.lastStack.resetCount += 1;
this.lastStack.hit = 1;
return true;
}
return false;
}
isConsecutiveDistance(distanceFromEnd: number) {
const lastStack =
this.sendOnEndReachedDistanceFromEndStack[
this.sendOnEndReachedDistanceFromEndStack.length - 1
];
if (lastStack) {
const { distancesFromEnd, ts } = lastStack;
const base = distancesFromEnd[0];
const _ts = ts[ts.length - 1];
const now = Date.now();
if (
isClamped(
base - this._distanceFromEndThresholdValue,
distanceFromEnd,
base + this._distanceFromEndThresholdValue
) &&
now - _ts < this._consecutiveDistanceTimeoutThresholdValue
) {
return true;
}
}
return false;
}
performEndReached(info: { isEndReached: boolean; distanceFromEnd: number }) {

@@ -102,9 +193,43 @@ if (this._waitingForDataChangedSinceEndReached) return;

if (typeof this.onEndReached !== 'function') return;
if (isEndReached) {
this.onEndReachedHandlerBatchinator.schedule({
distanceFromEnd,
});
if (isEndReached && !this.isConsecutiveDistance(distanceFromEnd)) {
if (
!this.reachCountLimitation() ||
this.shouldResetCountLimitation(distanceFromEnd)
) {
this.onEndReachedHandlerBatchinator.schedule({
distanceFromEnd,
});
}
}
}
updateStack(distanceFromEnd: number) {
const lastStack =
this.sendOnEndReachedDistanceFromEndStack[
this.sendOnEndReachedDistanceFromEndStack.length - 1
];
if (lastStack) {
const { distancesFromEnd } = lastStack;
const stackDistanceFromEnd = distancesFromEnd[0];
if (
isClamped(
stackDistanceFromEnd - this._distanceFromEndThresholdValue,
distanceFromEnd,
stackDistanceFromEnd + this._distanceFromEndThresholdValue
)
) {
lastStack.distancesFromEnd.push(distanceFromEnd);
lastStack.hit += 1;
lastStack.ts.push(Date.now());
return;
}
}
this.sendOnEndReachedDistanceFromEndStack.push(
OnEndReachedHelper.createStackToken(distanceFromEnd)
);
}
onEndReachedHandler(opts: { distanceFromEnd: number }) {

@@ -117,5 +242,7 @@ if (typeof this.onEndReached !== 'function') return;

this._onEndReachedTimeoutHandler = setTimeout(() => {
this._waitingForDataChangedSinceEndReached = false;
this.timeoutReleaseHandlerMutex();
}, this.onEndReachedHandlerTimeoutThreshold);
this.updateStack(distanceFromEnd);
this.onEndReached({

@@ -122,0 +249,0 @@ distanceFromEnd,

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc