virtual-scroller 1.11.0 → 1.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/virtual-scroller-dom.js +1 -1
- package/bundle/virtual-scroller-dom.js.map +1 -1
- package/bundle/virtual-scroller-react.js +1 -1
- package/bundle/virtual-scroller-react.js.map +1 -1
- package/bundle/virtual-scroller.js +1 -1
- package/bundle/virtual-scroller.js.map +1 -1
- package/commonjs/DOM/ItemsContainer.js +22 -9
- package/commonjs/DOM/ItemsContainer.js.map +1 -1
- package/commonjs/ItemHeights.js +1 -1
- package/commonjs/ItemHeights.js.map +1 -1
- package/commonjs/VirtualScroller.layout.js +2 -2
- package/commonjs/VirtualScroller.layout.js.map +1 -1
- package/commonjs/VirtualScroller.state.js +1 -1
- package/commonjs/VirtualScroller.state.js.map +1 -1
- package/commonjs/react/VirtualScroller.js +48 -16
- package/commonjs/react/VirtualScroller.js.map +1 -1
- package/commonjs/react/useHandleItemIndexesChange.js +53 -0
- package/commonjs/react/useHandleItemIndexesChange.js.map +1 -0
- package/commonjs/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +21 -40
- package/commonjs/react/useHandleItemsPropertyChange.js.map +1 -0
- package/commonjs/react/useOnItemHeightChange.js +2 -2
- package/commonjs/react/useOnItemHeightChange.js.map +1 -1
- package/commonjs/react/useSetItemState.js +2 -2
- package/commonjs/react/useSetItemState.js.map +1 -1
- package/commonjs/react/useState.js +47 -69
- package/commonjs/react/useState.js.map +1 -1
- package/commonjs/react/useStyle.js +4 -4
- package/commonjs/react/useStyle.js.map +1 -1
- package/index.d.ts +1 -1
- package/modules/DOM/ItemsContainer.js +22 -9
- package/modules/DOM/ItemsContainer.js.map +1 -1
- package/modules/ItemHeights.js +1 -1
- package/modules/ItemHeights.js.map +1 -1
- package/modules/VirtualScroller.layout.js +2 -2
- package/modules/VirtualScroller.layout.js.map +1 -1
- package/modules/VirtualScroller.state.js +1 -1
- package/modules/VirtualScroller.state.js.map +1 -1
- package/modules/react/VirtualScroller.js +47 -17
- package/modules/react/VirtualScroller.js.map +1 -1
- package/modules/react/useHandleItemIndexesChange.js +45 -0
- package/modules/react/useHandleItemIndexesChange.js.map +1 -0
- package/modules/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +20 -39
- package/modules/react/useHandleItemsPropertyChange.js.map +1 -0
- package/modules/react/useOnItemHeightChange.js +2 -2
- package/modules/react/useOnItemHeightChange.js.map +1 -1
- package/modules/react/useSetItemState.js +2 -2
- package/modules/react/useSetItemState.js.map +1 -1
- package/modules/react/useState.js +48 -70
- package/modules/react/useState.js.map +1 -1
- package/modules/react/useStyle.js +4 -4
- package/modules/react/useStyle.js.map +1 -1
- package/package.json +1 -1
- package/source/DOM/ItemsContainer.js +13 -3
- package/source/ItemHeights.js +1 -1
- package/source/VirtualScroller.layout.js +2 -2
- package/source/VirtualScroller.state.js +1 -1
- package/source/react/VirtualScroller.js +45 -13
- package/source/react/useHandleItemIndexesChange.js +49 -0
- package/source/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +20 -38
- package/source/react/useOnItemHeightChange.js +2 -2
- package/source/react/useSetItemState.js +2 -2
- package/source/react/useState.js +57 -72
- package/source/react/useStyle.js +2 -2
- package/commonjs/react/useHandleItemsChange.js.map +0 -1
- package/modules/react/useHandleItemsChange.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useState.js","names":["useState","useRef","useLayoutEffect","_useState","initialState","onRender","
|
|
1
|
+
{"version":3,"file":"useState.js","names":["useState","useRef","useCallback","useLayoutEffect","_useState","initialState","onRender","itemsProperty","USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION","_newState","_setNewState","state","setState","newState","current","nextState","undefined","getState","getNextState","updateState","stateUpdate","items"],"sources":["../../source/react/useState.js"],"sourcesContent":["import { useState, useRef, useCallback, useLayoutEffect } from 'react'\r\n\r\n// Creates state management functions.\r\nexport default function _useState({\r\n\tinitialState,\r\n\tonRender,\r\n\titemsProperty,\r\n\tUSE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION\r\n}) {\r\n\t// This is a utility state variable that is used to re-render the component.\r\n\t// It should not be used to access the current `VirtualScroller` state.\r\n\t// It's more of a \"requested\" `VirtualScroller` state.\r\n\t//\r\n\t// It will also be stale in cases when `USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION`\r\n\t// feature is used for setting new `items` in state.\r\n\t//\r\n\tconst [_newState, _setNewState] = useState(initialState)\r\n\r\n\t// This `state` reference is what `VirtualScroller` uses internally.\r\n\t// It's the \"source of truth\" on the actual `VirtualScroller` state.\r\n\tconst state = useRef(initialState)\r\n\r\n\tconst setState = useCallback((newState) => {\r\n\t\tstate.current = newState\r\n\t}, [])\r\n\r\n\t// Accumulates all \"pending\" state updates until they have been applied.\r\n\tconst nextState = useRef(initialState)\r\n\r\n\t// Updates the actual `VirtualScroller` state right after a requested state update\r\n\t// has been applied. Doesn't do anything at initial render.\r\n\tuseLayoutEffect(() => {\r\n\t\tsetState(_newState)\r\n\t}, [\r\n\t\t_newState\r\n\t])\r\n\r\n\t// Calls `onRender()` right after every state update (which is a re-render),\r\n\t// and also right after the initial render.\r\n\tuseLayoutEffect(() => {\r\n\t\tonRender()\r\n\t}, [\r\n\t\t_newState,\r\n\t\t// When using `USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION` feature,\r\n\t\t// there won't be a `_setNewState()` function call when `items` property changes,\r\n\t\t// hence the additional `itemsProperty` dependency.\r\n\t\tUSE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION ? itemsProperty : undefined\r\n\t])\r\n\r\n\treturn {\r\n\t\tgetState: () => state.current,\r\n\r\n\t\tgetNextState: () => nextState.current,\r\n\r\n\t\t// Requests a state update.\r\n\t\t//\r\n\t\t// State updates are incremental meaning that this function mimicks\r\n\t\t// the classic `React.Component`'s `this.setState()` behavior\r\n\t\t// when calling `this.setState()` didn't replace `state` but rather merged\r\n\t\t// the updated state properties over the \"old\" state properties.\r\n\t\t//\r\n\t\t// The reason for using pending state updates accumulation is that\r\n\t\t// `useState()` updates are \"asynchronous\" (not immediate),\r\n\t\t// and simply merging over `...state` would merge over potentially stale\r\n\t\t// property values in cases when more than a single `updateState()` call is made\r\n\t\t// before the state actually updates, resulting in losing some of those state updates.\r\n\t\t//\r\n\t\t// Example: the first `updateState()` call updates shown item indexes,\r\n\t\t// and the second `updateState()` call updates `verticalSpacing`.\r\n\t\t// If it was simply `updateState({ ...state, ...stateUpdate })`\r\n\t\t// then the second state update could overwrite the first state update,\r\n\t\t// resulting in incorrect items being shown/hidden.\r\n\t\t//\r\n\t\tupdateState: (stateUpdate) => {\r\n\t\t\tnextState.current = {\r\n\t\t\t\t...nextState.current,\r\n\t\t\t\t...stateUpdate\r\n\t\t\t}\r\n\t\t\t// If `items` property did change, the component detects it at render time\r\n\t\t\t// and updates `VirtualScroller` items immediately by calling `.setItems()`,\r\n\t\t\t// which, in turn, immediately calls this `updateState()` function\r\n\t\t\t// with a `stateUpdate` argument that contains the new `items`,\r\n\t\t\t// so checking for `stateUpdate.items` could detect situations like that.\r\n\t\t\t//\r\n\t\t\t// When the initial `VirtualScroller` state is being set, it contains the `.items`\r\n\t\t\t// property too, but that initial setting is done using another function called\r\n\t\t\t// `setInitialState()`, so using `if (stateUpdate.items)` condition here for describing\r\n\t\t\t// just the case when `state` has been updated as a result of a `setItems()` call\r\n\t\t\t// seems to be fine.\r\n\t\t\t//\r\n\t\t\tconst _newState = nextState.current\r\n\t\t\tif (stateUpdate.items && USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION) {\r\n\t\t\t\tsetState(_newState)\r\n\t\t\t} else {\r\n\t\t\t\t_setNewState(_newState)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,SAASA,QAAT,EAAmBC,MAAnB,EAA2BC,WAA3B,EAAwCC,eAAxC,QAA+D,OAA/D,C,CAEA;;AACA,eAAe,SAASC,SAAT,OAKZ;EAAA,IAJFC,YAIE,QAJFA,YAIE;EAAA,IAHFC,QAGE,QAHFA,QAGE;EAAA,IAFFC,aAEE,QAFFA,aAEE;EAAA,IADFC,8CACE,QADFA,8CACE;;EACF;EACA;EACA;EACA;EACA;EACA;EACA;EACA,iBAAkCR,QAAQ,CAACK,YAAD,CAA1C;EAAA;EAAA,IAAOI,SAAP;EAAA,IAAkBC,YAAlB,iBARE,CAUF;EACA;;;EACA,IAAMC,KAAK,GAAGV,MAAM,CAACI,YAAD,CAApB;EAEA,IAAMO,QAAQ,GAAGV,WAAW,CAAC,UAACW,QAAD,EAAc;IAC1CF,KAAK,CAACG,OAAN,GAAgBD,QAAhB;EACA,CAF2B,EAEzB,EAFyB,CAA5B,CAdE,CAkBF;;EACA,IAAME,SAAS,GAAGd,MAAM,CAACI,YAAD,CAAxB,CAnBE,CAqBF;EACA;;EACAF,eAAe,CAAC,YAAM;IACrBS,QAAQ,CAACH,SAAD,CAAR;EACA,CAFc,EAEZ,CACFA,SADE,CAFY,CAAf,CAvBE,CA6BF;EACA;;EACAN,eAAe,CAAC,YAAM;IACrBG,QAAQ;EACR,CAFc,EAEZ,CACFG,SADE,EAEF;EACA;EACA;EACAD,8CAA8C,GAAGD,aAAH,GAAmBS,SAL/D,CAFY,CAAf;EAUA,OAAO;IACNC,QAAQ,EAAE;MAAA,OAAMN,KAAK,CAACG,OAAZ;IAAA,CADJ;IAGNI,YAAY,EAAE;MAAA,OAAMH,SAAS,CAACD,OAAhB;IAAA,CAHR;IAKN;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACAK,WAAW,EAAE,qBAACC,WAAD,EAAiB;MAC7BL,SAAS,CAACD,OAAV,mCACIC,SAAS,CAACD,OADd,GAEIM,WAFJ,EAD6B,CAK7B;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;;MACA,IAAMX,SAAS,GAAGM,SAAS,CAACD,OAA5B;;MACA,IAAIM,WAAW,CAACC,KAAZ,IAAqBb,8CAAzB,EAAyE;QACxEI,QAAQ,CAACH,SAAD,CAAR;MACA,CAFD,MAEO;QACNC,YAAY,CAACD,SAAD,CAAZ;MACA;IACD;EA/CK,CAAP;AAiDA"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import px from '../utility/px.js';
|
|
2
2
|
export default function useStyle(_ref) {
|
|
3
3
|
var tbody = _ref.tbody,
|
|
4
|
-
|
|
4
|
+
getNextState = _ref.getNextState;
|
|
5
5
|
|
|
6
6
|
if (tbody) {
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
var
|
|
11
|
-
beforeItemsHeight =
|
|
12
|
-
afterItemsHeight =
|
|
10
|
+
var _getNextState = getNextState(),
|
|
11
|
+
beforeItemsHeight = _getNextState.beforeItemsHeight,
|
|
12
|
+
afterItemsHeight = _getNextState.afterItemsHeight;
|
|
13
13
|
|
|
14
14
|
return {
|
|
15
15
|
paddingTop: px(beforeItemsHeight),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStyle.js","names":["px","useStyle","tbody","
|
|
1
|
+
{"version":3,"file":"useStyle.js","names":["px","useStyle","tbody","getNextState","beforeItemsHeight","afterItemsHeight","paddingTop","paddingBottom"],"sources":["../../source/react/useStyle.js"],"sourcesContent":["import px from '../utility/px.js'\r\n\r\nexport default function useStyle({\r\n\ttbody,\r\n\tgetNextState\r\n}) {\r\n\tif (tbody) {\r\n\t\treturn\r\n\t}\r\n\r\n\tconst {\r\n\t\tbeforeItemsHeight,\r\n\t\tafterItemsHeight\r\n\t} = getNextState()\r\n\r\n\treturn {\r\n\t\tpaddingTop: px(beforeItemsHeight),\r\n\t\tpaddingBottom: px(afterItemsHeight)\r\n\t}\r\n}"],"mappings":"AAAA,OAAOA,EAAP,MAAe,kBAAf;AAEA,eAAe,SAASC,QAAT,OAGZ;EAAA,IAFFC,KAEE,QAFFA,KAEE;EAAA,IADFC,YACE,QADFA,YACE;;EACF,IAAID,KAAJ,EAAW;IACV;EACA;;EAED,oBAGIC,YAAY,EAHhB;EAAA,IACCC,iBADD,iBACCA,iBADD;EAAA,IAECC,gBAFD,iBAECA,gBAFD;;EAKA,OAAO;IACNC,UAAU,EAAEN,EAAE,CAACI,iBAAD,CADR;IAENG,aAAa,EAAEP,EAAE,CAACK,gBAAD;EAFX,CAAP;AAIA"}
|
package/package.json
CHANGED
|
@@ -7,13 +7,23 @@ export default class ItemsContainer {
|
|
|
7
7
|
this.getElement = getElement
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
_getNthRenderedItemElement(renderedElementIndex) {
|
|
11
|
+
const childNodes = this.getElement().childNodes
|
|
12
|
+
if (renderedElementIndex > childNodes.length - 1) {
|
|
13
|
+
console.log('~ Items Container Contents ~')
|
|
14
|
+
console.log(this.getElement().innerHTML)
|
|
15
|
+
throw new Error(`Element with index ${renderedElementIndex} was not found in the list of Rendered Item Elements in the Items Container of Virtual Scroller. There're only ${childNodes.length} Elements there.`)
|
|
16
|
+
}
|
|
17
|
+
return childNodes[renderedElementIndex]
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
/**
|
|
11
21
|
* Returns an item element's "top offset", relative to the items `container`'s top edge.
|
|
12
22
|
* @param {number} renderedElementIndex — An index of an item relative to the "first shown item index". For example, if the list is showing items from index 8 to index 12 then `renderedElementIndex = 0` would mean the item at index `8`.
|
|
13
23
|
* @return {number}
|
|
14
24
|
*/
|
|
15
25
|
getNthRenderedItemTopOffset(renderedElementIndex) {
|
|
16
|
-
return this.
|
|
26
|
+
return this._getNthRenderedItemElement(renderedElementIndex).getBoundingClientRect().top - this.getElement().getBoundingClientRect().top
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
/**
|
|
@@ -23,8 +33,8 @@ export default class ItemsContainer {
|
|
|
23
33
|
*/
|
|
24
34
|
getNthRenderedItemHeight(renderedElementIndex) {
|
|
25
35
|
// `offsetHeight` is not precise enough (doesn't return fractional pixels).
|
|
26
|
-
// return this.
|
|
27
|
-
return this.
|
|
36
|
+
// return this._getNthRenderedItemElement(renderedElementIndex).offsetHeight
|
|
37
|
+
return this._getNthRenderedItemElement(renderedElementIndex).getBoundingClientRect().height
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
/**
|
package/source/ItemHeights.js
CHANGED
|
@@ -169,7 +169,7 @@ export default class ItemHeights {
|
|
|
169
169
|
const previousHeight = this._get(i)
|
|
170
170
|
const height = this._measureItemHeight(i, firstShownItemIndex)
|
|
171
171
|
if (previousHeight !== height) {
|
|
172
|
-
warn('Item index', i, 'height changed unexpectedly: it was', previousHeight, 'before, but now it is', height, '. An item\'s height is allowed to change only in two cases: when the item\'s "state" changes and the developer calls `setItemState(i, newState)`, or when the item\'s height changes for
|
|
172
|
+
warn('Item index', i, 'height changed unexpectedly: it was', previousHeight, 'before, but now it is', height, '. An item\'s height is allowed to change only in two cases: when the item\'s "state" changes and the developer calls `setItemState(i, newState)`, or when the item\'s height changes for any reason and the developer calls `onItemHeightChange(i)`. Perhaps you forgot to persist the item\'s "state" by calling `setItemState(i, newState)` when it changed, and that "state" got lost when the item element was unmounted, which resulted in a different height when the item was shown again having its "state" reset.')
|
|
173
173
|
// Update the item's height as an attempt to fix things.
|
|
174
174
|
this._set(i, height)
|
|
175
175
|
}
|
|
@@ -372,7 +372,7 @@ export default function() {
|
|
|
372
372
|
|
|
373
373
|
this._onItemHeightChange = (i) => {
|
|
374
374
|
log('~ Re-measure item height ~')
|
|
375
|
-
log('Item', i)
|
|
375
|
+
log('Item index', i)
|
|
376
376
|
|
|
377
377
|
const {
|
|
378
378
|
itemHeights,
|
|
@@ -407,7 +407,7 @@ export default function() {
|
|
|
407
407
|
// So, even though the developer has written their code properly, there're
|
|
408
408
|
// still situations when the item could be no longer rendered by the time
|
|
409
409
|
// `.onItemHeightChange(i)` gets called.
|
|
410
|
-
return warn('The item is no longer rendered. This is not necessarily a bug, and could happen, for example, when
|
|
410
|
+
return warn('The item is no longer rendered. This is not necessarily a bug, and could happen, for example, when when a developer calls `onItemHeightChange(i)` while looping through a batch of items.')
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
const previousHeight = itemHeights[i]
|
|
@@ -40,7 +40,7 @@ export default function createStateHelpers({
|
|
|
40
40
|
this._setItemState = (i, newItemState) => {
|
|
41
41
|
if (isDebug()) {
|
|
42
42
|
log('~ Item state changed ~')
|
|
43
|
-
log('Item', i)
|
|
43
|
+
log('Item index', i)
|
|
44
44
|
// Uses `JSON.stringify()` here instead of just outputting the JSON objects as is
|
|
45
45
|
// because outputting JSON objects as is would show different results later when
|
|
46
46
|
// the developer inspects those in the web browser console if those state objects
|
|
@@ -8,13 +8,36 @@ import useInstanceMethods from './useInstanceMethods.js'
|
|
|
8
8
|
import useItemKeys from './useItemKeys.js'
|
|
9
9
|
import useSetItemState from './useSetItemState.js'
|
|
10
10
|
import useOnItemHeightChange from './useOnItemHeightChange.js'
|
|
11
|
-
import
|
|
11
|
+
import useHandleItemsPropertyChange from './useHandleItemsPropertyChange.js'
|
|
12
|
+
import useHandleItemIndexesChange from './useHandleItemIndexesChange.js'
|
|
12
13
|
import useClassName from './useClassName.js'
|
|
13
14
|
import useStyle from './useStyle.js'
|
|
14
15
|
|
|
16
|
+
// When `items` property changes, `useHandleItemsPropertyChange()` hook detects that
|
|
17
|
+
// and calls `VirtualScroller.setItems()` which in turn calls the `updateState()` function.
|
|
18
|
+
// At this point, an insignificant optimization could be applied:
|
|
19
|
+
// the component could avoid re-rendering the second time.
|
|
20
|
+
// Instead, the state update could be applied "immediately" if it originated
|
|
21
|
+
// from `.setItems()` function call, eliminating the unneeded second re-render.
|
|
22
|
+
//
|
|
23
|
+
// I could see how this minor optimization could get brittle when modifiying the code,
|
|
24
|
+
// so I put it under a feature flag so that it could potentially be turned off
|
|
25
|
+
// in case of any potential weird issues in some future.
|
|
26
|
+
//
|
|
27
|
+
// Another reason for using this feature is:
|
|
28
|
+
//
|
|
29
|
+
// Since `useHandleItemsPropertyChange()` runs at render time
|
|
30
|
+
// and not after the render has finished (not in an "effect"),
|
|
31
|
+
// if the state update was done "conventionally" (by calling `_setNewState()`),
|
|
32
|
+
// React would throw an error about updating state during render.
|
|
33
|
+
// No one knows what the original error message was.
|
|
34
|
+
// Perhaps it's no longer relevant in newer versions of React.
|
|
35
|
+
//
|
|
36
|
+
const USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION = true
|
|
37
|
+
|
|
15
38
|
function VirtualScroller({
|
|
16
39
|
as: AsComponent,
|
|
17
|
-
items,
|
|
40
|
+
items: itemsProperty,
|
|
18
41
|
itemComponent: Component,
|
|
19
42
|
itemComponentProps,
|
|
20
43
|
// `estimatedItemHeight` property name is deprecated,
|
|
@@ -52,7 +75,7 @@ function VirtualScroller({
|
|
|
52
75
|
|
|
53
76
|
// Create a `VirtualScroller` instance.
|
|
54
77
|
const virtualScroller = useVirtualScroller({
|
|
55
|
-
items,
|
|
78
|
+
items: itemsProperty,
|
|
56
79
|
// `estimatedItemHeight` property name is deprecated,
|
|
57
80
|
// use `getEstimatedItemHeight` property instead.
|
|
58
81
|
estimatedItemHeight,
|
|
@@ -90,11 +113,13 @@ function VirtualScroller({
|
|
|
90
113
|
// This way, React will re-render the component on every state update.
|
|
91
114
|
const {
|
|
92
115
|
getState,
|
|
93
|
-
updateState
|
|
116
|
+
updateState,
|
|
117
|
+
getNextState
|
|
94
118
|
} = useState({
|
|
95
119
|
initialState: _initialState,
|
|
96
120
|
onRender: virtualScroller.onRender,
|
|
97
|
-
|
|
121
|
+
itemsProperty,
|
|
122
|
+
USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION
|
|
98
123
|
})
|
|
99
124
|
|
|
100
125
|
// Use custom (external) state storage in the `VirtualScroller`.
|
|
@@ -121,24 +146,31 @@ function VirtualScroller({
|
|
|
121
146
|
// Cache per-item `setItemState` functions' "references"
|
|
122
147
|
// so that item components don't get re-rendered needlessly.
|
|
123
148
|
const getSetItemState = useSetItemState({
|
|
124
|
-
|
|
149
|
+
initialItemsCount: itemsProperty.length,
|
|
125
150
|
virtualScroller
|
|
126
151
|
})
|
|
127
152
|
|
|
128
153
|
// Cache per-item `onItemHeightChange` functions' "references"
|
|
129
154
|
// so that item components don't get re-rendered needlessly.
|
|
130
155
|
const getOnItemHeightChange = useOnItemHeightChange({
|
|
131
|
-
|
|
156
|
+
initialItemsCount: itemsProperty.length,
|
|
132
157
|
virtualScroller
|
|
133
158
|
})
|
|
134
159
|
|
|
135
|
-
//
|
|
136
|
-
|
|
160
|
+
// Calls `.setItems()` if `items` property has changed.
|
|
161
|
+
useHandleItemsPropertyChange(itemsProperty, {
|
|
137
162
|
virtualScroller,
|
|
138
163
|
// `preserveScrollPosition` property name is deprecated,
|
|
139
164
|
// use `preserveScrollPositionOnPrependItems` property instead.
|
|
140
165
|
preserveScrollPosition,
|
|
141
166
|
preserveScrollPositionOnPrependItems,
|
|
167
|
+
nextItems: getNextState().items
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// Updates `key`s if item indexes have changed.
|
|
171
|
+
useHandleItemIndexesChange({
|
|
172
|
+
virtualScroller,
|
|
173
|
+
itemsBeingRendered: getNextState().items,
|
|
142
174
|
updateItemKeysForNewItems
|
|
143
175
|
})
|
|
144
176
|
|
|
@@ -177,15 +209,15 @@ function VirtualScroller({
|
|
|
177
209
|
|
|
178
210
|
const style = useStyle({
|
|
179
211
|
tbody,
|
|
180
|
-
|
|
212
|
+
getNextState
|
|
181
213
|
})
|
|
182
214
|
|
|
183
215
|
const {
|
|
184
|
-
items:
|
|
216
|
+
items: currentItems,
|
|
185
217
|
itemStates,
|
|
186
218
|
firstShownItemIndex,
|
|
187
219
|
lastShownItemIndex
|
|
188
|
-
} =
|
|
220
|
+
} = getNextState()
|
|
189
221
|
|
|
190
222
|
return (
|
|
191
223
|
<AsComponent
|
|
@@ -193,7 +225,7 @@ function VirtualScroller({
|
|
|
193
225
|
ref={container}
|
|
194
226
|
className={className}
|
|
195
227
|
style={style}>
|
|
196
|
-
{
|
|
228
|
+
{currentItems.map((item, i) => {
|
|
197
229
|
if (i >= firstShownItemIndex && i <= lastShownItemIndex) {
|
|
198
230
|
// * Passing `item` as `children` property is legacy and is deprecated.
|
|
199
231
|
// If version `2.x` is published in some hypothetical future,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
// If the order of the `items` changes, or new `items` get prepended resulting in a "shift":
|
|
4
|
+
//
|
|
5
|
+
// * Re-generate the React `key` prefix for item elements
|
|
6
|
+
// so that all item components are re-rendered for the new `items` list.
|
|
7
|
+
// That's because item components may have their own internal state,
|
|
8
|
+
// and simply passing another `item` property for an item component
|
|
9
|
+
// might result in bugs, which React would do with its "re-using" policy
|
|
10
|
+
// if the unique `key` workaround hasn't been used.
|
|
11
|
+
//
|
|
12
|
+
export default function useHandleItemIndexesChange({
|
|
13
|
+
virtualScroller,
|
|
14
|
+
itemsBeingRendered,
|
|
15
|
+
updateItemKeysForNewItems
|
|
16
|
+
}) {
|
|
17
|
+
const previousItemsBeingRenderedRef = useRef(itemsBeingRendered)
|
|
18
|
+
const previousItemsBeingRendered = previousItemsBeingRenderedRef.current
|
|
19
|
+
const haveItemsChanged = itemsBeingRendered !== previousItemsBeingRendered
|
|
20
|
+
previousItemsBeingRenderedRef.current = itemsBeingRendered
|
|
21
|
+
|
|
22
|
+
if (haveItemsChanged) {
|
|
23
|
+
let shouldUpdateItemKeys = true
|
|
24
|
+
|
|
25
|
+
const itemsDiff = virtualScroller.getItemsDiff(previousItemsBeingRendered, itemsBeingRendered)
|
|
26
|
+
// `itemsDiff` will be `undefined` in case of a non-incremental items list change.
|
|
27
|
+
if (itemsDiff) {
|
|
28
|
+
const {
|
|
29
|
+
prependedItemsCount,
|
|
30
|
+
appendedItemsCount
|
|
31
|
+
} = itemsDiff
|
|
32
|
+
if (prependedItemsCount === 0 && appendedItemsCount === 0) {
|
|
33
|
+
// The items order hasn't changed.
|
|
34
|
+
// No need to re-generate the `key` prefix.
|
|
35
|
+
shouldUpdateItemKeys = false
|
|
36
|
+
}
|
|
37
|
+
else if (prependedItemsCount === 0 && appendedItemsCount > 0) {
|
|
38
|
+
// The item order hasn't changed.
|
|
39
|
+
// No need to re-generate the `key` prefix.
|
|
40
|
+
shouldUpdateItemKeys = false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Update React element `key`s for the new set of `items`.
|
|
45
|
+
if (shouldUpdateItemKeys) {
|
|
46
|
+
updateItemKeysForNewItems()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useRef } from 'react'
|
|
2
2
|
|
|
3
|
-
// If new `items`
|
|
3
|
+
// If new `items` property is passed:
|
|
4
4
|
//
|
|
5
5
|
// * Store the scroll Y position for the first one of the current items
|
|
6
6
|
// so that it could potentially (in some cases) be restored after the
|
|
@@ -8,26 +8,14 @@ import { useRef } from 'react'
|
|
|
8
8
|
//
|
|
9
9
|
// * Call `VirtualScroller.setItems()` function.
|
|
10
10
|
//
|
|
11
|
-
|
|
12
|
-
// so that all item components are re-rendered for the new `items` list.
|
|
13
|
-
// That's because item components may have their own internal state,
|
|
14
|
-
// and simply passing another `item` property for an item component
|
|
15
|
-
// might result in bugs, which React would do with its "re-using" policy
|
|
16
|
-
// if the unique `key` workaround hasn't been used.
|
|
17
|
-
//
|
|
18
|
-
export default function useHandleItemsChange(items, {
|
|
11
|
+
export default function useHandleItemsPropertyChange(itemsProperty, {
|
|
19
12
|
virtualScroller,
|
|
20
13
|
// `preserveScrollPosition` property name is deprecated,
|
|
21
14
|
// use `preserveScrollPositionOnPrependItems` property instead.
|
|
22
15
|
preserveScrollPosition,
|
|
23
16
|
preserveScrollPositionOnPrependItems,
|
|
24
|
-
|
|
17
|
+
nextItems
|
|
25
18
|
}) {
|
|
26
|
-
const {
|
|
27
|
-
items: renderedItems,
|
|
28
|
-
firstShownItemIndex
|
|
29
|
-
} = virtualScroller.getState()
|
|
30
|
-
|
|
31
19
|
// During render, check if the `items` list has changed.
|
|
32
20
|
// If it has, capture the Y scroll position and updated item element `key`s.
|
|
33
21
|
|
|
@@ -72,13 +60,16 @@ export default function useHandleItemsChange(items, {
|
|
|
72
60
|
// scroll Y position is captured while the "Show previous" button
|
|
73
61
|
// is still being shown.
|
|
74
62
|
|
|
75
|
-
const
|
|
76
|
-
const hasItemsPropertyChanged =
|
|
77
|
-
|
|
63
|
+
const previousItemsProperty = useRef(itemsProperty)
|
|
64
|
+
const hasItemsPropertyChanged = itemsProperty !== previousItemsProperty.current
|
|
65
|
+
previousItemsProperty.current = itemsProperty
|
|
66
|
+
|
|
78
67
|
if (hasItemsPropertyChanged) {
|
|
79
|
-
let
|
|
80
|
-
|
|
81
|
-
|
|
68
|
+
let shouldUpdateItems = true
|
|
69
|
+
|
|
70
|
+
// Analyze the upcoming `items` change.
|
|
71
|
+
const itemsDiff = virtualScroller.getItemsDiff(nextItems, itemsProperty)
|
|
72
|
+
|
|
82
73
|
// `itemsDiff` will be `undefined` in case of a non-incremental items list change.
|
|
83
74
|
if (itemsDiff) {
|
|
84
75
|
const {
|
|
@@ -86,30 +77,21 @@ export default function useHandleItemsChange(items, {
|
|
|
86
77
|
appendedItemsCount
|
|
87
78
|
} = itemsDiff
|
|
88
79
|
if (prependedItemsCount === 0 && appendedItemsCount === 0) {
|
|
89
|
-
// The items
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
shouldUpdateItemKeys = false
|
|
93
|
-
}
|
|
94
|
-
else if (prependedItemsCount === 0 && appendedItemsCount > 0) {
|
|
95
|
-
// Just some items got appended. No need to re-generate
|
|
96
|
-
// the `key` prefix or to snapshot the Y scroll position.
|
|
97
|
-
shouldUpdateItemKeys = false
|
|
80
|
+
// The items order hasn't changed.
|
|
81
|
+
// No need to update them in `VirtualScroller` or to snapshot the Y scroll position.
|
|
82
|
+
shouldUpdateItems = false
|
|
98
83
|
}
|
|
99
84
|
}
|
|
100
85
|
|
|
101
|
-
if (
|
|
102
|
-
//
|
|
103
|
-
|
|
86
|
+
if (shouldUpdateItems) {
|
|
87
|
+
// Request to update the `items` in `VirtualScroller`.
|
|
88
|
+
// This will result in a `setState()` call.
|
|
89
|
+
// The new items won't be rendered until that state update is applied.
|
|
90
|
+
virtualScroller.setItems(itemsProperty, {
|
|
104
91
|
// `preserveScrollPosition` property name is deprecated,
|
|
105
92
|
// use `preserveScrollPositionOnPrependItems` property instead.
|
|
106
93
|
preserveScrollPositionOnPrependItems: preserveScrollPositionOnPrependItems || preserveScrollPosition
|
|
107
94
|
})
|
|
108
|
-
|
|
109
|
-
// Update React element `key`s for the new set of `items`.
|
|
110
|
-
if (shouldUpdateItemKeys) {
|
|
111
|
-
updateItemKeysForNewItems()
|
|
112
|
-
}
|
|
113
95
|
}
|
|
114
96
|
}
|
|
115
97
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useMemo, useRef, useCallback } from 'react'
|
|
2
2
|
|
|
3
3
|
export default function useOnItemHeightChange({
|
|
4
|
-
|
|
4
|
+
initialItemsCount,
|
|
5
5
|
virtualScroller
|
|
6
6
|
}) {
|
|
7
7
|
// Only compute the initial cache value once.
|
|
8
8
|
const initialCacheValue = useMemo(() => {
|
|
9
|
-
return new Array(
|
|
9
|
+
return new Array(initialItemsCount)
|
|
10
10
|
}, [])
|
|
11
11
|
|
|
12
12
|
// Handler functions cache.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useMemo, useRef, useCallback } from 'react'
|
|
2
2
|
|
|
3
3
|
export default function useSetItemState({
|
|
4
|
-
|
|
4
|
+
initialItemsCount,
|
|
5
5
|
virtualScroller
|
|
6
6
|
}) {
|
|
7
7
|
// Only compute the initial cache value once.
|
|
8
8
|
const initialCacheValue = useMemo(() => {
|
|
9
|
-
return new Array(
|
|
9
|
+
return new Array(initialItemsCount)
|
|
10
10
|
}, [])
|
|
11
11
|
|
|
12
12
|
// Handler functions cache.
|