@infinite-list/data-model
Advanced tools
Comparing version 2.0.0 to 2.1.0
{ | ||
"name": "@infinite-list/data-model", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"files": [ | ||
@@ -5,0 +5,0 @@ "dist", |
@@ -12,1 +12,34 @@ # data-model | ||
Run `nx test data-model` to execute the unit tests via [Vitest](https://vitest.dev/). | ||
```bash | ||
# watch test running | ||
$ nx test data-model --watch | ||
``` | ||
## Domain | ||
### Masonry | ||
### Section | ||
- https://reactnative.dev/docs/sectionlist | ||
### Table | ||
- xxxx | ||
### Impression | ||
- xxx | ||
### Dnd | ||
- xxx | ||
### Carousel | ||
### SnapList | ||
### ChatList |
@@ -15,2 +15,3 @@ import BaseLayout from './BaseLayout'; | ||
import * as log from './utils/logger'; | ||
import KeyIndexManager from './utils/KeyIndexManager'; | ||
@@ -20,4 +21,2 @@ abstract class BaseDimensions< | ||
> extends BaseLayout { | ||
_keyToIndexMap: Map<string, number> = new Map(); | ||
_indexKeys: Array<string> = []; | ||
_keyToMetaMap: Map<string, ItemMeta<ItemT>> = new Map(); | ||
@@ -30,2 +29,3 @@ _configTuple: ViewabilityConfigTuples; | ||
private _intervalTree: PrefixIntervalTree; | ||
public keyIndexManager: KeyIndexManager; | ||
@@ -43,2 +43,3 @@ constructor(props: BaseDimensionsProps) { | ||
this.keyIndexManager = new KeyIndexManager(); | ||
this._onUpdateIntervalTree = onUpdateIntervalTree; | ||
@@ -64,3 +65,4 @@ this._onUpdateItemLayout = onUpdateItemLayout; | ||
getKeyIndex(key: string) { | ||
const index = this._keyToIndexMap.get(key); | ||
// const index = this._keyToIndexMap.get(key); | ||
const index = this.keyIndexManager.getKeyIndex(key); | ||
if (typeof index === 'number') return index; | ||
@@ -71,3 +73,4 @@ return -1; | ||
getIndexKey(index: number) { | ||
return this._indexKeys[index]; | ||
return this.keyIndexManager.getIndexKey(index); | ||
// return this._indexKeys[index]; | ||
} | ||
@@ -82,3 +85,3 @@ | ||
this.getContainerOffset(), | ||
this.intervalTree.getHeap()[1], | ||
this._intervalTree.getHeap()[1], | ||
this._intervalTree.sumUntil(index) | ||
@@ -90,3 +93,3 @@ ); | ||
(index >= this._intervalTree.getMaxUsefulLength() | ||
? this.intervalTree.getHeap()[1] | ||
? this._intervalTree.getHeap()[1] | ||
: this._intervalTree.sumUntil(index)) | ||
@@ -155,3 +158,3 @@ ); | ||
) { | ||
const oldLen = this._indexKeys.length; | ||
const oldLen = this.keyIndexManager.getIndexKeysLength(); | ||
const newLen = keys.length; | ||
@@ -163,3 +166,3 @@ | ||
for (let index = 0; index < oldLen; index++) { | ||
const currentKey = this._indexKeys[index]; | ||
const currentKey = this.keyIndexManager.getIndexKey(index); | ||
const nextKey = keys[index]; | ||
@@ -166,0 +169,0 @@ |
@@ -12,3 +12,3 @@ import { | ||
class BaseLayout extends BaseContainer { | ||
abstract class BaseLayout extends BaseContainer { | ||
readonly _windowSize: number; | ||
@@ -142,2 +142,3 @@ readonly _maxToRenderPerBatch: number; | ||
const containerOffset = this.getContainerOffset(); | ||
if (exclusive) return { minOffset, maxOffset }; | ||
@@ -144,0 +145,0 @@ if (containerOffset > maxOffset) { |
@@ -14,4 +14,6 @@ export const DEFAULT_LAYOUT = { | ||
export const RECYCLER_THRESHOLD_INDEX_VALUE = 0; | ||
export const RECYCLER_RESERVED_BUFFER_PER_BATCH = 4; | ||
export const RECYCLER_BUFFER_SIZE = 12; | ||
export const RECYCLER_BUFFER_SIZE = 40; | ||
export const RECYCLER_RESERVED_BUFFER_PER_BATCH = 20; | ||
export const RECYCLER_RESERVED_BUFFER_SIZE_RATIO = 1.5; | ||
@@ -18,0 +20,0 @@ |
export { default as ItemMeta } from './ItemMeta'; | ||
export { default as DimensionExperimental } from './Dimension'; | ||
export { default as Dimension } from './Dimension'; | ||
export { default as ListDimensions } from './ListDimensions'; | ||
export { default as ItemsDimensionsExperimental } from './ItemsDimensions'; | ||
export { default as ListGroupDimensionsExperimental } from './ListGroupDimensions'; | ||
export { default as ListGroupDimensions } from './ListGroupDimensions'; | ||
export { default as PseudoListDimensions } from './PseudoListDimensions'; | ||
export { default as ItemMetaExperimental } from './ItemMeta'; | ||
export { default as ItemsDimensions } from './ItemsDimensions'; | ||
export { default as MasonryDimension } from './masonry/MasonryDimensions'; | ||
export { default as ListSpyUtils } from './utils/ListSpyUtils'; | ||
@@ -10,0 +12,0 @@ export * from './exportedUtils'; |
@@ -185,4 +185,4 @@ import BaseDimensions from './BaseDimensions'; | ||
getFinalItemLength() { | ||
if (this._useSeparatorLength) { | ||
getFinalItemLength(useSeparatorLength = false) { | ||
if (useSeparatorLength || this._useSeparatorLength) { | ||
return this.getItemLength() + this.getSeparatorLength(); | ||
@@ -189,0 +189,0 @@ } |
@@ -98,3 +98,6 @@ import Batchinator from '@x-oasis/batchinator'; | ||
dispatchMetrics(scrollMetrics: ScrollMetrics) { | ||
dispatchMetrics( | ||
scrollMetrics: ScrollMetrics | undefined = this._scrollMetrics | ||
) { | ||
if (!scrollMetrics) return; | ||
const { offset: scrollOffset, visibleLength } = scrollMetrics; | ||
@@ -101,0 +104,0 @@ const minOffset = scrollOffset; |
@@ -22,2 +22,3 @@ import PrefixIntervalTree from '@x-oasis/prefix-interval-tree'; | ||
GenericItemT, | ||
OnListDimensionsModelDataChanged, | ||
} from './types'; | ||
@@ -31,2 +32,3 @@ import * as log from './utils/logger'; | ||
private _initialData: Array<ItemT> = []; | ||
private _onDataChanged?: OnListDimensionsModelDataChanged<ItemT>; | ||
@@ -65,2 +67,3 @@ private _keyExtractor: KeyExtractor<ItemT>; | ||
manuallyApplyInitialData = false, | ||
onListDimensionsModelDataChanged, | ||
itemApproximateLength = DEFAULT_ITEM_APPROXIMATE_LENGTH, | ||
@@ -75,2 +78,3 @@ } = props; | ||
this._isFixedLength = isFixedLength; | ||
this._onDataChanged = onListDimensionsModelDataChanged; | ||
@@ -211,3 +215,3 @@ // `_approximateMode` is enabled on default | ||
getItemKey(item: ItemT, index: number) { | ||
getItemKey(item: ItemT, index?: number) { | ||
const cachedKey = this._itemToKeyMap.get(item); | ||
@@ -309,7 +313,7 @@ if (cachedKey) return cachedKey; | ||
hasKey(key: string) { | ||
return this._indexKeys.indexOf(key) !== -1; | ||
return this.keyIndexManager.hasKey(key); | ||
} | ||
performKeyOperationGuard(key: string) { | ||
if (this._indexKeys.indexOf(key) !== -1) return true; | ||
if (this.keyIndexManager.hasKey(key)) return true; | ||
return false; | ||
@@ -326,3 +330,5 @@ } | ||
// 如果没有值,这个时候要触发一次触底 | ||
// If data is empty, then trigger onEndReached one time.. | ||
// TODO: maybe there is a bug... if the list the beneath viewport, the trigger | ||
// may not required... | ||
if (!data.length && this.initialNumToRender) { | ||
@@ -337,2 +343,26 @@ this._container.onEndReachedHelper.attemptToHandleOnEndReachedBatchinator.schedule(); | ||
/** | ||
* | ||
* @param dataChangedType | ||
* @param data | ||
*/ | ||
handleDataChange(dataChangedType: KeysChangedType, data: ItemT[]) { | ||
switch (dataChangedType) { | ||
case KeysChangedType.Equal: | ||
break; | ||
case KeysChangedType.Append: | ||
this.updateTheLastItemIntervalValue(); | ||
this.append(data); | ||
break; | ||
case KeysChangedType.Initial: | ||
this.append(data); | ||
break; | ||
case KeysChangedType.Add: | ||
case KeysChangedType.Remove: | ||
case KeysChangedType.Reorder: | ||
this.shuffle(data); | ||
break; | ||
} | ||
} | ||
_setData(_data: Array<ItemT>) { | ||
@@ -369,24 +399,21 @@ if (_data === this._data) return KeysChangedType.Equal; | ||
switch (dataChangedType) { | ||
case KeysChangedType.Equal: | ||
break; | ||
case KeysChangedType.Append: | ||
this.updateTheLastItemIntervalValue(); | ||
this.append(data); | ||
break; | ||
case KeysChangedType.Initial: | ||
this.append(data); | ||
break; | ||
case KeysChangedType.Add: | ||
case KeysChangedType.Remove: | ||
case KeysChangedType.Reorder: | ||
this.shuffle(data); | ||
break; | ||
} | ||
this.handleDataChange(dataChangedType, data); | ||
// _onDataChanged should be placed after handleDataChange. | ||
// Because the itemMeta may required... | ||
const oldData = this._data.slice(); | ||
this._data = data; | ||
this.keyIndexManager.setKeyToIndexMap(keyToIndexMap); | ||
this.keyIndexManager.setIndexKeys(keyToIndexArray); | ||
this._itemToKeyMap = itemToKeyMap; | ||
this._keyToIndexMap = keyToIndexMap; | ||
this._indexKeys = keyToIndexArray; | ||
this._itemToKeyMap = itemToKeyMap; | ||
this._onDataChanged?.({ | ||
dataModel: this, | ||
data, | ||
oldData, | ||
dataChangedType, | ||
}); | ||
return dataChangedType; | ||
@@ -420,5 +447,2 @@ } | ||
getIndexInfo(key: string): IndexInfo<ItemT> | null { | ||
const info = {} as IndexInfo; | ||
info.index = this._indexKeys.indexOf(key); | ||
return this._container.getFinalKeyIndexInfo(key, this.id); | ||
@@ -483,3 +507,3 @@ } | ||
append(data: Array<ItemT>) { | ||
const baseIndex = this._indexKeys.length; | ||
const baseIndex = this.keyIndexManager.getIndexKeysLength(); | ||
this.pump(data, baseIndex, this._keyToMetaMap, this.intervalTree); | ||
@@ -494,6 +518,4 @@ | ||
this.intervalTree = this.createIntervalTree(); | ||
// const itemIntervalTree = this.createIntervalTree(); | ||
const keyToMetaMap = new Map(); | ||
this.pump(data, 0, keyToMetaMap, this.intervalTree); | ||
// this.replaceIntervalTree(itemIntervalTree); | ||
this._keyToMetaMap = keyToMetaMap; | ||
@@ -511,3 +533,4 @@ const nextLength = this.intervalTree.getHeap()[1]; | ||
* @param info | ||
* @param updateIntervalTree target IntervalTree | ||
* @param updateIntervalTree target IntervalTree, now this property is used in | ||
* MasonryList | ||
* @returns boolean value, true for updating intervalTree successfully. | ||
@@ -514,0 +537,0 @@ */ |
@@ -38,3 +38,4 @@ import BaseDimensions from './BaseDimensions'; | ||
} | ||
this._indexKeys = keys; | ||
this.keyIndexManager.setIndexKeys(keys); | ||
// this._indexKeys = keys; | ||
} | ||
@@ -44,3 +45,4 @@ | ||
const info = {} as IndexInfo; | ||
info.index = this._indexKeys.indexOf(key); | ||
// info.index = this._indexKeys.indexOf(key); | ||
info.index = this.keyIndexManager.findIndex(key); | ||
return info; | ||
@@ -84,8 +86,11 @@ } | ||
append(keys: Array<string>) { | ||
const baseIndex = this._indexKeys.length; | ||
// const baseIndex = this._indexKeys.length; | ||
const baseIndex = this.keyIndexManager.getIndexKeysLength(); | ||
this.pump( | ||
keys, | ||
baseIndex, | ||
this._keyToIndexMap, | ||
this._indexKeys, | ||
this.keyIndexManager.keyToIndexMap, | ||
this.keyIndexManager.indexKeys, | ||
// this._keyToIndexMap, | ||
// this._indexKeys, | ||
this._keyToMetaMap, | ||
@@ -109,4 +114,6 @@ this.intervalTree | ||
); | ||
this._indexKeys = keyToIndexArray; | ||
this._keyToIndexMap = keyToIndexMap; | ||
this.keyIndexManager.setIndexKeys(keyToIndexArray); | ||
this.keyIndexManager.setKeyToIndexMap(keyToIndexMap); | ||
// this._indexKeys = keyToIndexArray; | ||
// this._keyToIndexMap = keyToIndexMap; | ||
this._keyToMetaMap = keyToMetaMap; | ||
@@ -117,3 +124,4 @@ this.intervalTree = itemIntervalTree; | ||
override resolveKeysChangedType(keys: Array<string>) { | ||
const oldLen = this._indexKeys.length; | ||
// const oldLen = this._indexKeys.length; | ||
const oldLen = this.keyIndexManager.getIndexKeysLength(); | ||
const newLen = keys.length; | ||
@@ -123,3 +131,4 @@ | ||
for (let index = 0; index < oldLen; index++) { | ||
const currentKey = this._indexKeys[index]; | ||
// const currentKey = this._indexKeys[index]; | ||
const currentKey = this.keyIndexManager.getIndexKey(index); | ||
const nextKey = keys[index]; | ||
@@ -126,0 +135,0 @@ if (currentKey !== nextKey) { |
@@ -11,2 +11,3 @@ import { ActionPayload, Ctx, ReducerResult } from '../types'; | ||
const { contentLength, offset, visibleLength = 0 } = scrollMetrics; | ||
const bufferSize = dimension.getBufferSize(); | ||
@@ -16,3 +17,8 @@ const { minOffset, maxOffset } = dimension.resolveOffsetRange( | ||
// should less than content length | ||
Math.min(offset + visibleLength * (bufferSize + 1), contentLength) | ||
// for buffered max and min, only if contentLength is greater than 0, | ||
// it should be in consider | ||
contentLength | ||
? Math.min(offset + visibleLength * (bufferSize + 1), contentLength) | ||
: offset + visibleLength * (bufferSize + 1) | ||
); | ||
@@ -30,3 +36,3 @@ | ||
minOffset, | ||
Math.min(maxOffset, contentLength) | ||
contentLength ? Math.min(maxOffset, contentLength) : maxOffset | ||
); | ||
@@ -33,0 +39,0 @@ |
@@ -20,2 +20,3 @@ import Batchinator from '@x-oasis/batchinator'; | ||
IndexToOffsetMap, | ||
ListStateResult, | ||
} from '../types'; | ||
@@ -40,3 +41,3 @@ import ListSpyUtils from '../utils/ListSpyUtils'; | ||
> extends BaseLayout { | ||
private _dispatchMetricsBatchinator: Batchinator; | ||
private _setMetricsBatchinator: Batchinator; | ||
@@ -117,4 +118,4 @@ private _onEndReachedThreshold: number; | ||
this._dispatchMetricsBatchinator = new Batchinator( | ||
this.dispatchMetrics.bind(this), | ||
this._setMetricsBatchinator = new Batchinator( | ||
this.setMetrics.bind(this), | ||
dispatchMetricsThreshold | ||
@@ -181,11 +182,11 @@ ); | ||
abstract getData(): any[]; | ||
abstract getData(): ItemT[]; | ||
abstract getDataLength(): number; | ||
abstract getTotalLength(): number | string; | ||
abstract getReflowItemsLength(): number; | ||
abstract getFinalItemKey(item: any): string; | ||
abstract getFinalItemKey(item: ItemT): string; | ||
abstract getFinalIndexItemMeta( | ||
index: number | ||
): ItemMeta<ItemT> | null | undefined; | ||
abstract getFinalItemMeta(item: any): ItemMeta<ItemT> | null | undefined; | ||
abstract getFinalItemMeta(item: ItemT): ItemMeta<ItemT> | null | undefined; | ||
abstract getFinalIndexItemLength(index: number): number; | ||
@@ -244,3 +245,3 @@ abstract getFinalIndexKeyOffset(index: number, exclusive?: boolean): number; | ||
resetViewableItems() { | ||
if (this._scrollMetrics) this.dispatchMetrics(this._scrollMetrics); | ||
if (this._scrollMetrics) this.setMetrics(this._scrollMetrics); | ||
} | ||
@@ -265,3 +266,3 @@ | ||
dispatchStoreMetrics(scrollMetrics: ScrollMetrics) { | ||
setStoreMetrics(scrollMetrics: ScrollMetrics) { | ||
const state = this._store.dispatchMetrics({ | ||
@@ -273,13 +274,26 @@ // @ts-ignore | ||
if (isEmpty(state)) return state; | ||
this._stateHub.setState({ ...state }); | ||
if (!isEmpty(state)) { | ||
this._stateHub.setState({ ...state }); | ||
} | ||
} | ||
return state; | ||
dispatchMetrics( | ||
scrollMetrics: ScrollMetrics | undefined = this._scrollMetrics | ||
): [ListStateResult<ItemT>, ListStateResult<ItemT>] { | ||
if (!scrollMetrics) | ||
return [this._stateHub.getStateResult(), this._stateHub.getStateResult()]; | ||
const state = this._store.dispatchMetrics({ | ||
// @ts-ignore | ||
dimension: this, | ||
scrollMetrics, | ||
}); | ||
return this._stateHub.dispatchState(state); | ||
} | ||
dispatchMetrics(scrollMetrics: ScrollMetrics | undefined) { | ||
setMetrics(scrollMetrics: ScrollMetrics | undefined = this._scrollMetrics) { | ||
if (!scrollMetrics) return; | ||
const state = this.dispatchStoreMetrics(scrollMetrics); | ||
this.setStoreMetrics(scrollMetrics); | ||
const { isEndReached, distanceFromEnd } = state; | ||
const { isEndReached, distanceFromEnd } = this._store.getState(); | ||
@@ -300,7 +314,7 @@ this.onEndReachedHelper?.performEndReached({ | ||
onEnableDispatchScrollMetrics() { | ||
this.dispatchMetrics(this._scrollMetrics); | ||
this.setMetrics(this._scrollMetrics); | ||
} | ||
stillnessHandler() { | ||
this.dispatchMetrics(this._scrollMetrics); | ||
this.setMetrics(this._scrollMetrics); | ||
} | ||
@@ -339,5 +353,5 @@ | ||
if (flush) { | ||
this._dispatchMetricsBatchinator.flush(scrollMetrics); | ||
this._setMetricsBatchinator.flush(scrollMetrics); | ||
} else { | ||
this._dispatchMetricsBatchinator.schedule(scrollMetrics); | ||
this._setMetricsBatchinator.schedule(scrollMetrics); | ||
} | ||
@@ -344,0 +358,0 @@ |
import Recycler, { OnRecyclerProcess } from '@x-oasis/recycler'; | ||
import memoizeOne from 'memoize-one'; | ||
import { buildStateTokenIndexKey, DEFAULT_RECYCLER_TYPE } from '../common'; | ||
import { | ||
buildStateTokenIndexKey, | ||
DEFAULT_RECYCLER_TYPE, | ||
RECYCLER_BUFFER_SIZE, | ||
RECYCLER_RESERVED_BUFFER_PER_BATCH, | ||
} from '../common'; | ||
import { | ||
ListState, | ||
@@ -12,2 +17,3 @@ RecycleStateResult, | ||
RecycleStateImplProps, | ||
ListGroupIndexInfo, | ||
} from '../types'; | ||
@@ -18,2 +24,3 @@ import ItemMeta from '../ItemMeta'; | ||
import * as log from '../utils/logger'; | ||
import defaultValue from '@x-oasis/default-value'; | ||
/** | ||
@@ -48,4 +55,4 @@ * item should be first class data model; item's value reference change will | ||
recyclerTypes = [DEFAULT_RECYCLER_TYPE], | ||
recyclerBufferSize, | ||
recyclerReservedBufferPerBatch, | ||
recyclerBufferSize = RECYCLER_BUFFER_SIZE, | ||
recyclerReservedBufferPerBatch = RECYCLER_RESERVED_BUFFER_PER_BATCH, | ||
@@ -62,2 +69,3 @@ onRecyclerProcess, | ||
recyclerBufferSize, | ||
recyclerReservedBufferPerBatch, | ||
/** | ||
@@ -67,7 +75,18 @@ * set recycle start item | ||
thresholdIndexValue: this.listContainer.initialNumToRender, | ||
recyclerReservedBufferPerBatch, | ||
metaExtractor: (index) => this.listContainer.getFinalIndexItemMeta(index), | ||
indexExtractor: (meta) => { | ||
metaExtractor: (index) => { | ||
// console.log('met ---- ', index, this.listContainer.getFinalIndexItemMeta(index)) | ||
return this.listContainer.getFinalIndexItemMeta(index); | ||
}, | ||
indexExtractor: (meta: ItemMeta<ItemT>) => { | ||
const indexInfo = meta.getIndexInfo(); | ||
return indexInfo?.indexInGroup || indexInfo.index; | ||
const index = | ||
(indexInfo as ListGroupIndexInfo<ItemT>)?.indexInGroup || | ||
indexInfo?.index; | ||
if (typeof index !== 'number') { | ||
console.error( | ||
'[RecycleStateImpl error]: index should has a valid number ' + | ||
'or will cause recycler not work correctly' | ||
); | ||
} | ||
return defaultValue(index, -1); | ||
}, | ||
@@ -79,2 +98,3 @@ getMetaType: (meta) => meta.recyclerType, | ||
}); | ||
// default recyclerTypes should be set immediately | ||
@@ -109,5 +129,3 @@ this.initializeDefaultRecycleBuffer(); | ||
applyStateResult(stateResult: RecycleStateResult<ItemT>) { | ||
const shouldStateUpdate = true; | ||
if (shouldStateUpdate && typeof this.stateListener === 'function') { | ||
if (typeof this.stateListener === 'function') { | ||
const { recycleState: _recycleState, spaceState } = stateResult; | ||
@@ -180,2 +198,15 @@ | ||
dispatchState( | ||
state: ListState, | ||
force = false | ||
): [RecycleStateResult<ItemT>, RecycleStateResult<ItemT>] { | ||
const oldStateResult = { ...this._stateResult }; | ||
const stateResult = force | ||
? this.resolveRecycleState(state) | ||
: this.memoizedResolveRecycleState(state); | ||
this._stateResult = stateResult; | ||
return [stateResult, oldStateResult]; | ||
} | ||
getStateResult() { | ||
@@ -182,0 +213,0 @@ return this._stateResult; |
@@ -203,2 +203,53 @@ // import memoizeOne from 'memoize-one'; | ||
export default class SpaceStateImpl {} | ||
import BaseState from './BaseState'; | ||
import { | ||
ListState, | ||
GenericItemT, | ||
StateListener, | ||
SpaceStateResult, | ||
} from '../types'; | ||
export default class SpaceStateImpl< | ||
ItemT extends GenericItemT = GenericItemT | ||
> extends BaseState<ItemT> { | ||
private _stateResult: SpaceStateResult<ItemT> = []; | ||
public stateListener?: StateListener<ItemT>; | ||
getStateResult() { | ||
return this._stateResult; | ||
} | ||
setState(state: ListState) { | ||
// @ts-ignore | ||
const stateResult = this.resolveSpaceState(state); | ||
this.applyStateResult(stateResult); | ||
} | ||
dispatchState( | ||
state: ListState | ||
): [SpaceStateResult<ItemT>, SpaceStateResult<ItemT>] { | ||
// return [] as any | ||
const oldStateResult = [...this._stateResult]; | ||
// @ts-ignore | ||
const stateResult = this.resolveSpaceState(state); | ||
return [stateResult, oldStateResult]; | ||
} | ||
// resolveSpaceState(state: ListState) { | ||
resolveSpaceState() { | ||
return [] as SpaceStateResult<ItemT>; | ||
} | ||
addStateListener(listener: StateListener<ItemT>) { | ||
if (typeof listener === 'function') this.stateListener = listener; | ||
return () => { | ||
if (typeof listener === 'function') this.stateListener = undefined; | ||
}; | ||
} | ||
applyStateResult(stateResult: SpaceStateResult<ItemT>) { | ||
if (typeof this.stateListener === 'function') { | ||
this.stateListener(stateResult, this._stateResult); | ||
} | ||
} | ||
} |
@@ -6,7 +6,9 @@ import { | ||
StateHubProps, | ||
ListStateResult, | ||
} from '../types'; | ||
import RecycleStateImpl from './RecycleStateImpl'; | ||
import SpaceStateImpl from './SpaceStateImpl'; | ||
class StateHub<ItemT extends GenericItemT = GenericItemT> { | ||
private _handler: RecycleStateImpl<ItemT>; | ||
private _handler: RecycleStateImpl<ItemT> | SpaceStateImpl<ItemT>; | ||
@@ -18,17 +20,27 @@ constructor(props: StateHubProps<ItemT>) { | ||
recyclerTypes, | ||
// recycleEnabled, | ||
onRecyclerProcess, | ||
recyclerBufferSize, | ||
recycleEnabled = true, | ||
recyclerReservedBufferPerBatch, | ||
} = props; | ||
this._handler = new RecycleStateImpl<ItemT>({ | ||
listContainer, | ||
recyclerTypes, | ||
onRecyclerProcess, | ||
recyclerBufferSize, | ||
recyclerReservedBufferPerBatch, | ||
}); | ||
this._handler = recycleEnabled | ||
? new RecycleStateImpl<ItemT>({ | ||
listContainer, | ||
recyclerTypes, | ||
onRecyclerProcess, | ||
recyclerBufferSize, | ||
recyclerReservedBufferPerBatch, | ||
}) | ||
: new SpaceStateImpl<ItemT>({ | ||
listContainer, | ||
}); | ||
} | ||
/** | ||
* | ||
* @param state | ||
* | ||
* return nothing | ||
*/ | ||
setState(state: ListState) { | ||
@@ -38,2 +50,19 @@ this._handler.setState(state); | ||
/** | ||
* | ||
* @param state | ||
* @returns StateResult | ||
* | ||
* comparing with setState, it should return StateResult after state hub handler | ||
*/ | ||
dispatchState( | ||
state: ListState | ||
): [ListStateResult<ItemT>, ListStateResult<ItemT>] { | ||
return this._handler.dispatchState(state); | ||
} | ||
getStateResult() { | ||
return this._handler.getStateResult(); | ||
} | ||
addStateListener(listener: StateListener<ItemT>) { | ||
@@ -44,3 +73,3 @@ this._handler.addStateListener(listener); | ||
addBuffer(type: string) { | ||
this._handler.addBuffer(type); | ||
(this._handler as RecycleStateImpl<ItemT>).addBuffer(type); | ||
} | ||
@@ -47,0 +76,0 @@ |
@@ -17,1 +17,2 @@ export * from './BaseContainer.types'; | ||
export * from './stateHub.types'; | ||
export * from './masonry.types'; |
@@ -65,3 +65,3 @@ import { ItemLayout } from './BaseLayout.types'; | ||
dimensions: ListDimensionsModel<ItemT>; | ||
index: number; | ||
index?: number; | ||
}; | ||
@@ -68,0 +68,0 @@ |
@@ -23,2 +23,6 @@ import ItemMeta from '../ItemMeta'; | ||
export type RecycleStateToken<ItemT extends GenericItemT = GenericItemT> = { | ||
/** | ||
* compare with `targetKey`, it's the key point of recycle compatibility. | ||
*/ | ||
key: string; | ||
targetKey: string; | ||
@@ -29,3 +33,3 @@ targetIndex: number; | ||
viewable: boolean; | ||
} & Omit<SpaceStateToken<ItemT>, 'isReserved' | 'position'>; | ||
} & Omit<SpaceStateToken<ItemT>, 'key' | 'isReserved' | 'position'>; | ||
@@ -32,0 +36,0 @@ export type SpaceStateResult<ItemT extends GenericItemT = GenericItemT> = Array< |
@@ -1,2 +0,2 @@ | ||
import { BaseDimensionsProps } from './BaseDimensions.types'; | ||
import { BaseDimensionsProps, KeysChangedType } from './BaseDimensions.types'; | ||
import { GenericItemT } from './generic.types'; | ||
@@ -6,2 +6,5 @@ import { ListBaseDimensionsProps } from './ListBaseDimensions.types'; | ||
import ListDimensions from '../ListDimensions'; | ||
import MasonryDimensions from '../masonry/MasonryDimensions'; | ||
import ListDimensionsModel from '../ListDimensionsModel'; | ||
import MasonryDimensionsModel from '../masonry/MasonryDimensionsModel'; | ||
@@ -16,4 +19,16 @@ export type GetItemSeparatorLength<ItemT> = ( | ||
) => { length: number; index: number }; | ||
export type KeyExtractor<ItemT> = (item: ItemT, index: number) => string; | ||
/** | ||
* TODO: `index` may not be | ||
*/ | ||
export type KeyExtractor<ItemT> = (item: ItemT, index?: number) => string; | ||
export type OnListDimensionsModelDataChanged< | ||
ItemT extends GenericItemT = GenericItemT | ||
> = (props: { | ||
dataModel: ListDimensionsModel<ItemT> | MasonryDimensionsModel<ItemT>; | ||
dataChangedType: KeysChangedType; | ||
data: ItemT[]; | ||
oldData: ItemT[]; | ||
}) => void; | ||
export interface ListDimensionsModelProps< | ||
@@ -25,2 +40,5 @@ ItemT extends GenericItemT = GenericItemT | ||
itemApproximateLength?: number; | ||
/** | ||
* only if in recycleEnabled mode, `useItemApproximateLength` is meaningful | ||
*/ | ||
useItemApproximateLength?: boolean; | ||
@@ -48,2 +66,4 @@ recyclerType?: string; | ||
recyclerTypes?: Array<string>; | ||
onListDimensionsModelDataChanged?: OnListDimensionsModelDataChanged<ItemT>; | ||
} | ||
@@ -53,2 +73,5 @@ | ||
ItemT extends GenericItemT = GenericItemT | ||
> = ListGroupDimensions<ItemT> | ListDimensions<ItemT>; | ||
> = | ||
| ListGroupDimensions<ItemT> | ||
| ListDimensions<ItemT> | ||
| MasonryDimensions<ItemT>; |
417056
83
13123
44