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.
Files changed (65) hide show
  1. package/bundle/virtual-scroller-dom.js +1 -1
  2. package/bundle/virtual-scroller-dom.js.map +1 -1
  3. package/bundle/virtual-scroller-react.js +1 -1
  4. package/bundle/virtual-scroller-react.js.map +1 -1
  5. package/bundle/virtual-scroller.js +1 -1
  6. package/bundle/virtual-scroller.js.map +1 -1
  7. package/commonjs/DOM/ItemsContainer.js +22 -9
  8. package/commonjs/DOM/ItemsContainer.js.map +1 -1
  9. package/commonjs/ItemHeights.js +1 -1
  10. package/commonjs/ItemHeights.js.map +1 -1
  11. package/commonjs/VirtualScroller.layout.js +2 -2
  12. package/commonjs/VirtualScroller.layout.js.map +1 -1
  13. package/commonjs/VirtualScroller.state.js +1 -1
  14. package/commonjs/VirtualScroller.state.js.map +1 -1
  15. package/commonjs/react/VirtualScroller.js +48 -16
  16. package/commonjs/react/VirtualScroller.js.map +1 -1
  17. package/commonjs/react/useHandleItemIndexesChange.js +53 -0
  18. package/commonjs/react/useHandleItemIndexesChange.js.map +1 -0
  19. package/commonjs/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +21 -40
  20. package/commonjs/react/useHandleItemsPropertyChange.js.map +1 -0
  21. package/commonjs/react/useOnItemHeightChange.js +2 -2
  22. package/commonjs/react/useOnItemHeightChange.js.map +1 -1
  23. package/commonjs/react/useSetItemState.js +2 -2
  24. package/commonjs/react/useSetItemState.js.map +1 -1
  25. package/commonjs/react/useState.js +47 -69
  26. package/commonjs/react/useState.js.map +1 -1
  27. package/commonjs/react/useStyle.js +4 -4
  28. package/commonjs/react/useStyle.js.map +1 -1
  29. package/index.d.ts +1 -1
  30. package/modules/DOM/ItemsContainer.js +22 -9
  31. package/modules/DOM/ItemsContainer.js.map +1 -1
  32. package/modules/ItemHeights.js +1 -1
  33. package/modules/ItemHeights.js.map +1 -1
  34. package/modules/VirtualScroller.layout.js +2 -2
  35. package/modules/VirtualScroller.layout.js.map +1 -1
  36. package/modules/VirtualScroller.state.js +1 -1
  37. package/modules/VirtualScroller.state.js.map +1 -1
  38. package/modules/react/VirtualScroller.js +47 -17
  39. package/modules/react/VirtualScroller.js.map +1 -1
  40. package/modules/react/useHandleItemIndexesChange.js +45 -0
  41. package/modules/react/useHandleItemIndexesChange.js.map +1 -0
  42. package/modules/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +20 -39
  43. package/modules/react/useHandleItemsPropertyChange.js.map +1 -0
  44. package/modules/react/useOnItemHeightChange.js +2 -2
  45. package/modules/react/useOnItemHeightChange.js.map +1 -1
  46. package/modules/react/useSetItemState.js +2 -2
  47. package/modules/react/useSetItemState.js.map +1 -1
  48. package/modules/react/useState.js +48 -70
  49. package/modules/react/useState.js.map +1 -1
  50. package/modules/react/useStyle.js +4 -4
  51. package/modules/react/useStyle.js.map +1 -1
  52. package/package.json +1 -1
  53. package/source/DOM/ItemsContainer.js +13 -3
  54. package/source/ItemHeights.js +1 -1
  55. package/source/VirtualScroller.layout.js +2 -2
  56. package/source/VirtualScroller.state.js +1 -1
  57. package/source/react/VirtualScroller.js +45 -13
  58. package/source/react/useHandleItemIndexesChange.js +49 -0
  59. package/source/react/{useHandleItemsChange.js → useHandleItemsPropertyChange.js} +20 -38
  60. package/source/react/useOnItemHeightChange.js +2 -2
  61. package/source/react/useSetItemState.js +2 -2
  62. package/source/react/useState.js +57 -72
  63. package/source/react/useStyle.js +2 -2
  64. package/commonjs/react/useHandleItemsChange.js.map +0 -1
  65. package/modules/react/useHandleItemsChange.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"useState.js","names":["useState","useRef","useLayoutEffect","_useState","initialState","onRender","items","_stateUpdate","_setStateUpdate","state","targetState","current","getState","updateState","stateUpdate","newState"],"sources":["../../source/react/useState.js"],"sourcesContent":["import { useState, useRef, useLayoutEffect } from 'react'\r\n\r\n// Creates state management functions.\r\nexport default function _useState({ initialState, onRender, items }) {\r\n\t// `VirtualScroller` state.\r\n\t//\r\n\t// The `_stateUpdate` variable shouldn't be used directly\r\n\t// because in some cases its value may not represent\r\n\t// the actual `state` of the `VirtualScroller`.\r\n\t//\r\n\t// * It will contain an incorrect initial value if `initialState` property is passed\r\n\t// because it doesn't get initialized to `initialState`.\r\n\t//\r\n\t// * If `items` property gets changed, `state` reference variable gets updated immediately\r\n\t// but the `_stateUpdate` variable here doesn't (until the component re-renders some other time).\r\n\t//\r\n\t// Instead, use the `state` reference below.\r\n\t//\r\n\tconst [_stateUpdate, _setStateUpdate] = useState()\r\n\r\n\t// This `state` reference is used for accessing the externally stored\r\n\t// virtual scroller state from inside a `VirtualScroller` instance.\r\n\t//\r\n\t// It's also the \"source of truth\" on the actual `VirtualScroller` state.\r\n\t//\r\n\tconst state = useRef(initialState)\r\n\r\n\t// Accumulates state updates until they have been applied.\r\n\tconst targetState = useRef(initialState)\r\n\r\n\t// Update the current state reference.\r\n\t//\r\n\t// Ignores the cases when `state` reference has already been updated\r\n\t// \"immediately\" bypassing a `_setStateUpdate()` call, because\r\n\t// in that case, `_stateUpdate` holds a stale value.\r\n\t//\r\n\tif (state.current !== targetState.current) {\r\n\t\tstate.current = _stateUpdate\r\n\t}\r\n\r\n\t// Call `onRender()` right after every state update.\r\n\t//\r\n\t// When `items` property changes, `useHandleItemsChange()` hook doesn't call\r\n\t// `_setStateUpdate()` because there's no need for a re-render.\r\n\t// But chaning `items` still does trigger a `VirtualScroller` state update,\r\n\t// so added `items` property in the list of this \"effect\"'s dependencies.\r\n\t//\r\n\tuseLayoutEffect(() => {\r\n\t\tonRender()\r\n\t}, [\r\n\t\t_stateUpdate,\r\n\t\titems\r\n\t])\r\n\r\n\treturn {\r\n\t\tgetState: () => state.current,\r\n\r\n\t\t// Updates existing state.\r\n\t\t//\r\n\t\t// State updates are incremental meaning that this code should mimick\r\n\t\t// the classic `React.Component`'s `this.setState()` behavior\r\n\t\t// when calling `this.setState()` doesn't replace `state` but rather merges\r\n\t\t// a set of the updated state properties with the rest of the old ones.\r\n\t\t//\r\n\t\t// The reason is that `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 the state updates.\r\n\t\t//\r\n\t\t// For 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\t// Using `...state.current` instead of `...pendingState.current` here\r\n\t\t// would produce \"stale\" results.\r\n\t\t//\r\n\t\tupdateState: (stateUpdate) => {\r\n\t\t\tconst newState = {\r\n\t\t\t\t...targetState.current,\r\n\t\t\t\t...stateUpdate\r\n\t\t\t}\r\n\t\t\ttargetState.current = newState\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// But, since all of that happens at render time and not in an \"effect\",\r\n\t\t\t// if the state update was done as usual by calling `_setStateUpdate()`,\r\n\t\t\t// React would throw an error about updating state during render.\r\n\t\t\t// Hence, state update in that particular case should happen \"directly\",\r\n\t\t\t// without waiting for an \"asynchronous\" effect to trigger and call\r\n\t\t\t// an \"asyncronous\" `_setStateUpdate()` function.\r\n\t\t\t//\r\n\t\t\t// Updating state directly in that particular case works because there\r\n\t\t\t// already is a render ongoing, so there's no need to re-render the component\r\n\t\t\t// again after such render-time state update.\r\n\t\t\t//\r\n\t\t\t// When the initial `VirtualScroller` state is being set, it contains an `.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\tif (stateUpdate.items) {\r\n\t\t\t\t// If a `stateUpdate` contains `items` then it means that there was a `setItems()` call.\r\n\t\t\t\t// No need to trigger a re-render — the component got re-rendered anyway.\r\n\t\t\t\t// Just update the `state` \"in place\".\r\n\t\t\t\tstate.current = newState\r\n\t\t\t} else {\r\n\t\t\t\t_setStateUpdate(newState)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,SAASA,QAAT,EAAmBC,MAAnB,EAA2BC,eAA3B,QAAkD,OAAlD,C,CAEA;;AACA,eAAe,SAASC,SAAT,OAAsD;EAAA,IAAjCC,YAAiC,QAAjCA,YAAiC;EAAA,IAAnBC,QAAmB,QAAnBA,QAAmB;EAAA,IAATC,KAAS,QAATA,KAAS;;EACpE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,iBAAwCN,QAAQ,EAAhD;EAAA;EAAA,IAAOO,YAAP;EAAA,IAAqBC,eAArB,iBAfoE,CAiBpE;EACA;EACA;EACA;EACA;;;EACA,IAAMC,KAAK,GAAGR,MAAM,CAACG,YAAD,CAApB,CAtBoE,CAwBpE;;EACA,IAAMM,WAAW,GAAGT,MAAM,CAACG,YAAD,CAA1B,CAzBoE,CA2BpE;EACA;EACA;EACA;EACA;EACA;;EACA,IAAIK,KAAK,CAACE,OAAN,KAAkBD,WAAW,CAACC,OAAlC,EAA2C;IAC1CF,KAAK,CAACE,OAAN,GAAgBJ,YAAhB;EACA,CAnCmE,CAqCpE;EACA;EACA;EACA;EACA;EACA;EACA;;;EACAL,eAAe,CAAC,YAAM;IACrBG,QAAQ;EACR,CAFc,EAEZ,CACFE,YADE,EAEFD,KAFE,CAFY,CAAf;EAOA,OAAO;IACNM,QAAQ,EAAE;MAAA,OAAMH,KAAK,CAACE,OAAZ;IAAA,CADJ;IAGN;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACAE,WAAW,EAAE,qBAACC,WAAD,EAAiB;MAC7B,IAAMC,QAAQ,mCACVL,WAAW,CAACC,OADF,GAEVG,WAFU,CAAd;;MAIAJ,WAAW,CAACC,OAAZ,GAAsBI,QAAtB,CAL6B,CAM7B;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;;MACA,IAAID,WAAW,CAACR,KAAhB,EAAuB;QACtB;QACA;QACA;QACAG,KAAK,CAACE,OAAN,GAAgBI,QAAhB;MACA,CALD,MAKO;QACNP,eAAe,CAACO,QAAD,CAAf;MACA;IACD;EAzDK,CAAP;AA2DA"}
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
- virtualScroller = _ref.virtualScroller;
4
+ getNextState = _ref.getNextState;
5
5
 
6
6
  if (tbody) {
7
7
  return;
8
8
  }
9
9
 
10
- var _virtualScroller$getS = virtualScroller.getState(),
11
- beforeItemsHeight = _virtualScroller$getS.beforeItemsHeight,
12
- afterItemsHeight = _virtualScroller$getS.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","virtualScroller","getState","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\tvirtualScroller\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} = virtualScroller.getState()\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,eACE,QADFA,eACE;;EACF,IAAID,KAAJ,EAAW;IACV;EACA;;EAED,4BAGIC,eAAe,CAACC,QAAhB,EAHJ;EAAA,IACCC,iBADD,yBACCA,iBADD;EAAA,IAECC,gBAFD,yBAECA,gBAFD;;EAKA,OAAO;IACNC,UAAU,EAAEP,EAAE,CAACK,iBAAD,CADR;IAENG,aAAa,EAAER,EAAE,CAACM,gBAAD;EAFX,CAAP;AAIA"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "virtual-scroller",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
4
4
  "description": "A component for efficiently rendering large lists of variable height items",
5
5
  "main": "index.cjs",
6
6
  "module": "index.js",
@@ -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.getElement().childNodes[renderedElementIndex].getBoundingClientRect().top - this.getElement().getBoundingClientRect().top
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.getElement().childNodes[renderedElementIndex].offsetHeight
27
- return this.getElement().childNodes[renderedElementIndex].getBoundingClientRect().height
36
+ // return this._getNthRenderedItemElement(renderedElementIndex).offsetHeight
37
+ return this._getNthRenderedItemElement(renderedElementIndex).getBoundingClientRect().height
28
38
  }
29
39
 
30
40
  /**
@@ -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 some other 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.')
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 there\'re several `onItemHeightChange(i)` calls issued at the same time.')
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 useHandleItemsChange from './useHandleItemsChange.js'
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
- items
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
- items,
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
- items,
156
+ initialItemsCount: itemsProperty.length,
132
157
  virtualScroller
133
158
  })
134
159
 
135
- // Detect if `items` have changed.
136
- useHandleItemsChange(items, {
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
- virtualScroller
212
+ getNextState
181
213
  })
182
214
 
183
215
  const {
184
- items: renderedItems,
216
+ items: currentItems,
185
217
  itemStates,
186
218
  firstShownItemIndex,
187
219
  lastShownItemIndex
188
- } = virtualScroller.getState()
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
- {renderedItems.map((item, i) => {
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` are passed:
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
- // * Re-generate the React `key` prefix for item elements
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
- updateItemKeysForNewItems
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 previousItems = useRef(items)
76
- const hasItemsPropertyChanged = items !== previousItems.current
77
- previousItems.current = items
63
+ const previousItemsProperty = useRef(itemsProperty)
64
+ const hasItemsPropertyChanged = itemsProperty !== previousItemsProperty.current
65
+ previousItemsProperty.current = itemsProperty
66
+
78
67
  if (hasItemsPropertyChanged) {
79
- let itemsHaveChanged = true
80
- let shouldUpdateItemKeys = true
81
- const itemsDiff = virtualScroller.getItemsDiff(renderedItems, items)
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 haven't changed. No need to re-generate
90
- // the `key` prefix or to snapshot the Y scroll position.
91
- itemsHaveChanged = false
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 (itemsHaveChanged) {
102
- // Set the new `items`.
103
- virtualScroller.setItems(items, {
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
- items,
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(items.length)
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
- items,
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(items.length)
9
+ return new Array(initialItemsCount)
10
10
  }, [])
11
11
 
12
12
  // Handler functions cache.