Huge News!Announcing our $40M Series B led by Abstract Ventures.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.1.1 to 0.1.2-config.0

dist/viewable/constants.d.ts

10

dist/BaseDimensions.d.ts
import BaseLayout from './BaseLayout';
import ItemMeta from './ItemMeta';
import PrefixIntervalTree from '@x-oasis/prefix-interval-tree';
import ViewabilityConfigTuples from './configs/ViewabilityConfigTuples';
import ViewabilityConfigTuples from './viewable/ViewabilityConfigTuples';
import { BaseDimensionsProps, BoundInfo, IndexInfo, ItemLayout, KeysChangedType, ScrollMetrics } from './types';

@@ -10,3 +10,3 @@ declare abstract class BaseDimensions extends BaseLayout {

_keyToMetaMap: Map<string, ItemMeta>;
_configTuples: ViewabilityConfigTuples;
_configTuple: ViewabilityConfigTuples;
_onUpdateItemLayout?: Function;

@@ -41,4 +41,6 @@ _onUpdateIntervalTree?: Function;

greatestLowerBoundInfo(offset: number, exclusive?: boolean): BoundInfo;
getConfigTuples(): ViewabilityConfigTuples;
resolveConfigTuplesDefaultState(defaultValue?: boolean): {};
getConfigTuple(): ViewabilityConfigTuples;
resolveConfigTuplesDefaultState(defaultValue?: boolean): {
[key: string]: boolean;
};
abstract getIndexInfo(key: string): IndexInfo;

@@ -45,0 +47,0 @@ onUpdateItemsMetaChange(itemsMeta: Array<ItemMeta>, scrollMetrics: ScrollMetrics): void;

3

dist/BaseLayout.d.ts

@@ -14,2 +14,3 @@ import SelectValue from '@x-oasis/select-value';

private _stickyHeaderIndices;
private _reservedIndices;
private _recycleThreshold;

@@ -33,2 +34,4 @@ readonly _onEndReachedThreshold: number;

set initialNumToRender(num: number);
get reservedIndices(): any[];
updateReservedIndices(): void;
get persistanceIndices(): Array<number>;

@@ -35,0 +38,0 @@ set persistanceIndices(indices: Array<number>);

@@ -12,3 +12,3 @@ export declare const DEFAULT_LAYOUT: {

export declare const WINDOW_SIZE = 5;
export declare const INITIAL_NUM_TO_RENDER = 0;
export declare const INITIAL_NUM_TO_RENDER = 10;
export declare const MAX_TO_RENDER_PER_BATCH = 10;

@@ -19,1 +19,2 @@ export declare const INVALID_LENGTH = "invalid_length";

export declare const isNotEmpty: (obj: any) => boolean;
export declare const buildStateTokenIndexKey: (startIndex: number, endIndex: number) => string;

@@ -24,3 +24,5 @@ import ItemMeta from './ItemMeta';

getIgnoredToPerBatch(): boolean;
resolveConfigTuplesDefaultState(): {};
resolveConfigTuplesDefaultState(): {
[key: string]: boolean;
};
set offsetInListGroup(value: number);

@@ -27,0 +29,0 @@ getContainerOffset(): number;

import ItemMetaStateEventHelper from './ItemMetaStateEventHelper';
import { ItemLayout, ItemMetaOwner, ItemMetaState, StateEventListener } from './types';
declare class ItemMeta {
import ViewabilityItemMeta from './viewable/ViewabilityItemMeta';
declare class ItemMeta extends ViewabilityItemMeta {
private _isListItem;

@@ -12,3 +13,2 @@ private _layout?;

readonly getMetaOnViewableItemsChanged?: any;
readonly _key: string;
readonly _ownerId: string;

@@ -15,0 +15,0 @@ constructor(props: {

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

import PrefixIntervalTree from '@x-oasis/prefix-interval-tree';
import { SpaceStateToken, IndexInfo, ItemLayout, KeysChangedType, ListDimensionsProps, ListRenderState, ListState, OnEndReached, PreStateResult, ScrollMetrics, StateListener, SpaceStateTokenPosition, RecycleStateResult, SpaceStateResult } from './types';
import { SpaceStateToken, IndexInfo, ItemLayout, KeysChangedType, ListDimensionsProps, ListRenderState, ListState, OnEndReached, PreStateResult, ScrollMetrics, StateListener, ListStateResult, SpaceStateTokenPosition } from './types';
import OnEndReachedHelper from './viewable/OnEndReachedHelper';

@@ -32,3 +32,2 @@ import EnabledSelector from './utils/EnabledSelector';

private _removeList;
private _onUpdateItemsMetaChangeBatchinator;
private _initializeMode;

@@ -46,11 +45,5 @@ private _onBatchLayoutFinished;

get selector(): EnabledSelector;
get stateResult(): SpaceStateResult<ItemT> | RecycleStateResult<ItemT> | {
recycleState: SpaceStateToken<ItemT>[];
spaceState: SpaceStateToken<ItemT>[];
};
get stateResult(): ListStateResult<ItemT>;
set offsetInListGroup(offset: number);
getState(): SpaceStateResult<ItemT> | RecycleStateResult<ItemT> | {
recycleState: SpaceStateToken<ItemT>[];
spaceState: SpaceStateToken<ItemT>[];
};
get state(): ListState<ItemT>;
cleanup(): void;

@@ -111,4 +104,7 @@ resolveInitialActiveValue(active: boolean): boolean;

isSticky: boolean;
isReserved: boolean;
position: SpaceStateTokenPosition;
viewable: boolean;
};
getIndexRangeOffsetMap(startIndex: number, endIndex: number, exclusive?: boolean): {};
hydrateSpaceStateToken(spaceStateResult: Array<SpaceStateToken<ItemT>>, item: ItemT, index: number, position: SpaceStateTokenPosition): void;

@@ -120,4 +116,17 @@ getPosition(rowIndex: number, startIndex: number, endIndex: number): number;

};
resolveSpaceState(state: ListState<ItemT>): SpaceStateToken<ItemT>[];
updateState(newState: PreStateResult, scrollMetrics: ScrollMetrics, performItemsMetaChange?: boolean): void;
resolveToken(startIndex: number, endIndex: number): {
startIndex: number;
endIndex: number;
isSticky: boolean;
isReserved: boolean;
isSpace: boolean;
}[];
resolveRecycleSpaceState(state: ListState<ItemT>): any[];
resolveSpaceState(state: ListState<ItemT>, resolver?: {
bufferedStartIndex?: (state: ListState<ItemT>) => number;
bufferedEndIndex?: (state: ListState<ItemT>) => number;
visibleStartIndex?: (state: ListState<ItemT>) => number;
visibleEndIndex?: (state: ListState<ItemT>) => number;
}): any[];
updateState(newState: PreStateResult, scrollMetrics: ScrollMetrics): void;
dispatchMetrics(scrollMetrics: ScrollMetrics): void;

@@ -124,0 +133,0 @@ dispatchScrollMetricsEnabled(): boolean;

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

import ListDimensions from './ListDimensions';
import ViewabilityConfigTuples from './configs/ViewabilityConfigTuples';
import ViewabilityConfigTuples from './viewable/ViewabilityConfigTuples';
import { InspectingAPI, InspectingListener, ItemLayout, ListDimensionsProps, ListGroupDimensionsProps, ListRangeResult, ListRenderState, OnEndReached, ScrollMetrics, StateListener } from './types';

@@ -68,4 +68,6 @@ import EnabledSelector from './utils/EnabledSelector';

calculateReflowItemsLength(): void;
getConfigTuples(): ViewabilityConfigTuples;
resolveConfigTuplesDefaultState(defaultValue?: boolean): {};
getConfigTuple(): ViewabilityConfigTuples;
resolveConfigTuplesDefaultState(defaultValue?: boolean): {
[key: string]: boolean;
};
notifyRenderFinished(): void;

@@ -72,0 +74,0 @@ getState(listKey: string): {};

@@ -55,2 +55,3 @@ import BaseDimensions from '../BaseDimensions';

active?: boolean;
lazy?: boolean;
initialNumToRender?: number;

@@ -167,3 +168,5 @@ stickyHeaderIndices?: Array<number>;

export declare type SpaceStateTokenPosition = 'before' | 'buffered' | 'after';
export declare type SpaceStateToken<ItemT> = {
export declare type SpaceStateToken<ItemT, ViewabilityState = {
viewable: boolean;
}> = {
item: ItemT;

@@ -174,9 +177,23 @@ key: string;

isSticky: boolean;
isReserved: boolean;
position: SpaceStateTokenPosition;
} & ViewabilityState;
export declare type RecycleStateToken<ItemT, ViewabilityState = {
viewable: boolean;
}> = {
targetKey: string;
offset: number;
} & SpaceStateToken<ItemT, ViewabilityState>;
export declare type SpaceStateResult<ItemT, ViewabilityState = {
viewable: boolean;
}> = Array<SpaceStateToken<ItemT, ViewabilityState>>;
export declare type RecycleState<ItemT, ViewabilityState = {
viewable: boolean;
}> = Array<RecycleStateToken<ItemT, ViewabilityState>>;
export declare type RecycleStateResult<ItemT, ViewabilityState = {
viewable: boolean;
}> = {
spaceState: SpaceStateResult<ItemT, ViewabilityState>;
recycleState: RecycleState<ItemT, ViewabilityState>;
};
export declare type SpaceStateResult<ItemT> = Array<SpaceStateToken<ItemT>>;
export declare type RecycleStateResult<ItemT> = {
spaceState: SpaceStateResult<ItemT>;
recycleState: SpaceStateResult<ItemT>;
};
export declare type ListStateResult<ItemT> = SpaceStateResult<ItemT> | RecycleStateResult<ItemT>;

@@ -183,0 +200,0 @@ export declare type StateListener<ItemT = {}> = (newState: ListStateResult<ItemT>, oldState: ListStateResult<ItemT>) => void;

@@ -1,38 +0,24 @@

import ItemMeta from '../ItemMeta';
import ViewabilityItemMeta from '../viewable/ViewabilityItemMeta';
export declare type CommonViewabilityConfig = {
name?: string;
viewport?: number;
minimumViewTime?: number;
waitForInteraction?: boolean;
impression?: boolean;
exclusive?: boolean;
};
export declare type ViewabilityConfigTuple = {
configMap: {
[key: string]: ViewabilityConfig;
};
changed: {
[key: string]: Array<ItemMeta>;
};
callbackMap: {
[key: string]: Function;
};
primary: boolean;
export declare type NormalizedViewablityConfig = {
exclusive: boolean;
viewport: number;
minimumViewTime?: number;
waitForInteraction?: boolean;
viewAreaMode: boolean;
viewablePercentThreshold: number;
};
export declare type ViewAreaModeConfig = {
viewAreaCoveragePercentThreshold: number;
viewAreaCoveragePercentThreshold?: number;
} & CommonViewabilityConfig;
export declare type VisiblePercentModeConfig = {
itemVisiblePercentThreshold: number;
itemVisiblePercentThreshold?: number;
} & CommonViewabilityConfig;
export declare type ViewabilityConfig = ViewAreaModeConfig | VisiblePercentModeConfig;
export declare type ViewabilityConfigMap = {
[key: string]: ViewabilityConfig;
};
export declare type ViewabilityConfigCallbackPair = {
viewabilityConfig?: ViewabilityConfig;
viewabilityConfigMap?: ViewabilityConfigMap;
onViewableItemsChanged?: OnViewableItemsChanged;
primary?: boolean;
};
export declare type ViewabilityConfigCallbackPairs = Array<ViewabilityConfigCallbackPair>;
export interface ViewToken {

@@ -49,6 +35,22 @@ item: any;

};
export declare type ViewabilityScrollMetrics = {
offset: number;
visibleLength: number;
};
export declare type IsItemViewableOptions = {
viewport: number;
viewabilityItemMeta: ViewabilityItemMeta | {
offset: number;
length: number;
};
viewabilityScrollMetrics: ViewabilityScrollMetrics;
viewAreaMode?: boolean;
viewablePercentThreshold?: number;
getItemOffset?: (itemMeta: ViewabilityItemMeta) => number;
};
export declare type OnViewableItemsChanged = ((info: OnViewableItemChangedInfo) => void) | null;
export declare type ViewabilityHelperCallbackTuple = {
export declare type ViewabilityConfigCallbackPair = {
viewabilityConfig?: ViewabilityConfig;
onViewableItemsChanged: OnViewableItemsChanged;
onViewableItemsChanged?: OnViewableItemsChanged;
};
export declare type ViewabilityConfigCallbackPairs = Array<ViewabilityConfigCallbackPair>;

@@ -1,17 +0,3 @@

import ItemMeta from '../ItemMeta';
import SelectValue from '@x-oasis/select-value';
import { ScrollEventMetrics, ScrollMetrics } from '../types';
export declare function resolveMeasureMetrics(scrollEventMetrics: ScrollEventMetrics, selectValue: SelectValue): {
contentLength: number;
scrollOffset: number;
viewportLength: number;
};
export declare function isItemViewable(options: {
viewport: number;
itemMeta: ItemMeta;
scrollMetrics: ScrollMetrics;
viewAreaMode: boolean;
viewablePercentThreshold: number;
}): boolean;
export declare function _getPixelsVisible(props: {
import { IsItemViewableOptions } from '../types';
export declare function getPixelsVisible(props: {
top: number;

@@ -21,3 +7,3 @@ bottom: number;

}): number;
export declare function _isEntirelyVisible(props: {
export declare function isEntirelyVisible(props: {
top: number;

@@ -32,4 +18,6 @@ bottom: number;

viewportHeight: number;
viewportLength: number;
viewAreaMode: boolean;
viewablePercentThreshold: number;
}): boolean;
export declare function isItemViewable(options: IsItemViewableOptions): boolean;

@@ -1,35 +0,34 @@

import SelectValue from '@x-oasis/select-value';
import BaseDimensions from '../BaseDimensions';
import ItemMeta from '../ItemMeta';
import { ScrollMetrics, ViewabilityConfig, ViewabilityConfigTuple } from '../types';
import ViewabilityItemMeta from './ViewabilityItemMeta';
import { ScrollMetrics, ViewabilityScrollMetrics, ViewabilityConfig, NormalizedViewablityConfig, ViewabilityConfigCallbackPair } from '../types';
declare class ViewablityHelper {
readonly selectValue: SelectValue;
readonly tuple: ViewabilityConfigTuple;
readonly isListItem: boolean;
readonly horizontal: boolean;
private _configName;
private _changed;
private _config;
private _callback;
readonly _pair: ViewabilityConfigCallbackPair;
constructor(props: {
horizontal: boolean;
isListItem: boolean;
tuple: ViewabilityConfigTuple;
pair: ViewabilityConfigCallbackPair;
});
onItemsMetaChange(itemsMeta: Array<ItemMeta>, configKey: string, tupleConfig: ViewabilityConfig, options: {
dimensions: BaseDimensions;
scrollMetrics: ScrollMetrics;
}): ItemMeta[];
onUpdateTupleConfig(configKey: string, tupleConfig: ViewabilityConfig, options: {
dimensions: BaseDimensions;
scrollMetrics: ScrollMetrics;
}): ItemMeta[];
onUpdateItemsMeta(itemsMeta: Array<ItemMeta>, options: {
dimensions: BaseDimensions;
scrollMetrics: ScrollMetrics;
}): void;
onUpdateMetrics(options: {
dimensions: BaseDimensions;
scrollMetrics: ScrollMetrics;
}): void;
mergeState(nextChanged: {
[key: string]: Array<ItemMeta>;
}): void;
get defaultStateViewToken(): {
[x: string]: boolean;
};
get configName(): string;
get config(): NormalizedViewablityConfig;
get callback(): (info: import("../types").OnViewableItemChangedInfo) => void;
createChangedViewToken(): void;
resolveChangedViewTokenCallbackInfo(): void;
normalizeTupleConfig(tupleConfig: ViewabilityConfig): {
viewport: number;
exclusive: boolean;
viewAreaMode: boolean;
viewablePercentThreshold: number;
};
checkItemViewability(viewabilityItemMeta: ViewabilityItemMeta, scrollMetrics: ViewabilityScrollMetrics, getItemOffset?: (itemMeta: ViewabilityItemMeta) => number): boolean;
resolveViewableItems(itemsMeta: Array<ViewabilityItemMeta>, scrollMetrics: ViewabilityScrollMetrics): ViewabilityItemMeta[];
onUpdateItemsMeta(itemsMeta: Array<ViewabilityItemMeta>, scrollMetrics: ScrollMetrics): void;
performViewableItemsChangedCallback(nextViewableItems?: Array<ViewabilityItemMeta>): void;
mergeState(nextViewableItems: Array<ViewabilityItemMeta>): void;
}
export default ViewablityHelper;
{
"name": "@infinite-list/data-model",
"version": "0.1.1",
"version": "0.1.2-config.0",
"files": [

@@ -27,3 +27,3 @@ "dist",

"@x-oasis/omit": "^0.0.8",
"@x-oasis/prefix-interval-tree": "^0.0.10",
"@x-oasis/prefix-interval-tree": "^0.1.2",
"@x-oasis/resolve-changed": "^0.0.6",

@@ -33,2 +33,3 @@ "@x-oasis/select-value": "^0.0.6",

"@x-oasis/shallow-equal": "^0.0.6",
"@x-oasis/unique-array-object": "^0.1.2",
"memoize-one": "^6.0.0"

@@ -35,0 +36,0 @@ },

import ListDimensions from '../ListDimensions';
import Batchinator from '@x-oasis/batchinator';
import { KeysChangedType } from '../types';
import {
KeysChangedType,
SpaceStateResult,
RecycleStateResult,
} from '../types';
import { defaultKeyExtractor } from '../exportedUtils';
import { buildStateTokenIndexKey } from '../common';
import { vi, describe, it, expect } from 'vitest';

@@ -19,2 +24,22 @@ const buildData = (count: number) =>

describe('basic', () => {
it('constructor - default value', () => {
const listDimensions = new ListDimensions({
id: 'list_group',
keyExtractor: defaultKeyExtractor,
data: [],
getContainerLayout: () => ({
x: 0,
y: 2000,
width: 375,
height: 2000,
}),
});
expect(listDimensions.maxToRenderPerBatch).toBe(10);
expect(listDimensions.windowSize).toBe(5);
expect(listDimensions.initialNumToRender).toBe(10);
expect(listDimensions.onEndReachedThreshold).toBe(2);
expect(listDimensions.horizontal).toBe(false);
});
it('constructor', () => {

@@ -28,3 +53,3 @@ const listDimensions = new ListDimensions({

initialNumToRender: 20,
onEndReachedThreshold: 300,
onEndReachedThreshold: 2,
horizontal: true,

@@ -42,3 +67,3 @@ getContainerLayout: () => ({

expect(listDimensions.initialNumToRender).toBe(20);
expect(listDimensions.onEndReachedThreshold).toBe(300);
expect(listDimensions.onEndReachedThreshold).toBe(2);
expect(listDimensions.horizontal).toBe(true);

@@ -129,3 +154,3 @@ });

const stateResult = listDimensions.stateResult;
const stateResult = listDimensions.stateResult as SpaceStateResult<any>;

@@ -169,9 +194,15 @@ expect(stateResult.length).toBe(39);

expect(listDimensions.stateResult.length).toBe(39);
expect(listDimensions.stateResult[0].length).toBe(100);
expect(listDimensions.stateResult[0].isSpace).toBe(false);
expect(listDimensions.stateResult[0].isSticky).toBe(true);
expect(listDimensions.stateResult[38].length).toBe(6200);
expect(listDimensions.stateResult[38].isSpace).toBe(true);
let stateResult = listDimensions.stateResult as SpaceStateResult<any>;
expect(stateResult.length).toBe(39);
expect(stateResult[0].length).toBe(100);
expect(stateResult[0].isSpace).toBe(false);
expect(stateResult[0].isSticky).toBe(true);
expect(stateResult[0].isReserved).toBe(false);
expect(stateResult[10].isSpace).toBe(false);
expect(stateResult[10].isSticky).toBe(true);
expect(stateResult[10].isReserved).toBe(false);
expect(stateResult[38].length).toBe(6200);
expect(stateResult[38].isSpace).toBe(true);
// @ts-ignore

@@ -184,8 +215,16 @@ listDimensions.updateScrollMetrics({

expect(listDimensions.stateResult.length).toBe(51);
expect(listDimensions.stateResult[0].length).toBe(100);
expect(listDimensions.stateResult[0].isSpace).toBe(false);
expect(listDimensions.stateResult[0].isSticky).toBe(true);
expect(listDimensions.stateResult[50].length).toBe(4200);
expect(listDimensions.stateResult[50].isSpace).toBe(true);
stateResult = listDimensions.stateResult as SpaceStateResult<any>;
expect(stateResult.length).toBe(51);
expect(stateResult[0].length).toBe(100);
expect(stateResult[0].isSpace).toBe(false);
expect(stateResult[0].isSticky).toBe(true);
expect(stateResult[1].length).toBe(900);
expect(stateResult[1].isSpace).toBe(true);
expect(stateResult[21].viewable).toBe(false);
expect(stateResult[22].viewable).toBe(true);
expect(stateResult[31].viewable).toBe(true);
expect(stateResult[32].viewable).toBe(false);
expect(stateResult[50].length).toBe(4200);
expect(stateResult[50].isSpace).toBe(true);
});

@@ -246,3 +285,3 @@

const stateResult = listDimensions.stateResult;
let stateResult = listDimensions.stateResult as SpaceStateResult<any>;

@@ -252,2 +291,19 @@ expect(stateResult.length).toBe(39);

expect(stateResult[0].isSpace).toBe(false);
expect(stateResult[0].isSticky).toBe(false);
expect(stateResult[0].isReserved).toBe(false);
expect(stateResult[1].length).toBe(100);
expect(stateResult[1].isSpace).toBe(false);
expect(stateResult[1].isSticky).toBe(false);
expect(stateResult[1].isReserved).toBe(true);
expect(stateResult[2].length).toBe(100);
expect(stateResult[2].isSpace).toBe(false);
expect(stateResult[2].isSticky).toBe(false);
expect(stateResult[2].isReserved).toBe(true);
expect(stateResult[10].isSpace).toBe(false);
expect(stateResult[10].isSticky).toBe(false);
expect(stateResult[10].isReserved).toBe(true);
expect(stateResult[38].length).toBe(6200);

@@ -264,3 +320,1389 @@ expect(stateResult[38].isSpace).toBe(true);

expect(stateResult).toBe(listDimensions.stateResult);
// @ts-ignore
listDimensions.updateScrollMetrics({
offset: 3009,
visibleLength: 926,
contentLength: 10000,
});
stateResult = listDimensions.stateResult as SpaceStateResult<any>;
expect(stateResult.length).toBe(53);
expect(stateResult[0].length).toBe(100);
expect(stateResult[0].isSpace).toBe(true);
expect(stateResult[1].isSpace).toBe(false);
expect(stateResult[2].isSpace).toBe(false);
expect(stateResult[3].isSpace).toBe(true);
expect(stateResult[3].length).toBe(700);
expect(stateResult[4].isSpace).toBe(false);
expect(stateResult[4].isReserved).toBe(true);
expect(stateResult[14].isSpace).toBe(false);
expect(stateResult[14].isReserved).toBe(true);
expect(stateResult[52].length).toBe(4200);
expect(stateResult[52].isSpace).toBe(true);
// @ts-ignore
listDimensions.updateScrollMetrics({
offset: 5009,
visibleLength: 926,
contentLength: 10000,
});
stateResult = listDimensions.stateResult as SpaceStateResult<any>;
expect(stateResult.length).toBe(56);
expect(stateResult[0].length).toBe(100);
expect(stateResult[0].isSpace).toBe(true);
expect(stateResult[1].isSpace).toBe(false);
expect(stateResult[1].isReserved).toBe(true);
expect(stateResult[2].isSpace).toBe(false);
expect(stateResult[2].isReserved).toBe(true);
expect(stateResult[3].isSpace).toBe(true);
expect(stateResult[3].length).toBe(700);
expect(stateResult[4].isSpace).toBe(false);
expect(stateResult[4].isReserved).toBe(true);
expect(stateResult[5].isSpace).toBe(true);
expect(stateResult[5].length).toBe(900);
expect(stateResult[6].isSpace).toBe(false);
expect(stateResult[6].isReserved).toBe(true);
expect(stateResult[6].length).toBe(100);
expect(stateResult[7].isSpace).toBe(true);
expect(stateResult[7].length).toBe(1000);
expect(stateResult[8].isSpace).toBe(false);
expect(stateResult[8].isReserved).toBe(false);
expect(stateResult[55].length).toBe(2200);
expect(stateResult[55].isSpace).toBe(true);
});
it('hydrationWithBatchUpdate', () => {
const data = buildData(100);
const list = new ListDimensions({
data: [],
id: 'list_group',
keyExtractor: defaultKeyExtractor,
maxToRenderPerBatch: 7,
windowSize: 2,
initialNumToRender: 4,
onEndReachedThreshold: 2,
getItemLayout: (item, index) => ({
length: 100,
index,
}),
getContainerLayout: () => ({
x: 0,
y: 0,
width: 375,
height: 2000,
}),
viewabilityConfigCallbackPairs: [
{
viewabilityConfig: {
viewport: 1,
name: 'imageViewable',
viewAreaCoveragePercentThreshold: 20,
},
},
{
viewabilityConfig: {
name: 'viewable',
viewAreaCoveragePercentThreshold: 30,
},
},
],
});
expect(list.state).toEqual({
visibleStartIndex: -1,
visibleEndIndex: -1,
bufferedStartIndex: -1,
bufferedEndIndex: -1,
isEndReached: false,
distanceFromEnd: 0,
data: [],
actionType: 'initial',
});
list.setData(data);
expect(list.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 3,
bufferedStartIndex: 0,
bufferedEndIndex: 3,
isEndReached: false,
distanceFromEnd: 0,
data: data.slice(0, 4),
actionType: 'initial',
});
// @ts-ignore
list.updateScrollMetrics({
offset: 0,
visibleLength: 926,
contentLength: 400,
});
expect(list.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 9,
bufferedStartIndex: 0,
bufferedEndIndex: 10,
isEndReached: true,
distanceFromEnd: -526,
data: data.slice(0, 100),
actionType: 'hydrationWithBatchUpdate',
});
});
it('hydrationWithBatchUpdate - contentLength change will cause different bufferedIndexRange', () => {
const data = buildData(100);
const list = new ListDimensions({
data: [],
id: 'list_group',
keyExtractor: defaultKeyExtractor,
maxToRenderPerBatch: 7,
windowSize: 2,
initialNumToRender: 4,
onEndReachedThreshold: 2,
getItemLayout: (item, index) => ({
length: 100,
index,
}),
getContainerLayout: () => ({
x: 0,
y: 0,
width: 375,
height: 2000,
}),
viewabilityConfigCallbackPairs: [
{
viewabilityConfig: {
viewport: 1,
name: 'imageViewable',
viewAreaCoveragePercentThreshold: 20,
},
},
{
viewabilityConfig: {
name: 'viewable',
viewAreaCoveragePercentThreshold: 30,
},
},
],
});
expect(list.state).toEqual({
visibleStartIndex: -1,
visibleEndIndex: -1,
bufferedStartIndex: -1,
bufferedEndIndex: -1,
isEndReached: false,
distanceFromEnd: 0,
data: [],
actionType: 'initial',
});
list.setData(data);
expect(list.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 3,
bufferedStartIndex: 0,
bufferedEndIndex: 3,
isEndReached: false,
distanceFromEnd: 0,
data: data.slice(0, 4),
actionType: 'initial',
});
// @ts-ignore
list.updateScrollMetrics({
offset: 0,
visibleLength: 926,
contentLength: 1000,
});
expect(list.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 9,
bufferedStartIndex: 0,
bufferedEndIndex: 16,
isEndReached: true,
distanceFromEnd: 74,
data: data.slice(0, 100),
actionType: 'hydrationWithBatchUpdate',
});
// @ts-ignore
list.updateScrollMetrics({
offset: 0,
visibleLength: 926,
contentLength: 1100,
});
expect(list.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 9,
bufferedStartIndex: 0,
bufferedEndIndex: 17,
isEndReached: true,
distanceFromEnd: 174,
data: data.slice(0, 100),
actionType: 'hydrationWithBatchUpdate',
});
// @ts-ignore
list.updateScrollMetrics({
offset: 0,
visibleLength: 926,
contentLength: 1200,
});
expect(list.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 9,
bufferedStartIndex: 0,
bufferedEndIndex: 18,
isEndReached: true,
distanceFromEnd: 274,
data: data.slice(0, 100),
actionType: 'hydrationWithBatchUpdate',
});
});
it('persistanceIndices and stickyIndices', () => {
const data = buildData(100);
const listDimensions = new ListDimensions({
id: 'list_1',
data,
stickyHeaderIndices: [0, 1],
persistanceIndices: [1, 2, 10, 20],
keyExtractor: defaultKeyExtractor,
getItemLayout: (item, index) => ({
index,
length: 100,
}),
});
// @ts-ignore
listDimensions.updateScrollMetrics({
offset: 990,
visibleLength: 926,
contentLength: 10000,
});
let stateResult = listDimensions.stateResult as SpaceStateResult<any>;
expect(stateResult.length).toBe(39);
expect(stateResult[0].length).toBe(100);
expect(stateResult[0].isSpace).toBe(false);
expect(stateResult[0].isSticky).toBe(true);
expect(stateResult[0].isReserved).toBe(false);
expect(stateResult[1].length).toBe(100);
expect(stateResult[1].isSpace).toBe(false);
expect(stateResult[1].isSticky).toBe(true);
expect(stateResult[1].isReserved).toBe(true);
expect(stateResult[2].length).toBe(100);
expect(stateResult[2].isSpace).toBe(false);
expect(stateResult[2].isSticky).toBe(false);
expect(stateResult[2].isReserved).toBe(true);
expect(stateResult[10].isSpace).toBe(false);
expect(stateResult[10].isSticky).toBe(false);
expect(stateResult[10].isReserved).toBe(true);
expect(stateResult[38].length).toBe(6200);
expect(stateResult[38].isSpace).toBe(true);
// @ts-ignore
listDimensions.updateScrollMetrics({
offset: 995,
visibleLength: 926,
contentLength: 10000,
});
expect(stateResult).toBe(listDimensions.stateResult);
// @ts-ignore
listDimensions.updateScrollMetrics({
offset: 3009,
visibleLength: 926,
contentLength: 10000,
});
stateResult = listDimensions.stateResult as SpaceStateResult<any>;
expect(stateResult.length).toBe(53);
expect(stateResult[0].length).toBe(100);
expect(stateResult[0].isSpace).toBe(false);
expect(stateResult[1].isSpace).toBe(false);
expect(stateResult[2].isSpace).toBe(false);
expect(stateResult[3].isSpace).toBe(true);
expect(stateResult[3].length).toBe(700);
expect(stateResult[4].isSpace).toBe(false);
expect(stateResult[4].isReserved).toBe(true);
expect(stateResult[14].isSpace).toBe(false);
expect(stateResult[14].isReserved).toBe(true);
expect(stateResult[52].length).toBe(4200);
expect(stateResult[52].isSpace).toBe(true);
// @ts-ignore
listDimensions.updateScrollMetrics({
offset: 5009,
visibleLength: 926,
contentLength: 10000,
});
stateResult = listDimensions.stateResult as SpaceStateResult<any>;
expect(stateResult.length).toBe(56);
expect(stateResult[0].length).toBe(100);
expect(stateResult[0].isSpace).toBe(false);
expect(stateResult[1].isSpace).toBe(false);
expect(stateResult[1].isReserved).toBe(true);
expect(stateResult[2].isSpace).toBe(false);
expect(stateResult[2].isReserved).toBe(true);
expect(stateResult[3].isSpace).toBe(true);
expect(stateResult[3].length).toBe(700);
expect(stateResult[4].isSpace).toBe(false);
expect(stateResult[4].isReserved).toBe(true);
expect(stateResult[5].isSpace).toBe(true);
expect(stateResult[5].length).toBe(900);
expect(stateResult[6].isSpace).toBe(false);
expect(stateResult[6].isReserved).toBe(true);
expect(stateResult[6].length).toBe(100);
expect(stateResult[7].isSpace).toBe(true);
expect(stateResult[7].length).toBe(1000);
expect(stateResult[8].isSpace).toBe(false);
expect(stateResult[8].isReserved).toBe(false);
expect(stateResult[55].length).toBe(2200);
expect(stateResult[55].isSpace).toBe(true);
});
});
describe('viewability', () => {
it('default viewabilityConfig', () => {
const data = buildData(100);
const listDimensions = new ListDimensions({
id: 'list_1',
data,
stickyHeaderIndices: [0, 10],
keyExtractor: defaultKeyExtractor,
getItemLayout: (item, index) => ({
index,
length: 100,
}),
});
// @ts-ignore
listDimensions.updateScrollMetrics({
offset: 990,
visibleLength: 926,
contentLength: 10000,
});
const stateResult = listDimensions.stateResult as SpaceStateResult<any>;
expect(stateResult.length).toBe(39);
expect(stateResult[8].viewable).toBe(false);
expect(stateResult[9].viewable).toBe(true);
expect(stateResult[19].viewable).toBe(true);
expect(stateResult[20].viewable).toBe(false);
});
it('imageViewable - viewport: 0, thresholdValue: 0', () => {
const data = buildData(100);
const listDimensions = new ListDimensions({
id: 'list_1',
data,
stickyHeaderIndices: [0, 10],
keyExtractor: defaultKeyExtractor,
getItemLayout: (item, index) => ({
index,
length: 100,
}),
viewabilityConfigCallbackPairs: [
{
viewabilityConfig: {
name: 'imageViewable',
viewAreaCoveragePercentThreshold: 0,
},
},
{
viewabilityConfig: {
name: 'viewable',
viewAreaCoveragePercentThreshold: 0,
},
},
],
});
// @ts-ignore
listDimensions.updateScrollMetrics({
offset: 990,
visibleLength: 926,
contentLength: 10000,
});
const stateResult = listDimensions.stateResult as SpaceStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(stateResult.length).toBe(39);
expect(stateResult[8].viewable).toBe(false);
expect(stateResult[9].viewable).toBe(true);
expect(stateResult[19].viewable).toBe(true);
expect(stateResult[20].viewable).toBe(false);
expect(stateResult[8].imageViewable).toBe(false);
expect(stateResult[9].imageViewable).toBe(true);
expect(stateResult[19].imageViewable).toBe(true);
expect(stateResult[20].imageViewable).toBe(false);
});
it('imageViewable - viewport: 1, thresholdValue: 0', () => {
const data = buildData(100);
const listDimensions = new ListDimensions({
id: 'list_1',
data,
stickyHeaderIndices: [0, 10],
keyExtractor: defaultKeyExtractor,
getItemLayout: (item, index) => ({
index,
length: 100,
}),
viewabilityConfigCallbackPairs: [
{
viewabilityConfig: {
name: 'imageViewable',
viewport: 1,
viewAreaCoveragePercentThreshold: 0,
},
},
{
viewabilityConfig: {
name: 'viewable',
viewAreaCoveragePercentThreshold: 0,
},
},
],
});
// @ts-ignore
listDimensions.updateScrollMetrics({
offset: 990,
visibleLength: 926,
contentLength: 10000,
});
const stateResult = listDimensions.stateResult as SpaceStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(stateResult.length).toBe(39);
expect(stateResult[0].viewable).toBe(false);
expect(stateResult[0].imageViewable).toBe(true);
expect(stateResult[8].viewable).toBe(false);
expect(stateResult[8].imageViewable).toBe(true);
expect(stateResult[9].viewable).toBe(true);
expect(stateResult[9].imageViewable).toBe(true);
expect(stateResult[19].viewable).toBe(true);
expect(stateResult[19].imageViewable).toBe(true);
expect(stateResult[20].viewable).toBe(false);
expect(stateResult[20].imageViewable).toBe(true);
expect(stateResult[28].viewable).toBe(false);
expect(stateResult[28].imageViewable).toBe(true);
expect(stateResult[29].viewable).toBe(false);
expect(stateResult[29].imageViewable).toBe(false);
});
});
describe('lifecycle', () => {
it('initialization - empty list', () => {
const spaceList = new ListDimensions({
id: 'list_group',
keyExtractor: defaultKeyExtractor,
data: [],
maxToRenderPerBatch: 7,
windowSize: 9,
initialNumToRender: 10,
onEndReachedThreshold: 2,
getContainerLayout: () => ({
x: 0,
y: 2000,
width: 375,
height: 2000,
}),
});
expect(spaceList.state).toEqual({
visibleStartIndex: -1,
visibleEndIndex: -1,
bufferedStartIndex: -1,
bufferedEndIndex: -1,
isEndReached: false,
distanceFromEnd: 0,
data: [],
actionType: 'initial',
});
expect(spaceList.stateResult).toEqual([]);
const recycleList = new ListDimensions({
id: 'list_group',
keyExtractor: defaultKeyExtractor,
data: [],
recycleEnabled: true,
maxToRenderPerBatch: 7,
windowSize: 9,
initialNumToRender: 10,
onEndReachedThreshold: 2,
getContainerLayout: () => ({
x: 0,
y: 2000,
width: 375,
height: 2000,
}),
});
expect(recycleList.state).toEqual({
visibleStartIndex: -1,
visibleEndIndex: -1,
bufferedStartIndex: -1,
bufferedEndIndex: -1,
isEndReached: false,
distanceFromEnd: 0,
data: [],
actionType: 'initial',
});
expect(recycleList.stateResult).toEqual({
recycleState: [],
spaceState: [],
});
});
it('initialization - data source', () => {
const data = buildData(20);
const spaceList = new ListDimensions({
data,
id: 'list_group',
keyExtractor: defaultKeyExtractor,
maxToRenderPerBatch: 7,
windowSize: 9,
initialNumToRender: 4,
onEndReachedThreshold: 2,
getContainerLayout: () => ({
x: 0,
y: 2000,
width: 375,
height: 2000,
}),
});
expect(spaceList.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 3,
bufferedStartIndex: 0,
bufferedEndIndex: 3,
isEndReached: false,
distanceFromEnd: 0,
data: data.slice(0, 4),
actionType: 'initial',
});
const spaceListStateResult = spaceList.stateResult as SpaceStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(spaceListStateResult.length).toBe(4);
expect(spaceListStateResult[0].viewable).toBe(true);
expect(spaceListStateResult[1].viewable).toBe(true);
expect(spaceListStateResult[2].viewable).toBe(true);
expect(spaceListStateResult[3].viewable).toBe(true);
const recycleList = new ListDimensions({
id: 'list_group',
keyExtractor: defaultKeyExtractor,
data,
recycleEnabled: true,
maxToRenderPerBatch: 7,
windowSize: 9,
initialNumToRender: 4,
onEndReachedThreshold: 2,
getContainerLayout: () => ({
x: 0,
y: 2000,
width: 375,
height: 2000,
}),
});
expect(recycleList.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 3,
bufferedStartIndex: 0,
bufferedEndIndex: 3,
isEndReached: false,
distanceFromEnd: 0,
data: data.slice(0, 4),
actionType: 'initial',
});
const recycleListStateResult =
recycleList.stateResult as RecycleStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(recycleListStateResult.spaceState.length).toBe(4);
expect(recycleListStateResult.spaceState[0].viewable).toBe(true);
expect(recycleListStateResult.spaceState[1].viewable).toBe(true);
expect(recycleListStateResult.spaceState[2].viewable).toBe(true);
expect(recycleListStateResult.spaceState[3].viewable).toBe(true);
});
it('initialization - update data source', () => {
const data = buildData(20);
const spaceList = new ListDimensions({
data: [],
id: 'list_group',
keyExtractor: defaultKeyExtractor,
maxToRenderPerBatch: 7,
windowSize: 9,
initialNumToRender: 4,
onEndReachedThreshold: 2,
getContainerLayout: () => ({
x: 0,
y: 2000,
width: 375,
height: 2000,
}),
});
expect(spaceList.state).toEqual({
visibleStartIndex: -1,
visibleEndIndex: -1,
bufferedStartIndex: -1,
bufferedEndIndex: -1,
isEndReached: false,
distanceFromEnd: 0,
data: [],
actionType: 'initial',
});
spaceList.setData(data);
expect(spaceList.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 3,
bufferedStartIndex: 0,
bufferedEndIndex: 3,
isEndReached: false,
distanceFromEnd: 0,
data: data.slice(0, 4),
actionType: 'initial',
});
const spaceListStateResult = spaceList.stateResult as SpaceStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(spaceListStateResult.length).toBe(4);
expect(spaceListStateResult[0].viewable).toBe(true);
expect(spaceListStateResult[1].viewable).toBe(true);
expect(spaceListStateResult[2].viewable).toBe(true);
expect(spaceListStateResult[3].viewable).toBe(true);
const recycleList = new ListDimensions({
id: 'list_group',
keyExtractor: defaultKeyExtractor,
data: [],
recycleEnabled: true,
maxToRenderPerBatch: 7,
windowSize: 5,
initialNumToRender: 4,
onEndReachedThreshold: 2,
getContainerLayout: () => ({
x: 0,
y: 2000,
width: 375,
height: 2000,
}),
});
expect(recycleList.state).toEqual({
visibleStartIndex: -1,
visibleEndIndex: -1,
bufferedStartIndex: -1,
bufferedEndIndex: -1,
isEndReached: false,
distanceFromEnd: 0,
data: [],
actionType: 'initial',
});
recycleList.setData(data);
expect(recycleList.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 3,
bufferedStartIndex: 0,
bufferedEndIndex: 3,
isEndReached: false,
distanceFromEnd: 0,
data: data.slice(0, 4),
actionType: 'initial',
});
const recycleListStateResult =
recycleList.stateResult as RecycleStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(recycleListStateResult.spaceState.length).toBe(4);
expect(recycleListStateResult.spaceState[0].viewable).toBe(true);
expect(recycleListStateResult.spaceState[1].viewable).toBe(true);
expect(recycleListStateResult.spaceState[2].viewable).toBe(true);
expect(recycleListStateResult.spaceState[3].viewable).toBe(true);
});
it('initialization - update data source (initialNumToRender: 10)', () => {
const data = buildData(100);
const spaceList = new ListDimensions({
data: [],
id: 'list_group',
keyExtractor: defaultKeyExtractor,
maxToRenderPerBatch: 7,
windowSize: 5,
initialNumToRender: 10,
onEndReachedThreshold: 2,
getItemLayout: (item, index) => ({
length: 100,
index,
}),
getContainerLayout: () => ({
x: 0,
y: 2000,
width: 375,
height: 2000,
}),
});
expect(spaceList.state).toEqual({
visibleStartIndex: -1,
visibleEndIndex: -1,
bufferedStartIndex: -1,
bufferedEndIndex: -1,
isEndReached: false,
distanceFromEnd: 0,
data: [],
actionType: 'initial',
});
spaceList.setData(data);
expect(spaceList.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 9,
bufferedStartIndex: 0,
bufferedEndIndex: 9,
isEndReached: false,
distanceFromEnd: 0,
data: data.slice(0, 10),
actionType: 'initial',
});
let spaceListStateResult = spaceList.stateResult as SpaceStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(spaceListStateResult.length).toBe(10);
expect(spaceListStateResult[0].viewable).toBe(true);
expect(spaceListStateResult[1].viewable).toBe(true);
expect(spaceListStateResult[2].viewable).toBe(true);
expect(spaceListStateResult[3].viewable).toBe(true);
// @ts-ignore
spaceList.updateScrollMetrics({
offset: 3009,
visibleLength: 926,
contentLength: 10000,
});
expect(spaceList.state).toEqual({
visibleStartIndex: 10,
visibleEndIndex: 19,
bufferedStartIndex: 0,
bufferedEndIndex: 37,
isEndReached: false,
distanceFromEnd: 6065,
data: data.slice(0, 100),
actionType: 'recalculate',
});
expect(spaceList.stateResult[38].key).toBe(buildStateTokenIndexKey(38, 99));
expect(spaceList.stateResult[38].length).toBe(6200);
spaceListStateResult = spaceList.stateResult as SpaceStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(spaceListStateResult[9].viewable).toBe(false);
expect(spaceListStateResult[10].viewable).toBe(true);
expect(spaceListStateResult[11].viewable).toBe(true);
expect(spaceListStateResult[12].viewable).toBe(true);
expect(spaceListStateResult[19].viewable).toBe(true);
expect(spaceListStateResult[20].viewable).toBe(false);
});
it('initialization - update data source (initialNumToRender: 10) and use viewabilityConfig', () => {
const data = buildData(100);
const spaceList = new ListDimensions({
data: [],
id: 'list_group',
keyExtractor: defaultKeyExtractor,
maxToRenderPerBatch: 7,
windowSize: 5,
initialNumToRender: 10,
onEndReachedThreshold: 2,
getItemLayout: (item, index) => ({
length: 100,
index,
}),
getContainerLayout: () => ({
x: 0,
y: 2000,
width: 375,
height: 2000,
}),
viewabilityConfigCallbackPairs: [
{
viewabilityConfig: {
viewport: 1,
name: 'imageViewable',
viewAreaCoveragePercentThreshold: 20,
},
},
{
viewabilityConfig: {
name: 'viewable',
viewAreaCoveragePercentThreshold: 30,
},
},
],
});
expect(spaceList.state).toEqual({
visibleStartIndex: -1,
visibleEndIndex: -1,
bufferedStartIndex: -1,
bufferedEndIndex: -1,
isEndReached: false,
distanceFromEnd: 0,
data: [],
actionType: 'initial',
});
spaceList.setData(data);
expect(spaceList.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 9,
bufferedStartIndex: 0,
bufferedEndIndex: 9,
isEndReached: false,
distanceFromEnd: 0,
data: data.slice(0, 10),
actionType: 'initial',
});
let spaceListStateResult = spaceList.stateResult as SpaceStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(spaceListStateResult.length).toBe(10);
expect(spaceListStateResult[0].viewable).toBe(true);
expect(spaceListStateResult[0].imageViewable).toBe(true);
expect(spaceListStateResult[1].viewable).toBe(true);
expect(spaceListStateResult[1].imageViewable).toBe(true);
expect(spaceListStateResult[2].viewable).toBe(true);
expect(spaceListStateResult[2].imageViewable).toBe(true);
expect(spaceListStateResult[3].viewable).toBe(true);
expect(spaceListStateResult[3].imageViewable).toBe(true);
expect(spaceListStateResult[9].viewable).toBe(true);
expect(spaceListStateResult[9].imageViewable).toBe(true);
// @ts-ignore
spaceList.updateScrollMetrics({
offset: 3009,
visibleLength: 926,
contentLength: 10000,
});
expect(spaceList.state).toEqual({
visibleStartIndex: 10,
visibleEndIndex: 19,
bufferedStartIndex: 0,
bufferedEndIndex: 37,
isEndReached: false,
distanceFromEnd: 6065,
data: data.slice(0, 100),
actionType: 'recalculate',
});
expect(spaceList.stateResult[38].key).toBe(buildStateTokenIndexKey(38, 99));
expect(spaceList.stateResult[38].length).toBe(6200);
spaceListStateResult = spaceList.stateResult as SpaceStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(spaceListStateResult[0].viewable).toBe(false);
// (2100 - (3009 - 926)) / 926 = 0.018
expect(spaceListStateResult[0].imageViewable).toBe(false);
expect(spaceListStateResult[1].viewable).toBe(false);
// (3009 - 926) < 2100 < (3009 + 926) entirely
expect(spaceListStateResult[1].imageViewable).toBe(true);
expect(spaceListStateResult[9].viewable).toBe(false);
expect(spaceListStateResult[9].imageViewable).toBe(true);
// (3100 - 3009) / 925 = 0.098
expect(spaceListStateResult[10].viewable).toBe(false);
expect(spaceListStateResult[10].imageViewable).toBe(true);
expect(spaceListStateResult[11].viewable).toBe(true);
expect(spaceListStateResult[11].imageViewable).toBe(true);
expect(spaceListStateResult[12].viewable).toBe(true);
expect(spaceListStateResult[12].imageViewable).toBe(true);
expect(spaceListStateResult[19].viewable).toBe(false);
expect(spaceListStateResult[19].imageViewable).toBe(true);
expect(spaceListStateResult[20].viewable).toBe(false);
expect(spaceListStateResult[20].imageViewable).toBe(true);
expect(spaceListStateResult[27].viewable).toBe(false);
// top: 2000 + 2700, bottom: 2000 + 2800; viewHeight = 3009 + 2 * 926 = 4861
expect(spaceListStateResult[27].imageViewable).toBe(true);
expect(spaceListStateResult[28].viewable).toBe(false);
// top: 2000 + 2800, bottom: 2000 + 2900; viewHeight = 3009 + 2 * 926 = 4861
// 61 / 926 = 0.0658
expect(spaceListStateResult[28].imageViewable).toBe(false);
});
it('initialization - update data source (initialNumToRender: 10) and use viewabilityConfig', () => {
const data = buildData(100);
const recycleList = new ListDimensions({
data: [],
id: 'list_group',
recycleEnabled: true,
keyExtractor: defaultKeyExtractor,
maxToRenderPerBatch: 7,
windowSize: 5,
initialNumToRender: 10,
onEndReachedThreshold: 2,
getItemLayout: (item, index) => ({
length: 100,
index,
}),
getContainerLayout: () => ({
x: 0,
y: 0,
width: 375,
height: 2000,
}),
viewabilityConfigCallbackPairs: [
{
viewabilityConfig: {
viewport: 1,
name: 'imageViewable',
viewAreaCoveragePercentThreshold: 0,
},
},
{
viewabilityConfig: {
name: 'viewable',
viewAreaCoveragePercentThreshold: 0,
},
},
],
});
expect(recycleList.state).toEqual({
visibleStartIndex: -1,
visibleEndIndex: -1,
bufferedStartIndex: -1,
bufferedEndIndex: -1,
isEndReached: false,
distanceFromEnd: 0,
data: [],
actionType: 'initial',
});
recycleList.setData(data);
expect(recycleList.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 9,
bufferedStartIndex: 0,
bufferedEndIndex: 9,
isEndReached: false,
distanceFromEnd: 0,
data: data.slice(0, 10),
actionType: 'initial',
});
let recycleListStateResult = recycleList.stateResult as RecycleStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(recycleListStateResult.spaceState.length).toBe(10);
expect(recycleListStateResult.recycleState.length).toBe(0);
expect(recycleListStateResult.spaceState.map((v) => v.viewable)).toEqual([
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
]);
expect(
recycleListStateResult.spaceState.map((v) => v.imageViewable)
).toEqual([true, true, true, true, true, true, true, true, true, true]);
// @ts-ignore
recycleList.updateScrollMetrics({
offset: 0,
visibleLength: 926,
contentLength: 1000,
});
expect(recycleList.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 9,
bufferedStartIndex: 0,
bufferedEndIndex: 16,
isEndReached: true,
distanceFromEnd: 74,
data: data.slice(0, 100),
actionType: 'hydrationWithBatchUpdate',
});
recycleListStateResult = recycleList.stateResult as RecycleStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(recycleListStateResult.spaceState.length).toBe(11);
expect(recycleListStateResult.spaceState[10].key).toBe(
buildStateTokenIndexKey(10, 99)
);
expect(recycleListStateResult.spaceState[10].length).toBe(9000);
expect(recycleListStateResult.recycleState.length).toBe(2);
// @ts-ignore
recycleList.updateScrollMetrics({
offset: 0,
visibleLength: 926,
contentLength: 1200,
});
expect(recycleList.state).toEqual({
visibleStartIndex: 0,
visibleEndIndex: 9,
bufferedStartIndex: 0,
bufferedEndIndex: 18,
isEndReached: true,
distanceFromEnd: 274,
data: data.slice(0, 100),
actionType: 'hydrationWithBatchUpdate',
});
recycleListStateResult = recycleList.stateResult as RecycleStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(recycleListStateResult.recycleState.length).toBe(2);
expect(recycleListStateResult.recycleState.map((v) => v.item.key)).toEqual([
10, 11,
]);
expect(recycleListStateResult.recycleState.map((v) => v.key)).toEqual([
'recycle_0',
'recycle_1',
]);
expect(recycleListStateResult.recycleState.map((v) => v.viewable)).toEqual([
false,
false,
]);
expect(
recycleListStateResult.recycleState.map((v) => v.imageViewable)
).toEqual([true, true]);
// @ts-ignore
recycleList.updateScrollMetrics({
offset: 3000,
visibleLength: 926,
contentLength: 4500,
});
recycleListStateResult = recycleList.stateResult as RecycleStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(recycleList.state).toEqual({
isEndReached: true,
distanceFromEnd: 574,
actionType: 'hydrationWithBatchUpdate',
visibleStartIndex: 29,
visibleEndIndex: 39,
bufferedStartIndex: 11,
bufferedEndIndex: 51,
data,
});
expect(recycleListStateResult.recycleState.map((v) => v.item.key)).toEqual([
41, 11, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
]);
// @ts-ignore
recycleList.updateScrollMetrics({
offset: 2700,
visibleLength: 926,
contentLength: 4500,
});
recycleListStateResult = recycleList.stateResult as RecycleStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(recycleList.state).toEqual({
isEndReached: true,
distanceFromEnd: 874,
actionType: 'hydrationWithBatchUpdate',
visibleStartIndex: 26,
visibleEndIndex: 36,
bufferedStartIndex: 8,
bufferedEndIndex: 51,
data,
});
expect(recycleListStateResult.recycleState.map((v) => v.item.key)).toEqual([
27, 26, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 28,
]);
expect(recycleListStateResult.recycleState.map((v) => v.offset)).toEqual([
2700, 2600, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800,
3900, 2800,
]);
expect(recycleListStateResult.recycleState.map((v) => v.key)).toEqual([
'recycle_0',
'recycle_1',
'recycle_2',
'recycle_3',
'recycle_4',
'recycle_5',
'recycle_6',
'recycle_7',
'recycle_8',
'recycle_9',
'recycle_10',
'recycle_11',
'recycle_12',
'recycle_13',
]);
expect(recycleListStateResult.recycleState.map((v) => v.viewable)).toEqual([
true,
false,
true,
true,
true,
true,
true,
true,
true,
true,
false,
false,
false,
true,
]);
expect(
recycleListStateResult.recycleState.map((v) => v.imageViewable)
).toEqual([
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
]);
expect(recycleListStateResult.spaceState.length).toBe(4);
expect(recycleListStateResult.spaceState.map((v) => v.key)).toEqual([
'space_0_7',
'8',
'9',
'space_10_99',
]);
expect(recycleListStateResult.spaceState.map((v) => v.length)).toEqual([
800, 100, 100, 9000,
]);
// @ts-ignore
recycleList.updateScrollMetrics({
offset: 5000,
visibleLength: 926,
contentLength: 6500,
});
recycleListStateResult = recycleList.stateResult as RecycleStateResult<
any,
{
viewable: boolean;
imageViewable: boolean;
}
>;
expect(recycleList.state).toEqual({
isEndReached: true,
distanceFromEnd: 574,
actionType: 'hydrationWithBatchUpdate',
visibleStartIndex: 49,
visibleEndIndex: 59,
bufferedStartIndex: 31,
bufferedEndIndex: 71,
data,
});
expect(recycleListStateResult.recycleState.map((v) => v.item.key)).toEqual([
50, 49, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 39, 51,
]);
expect(recycleListStateResult.recycleState.map((v) => v.offset)).toEqual([
5000, 4900, 5200, 5300, 5400, 5500, 5600, 5700, 5800, 5900, 6000, 6100,
3900, 5100,
]);
expect(recycleListStateResult.recycleState.map((v) => v.key)).toEqual([
'recycle_0',
'recycle_1',
'recycle_2',
'recycle_3',
'recycle_4',
'recycle_5',
'recycle_6',
'recycle_7',
'recycle_8',
'recycle_9',
'recycle_10',
'recycle_11',
'recycle_12',
'recycle_13',
]);
expect(recycleListStateResult.recycleState.map((v) => v.viewable)).toEqual([
true,
false,
true,
true,
true,
true,
true,
true,
true,
true,
false,
false,
false,
true,
]);
expect(
recycleListStateResult.recycleState.map((v) => v.imageViewable)
).toEqual([
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
false,
true,
]);
expect(recycleListStateResult.spaceState.length).toBe(2);
expect(recycleListStateResult.spaceState.map((v) => v.key)).toEqual([
'space_0_9',
'space_10_99',
]);
expect(recycleListStateResult.spaceState.map((v) => v.length)).toEqual([
1000, 9000,
]);
});
});
import BaseLayout from './BaseLayout';
import ItemMeta from './ItemMeta';
import PrefixIntervalTree from '@x-oasis/prefix-interval-tree';
import { removeItemsKeyword } from './common';
import ViewabilityConfigTuples from './configs/ViewabilityConfigTuples';
import ViewabilityConfigTuples from './viewable/ViewabilityConfigTuples';
import {

@@ -20,3 +19,3 @@ BaseDimensionsProps,

_keyToMetaMap: Map<string, ItemMeta> = new Map();
_configTuples: ViewabilityConfigTuples;
_configTuple: ViewabilityConfigTuples;

@@ -42,4 +41,3 @@ _onUpdateItemLayout?: Function;

this._intervalTree = this.createIntervalTree();
this._configTuples = new ViewabilityConfigTuples({
horizontal: this.getHorizontal(),
this._configTuple = new ViewabilityConfigTuples({
viewabilityConfig,

@@ -71,4 +69,13 @@ onViewableItemsChanged,

getIndexKeyOffset(index: number, exclusive?: boolean) {
const key = this.getIndexKey(index);
return this.getKeyItemOffset(key, exclusive);
const listOffset = exclusive ? 0 : this.getContainerOffset();
if (typeof index === 'number') {
return (
listOffset +
(index >= this._intervalTree.getMaxUsefulLength()
? this.intervalTree.getHeap()[1]
: this._intervalTree.sumUntil(index))
);
}
return 0;
}

@@ -84,8 +91,4 @@

getKeyItemOffset(key: string, exclusive?: boolean) {
const listOffset = exclusive ? 0 : this.getContainerOffset();
const index = this.getKeyIndex(key);
if (typeof index === 'number') {
return listOffset + this._intervalTree.sumUntil(index);
}
return -1;
return this.getIndexKeyOffset(index, exclusive);
}

@@ -246,18 +249,8 @@

getConfigTuples() {
return this._configTuples;
getConfigTuple() {
return this._configTuple;
}
resolveConfigTuplesDefaultState(defaultValue?: boolean) {
const state = {};
this._configTuples.getTuples().forEach((tuple) => {
const { configMap } = tuple;
const keys = Object.keys(configMap);
keys.forEach((key) => {
const k = removeItemsKeyword(key);
state[k] = !!defaultValue;
});
});
return state;
return this._configTuple.getDefaultState(defaultValue);
}

@@ -271,7 +264,4 @@

) {
this._configTuples.getViewabilityHelpers().forEach((helper) => {
helper.onUpdateItemsMeta(itemsMeta, {
dimensions: this,
scrollMetrics,
});
this._configTuple.getViewabilityHelpers().forEach((helper) => {
helper.onUpdateItemsMeta(itemsMeta, scrollMetrics);
});

@@ -278,0 +268,0 @@ }

@@ -26,2 +26,3 @@ import {

private _stickyHeaderIndices = [];
private _reservedIndices = [];
private _recycleThreshold: number;

@@ -48,7 +49,7 @@ readonly _onEndReachedThreshold: number;

recycleThreshold,
persistanceIndices,
persistanceIndices = [],
recycleEnabled = false,
horizontal = false,
getContainerLayout,
stickyHeaderIndices,
stickyHeaderIndices = [],
windowSize = WINDOW_SIZE,

@@ -77,4 +78,4 @@ maxToRenderPerBatch = MAX_TO_RENDER_PER_BATCH,

this._onEndReachedThreshold = onEndReachedThreshold;
this._stickyHeaderIndices = (stickyHeaderIndices || []).sort();
this._persistanceIndices = (persistanceIndices || []).sort((a, b) => a - b);
this.persistanceIndices = persistanceIndices;
this.stickyHeaderIndices = stickyHeaderIndices;
}

@@ -90,2 +91,13 @@

get reservedIndices() {
return this._reservedIndices;
}
updateReservedIndices() {
const indices = new Set(
[].concat(this.persistanceIndices, this.stickyHeaderIndices)
);
this._reservedIndices = [...indices].sort((a, b) => a - b);
}
get persistanceIndices() {

@@ -97,2 +109,3 @@ return this._persistanceIndices;

this._persistanceIndices = indices.sort((a, b) => a - b);
this.updateReservedIndices();
}

@@ -106,2 +119,3 @@

this._stickyHeaderIndices = indices.sort((a, b) => a - b);
this.updateReservedIndices();
}

@@ -108,0 +122,0 @@

@@ -17,3 +17,3 @@ export const DEFAULT_LAYOUT = {

export const WINDOW_SIZE = 5;
export const INITIAL_NUM_TO_RENDER = 0;
export const INITIAL_NUM_TO_RENDER = 10;
export const MAX_TO_RENDER_PER_BATCH = 10;

@@ -47,1 +47,4 @@ export const INVALID_LENGTH = 'invalid_length';

};
export const buildStateTokenIndexKey = (startIndex: number, endIndex: number) =>
`space_${startIndex}_${endIndex}`;

@@ -11,5 +11,6 @@ import BaseDimensions from './BaseDimensions';

} from './types';
import noop from '@x-oasis/noop'
import noop from '@x-oasis/noop';
import ViewabilityItemMeta from './viewable/ViewabilityItemMeta';
class ItemMeta {
class ItemMeta extends ViewabilityItemMeta {
private _isListItem: boolean;

@@ -23,3 +24,2 @@ private _layout?: ItemLayout;

readonly getMetaOnViewableItemsChanged?: any;
readonly _key: string;
readonly _ownerId: string;

@@ -39,2 +39,3 @@

}) {
super(props);
const {

@@ -50,3 +51,2 @@ key,

} = props;
this._key = key;
this._owner = owner;

@@ -53,0 +53,0 @@ this._setState = setState;

@@ -10,3 +10,8 @@ import noop from '@x-oasis/noop';

import omit from '@x-oasis/omit';
import { INVALID_LENGTH, isNotEmpty, shallowDiffers } from './common';
import {
INVALID_LENGTH,
isNotEmpty,
shallowDiffers,
buildStateTokenIndexKey,
} from './common';
import resolveChanged from '@x-oasis/resolve-changed';

@@ -58,8 +63,3 @@ import manager from './manager';

private _state: ListState<ItemT>;
private _stateResult:
| ListStateResult<ItemT>
| {
recycleState: Array<SpaceStateToken<ItemT>>;
spaceState: Array<SpaceStateToken<ItemT>>;
};
private _stateResult: ListStateResult<ItemT>;

@@ -89,4 +89,2 @@ private _listGroupDimension: ListGroupDimensions;

private _onUpdateItemsMetaChangeBatchinator: Batchinator;
private _initializeMode = false;

@@ -130,3 +128,2 @@

onBatchLayoutFinished,
initialNumToRender,
persistanceIndices,

@@ -157,8 +154,9 @@ onEndReachedTimeoutThreshold,

if (this._listGroupDimension && initialNumToRender) {
console.warn(
'[Spectrum warning] : As a `ListGroup` child list, List Props ' +
' initialNumToRender value should be controlled' +
'by `ListGroup` commander. So value is reset to `0`.'
);
if (this._listGroupDimension && this.initialNumToRender) {
if (process.env.NODE_ENV === 'development')
console.warn(
'[Spectrum warning] : As a `ListGroup` child list, List Props ' +
' initialNumToRender value should be controlled' +
'by `ListGroup` commander. So value is reset to `0`.'
);
this.initialNumToRender = 0;

@@ -168,7 +166,8 @@ }

if (this._listGroupDimension && persistanceIndices) {
console.warn(
'[Spectrum warning] : As a `ListGroup` child list, List Props ' +
' persistanceIndices value should be controlled' +
'by `ListGroup` commander. So value is reset to `[]`.'
);
if (process.env.NODE_ENV === 'development')
console.warn(
'[Spectrum warning] : As a `ListGroup` child list, List Props ' +
' persistanceIndices value should be controlled' +
'by `ListGroup` commander. So value is reset to `[]`.'
);
this.persistanceIndices = [];

@@ -200,6 +199,6 @@ }

this._onUpdateItemsMetaChangeBatchinator = new Batchinator(
this.onUpdateItemsMetaChange.bind(this),
50
);
// this._onUpdateItemsMetaChangeBatchinator = new Batchinator(
// this.onUpdateItemsMetaChange.bind(this),
// 50
// );
this.attemptToHandleEndReached();

@@ -238,4 +237,4 @@ this.handleDeps = this.handleDeps.bind(this);

getState() {
return this._stateResult;
get state() {
return this._state;
}

@@ -398,4 +397,3 @@

if (this.fillingMode !== FillingMode.RECYCLE) return false;
const originalPositionSize = this._bufferSet.getSize();
return originalPositionSize >= this.recycleThreshold;
return this.getReflowItemsLength() >= this.initialNumToRender;
}

@@ -597,2 +595,3 @@

this.setState(state);
this._state = state;
return changedType;

@@ -876,2 +875,4 @@ }

isSticky: false,
isReserved: false,
viewable: false,
...options,

@@ -881,2 +882,28 @@ };

/**
* For performance boosting. `getIndexKeyOffset` will call intervalTree's sumUtil
* function. this method may cause performance issue.
* @param startIndex
* @param endIndex
* @param exclusive
* @returns
*/
getIndexRangeOffsetMap(
startIndex: number,
endIndex: number,
exclusive?: boolean
) {
const indexToOffsetMap = {};
let startOffset = this.getIndexKeyOffset(startIndex, exclusive);
for (let index = startIndex; index <= endIndex; index++) {
indexToOffsetMap[index] = startOffset;
const item = this._data[index];
const itemMeta = this.getItemMeta(item, index);
startOffset +=
(itemMeta?.getLayout()?.height || 0) +
(itemMeta?.getSeparatorLength() || 0);
}
return indexToOffsetMap;
}
hydrateSpaceStateToken(

@@ -889,3 +916,2 @@ spaceStateResult: Array<SpaceStateToken<ItemT>>,

const itemMeta = this.getItemMeta(item, index);
const { index: currentIndex } = itemMeta.getIndexInfo();
const lastTokenIndex = spaceStateResult.length - 1;

@@ -896,6 +922,4 @@ const lastToken = spaceStateResult[lastTokenIndex];

const isSticky = this.stickyHeaderIndices.indexOf(index) !== -1;
const isSpace =
!isSticky &&
position !== 'buffered' &&
this.persistanceIndices.indexOf(currentIndex) === -1;
const isReserved = this.persistanceIndices.indexOf(index) !== -1;
const isSpace = !isSticky && position !== 'buffered' && !isReserved;
const itemLength =

@@ -910,2 +934,3 @@ (itemLayout?.height || 0) + (itemMeta?.getSeparatorLength() || 0);

key,
isReserved,
length: lastToken.length + itemLength,

@@ -919,2 +944,3 @@ };

isSticky,
isReserved,
item,

@@ -928,2 +954,4 @@ position,

getPosition(rowIndex: number, startIndex: number, endIndex: number) {
// 初始化的item不参与absolute替换
if (rowIndex < this.initialNumToRender) return null;
let position = this._bufferSet.getValuePosition(rowIndex);

@@ -965,61 +993,58 @@

const recycleEnabled = this._recycleEnabled();
const recycleStateResult = [];
let spaceStateResult = [];
if (visibleEndIndex >= 0) {
for (let index = visibleStartIndex; index <= visibleEndIndex; index++) {
// 只有当recycleEnabled为true的时候,才进行位置替换
if (recycleEnabled) {
if (visibleEndIndex >= 0) {
for (let index = visibleStartIndex; index <= visibleEndIndex; index++) {
const position = this.getPosition(
index,
visibleStartIndex,
visibleEndIndex
);
if (position !== null) targetIndices[position] = index;
}
}
const visibleSize = Math.max(visibleEndIndex - visibleStartIndex + 1, 0);
const beforeSize = Math.floor((this.recycleThreshold - visibleSize) / 2);
const afterSize = this.recycleThreshold - visibleSize - beforeSize;
for (
let index = visibleStartIndex, size = beforeSize;
size > 0 && index >= 0;
size--, index--
) {
const position = this.getPosition(
index,
visibleStartIndex,
visibleEndIndex
bufferedStartIndex,
visibleStartIndex
);
if (position !== null) targetIndices[position] = index;
}
}
const visibleSize = Math.max(visibleEndIndex - visibleStartIndex + 1, 0);
const beforeSize = Math.floor((this.recycleThreshold - visibleSize) / 2);
const afterSize = this.recycleThreshold - visibleSize - beforeSize;
for (
let index = visibleEndIndex + 1, size = afterSize;
size > 0 && index <= bufferedEndIndex;
size--, index++
) {
const position = this.getPosition(
index,
visibleEndIndex + 1,
bufferedEndIndex
);
if (position !== null) targetIndices[position] = index;
}
for (
let index = visibleStartIndex, size = beforeSize;
size > 0 && index >= 0;
size--, index--
) {
const position = this.getPosition(
index,
bufferedStartIndex,
visibleStartIndex
);
if (position !== null) targetIndices[position] = index;
}
for (
let index = visibleEndIndex + 1, size = afterSize;
size > 0 && index <= bufferedEndIndex;
size--, index++
) {
const position = this.getPosition(
index,
visibleEndIndex + 1,
bufferedEndIndex
);
if (position !== null) targetIndices[position] = index;
}
const recycleStateResult = [];
if (recycleEnabled) {
const indexToOffsetMap = {};
const minValue = this._bufferSet.getMinValue();
const maxValue = this._bufferSet.getMaxValue();
let startOffset = this.getIndexKeyOffset(minValue, true);
const indexToOffsetMap = this.getIndexRangeOffsetMap(
minValue,
maxValue,
true
);
for (let index = minValue; index <= maxValue; index++) {
indexToOffsetMap[index] = startOffset;
const item = data[index];
const itemMeta = this.getItemMeta(item, index);
startOffset +=
(itemMeta?.getLayout()?.height || 0) +
(itemMeta?.getSeparatorLength() || 0);
}
targetIndices.forEach((targetIndex, index) => {

@@ -1035,2 +1060,12 @@ const item = data[targetIndex];

(itemLayout?.height || 0) + (itemMeta?.getSeparatorLength() || 0);
const itemMetaState = this._scrollMetrics
? this._configTuple.resolveItemMetaState(
itemMeta,
this._scrollMetrics,
() => indexToOffsetMap[targetIndex]
)
: itemMeta.getState();
itemMeta.setItemMetaState(itemMetaState);
recycleStateResult.push({

@@ -1043,4 +1078,6 @@ key: `recycle_${index}`,

item,
// 如果没有offset,说明item是新增的,那么它渲染就在最开始位置好了
offset: itemLayout ? indexToOffsetMap[targetIndex] : 0,
position: 'buffered',
...itemMetaState,
});

@@ -1050,58 +1087,4 @@ });

let spaceStateResult = [];
spaceStateResult = this.resolveRecycleSpaceState(state);
// 滚动中
if (recycleEnabled) {
spaceStateResult.push({
key: `spacer_${this.getTotalLength()}`,
length: this.getTotalLength(),
isSpace: true,
isSticky: false,
item: null,
position: 'buffered',
});
} else {
// if (recycleEnabled) {
// if (visibleStartIndex > 0) {
// spaceStateResult.push({
// key: 'spacer_before',
// length: this.getIndexKeyOffset(visibleStartIndex, true),
// isSpace: true,
// isSticky: false,
// item: null,
// position: 'buffered',
// });
// }
// for (let index = visibleStartIndex; index <= visibleEndIndex; index++) {
// const item = data[index];
// if (item)
// this.hydrateSpaceStateToken(
// spaceStateResult,
// item,
// index,
// 'buffered'
// );
// }
// if (
// visibleEndIndex < data.length - 1 &&
// typeof this.getTotalLength() === 'number'
// ) {
// spaceStateResult.push({
// key: 'spacer_after',
// length:
// (this.getTotalLength() as number) -
// this.getIndexKeyOffset(visibleEndIndex + 1, true),
// isSpace: true,
// isSticky: false,
// item: null,
// position: 'buffered',
// });
// }
// } else {
spaceStateResult = this.resolveSpaceState(state);
// }
}
const stateResult = {

@@ -1115,13 +1098,133 @@ recycleState: recycleStateResult,

resolveSpaceState(state: ListState<ItemT>) {
const { data, bufferedEndIndex, bufferedStartIndex } = state;
const afterStartIndex = bufferedEndIndex + 1;
const beforeData = data.slice(0, bufferedStartIndex);
const afterData = data.slice(afterStartIndex);
const remainingData = data.slice(bufferedStartIndex, bufferedEndIndex + 1);
/**
*
* @param startIndex included
* @param endIndex exclusive
*/
resolveToken(startIndex: number, endIndex: number) {
if (startIndex >= endIndex) return [];
const tokens = [
{
startIndex,
endIndex: startIndex + 1,
isSticky: false,
isReserved: false,
isSpace: true,
},
];
const spaceStateResult = [] as Array<SpaceStateToken<ItemT>>;
this.reservedIndices.forEach((index) => {
const lastToken = tokens[tokens.length - 1];
if (isClamped(startIndex, index, endIndex - 1)) {
const isSticky = this.stickyHeaderIndices.indexOf(index) !== -1;
const isReserved = this.persistanceIndices.indexOf(index) !== -1;
if (lastToken.startIndex === index) {
lastToken.isSticky = isSticky;
lastToken.isReserved = isReserved;
if (index + 1 !== endIndex) {
tokens.push({
startIndex: index + 1,
endIndex: index + 2,
isSticky: false,
isReserved: false,
isSpace: true,
});
}
} else {
lastToken.endIndex = index;
tokens.push({
startIndex: index,
endIndex: index + 1,
isSticky,
isReserved,
isSpace: !isSticky && !isReserved,
});
if (index + 1 !== endIndex) {
tokens.push({
startIndex: index + 1,
endIndex: index + 2,
isSticky: false,
isReserved: false,
isSpace: true,
});
}
}
}
});
beforeData.forEach((item, index) =>
this.hydrateSpaceStateToken(spaceStateResult, item, index, 'before')
const lastToken = tokens[tokens.length - 1];
if (lastToken.endIndex !== endIndex) lastToken.endIndex = endIndex;
return tokens;
}
resolveRecycleSpaceState(state: ListState<ItemT>) {
return this.resolveSpaceState(state, {
bufferedStartIndex: (state) =>
state.bufferedStartIndex >= this.initialNumToRender
? this.initialNumToRender
: state.bufferedStartIndex,
bufferedEndIndex: (state) =>
state.bufferedEndIndex >= this.initialNumToRender
? this.initialNumToRender - 1
: state.bufferedEndIndex,
});
}
resolveSpaceState(
state: ListState<ItemT>,
resolver?: {
bufferedStartIndex?: (state: ListState<ItemT>) => number;
bufferedEndIndex?: (state: ListState<ItemT>) => number;
visibleStartIndex?: (state: ListState<ItemT>) => number;
visibleEndIndex?: (state: ListState<ItemT>) => number;
}
) {
const {
data,
bufferedEndIndex: _bufferedEndIndex,
bufferedStartIndex: _bufferedStartIndex,
} = state;
const bufferedEndIndex = resolver?.bufferedEndIndex
? resolver?.bufferedEndIndex(state)
: _bufferedEndIndex;
const bufferedStartIndex = resolver?.bufferedStartIndex
? resolver?.bufferedStartIndex(state)
: _bufferedStartIndex;
const nextStart = bufferedStartIndex;
const nextEnd = bufferedEndIndex + 1;
const remainingData = data.slice(nextStart, nextEnd);
const beforeTokens = this.resolveToken(0, nextStart);
const spaceState = [];
beforeTokens.forEach((token) => {
const { isSticky, isReserved, startIndex, endIndex } = token;
if (isSticky || isReserved) {
const item = data[startIndex];
spaceState.push({
item,
key: this.getItemKey(item, startIndex),
isSpace: false,
isSticky,
length: this.getIndexItemLength(startIndex),
isReserved,
});
} else {
const startIndexOffset = this.getIndexKeyOffset(startIndex);
const endIndexOffset = this.getIndexKeyOffset(endIndex);
spaceState.push({
isSpace: true,
item: null,
isSticky: false,
isReserved: false,
key: buildStateTokenIndexKey(startIndex, endIndex - 1),
length: endIndexOffset - startIndexOffset,
});
}
});
const indexToOffsetMap = this.getIndexRangeOffsetMap(
bufferedStartIndex,
bufferedEndIndex
);

@@ -1131,11 +1234,61 @@

const index = bufferedStartIndex + _index;
this.hydrateSpaceStateToken(spaceStateResult, item, index, 'buffered');
const itemMeta = this.getItemMeta(item, index);
const isSticky = this.stickyHeaderIndices.indexOf(index) !== -1;
const isReserved = this.persistanceIndices.indexOf(index) !== -1;
const itemLayout = itemMeta?.getLayout();
const itemKey = itemMeta.getKey();
const itemLength =
(itemLayout?.height || 0) + (itemMeta?.getSeparatorLength() || 0);
const itemMetaState = this._scrollMetrics
? this._configTuple.resolveItemMetaState(
itemMeta,
this._scrollMetrics,
() => indexToOffsetMap[index]
)
: itemMeta.getState();
itemMeta.setItemMetaState(itemMetaState);
spaceState.push({
key: itemKey,
item,
isSpace: false,
isSticky,
isReserved,
length: itemLength,
...itemMetaState,
});
});
afterData.forEach((item, _index) => {
const index = afterStartIndex + _index;
this.hydrateSpaceStateToken(spaceStateResult, item, index, 'after');
const afterTokens = this.resolveToken(nextEnd, data.length);
afterTokens.forEach((token) => {
const { isSticky, isReserved, startIndex, endIndex } = token;
if (isSticky || isReserved) {
const item = data[startIndex];
spaceState.push({
item,
isSpace: false,
key: this.getItemKey(item, startIndex),
isSticky,
isReserved,
length: this.getIndexItemLength(startIndex),
});
} else {
const startIndexOffset = this.getIndexKeyOffset(startIndex);
const endIndexOffset = this.getIndexKeyOffset(endIndex);
spaceState.push({
item: null,
isSpace: true,
isSticky: false,
isReserved: false,
length: endIndexOffset - startIndexOffset,
// endIndex is not included
key: buildStateTokenIndexKey(startIndex, endIndex - 1),
});
}
});
return spaceStateResult;
return spaceState;
}

@@ -1145,4 +1298,4 @@

newState: PreStateResult,
scrollMetrics: ScrollMetrics,
performItemsMetaChange = true
scrollMetrics: ScrollMetrics
// performItemsMetaChange = true
) {

@@ -1181,46 +1334,2 @@ const {

this._offsetTriggerCachedState = scrollMetrics.offset;
if (performItemsMetaChange) {
// _recycleEnabled()它的时机会比resolveRecycleState要早,
// 所以这里面先确保recycle state有东西,要不然的话,itemMeta的state(viewable & imageViewable)
// 会出现bug
if (
this._recycleEnabled() &&
(
this._stateResult as {
recycleState: Array<SpaceStateToken<ItemT>>;
}
).recycleState.length
) {
const bufferedItemsMeta = (
this._stateResult as {
recycleState: Array<SpaceStateToken<ItemT>>;
}
).recycleState
// @ts-ignore
.map((item) => this.getKeyMeta(item.targetKey))
.filter((v) => v);
this._onUpdateItemsMetaChangeBatchinator.schedule(
bufferedItemsMeta,
scrollMetrics
);
} else {
const bufferedItems = this._data.slice(
nextBufferedStartIndex,
nextBufferedEndIndex + 1
);
const bufferedItemsMeta = bufferedItems
.map((item, index) =>
this.getItemMeta(item, nextBufferedStartIndex + index)
)
.filter((v) => v);
this._onUpdateItemsMetaChangeBatchinator.schedule(
bufferedItemsMeta,
scrollMetrics
);
}
}
}

@@ -1234,2 +1343,3 @@ }

});
if (!isNotEmpty(state)) return;

@@ -1255,2 +1365,9 @@ this.updateState(state, scrollMetrics);

/**
* When to trigger updateScrollMetrics..
* - on scroll
* - layout change. In rn, use contentSizeChanged. in web, maybe `_setKeyItemLayout`
* to trigger state updating..
*/
updateScrollMetrics(

@@ -1257,0 +1374,0 @@ scrollMetrics: ScrollMetrics = this._scrollMetrics,

@@ -13,4 +13,4 @@ import Batchinator from '@x-oasis/batchinator';

import ListDimensions from './ListDimensions';
import { isNotEmpty, removeItemsKeyword } from './common';
import ViewabilityConfigTuples from './configs/ViewabilityConfigTuples';
import { isNotEmpty } from './common';
import ViewabilityConfigTuples from './viewable/ViewabilityConfigTuples';
import manager from './manager';

@@ -107,3 +107,3 @@ import createStore from './state/createStore';

this._configTuples = new ViewabilityConfigTuples({
horizontal: this.getHorizontal(),
// horizontal: this.getHorizontal(),
viewabilityConfig,

@@ -254,3 +254,3 @@ onViewableItemsChanged,

getConfigTuples() {
getConfigTuple() {
return this._configTuples;

@@ -260,13 +260,3 @@ }

resolveConfigTuplesDefaultState(defaultValue?: boolean) {
const state = {};
this._configTuples.getTuples().forEach((tuple) => {
const { configMap } = tuple;
const keys = Object.keys(configMap);
keys.forEach((key) => {
const k = removeItemsKeyword(key);
state[k] = defaultValue ?? false;
});
});
return state;
return this._configTuples.getDefaultState(defaultValue);
}

@@ -1049,7 +1039,3 @@

this._configTuples.getViewabilityHelpers().forEach((helper) => {
helper.onUpdateItemsMeta(itemsMeta, {
// @ts-ignore
dimensions: this,
scrollMetrics,
});
helper.onUpdateItemsMeta(itemsMeta, scrollMetrics);
});

@@ -1056,0 +1042,0 @@ }

@@ -29,3 +29,2 @@ import BaseDimensions from '../BaseDimensions';

isIntervalTreeItems?: boolean;
// getContainerOffset?: ContainerOffsetGetter;
viewabilityConfig?: ViewabilityConfig;

@@ -68,3 +67,10 @@ onViewableItemsChanged?: OnViewableItemsChanged;

active?: boolean;
/**
* If lazy is true, initialNumToRender default value is 10 or should be 0.
* If lazy is true, list state will be hydrated on initialization.
*/
lazy?: boolean;
initialNumToRender?: number;
stickyHeaderIndices?: Array<number>;

@@ -217,3 +223,8 @@ persistanceIndices?: Array<number>;

export type SpaceStateToken<ItemT> = {
export type SpaceStateToken<
ItemT,
ViewabilityState = {
viewable: boolean;
}
> = {
item: ItemT;

@@ -224,9 +235,36 @@ key: string;

isSticky: boolean;
isReserved: boolean;
position: SpaceStateTokenPosition;
};
} & ViewabilityState;
export type RecycleStateToken<
ItemT,
ViewabilityState = {
viewable: boolean;
}
> = {
targetKey: string;
offset: number;
} & SpaceStateToken<ItemT, ViewabilityState>;
export type SpaceStateResult<ItemT> = Array<SpaceStateToken<ItemT>>;
export type RecycleStateResult<ItemT> = {
spaceState: SpaceStateResult<ItemT>;
recycleState: SpaceStateResult<ItemT>;
export type SpaceStateResult<
ItemT,
ViewabilityState = {
viewable: boolean;
}
> = Array<SpaceStateToken<ItemT, ViewabilityState>>;
export type RecycleState<
ItemT,
ViewabilityState = {
viewable: boolean;
}
> = Array<RecycleStateToken<ItemT, ViewabilityState>>;
export type RecycleStateResult<
ItemT,
ViewabilityState = {
viewable: boolean;
}
> = {
spaceState: SpaceStateResult<ItemT, ViewabilityState>;
recycleState: RecycleState<ItemT, ViewabilityState>;
};

@@ -233,0 +271,0 @@

@@ -1,8 +0,8 @@

import ItemMeta from '../ItemMeta';
import ViewabilityItemMeta from '../viewable/ViewabilityItemMeta';
export type CommonViewabilityConfig = {
name?: string;
viewport?: number;
minimumViewTime?: number;
waitForInteraction?: boolean;
impression?: boolean;

@@ -14,38 +14,21 @@ // 目前主要用在比如只希望有一个曝光出来;比如viewport中可以

export type ViewabilityConfigTuple = {
configMap: {
[key: string]: ViewabilityConfig;
};
changed: {
[key: string]: Array<ItemMeta>;
};
callbackMap: {
[key: string]: Function;
};
primary: boolean;
export type NormalizedViewablityConfig = {
exclusive: boolean;
viewport: number;
minimumViewTime?: number;
waitForInteraction?: boolean;
viewAreaMode: boolean;
viewablePercentThreshold: number;
};
export type ViewAreaModeConfig = {
viewAreaCoveragePercentThreshold: number;
viewAreaCoveragePercentThreshold?: number;
} & CommonViewabilityConfig;
export type VisiblePercentModeConfig = {
itemVisiblePercentThreshold: number;
itemVisiblePercentThreshold?: number;
} & CommonViewabilityConfig;
export type ViewabilityConfig = ViewAreaModeConfig | VisiblePercentModeConfig;
export type ViewabilityConfigMap = {
[key: string]: ViewabilityConfig;
};
export type ViewabilityConfigCallbackPair = {
viewabilityConfig?: ViewabilityConfig;
viewabilityConfigMap?: ViewabilityConfigMap;
onViewableItemsChanged?: OnViewableItemsChanged;
primary?: boolean;
};
export type ViewabilityConfigCallbackPairs =
Array<ViewabilityConfigCallbackPair>;
export interface ViewToken {

@@ -63,2 +46,27 @@ item: any;

// export type ViewabilityItemMeta = {
// offset: number;
// length: number;
// };
export type ViewabilityScrollMetrics = {
offset: number;
visibleLength: number;
};
export type IsItemViewableOptions = {
viewport: number;
viewabilityItemMeta:
| ViewabilityItemMeta
| {
offset: number;
length: number;
};
viewabilityScrollMetrics: ViewabilityScrollMetrics;
viewAreaMode?: boolean;
viewablePercentThreshold?: number;
// for performance boost....
getItemOffset?: (itemMeta: ViewabilityItemMeta) => number;
};
export type OnViewableItemsChanged =

@@ -68,5 +76,8 @@ | ((info: OnViewableItemChangedInfo) => void)

export type ViewabilityHelperCallbackTuple = {
export type ViewabilityConfigCallbackPair = {
viewabilityConfig?: ViewabilityConfig;
onViewableItemsChanged: OnViewableItemsChanged;
onViewableItemsChanged?: OnViewableItemsChanged;
};
export type ViewabilityConfigCallbackPairs =
Array<ViewabilityConfigCallbackPair>;

@@ -1,54 +0,20 @@

import ItemMeta from '../ItemMeta';
import SelectValue from '@x-oasis/select-value';
import { ScrollEventMetrics, ScrollMetrics } from '../types';
// import SelectValue from '@x-oasis/select-value';
import { IsItemViewableOptions } from '../types';
import ViewabilityItemMeta from './ViewabilityItemMeta';
export function resolveMeasureMetrics(
scrollEventMetrics: ScrollEventMetrics,
selectValue: SelectValue
) {
const { contentOffset, layoutMeasurement, contentSize } = scrollEventMetrics;
const contentLength = selectValue.selectLength(contentSize);
const scrollOffset = selectValue.selectOffset(contentOffset);
const viewportLength = selectValue.selectLength(layoutMeasurement);
return {
contentLength,
scrollOffset,
viewportLength,
};
}
// export function resolveMeasureMetrics(
// scrollEventMetrics: ScrollEventMetrics,
// selectValue: SelectValue
// ) {
// const { contentOffset, layoutMeasurement, contentSize } = scrollEventMetrics;
// const contentLength = selectValue.selectLength(contentSize);
// const scrollOffset = selectValue.selectOffset(contentOffset);
// const viewportLength = selectValue.selectLength(layoutMeasurement);
// return {
// contentLength,
// scrollOffset,
// viewportLength,
// };
// }
export function isItemViewable(options: {
viewport: number;
itemMeta: ItemMeta;
scrollMetrics: ScrollMetrics;
viewAreaMode: boolean;
viewablePercentThreshold: number;
}) {
const {
itemMeta,
viewport,
viewAreaMode,
scrollMetrics,
viewablePercentThreshold,
} = options;
const { offset: scrollOffset, visibleLength: viewportLength } = scrollMetrics;
if (!itemMeta) return false;
const itemOffset = itemMeta.getItemOffset();
const itemLength = itemMeta.getItemLength();
const top = itemOffset - scrollOffset + viewport * viewportLength;
const bottom = top + itemLength;
const value = _isViewable({
top,
bottom,
itemLength,
viewAreaMode,
viewablePercentThreshold,
viewportHeight: (2 * viewport + 1) * viewportLength,
});
return value;
}
/**

@@ -59,3 +25,3 @@ * 获取在视窗中的可见高度

*/
export function _getPixelsVisible(props: {
export function getPixelsVisible(props: {
top: number;

@@ -70,3 +36,3 @@ bottom: number;

export function _isEntirelyVisible(props: {
export function isEntirelyVisible(props: {
top: number;

@@ -86,2 +52,3 @@ bottom: number;

viewportHeight: number;
viewportLength: number;
viewAreaMode: boolean;

@@ -94,2 +61,3 @@ viewablePercentThreshold: number;

itemLength,
viewportLength,
viewportHeight,

@@ -100,3 +68,3 @@ viewAreaMode,

if (
_isEntirelyVisible({
isEntirelyVisible({
top,

@@ -109,3 +77,3 @@ bottom,

} else {
const pixels = _getPixelsVisible({
const pixels = getPixelsVisible({
top,

@@ -115,4 +83,8 @@ bottom,

});
const percent =
100 * (viewAreaMode ? pixels / viewportHeight : pixels / itemLength);
100 * (viewAreaMode ? pixels / viewportLength : pixels / itemLength);
// viewablePercentThreshold is 0 on default, so item is viewable even if 1 pixel
// in viewport.
return viewablePercentThreshold

@@ -123,1 +95,50 @@ ? percent >= viewablePercentThreshold

}
/**
*
* @param options
* - viewAreaMode false as default, which means it compares with self length.
* if value is true, then compare with viewport length.
*
* @returns
*/
export function isItemViewable(options: IsItemViewableOptions) {
const {
getItemOffset,
viewabilityItemMeta,
viewport: _viewport,
viewAreaMode = false,
viewabilityScrollMetrics,
viewablePercentThreshold = 0,
} = options;
const { offset: scrollOffset, visibleLength: viewportLength } =
viewabilityScrollMetrics;
if (!viewabilityItemMeta) return false;
const itemOffset =
typeof getItemOffset === 'function'
? getItemOffset(viewabilityItemMeta as ViewabilityItemMeta)
: viewabilityItemMeta instanceof ViewabilityItemMeta
? viewabilityItemMeta.getItemOffset()
: viewabilityItemMeta.offset;
const itemLength =
viewabilityItemMeta instanceof ViewabilityItemMeta
? viewabilityItemMeta.getItemLength()
: viewabilityItemMeta.length;
const viewport = Math.max(_viewport, 0);
const top = itemOffset - scrollOffset + viewport * viewportLength;
const bottom = top + itemLength;
const value = _isViewable({
top,
bottom,
itemLength,
viewAreaMode,
viewportLength,
viewablePercentThreshold,
viewportHeight: (2 * viewport + 1) * viewportLength,
});
return value;
}
import resolveChanged from '@x-oasis/resolve-changed';
import capitalize from '@x-oasis/capitalize';
import SelectValue, {
selectHorizontalValue,
selectVerticalValue,
} from '@x-oasis/select-value';
import BaseDimensions from '../BaseDimensions';
import ItemMeta from '../ItemMeta';
import { removeItemsKeyword } from '../common';
import ViewabilityItemMeta from './ViewabilityItemMeta';
import {
ScrollMetrics,
ViewabilityScrollMetrics,
ViewAreaModeConfig,
ViewabilityConfig,
ViewabilityConfigTuple,
OnViewableItemsChanged,
NormalizedViewablityConfig,
ViewabilityConfigCallbackPair,
VisiblePercentModeConfig,

@@ -21,3 +16,3 @@ } from '../types';

const createIntervalTreeItemChangedToken = (opts: {
helper: ItemMeta;
helper: ViewabilityItemMeta;
falsy?: boolean;

@@ -27,5 +22,6 @@ propsKey: string;

const { helper, falsy, propsKey } = opts;
const helperMeta = helper?.getMetaOnViewableItemsChanged
? helper.getMetaOnViewableItemsChanged() || {}
: {};
const helperMeta = {};
// const helperMeta = helper?.getMetaOnViewableItemsChanged
// ? helper.getMetaOnViewableItemsChanged() || {}
// : {};
return {

@@ -40,7 +36,7 @@ helper,

const createBasicItemChangedToken = (opts: {
helper: ItemMeta;
helper: ViewabilityItemMeta;
falsy?: boolean;
propsKey: string;
}): {
helper: ItemMeta;
helper: ViewabilityItemMeta;
key: string;

@@ -57,44 +53,86 @@ } => {

const createChangedToken = (opts: {
helper: ItemMeta;
falsy?: boolean;
propsKey: string;
helper: ViewabilityItemMeta;
isViewable: boolean;
// falsy?: boolean;
// propsKey: string;
isListItem?: boolean;
}) => {
const { isListItem = false, ...rest } = opts;
if (isListItem) return createIntervalTreeItemChangedToken(rest);
return createBasicItemChangedToken(rest);
const { helper, isViewable } = opts;
return {
item: null,
key: helper.getKey(),
isViewable,
// TODO
index: null,
};
// const { isListItem = false, ...rest } = opts;
// if (isListItem) return createIntervalTreeItemChangedToken(rest);
// return createBasicItemChangedToken(rest);
};
class ViewablityHelper {
readonly selectValue: SelectValue;
readonly tuple: ViewabilityConfigTuple;
readonly isListItem: boolean;
readonly horizontal: boolean;
private _configName: string;
private _changed: Array<ViewabilityItemMeta>;
private _config: NormalizedViewablityConfig;
private _callback: OnViewableItemsChanged;
readonly _pair: ViewabilityConfigCallbackPair;
constructor(props: {
horizontal: boolean;
isListItem: boolean;
tuple: ViewabilityConfigTuple;
pair: ViewabilityConfigCallbackPair;
}) {
const { tuple, isListItem, horizontal } = props;
const { pair, isListItem } = props;
this.tuple = tuple;
this.horizontal = !!horizontal;
this.selectValue = horizontal ? selectHorizontalValue : selectVerticalValue;
this._pair = pair;
this._configName = pair.viewabilityConfig.name;
this.isListItem = isListItem;
this._config = this.normalizeTupleConfig(this._pair.viewabilityConfig);
this._callback = this._pair.onViewableItemsChanged;
}
onItemsMetaChange(
itemsMeta: Array<ItemMeta>,
configKey: string,
tupleConfig: ViewabilityConfig,
options: {
dimensions: BaseDimensions;
scrollMetrics: ScrollMetrics;
}
) {
const { scrollMetrics } = options;
/**
* Return state for rendering. It could be { isViewable: true, isImageViewable: false, ...}
*/
get defaultStateViewToken() {
return {
[this._configName]: false,
};
}
get configName() {
return this._configName;
}
get config() {
return this._config;
}
get callback() {
return this._callback;
}
/**
* View Token for `onItemsChanged` callback
*/
createChangedViewToken() {}
resolveChangedViewTokenCallbackInfo() {}
/**
*
* @param tupleConfig
* @returns
* - viewAreaMode: if true then it will compare with viewport
* - viewablePercentThreshold: It's a merged value
*/
normalizeTupleConfig(tupleConfig: ViewabilityConfig): {
viewport: number;
exclusive: boolean;
viewAreaMode: boolean;
viewablePercentThreshold: number;
} {
const viewport = tupleConfig.viewport || 0;
const exclusive = tupleConfig.exclusive;
const exclusive = !!tupleConfig.exclusive;
const viewAreaCoveragePercentThreshold =

@@ -105,14 +143,36 @@ (tupleConfig as ViewAreaModeConfig).viewAreaCoveragePercentThreshold || 0;

0;
return {
...tupleConfig,
viewport,
exclusive,
viewAreaMode: !!viewAreaCoveragePercentThreshold,
viewablePercentThreshold:
viewAreaCoveragePercentThreshold || itemVisiblePercentThreshold,
};
}
let nextData = itemsMeta;
checkItemViewability(
viewabilityItemMeta: ViewabilityItemMeta,
scrollMetrics: ViewabilityScrollMetrics,
getItemOffset?: (itemMeta: ViewabilityItemMeta) => number
) {
const { viewport, viewAreaMode, viewablePercentThreshold } = this._config;
return isItemViewable({
viewport,
getItemOffset,
viewAreaMode,
viewabilityItemMeta,
viewablePercentThreshold,
viewabilityScrollMetrics: scrollMetrics,
});
}
nextData = nextData.filter((itemMeta) =>
isItemViewable({
itemMeta,
viewport,
scrollMetrics,
viewAreaMode: !!viewAreaCoveragePercentThreshold,
viewablePercentThreshold:
viewAreaCoveragePercentThreshold || itemVisiblePercentThreshold,
})
resolveViewableItems(
itemsMeta: Array<ViewabilityItemMeta>,
scrollMetrics: ViewabilityScrollMetrics
) {
const { exclusive } = this._config;
let nextData = itemsMeta.filter((itemMeta) =>
this.checkItemViewability(itemMeta, scrollMetrics)
);

@@ -122,38 +182,89 @@

const { changed, callbackMap } = this.tuple;
const callbackKey = `on${capitalize(configKey)}Changed`;
const callback = callbackMap[callbackKey];
const propsKey = `is${capitalize(
removeItemsKeyword(configKey) || 'Viewable'
)}`;
return nextData;
}
const oldItems = changed[configKey] || [];
const newItems = nextData || [];
const { removed, added } = resolveChanged(oldItems, newItems);
// onUpdateTupleConfig(
// configKey: string,
// tupleConfig: ViewabilityConfig,
// options: {
// dimensions: BaseDimensions;
// scrollMetrics: ScrollMetrics;
// }
// ) {
// const { scrollMetrics, dimensions } = options;
// const {
// offset: scrollOffset,
// contentLength,
// visibleLength: viewportLength,
// } = scrollMetrics;
// const length = dimensions.getContainerOffset();
// let nextData = [] as Array<ViewabilityItemMeta>;
// // 如果是一个List的话,那么它是基于container offset来算的
// const startOffset = this.isListItem ? length : 0;
// // const config = this.tuple.configMap[configKey];
// const viewport = tupleConfig.viewport || 0;
// const minOffset = Math.max(
// 0,
// scrollOffset - startOffset - viewportLength * viewport
// );
// const maxOffset = Math.min(
// scrollOffset - startOffset + viewportLength * (viewport + 1),
// contentLength - startOffset
// );
// nextData = dimensions.computeIndexRangeMeta(minOffset, maxOffset);
// nextData = this.resolveViewableItems(
// nextData,
// configKey,
// tupleConfig,
// options
// );
// return nextData;
// }
onUpdateItemsMeta(
itemsMeta: Array<ViewabilityItemMeta>,
scrollMetrics: ScrollMetrics
) {
const nextViewableItems = this.resolveViewableItems(
itemsMeta,
scrollMetrics
);
this.mergeState(nextViewableItems);
this.performViewableItemsChangedCallback(nextViewableItems);
}
performViewableItemsChangedCallback(
nextViewableItems: Array<ViewabilityItemMeta> = []
) {
// 触发changed items callback;
if (typeof callback === 'function') {
const addedTokens = added.map((itemMeta) =>
createChangedToken({
helper: itemMeta,
falsy: true,
propsKey,
isListItem: this.isListItem,
})
if (typeof this._callback === 'function') {
const { removed, added } = resolveChanged(
this._changed,
nextViewableItems
);
const removedTokens = removed.map((itemMeta) =>
createChangedToken({
helper: itemMeta,
falsy: false,
propsKey,
isListItem: this.isListItem,
})
const [addedTokens, removedTokens] = [added, removed].map(
(entry, entryIndex) =>
entry.map((itemMeta) =>
createChangedToken({
helper: itemMeta,
isViewable: !entryIndex,
// falsy: !entryIndex,
// propsKey: 'isViewable',
isListItem: this.isListItem,
})
)
);
callback({
[configKey]: newItems.map((helper) =>
this._callback({
viewableItems: nextViewableItems.map((helper) =>
createChangedToken({
helper,
falsy: true,
propsKey,
isViewable: true,
// falsy: true,
// propsKey: 'isViewable',
isListItem: this.isListItem,

@@ -165,107 +276,34 @@ })

}
return nextData;
this._changed = nextViewableItems;
}
onUpdateTupleConfig(
configKey: string,
tupleConfig: ViewabilityConfig,
options: {
dimensions: BaseDimensions;
scrollMetrics: ScrollMetrics;
}
) {
const { scrollMetrics, dimensions } = options;
const {
offset: scrollOffset,
contentLength,
visibleLength: viewportLength,
} = scrollMetrics;
const length = dimensions.getContainerOffset();
// onUpdateMetrics(options: {
// dimensions: BaseDimensions;
// scrollMetrics: ScrollMetrics;
// }) {
// const nextChanged = Object.entries(this.tuple.configMap).reduce<{
// [key: string]: Array<ViewabilityItemMeta>;
// }>((result, current) => {
// const [configKey, tupleConfig] = current;
// const nextData = this.onUpdateTupleConfig(
// configKey,
// tupleConfig,
// options
// );
// result[configKey] = nextData.slice();
// return result;
// }, {});
let nextData = [] as Array<ItemMeta>;
// this.mergeState(nextChanged);
// 如果是一个List的话,那么它是基于container offset来算的
const startOffset = this.isListItem ? length : 0;
// const config = this.tuple.configMap[configKey];
const viewport = tupleConfig.viewport || 0;
const minOffset = Math.max(
0,
scrollOffset - startOffset - viewportLength * viewport
);
const maxOffset = Math.min(
scrollOffset - startOffset + viewportLength * (viewport + 1),
contentLength - startOffset
);
// // tuple在处理都结束以后才进行更新
// Object.keys(this.tuple.configMap).forEach((configKey) => {
// const newItems = nextChanged[configKey];
// this.tuple.changed[configKey] = newItems;
// });
// }
nextData = dimensions.computeIndexRangeMeta(minOffset, maxOffset);
nextData = this.onItemsMetaChange(
nextData,
configKey,
tupleConfig,
options
);
return nextData;
}
onUpdateItemsMeta(
itemsMeta: Array<ItemMeta>,
options: {
dimensions: BaseDimensions;
scrollMetrics: ScrollMetrics;
}
) {
const nextChanged = Object.entries(this.tuple.configMap).reduce<{
[key: string]: Array<ItemMeta>;
}>((result, current) => {
const [configKey, tupleConfig] = current;
const nextData = this.onItemsMetaChange(
itemsMeta,
configKey,
tupleConfig,
options
);
result[configKey] = nextData.slice();
return result;
}, {});
this.mergeState(nextChanged);
// tuple在处理都结束以后才进行更新
Object.keys(this.tuple.configMap).forEach((configKey) => {
const newItems = nextChanged[configKey];
this.tuple.changed[configKey] = newItems;
});
}
onUpdateMetrics(options: {
dimensions: BaseDimensions;
scrollMetrics: ScrollMetrics;
}) {
const nextChanged = Object.entries(this.tuple.configMap).reduce<{
[key: string]: Array<ItemMeta>;
}>((result, current) => {
const [configKey, tupleConfig] = current;
const nextData = this.onUpdateTupleConfig(
configKey,
tupleConfig,
options
);
result[configKey] = nextData.slice();
return result;
}, {});
this.mergeState(nextChanged);
// tuple在处理都结束以后才进行更新
Object.keys(this.tuple.configMap).forEach((configKey) => {
const newItems = nextChanged[configKey];
this.tuple.changed[configKey] = newItems;
});
}
mergeState(nextChanged: { [key: string]: Array<ItemMeta> }) {
mergeState(nextViewableItems: Array<ViewabilityItemMeta>) {
const itemMetaStateMap = new Map<
ItemMeta,
ViewabilityItemMeta,
{

@@ -276,37 +314,18 @@ [key: string]: boolean;

const configKeys = Object.keys(this.tuple.configMap);
const defaultToken = configKeys.reduce((acc, cur) => {
const nextCur = removeItemsKeyword(cur);
acc[nextCur] = false;
return acc;
}, {});
configKeys.forEach((configKey) => {
const { changed } = this.tuple;
const items = changed[configKey] || [];
items.forEach((itemMeta) => {
if (!itemMetaStateMap.has(itemMeta)) {
itemMetaStateMap.set(itemMeta, { ...defaultToken });
}
});
this._changed.forEach((itemMeta) => {
if (!itemMetaStateMap.has(itemMeta)) {
itemMetaStateMap.set(itemMeta, { ...this.defaultStateViewToken });
}
});
configKeys.forEach((configKey) => {
const items = nextChanged[configKey];
items.forEach((itemMeta) => {
if (!itemMetaStateMap.has(itemMeta)) {
itemMetaStateMap.set(itemMeta, { ...defaultToken });
}
const nextConfigKey = removeItemsKeyword(configKey);
itemMetaStateMap.set(itemMeta, {
...itemMetaStateMap.get(itemMeta),
[nextConfigKey]: true,
});
nextViewableItems.forEach((itemMeta) => {
itemMetaStateMap.set(itemMeta, {
...(itemMetaStateMap.get(itemMeta) || {}),
[this._configName]: true,
});
});
for (const [itemMeta, state] of itemMetaStateMap) {
itemMeta.setItemMetaState(state);
}
// for (const [itemMeta, state] of itemMetaStateMap) {
// itemMeta.setItemMetaState(state);
// }
}

@@ -313,0 +332,0 @@ }

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

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