virtual-scroller 1.11.2 → 1.12.0

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 (115) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +13 -11
  3. package/bundle/virtual-scroller-dom.js +1 -1
  4. package/bundle/virtual-scroller-dom.js.map +1 -1
  5. package/bundle/virtual-scroller-react.js +1 -1
  6. package/bundle/virtual-scroller-react.js.map +1 -1
  7. package/bundle/virtual-scroller.js +1 -1
  8. package/bundle/virtual-scroller.js.map +1 -1
  9. package/commonjs/DOM/ItemsContainer.js +10 -3
  10. package/commonjs/DOM/ItemsContainer.js.map +1 -1
  11. package/commonjs/DOM/VirtualScroller.js +13 -1
  12. package/commonjs/DOM/VirtualScroller.js.map +1 -1
  13. package/commonjs/ItemHeights.js +5 -5
  14. package/commonjs/ItemHeights.js.map +1 -1
  15. package/commonjs/ItemNotRenderedError.js +64 -0
  16. package/commonjs/ItemNotRenderedError.js.map +1 -0
  17. package/commonjs/Layout.test.js +10 -0
  18. package/commonjs/Layout.test.js.map +1 -1
  19. package/commonjs/VirtualScroller.js +23 -5
  20. package/commonjs/VirtualScroller.js.map +1 -1
  21. package/commonjs/VirtualScroller.layout.js +81 -39
  22. package/commonjs/VirtualScroller.layout.js.map +1 -1
  23. package/commonjs/VirtualScroller.onRender.js +97 -45
  24. package/commonjs/VirtualScroller.onRender.js.map +1 -1
  25. package/commonjs/VirtualScroller.state.js +50 -18
  26. package/commonjs/VirtualScroller.state.js.map +1 -1
  27. package/commonjs/react/VirtualScroller.js +31 -46
  28. package/commonjs/react/VirtualScroller.js.map +1 -1
  29. package/commonjs/react/useItemKeys.js +11 -3
  30. package/commonjs/react/useItemKeys.js.map +1 -1
  31. package/commonjs/react/useOnChange.js +19 -0
  32. package/commonjs/react/useOnChange.js.map +1 -0
  33. package/commonjs/react/{useOnItemHeightChange.js → useOnItemHeightDidChange.js} +7 -7
  34. package/commonjs/react/useOnItemHeightDidChange.js.map +1 -0
  35. package/commonjs/react/{useHandleItemsPropertyChange.js → useSetNewItemsOnItemsPropertyChange.js} +15 -14
  36. package/commonjs/react/useSetNewItemsOnItemsPropertyChange.js.map +1 -0
  37. package/commonjs/react/useState.js +162 -69
  38. package/commonjs/react/useState.js.map +1 -1
  39. package/commonjs/react/useStyle.js +3 -5
  40. package/commonjs/react/useStyle.js.map +1 -1
  41. package/commonjs/react/useUpdateItemKeysOnItemsChange.js +61 -0
  42. package/commonjs/react/useUpdateItemKeysOnItemsChange.js.map +1 -0
  43. package/commonjs/test/ItemsContainer.js +22 -1
  44. package/commonjs/test/ItemsContainer.js.map +1 -1
  45. package/commonjs/utility/debug.js +30 -6
  46. package/commonjs/utility/debug.js.map +1 -1
  47. package/dom/index.d.ts +1 -1
  48. package/index.cjs +2 -0
  49. package/index.d.ts +7 -1
  50. package/index.js +1 -0
  51. package/modules/DOM/ItemsContainer.js +8 -3
  52. package/modules/DOM/ItemsContainer.js.map +1 -1
  53. package/modules/DOM/VirtualScroller.js +13 -1
  54. package/modules/DOM/VirtualScroller.js.map +1 -1
  55. package/modules/ItemHeights.js +5 -5
  56. package/modules/ItemHeights.js.map +1 -1
  57. package/modules/ItemNotRenderedError.js +57 -0
  58. package/modules/ItemNotRenderedError.js.map +1 -0
  59. package/modules/Layout.test.js +10 -0
  60. package/modules/Layout.test.js.map +1 -1
  61. package/modules/VirtualScroller.js +17 -5
  62. package/modules/VirtualScroller.js.map +1 -1
  63. package/modules/VirtualScroller.layout.js +78 -39
  64. package/modules/VirtualScroller.layout.js.map +1 -1
  65. package/modules/VirtualScroller.onRender.js +98 -46
  66. package/modules/VirtualScroller.onRender.js.map +1 -1
  67. package/modules/VirtualScroller.state.js +50 -18
  68. package/modules/VirtualScroller.state.js.map +1 -1
  69. package/modules/react/VirtualScroller.js +31 -46
  70. package/modules/react/VirtualScroller.js.map +1 -1
  71. package/modules/react/useItemKeys.js +8 -3
  72. package/modules/react/useItemKeys.js.map +1 -1
  73. package/modules/react/useOnChange.js +11 -0
  74. package/modules/react/useOnChange.js.map +1 -0
  75. package/modules/react/{useOnItemHeightChange.js → useOnItemHeightDidChange.js} +6 -6
  76. package/modules/react/useOnItemHeightDidChange.js.map +1 -0
  77. package/modules/react/{useHandleItemsPropertyChange.js → useSetNewItemsOnItemsPropertyChange.js} +11 -13
  78. package/modules/react/useSetNewItemsOnItemsPropertyChange.js.map +1 -0
  79. package/modules/react/useState.js +156 -73
  80. package/modules/react/useState.js.map +1 -1
  81. package/modules/react/useStyle.js +3 -5
  82. package/modules/react/useStyle.js.map +1 -1
  83. package/{commonjs/react/useHandleItemIndexesChange.js → modules/react/useUpdateItemKeysOnItemsChange.js} +18 -21
  84. package/modules/react/useUpdateItemKeysOnItemsChange.js.map +1 -0
  85. package/modules/test/ItemsContainer.js +20 -1
  86. package/modules/test/ItemsContainer.js.map +1 -1
  87. package/modules/utility/debug.js +31 -6
  88. package/modules/utility/debug.js.map +1 -1
  89. package/package.json +1 -1
  90. package/source/DOM/ItemsContainer.js +8 -3
  91. package/source/DOM/VirtualScroller.js +11 -1
  92. package/source/ItemHeights.js +5 -5
  93. package/source/ItemNotRenderedError.js +16 -0
  94. package/source/Layout.test.js +9 -0
  95. package/source/VirtualScroller.js +14 -3
  96. package/source/VirtualScroller.layout.js +77 -38
  97. package/source/VirtualScroller.onRender.js +95 -42
  98. package/source/VirtualScroller.state.js +57 -20
  99. package/source/react/VirtualScroller.js +28 -39
  100. package/source/react/useItemKeys.js +9 -2
  101. package/source/react/useOnChange.js +11 -0
  102. package/source/react/{useOnItemHeightChange.js → useOnItemHeightDidChange.js} +5 -5
  103. package/source/react/{useHandleItemsPropertyChange.js → useSetNewItemsOnItemsPropertyChange.js} +11 -11
  104. package/source/react/useState.js +159 -71
  105. package/source/react/useStyle.js +2 -2
  106. package/source/react/{useHandleItemIndexesChange.js → useUpdateItemKeysOnItemsChange.js} +17 -9
  107. package/source/test/ItemsContainer.js +22 -1
  108. package/source/utility/debug.js +18 -4
  109. package/commonjs/react/useHandleItemIndexesChange.js.map +0 -1
  110. package/commonjs/react/useHandleItemsPropertyChange.js.map +0 -1
  111. package/commonjs/react/useOnItemHeightChange.js.map +0 -1
  112. package/modules/react/useHandleItemIndexesChange.js +0 -45
  113. package/modules/react/useHandleItemIndexesChange.js.map +0 -1
  114. package/modules/react/useHandleItemsPropertyChange.js.map +0 -1
  115. package/modules/react/useOnItemHeightChange.js.map +0 -1
@@ -1,99 +1,187 @@
1
- import { useState, useRef, useCallback, useLayoutEffect } from 'react'
1
+ import log, { isDebug } from '../utility/debug.js'
2
+ import getStateSnapshot from '../utility/getStateSnapshot.js'
3
+
4
+ import { useState, useRef, useCallback, useLayoutEffect, useInsertionEffect } from 'react'
2
5
 
3
6
  // Creates state management functions.
4
7
  export default function _useState({
5
8
  initialState,
6
9
  onRender,
7
- itemsProperty,
8
- USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION
10
+ itemsProperty
9
11
  }) {
10
- // This is a utility state variable that is used to re-render the component.
11
- // It should not be used to access the current `VirtualScroller` state.
12
- // It's more of a "requested" `VirtualScroller` state.
13
- //
14
- // It will also be stale in cases when `USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION`
15
- // feature is used for setting new `items` in state.
16
- //
12
+ // This is a state variable that is used to re-render the component.
13
+ // Right after the component has finished re-rendering,
14
+ // `VirtualScroller` state gets updated from this variable.
15
+ // The reason for that is that `VirtualScroller` state must always
16
+ // correspond exactly to what's currently rendered on the screen.
17
17
  const [_newState, _setNewState] = useState(initialState)
18
18
 
19
19
  // This `state` reference is what `VirtualScroller` uses internally.
20
20
  // It's the "source of truth" on the actual `VirtualScroller` state.
21
21
  const state = useRef(initialState)
22
22
 
23
+ const getState = useCallback(() => {
24
+ return state.current
25
+ }, [])
26
+
23
27
  const setState = useCallback((newState) => {
24
28
  state.current = newState
25
29
  }, [])
26
30
 
27
- // Accumulates all "pending" state updates until they have been applied.
28
- const nextState = useRef(initialState)
31
+ // Updating of the actual `VirtualScroller` state is done in a
32
+ // `useInsertionEffect()` rather than in a `useLayoutEffect()`.
33
+ //
34
+ // The reason is that using `useLayoutEffect()` would result in
35
+ // "breaking" the `<VirtualScroller/>` when an `itemComponent`
36
+ // called `onHeightDidChange()` from its own `useLayoutEffect()`.
37
+ // In those cases, the `itemCompoent`'s effect would run before
38
+ // the `<VirtualScroller/>`'s effect, resulting in
39
+ // `VirtualScroller.onItemHeightDidChange(i)` being run at a moment in time
40
+ // when the DOM has already been updated for the next `VirtualScroller` state
41
+ // but the actual `VirtualScroller` state is still a previous ("stale") one
42
+ // containing "stale" first/last shown item indexes, which would result in an
43
+ // "index out of bounds" error when `onItemHeightDidChange(i)` tries to access
44
+ // and measure the DOM element from item index `i` which doesn't already/yet exist.
45
+ //
46
+ // An example of such situation could be seen from a `VirtualScroller` debug log
47
+ // which was captured for a case when using `useLayoutEffect()` to update the
48
+ // "actual" `VirtualScroller` state after the corresponding DOM changes have been applied:
29
49
 
30
- // Updates the actual `VirtualScroller` state right after a requested state update
31
- // has been applied. Doesn't do anything at initial render.
32
- useLayoutEffect(() => {
50
+ // The user has scrolled far enough: perform a re-layout
51
+ // ~ Update Layout (on scroll) ~
52
+ //
53
+ // Item index 2 height is required for calculations but hasn't been measured yet. Mark the item as "shown", rerender the list, measure the item's height and redo the layout.
54
+ //
55
+ // ~ Calculated Layout ~
56
+ // Columns count 1
57
+ // First shown item index 2
58
+ // Last shown item index 5
59
+ // …
60
+ // Item heights (231) [1056.578125, 783.125, empty × 229]
61
+ // Item states (231) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]
62
+ //
63
+ // ~ Set state ~
64
+ // {firstShownItemIndex: 2, lastShownItemIndex: 5, …}
65
+ //
66
+ // ~ Rendered ~
67
+ // State {firstShownItemIndex: 2, lastShownItemIndex: 5, …}
68
+ //
69
+ // ~ Measure item heights ~
70
+ // Item index 2 height 719.8828125
71
+ // Item index 3 height 961.640625
72
+ // Item index 4 height 677.6640625
73
+ // Item index 5 height 1510.1953125
74
+ //
75
+ // ~ Update Layout (on non-measured item heights have been measured) ~
76
+ //
77
+ // ~ Calculated Layout ~
78
+ // Columns count 1
79
+ // First shown item index 4
80
+ // Last shown item index 5
81
+ // …
82
+ // Item heights (231) [1056.578125, 783.125, 719.8828125, 961.640625, 677.6640625, 1510.1953125, empty × 225]
83
+ // Item states (231) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]
84
+ //
85
+ // ~ Set state ~
86
+ // {firstShownItemIndex: 4, lastShownItemIndex: 5, beforeItemsHeight: 3521.2265625, afterItemsHeight: 214090.72265624942}
87
+ //
88
+ // ~ On Item Height Did Change was called ~
89
+ // Item index 5
90
+ // ~ Re-measure item height ~
91
+ // ERROR "onItemHeightDidChange()" has been called for item index 5 but the item is not currently rendered and can't be measured. The exact error was: Element with index 3 was not found in the list of Rendered Item Elements in the Items Container of Virtual Scroller. There're only 2 Elements there.
92
+ //
93
+ // React: ~ The requested state is about to be applied in DOM. Set it as the `VirtualScroller` state. ~
94
+ // {firstShownItemIndex: 4, lastShownItemIndex: 5, …}
95
+ //
96
+ // ~ Rendered ~
97
+
98
+ // "~ Rendered ~" is what gets output when `onRender()` function gets called.
99
+ // It means that `useLayoutEffect()` was triggered after `onItemHeightDidChange(i)`
100
+ // was called and after the "ERROR" happened.
101
+ //
102
+ // The "ERROR" happened because new item indexes 4…5 were actually rendered instead of
103
+ // item indexes 2…5 by the time the application called `onItemHeightDidChange(i)` function
104
+ // inside `itemComponent`'s `useLayoutEffect()`.
105
+ // Item indexes 4…5 is what was requested in a `setState()` call, which called `_setNewState()`.
106
+ // This means that `_newState` changes have been applied to the DOM
107
+ // but `useLayoutEffect()` wasn't triggered immediately after that.
108
+ // Instead, it was triggered a right after the `itemComponent`'s `useLayoutEffect()`
109
+ // because child effects run before parent effects.
110
+ // So, the `itemComponent`'s `onHeightDidChange()` function call caught the
111
+ // `VirtualScroller` in an inconsistent state.
112
+ //
113
+ // To fix that, `useLayoutEffect()` gets replaced with `useInsertionEffect()`:
114
+ // https://blog.saeloun.com/2022/06/02/react-18-useinsertioneffect
115
+ // https://beta.reactjs.org/reference/react/useInsertionEffect
116
+ //
117
+ // After replacing `useLayoutEffect()` with `useInsertionEffect()`,
118
+ // the log shows that there's no more error:
119
+ //
120
+ // ~ Set state ~
121
+ // {firstShownItemIndex: 0, lastShownItemIndex: 2, …}
122
+ //
123
+ // React: ~ The requested state is about to be applied in DOM. Set it as the `VirtualScroller` state. ~
124
+ // {firstShownItemIndex: 0, lastShownItemIndex: 2, …}
125
+ //
126
+ // ~ On Item Height Did Change was called ~
127
+ // Item index 0
128
+ // ~ Re-measure item height ~
129
+ // Previous height 917
130
+ // New height 1064.453125
131
+ // ~ Item height has changed ~
132
+ //
133
+ // An alternative solution would be demanding the `itemComponent` to
134
+ // accept a `ref` and then measuring the corresponding DOM element height
135
+ // directly using the `ref`-ed DOM element rather than searching for that
136
+ // DOM element in the `ItemsContainer`.
137
+ // So if `useInsertionEffect()` gets removed from React in some hypothetical future,
138
+ // it could be replaced with using `ref`s on `ItemComponent`s to measure the DOM element heights.
139
+ //
140
+ useInsertionEffect(() => {
141
+ // Update the actual `VirtualScroller` state right before the DOM changes
142
+ // are going to be applied for the requested state update.
143
+ //
144
+ // This hook will run right before `useLayoutEffect()`.
145
+ //
146
+ // It doesn't make any difference which one of the two hooks to use to update
147
+ // the actual `VirtualScroller` state in this scenario because the two hooks
148
+ // run synchronously one right after another (insertion effect → DOM update → layout effect)
149
+ // without any free space for any `VirtualScroller` code (like the scroll event handler)
150
+ // to squeeze in and run in-between them, so the `VirtualScroller`'s `state`
151
+ // is always gonna stay consistent with what's currently rendered on screen
152
+ // from the `VirtualScroler`'s point of view, and the short transition period
153
+ // it simply doesn't see because it doesn't "wake up" during that period.
154
+ //
155
+ // Updating the actual `VirtualScroller` state right before `useLayoutEffect()`
156
+ // fixes the bug when an `itemComponent` calls `onHeightDidChange()` in its own
157
+ // `useLayoutEffect()` which would run before this `useLayoutEffect()`
158
+ // because children's effects run before parent's.
159
+ //
160
+ // This hook doesn't do anything at the initial render.
161
+ //
162
+ if (isDebug()) {
163
+ log('React: ~ The requested state is about to be applied in DOM. Set it as the `VirtualScroller` state. ~')
164
+ log(getStateSnapshot(_newState))
165
+ }
33
166
  setState(_newState)
34
- }, [
35
- _newState
36
- ])
167
+ }, [_newState])
37
168
 
38
- // Calls `onRender()` right after every state update (which is a re-render),
39
- // and also right after the initial render.
40
169
  useLayoutEffect(() => {
170
+ // Call `onRender()` right after a requested state update has been applied,
171
+ // and also right after the initial render.
41
172
  onRender()
42
- }, [
43
- _newState,
44
- // When using `USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION` feature,
45
- // there won't be a `_setNewState()` function call when `items` property changes,
46
- // hence the additional `itemsProperty` dependency.
47
- USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION ? itemsProperty : undefined
48
- ])
173
+ }, [_newState])
49
174
 
50
175
  return {
51
- getState: () => state.current,
176
+ // This is the state the component should render.
177
+ stateToRender: _newState,
52
178
 
53
- getNextState: () => nextState.current,
179
+ // Returns the current state of the `VirtualScroller`.
180
+ // This function is used in the `VirtualScroller` itself
181
+ // because the `state` is managed outside of it.
182
+ getState,
54
183
 
55
184
  // Requests a state update.
56
- //
57
- // State updates are incremental meaning that this function mimicks
58
- // the classic `React.Component`'s `this.setState()` behavior
59
- // when calling `this.setState()` didn't replace `state` but rather merged
60
- // the updated state properties over the "old" state properties.
61
- //
62
- // The reason for using pending state updates accumulation is that
63
- // `useState()` updates are "asynchronous" (not immediate),
64
- // and simply merging over `...state` would merge over potentially stale
65
- // property values in cases when more than a single `updateState()` call is made
66
- // before the state actually updates, resulting in losing some of those state updates.
67
- //
68
- // Example: the first `updateState()` call updates shown item indexes,
69
- // and the second `updateState()` call updates `verticalSpacing`.
70
- // If it was simply `updateState({ ...state, ...stateUpdate })`
71
- // then the second state update could overwrite the first state update,
72
- // resulting in incorrect items being shown/hidden.
73
- //
74
- updateState: (stateUpdate) => {
75
- nextState.current = {
76
- ...nextState.current,
77
- ...stateUpdate
78
- }
79
- // If `items` property did change, the component detects it at render time
80
- // and updates `VirtualScroller` items immediately by calling `.setItems()`,
81
- // which, in turn, immediately calls this `updateState()` function
82
- // with a `stateUpdate` argument that contains the new `items`,
83
- // so checking for `stateUpdate.items` could detect situations like that.
84
- //
85
- // When the initial `VirtualScroller` state is being set, it contains the `.items`
86
- // property too, but that initial setting is done using another function called
87
- // `setInitialState()`, so using `if (stateUpdate.items)` condition here for describing
88
- // just the case when `state` has been updated as a result of a `setItems()` call
89
- // seems to be fine.
90
- //
91
- const _newState = nextState.current
92
- if (stateUpdate.items && USE_ITEMS_UPDATE_NO_SECOND_RENDER_OPTIMIZATION) {
93
- setState(_newState)
94
- } else {
95
- _setNewState(_newState)
96
- }
97
- }
185
+ setState: _setNewState
98
186
  }
99
187
  }
@@ -2,7 +2,7 @@ import px from '../utility/px.js'
2
2
 
3
3
  export default function useStyle({
4
4
  tbody,
5
- getNextState
5
+ state
6
6
  }) {
7
7
  if (tbody) {
8
8
  return
@@ -11,7 +11,7 @@ export default function useStyle({
11
11
  const {
12
12
  beforeItemsHeight,
13
13
  afterItemsHeight
14
- } = getNextState()
14
+ } = state
15
15
 
16
16
  return {
17
17
  paddingTop: px(beforeItemsHeight),
@@ -1,4 +1,6 @@
1
- import { useRef } from 'react'
1
+ import log from '../utility/debug.js'
2
+
3
+ import useOnChange from './useOnChange.js'
2
4
 
3
5
  // If the order of the `items` changes, or new `items` get prepended resulting in a "shift":
4
6
  //
@@ -9,20 +11,23 @@ import { useRef } from 'react'
9
11
  // might result in bugs, which React would do with its "re-using" policy
10
12
  // if the unique `key` workaround hasn't been used.
11
13
  //
12
- export default function useHandleItemIndexesChange({
14
+ export default function useUpdateItemKeysOnItemsChange(itemsBeingRendered, {
13
15
  virtualScroller,
14
- itemsBeingRendered,
16
+ usesAutogeneratedItemKeys,
15
17
  updateItemKeysForNewItems
16
18
  }) {
17
- const previousItemsBeingRenderedRef = useRef(itemsBeingRendered)
18
- const previousItemsBeingRendered = previousItemsBeingRenderedRef.current
19
- const haveItemsChanged = itemsBeingRendered !== previousItemsBeingRendered
20
- previousItemsBeingRenderedRef.current = itemsBeingRendered
19
+ // Update item keys if the items being rendered have changed.
20
+ useOnChange(itemsBeingRendered, (itemsBeingRendered, previousItemsBeingRendered) => {
21
+ if (!usesAutogeneratedItemKeys) {
22
+ return
23
+ }
24
+
25
+ log('React: ~ Different `items` are about to be rendered', itemsBeingRendered)
21
26
 
22
- if (haveItemsChanged) {
23
27
  let shouldUpdateItemKeys = true
24
28
 
25
29
  const itemsDiff = virtualScroller.getItemsDiff(previousItemsBeingRendered, itemsBeingRendered)
30
+
26
31
  // `itemsDiff` will be `undefined` in case of a non-incremental items list change.
27
32
  if (itemsDiff) {
28
33
  const {
@@ -30,11 +35,13 @@ export default function useHandleItemIndexesChange({
30
35
  appendedItemsCount
31
36
  } = itemsDiff
32
37
  if (prependedItemsCount === 0 && appendedItemsCount === 0) {
38
+ log('React: ~ The `items` elements to be rendered are identical to the previously rendered ones')
33
39
  // The items order hasn't changed.
34
40
  // No need to re-generate the `key` prefix.
35
41
  shouldUpdateItemKeys = false
36
42
  }
37
43
  else if (prependedItemsCount === 0 && appendedItemsCount > 0) {
44
+ log('React: ~ The `items` elements order hasn\'t changed:', appendedItemsCount, 'items have been appended')
38
45
  // The item order hasn't changed.
39
46
  // No need to re-generate the `key` prefix.
40
47
  shouldUpdateItemKeys = false
@@ -43,7 +50,8 @@ export default function useHandleItemIndexesChange({
43
50
 
44
51
  // Update React element `key`s for the new set of `items`.
45
52
  if (shouldUpdateItemKeys) {
53
+ log('React: ~ Update item `key`s')
46
54
  updateItemKeysForNewItems()
47
55
  }
48
- }
56
+ })
49
57
  }
@@ -1,3 +1,5 @@
1
+ import ItemNotRenderedError from '../ItemNotRenderedError.js'
2
+
1
3
  export default class ItemsContainer {
2
4
  /**
3
5
  * Constructs a new "container" from an element.
@@ -16,9 +18,18 @@ export default class ItemsContainer {
16
18
  const children = this.getElement().children
17
19
  const maxWidth = this.getElement().width
18
20
  let topOffset = this.getElement().paddingTop
21
+
19
22
  let rowWidth
20
23
  let rowHeight
21
24
  let startNewRow = true
25
+
26
+ if (renderedElementIndex > children.length - 1) {
27
+ throw new ItemNotRenderedError({
28
+ renderedElementIndex,
29
+ renderedElementsCount: children.length
30
+ })
31
+ }
32
+
22
33
  let i = 0
23
34
  while (i <= renderedElementIndex) {
24
35
  if (startNewRow || rowWidth + children[i].width > maxWidth) {
@@ -39,6 +50,7 @@ export default class ItemsContainer {
39
50
  }
40
51
  i++
41
52
  }
53
+
42
54
  return topOffset
43
55
  }
44
56
 
@@ -48,7 +60,16 @@ export default class ItemsContainer {
48
60
  * @return {number}
49
61
  */
50
62
  getNthRenderedItemHeight(renderedElementIndex) {
51
- return this.getElement().children[renderedElementIndex].height
63
+ const children = this.getElement().children
64
+
65
+ if (renderedElementIndex > children.length - 1) {
66
+ throw new ItemNotRenderedError({
67
+ renderedElementIndex,
68
+ renderedElementsCount: children.length
69
+ })
70
+ }
71
+
72
+ return children[renderedElementIndex].height
52
73
  }
53
74
 
54
75
  /**
@@ -13,12 +13,17 @@ export function warn(...args) {
13
13
  }
14
14
  }
15
15
 
16
+ function error(...args) {
17
+ console.error(...['[virtual-scroller]'].concat(args))
18
+ }
19
+
16
20
  export function reportError(...args) {
21
+ const createError = () => new Error(['[virtual-scroller]'].concat(args).join(' '))
17
22
  if (typeof window !== 'undefined') {
18
23
  // In a web browser.
19
24
  // Output a debug message immediately so that it's known
20
25
  // at which point did the error occur between other debug logs.
21
- log.apply(this, ['ERROR'].concat(args))
26
+ error.apply(this, ['ERROR'].concat(args))
22
27
  setTimeout(() => {
23
28
  // Throw an error in a timeout so that it doesn't interrupt the application's flow.
24
29
  // At the same time, by throwing a client-side error, such error could be spotted
@@ -26,11 +31,20 @@ export function reportError(...args) {
26
31
  // in the console.
27
32
  // The `.join(' ')` part doesn't support stringifying JSON objects,
28
33
  // but those don't seem to be used in any of the error messages.
29
- throw new Error(['[virtual-scroller]'].concat(args).join(' '))
34
+ throw createError()
30
35
  }, 0)
31
36
  } else {
32
- // On a server.
33
- console.error(...['[virtual-scroller]'].concat(args))
37
+ // In Node.js.
38
+ // If tests are being run, throw in case of any errors.
39
+ const catchError = getGlobalVariable('VirtualScrollerCatchError')
40
+ if (catchError) {
41
+ return catchError(createError())
42
+ }
43
+ if (getGlobalVariable('VirtualScrollerThrowErrors')) {
44
+ throw createError()
45
+ }
46
+ // Print the error in the console.
47
+ error.apply(this, ['ERROR'].concat(args))
34
48
  }
35
49
  }
36
50
 
@@ -1 +0,0 @@
1
- {"version":3,"file":"useHandleItemIndexesChange.js","names":["useHandleItemIndexesChange","virtualScroller","itemsBeingRendered","updateItemKeysForNewItems","previousItemsBeingRenderedRef","useRef","previousItemsBeingRendered","current","haveItemsChanged","shouldUpdateItemKeys","itemsDiff","getItemsDiff","prependedItemsCount","appendedItemsCount"],"sources":["../../source/react/useHandleItemIndexesChange.js"],"sourcesContent":["import { useRef } from 'react'\r\n\r\n// If the order of the `items` changes, or new `items` get prepended resulting in a \"shift\":\r\n//\r\n// * Re-generate the React `key` prefix for item elements\r\n// so that all item components are re-rendered for the new `items` list.\r\n// That's because item components may have their own internal state,\r\n// and simply passing another `item` property for an item component\r\n// might result in bugs, which React would do with its \"re-using\" policy\r\n// if the unique `key` workaround hasn't been used.\r\n//\r\nexport default function useHandleItemIndexesChange({\r\n\tvirtualScroller,\r\n\titemsBeingRendered,\r\n\tupdateItemKeysForNewItems\r\n}) {\r\n\tconst previousItemsBeingRenderedRef = useRef(itemsBeingRendered)\r\n\tconst previousItemsBeingRendered = previousItemsBeingRenderedRef.current\r\n\tconst haveItemsChanged = itemsBeingRendered !== previousItemsBeingRendered\r\n\tpreviousItemsBeingRenderedRef.current = itemsBeingRendered\r\n\r\n\tif (haveItemsChanged) {\r\n\t\tlet shouldUpdateItemKeys = true\r\n\r\n\t\tconst itemsDiff = virtualScroller.getItemsDiff(previousItemsBeingRendered, itemsBeingRendered)\r\n\t\t// `itemsDiff` will be `undefined` in case of a non-incremental items list change.\r\n\t\tif (itemsDiff) {\r\n\t\t\tconst {\r\n\t\t\t\tprependedItemsCount,\r\n\t\t\t\tappendedItemsCount\r\n\t\t\t} = itemsDiff\r\n\t\t\tif (prependedItemsCount === 0 && appendedItemsCount === 0) {\r\n\t\t\t\t// The items order hasn't changed.\r\n\t\t\t\t// No need to re-generate the `key` prefix.\r\n\t\t\t\tshouldUpdateItemKeys = false\r\n\t\t\t}\r\n\t\t\telse if (prependedItemsCount === 0 && appendedItemsCount > 0) {\r\n\t\t\t\t// The item order hasn't changed.\r\n\t\t\t\t// No need to re-generate the `key` prefix.\r\n\t\t\t\tshouldUpdateItemKeys = false\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Update React element `key`s for the new set of `items`.\r\n\t\tif (shouldUpdateItemKeys) {\r\n\t\t\tupdateItemKeysForNewItems()\r\n\t\t}\r\n\t}\r\n}"],"mappings":";;;;;;;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAASA,0BAAT,OAIZ;EAAA,IAHFC,eAGE,QAHFA,eAGE;EAAA,IAFFC,kBAEE,QAFFA,kBAEE;EAAA,IADFC,yBACE,QADFA,yBACE;EACF,IAAMC,6BAA6B,GAAG,IAAAC,aAAA,EAAOH,kBAAP,CAAtC;EACA,IAAMI,0BAA0B,GAAGF,6BAA6B,CAACG,OAAjE;EACA,IAAMC,gBAAgB,GAAGN,kBAAkB,KAAKI,0BAAhD;EACAF,6BAA6B,CAACG,OAA9B,GAAwCL,kBAAxC;;EAEA,IAAIM,gBAAJ,EAAsB;IACrB,IAAIC,oBAAoB,GAAG,IAA3B;IAEA,IAAMC,SAAS,GAAGT,eAAe,CAACU,YAAhB,CAA6BL,0BAA7B,EAAyDJ,kBAAzD,CAAlB,CAHqB,CAIrB;;IACA,IAAIQ,SAAJ,EAAe;MACd,IACCE,mBADD,GAGIF,SAHJ,CACCE,mBADD;MAAA,IAECC,kBAFD,GAGIH,SAHJ,CAECG,kBAFD;;MAIA,IAAID,mBAAmB,KAAK,CAAxB,IAA6BC,kBAAkB,KAAK,CAAxD,EAA2D;QAC1D;QACA;QACAJ,oBAAoB,GAAG,KAAvB;MACA,CAJD,MAKK,IAAIG,mBAAmB,KAAK,CAAxB,IAA6BC,kBAAkB,GAAG,CAAtD,EAAyD;QAC7D;QACA;QACAJ,oBAAoB,GAAG,KAAvB;MACA;IACD,CApBoB,CAsBrB;;;IACA,IAAIA,oBAAJ,EAA0B;MACzBN,yBAAyB;IACzB;EACD;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useHandleItemsPropertyChange.js","names":["useHandleItemsPropertyChange","itemsProperty","virtualScroller","preserveScrollPosition","preserveScrollPositionOnPrependItems","nextItems","previousItemsProperty","useRef","hasItemsPropertyChanged","current","shouldUpdateItems","itemsDiff","getItemsDiff","prependedItemsCount","appendedItemsCount","setItems"],"sources":["../../source/react/useHandleItemsPropertyChange.js"],"sourcesContent":["import { useRef } from 'react'\r\n\r\n// If new `items` property is passed:\r\n//\r\n// * Store the scroll Y position for the first one of the current items\r\n// so that it could potentially (in some cases) be restored after the\r\n// new `items` are rendered.\r\n//\r\n// * Call `VirtualScroller.setItems()` function.\r\n//\r\nexport default function useHandleItemsPropertyChange(itemsProperty, {\r\n\tvirtualScroller,\r\n\t// `preserveScrollPosition` property name is deprecated,\r\n\t// use `preserveScrollPositionOnPrependItems` property instead.\r\n\tpreserveScrollPosition,\r\n\tpreserveScrollPositionOnPrependItems,\r\n\tnextItems\r\n}) {\r\n\t// During render, check if the `items` list has changed.\r\n\t// If it has, capture the Y scroll position and updated item element `key`s.\r\n\r\n\t// A long \"advanced\" sidenote on why capturing scroll Y position\r\n\t// is done during render instead of in an \"effect\":\r\n\t//\r\n\t// Previously, capturing scroll Y position was being done in `useLayoutEffect()`\r\n\t// but it was later found out that it wouldn't work for a \"Show previous\" button\r\n\t// scenario because that button would get hidden by the time `useLayoutEffect()`\r\n\t// gets called when there're no more \"previous\" items to show.\r\n\t//\r\n\t// Consider this code example:\r\n\t//\r\n\t// const { fromIndex, items } = this.state\r\n\t// const items = allItems.slice(fromIndex)\r\n\t// return (\r\n\t// \t{fromIndex > 0 &&\r\n\t// \t\t<button onClick={this.onShowPrevious}>\r\n\t// \t\t\tShow previous\r\n\t// \t\t</button>\r\n\t// \t}\r\n\t// \t<VirtualScroller\r\n\t// \t\titems={items}\r\n\t// \t\titemComponent={ItemComponent}/>\r\n\t// )\r\n\t//\r\n\t// Consider a user clicks \"Show previous\" to show the items from the start.\r\n\t// By the time `componentDidUpdate()` is called on `<VirtualScroller/>`,\r\n\t// the \"Show previous\" button has already been hidden\r\n\t// (because there're no more \"previous\" items)\r\n\t// which results in the scroll Y position jumping forward\r\n\t// by the height of that \"Show previous\" button.\r\n\t// This is because `<VirtualScroller/>` captures scroll Y\r\n\t// position when items are prepended via `.setItems()`\r\n\t// when the \"Show previous\" button is still being shown,\r\n\t// and then restores scroll Y position in `.onRender()`\r\n\t// when the \"Show previous\" button has already been hidden:\r\n\t// that's the reason for the scroll Y \"jump\".\r\n\t//\r\n\t// To prevent that, scroll Y position is captured at `render()`\r\n\t// time rather than later in `componentDidUpdate()`: this way,\r\n\t// scroll Y position is captured while the \"Show previous\" button\r\n\t// is still being shown.\r\n\r\n\tconst previousItemsProperty = useRef(itemsProperty)\r\n\tconst hasItemsPropertyChanged = itemsProperty !== previousItemsProperty.current\r\n\tpreviousItemsProperty.current = itemsProperty\r\n\r\n\tif (hasItemsPropertyChanged) {\r\n\t\tlet shouldUpdateItems = true\r\n\r\n\t\t// Analyze the upcoming `items` change.\r\n\t\tconst itemsDiff = virtualScroller.getItemsDiff(nextItems, itemsProperty)\r\n\r\n\t\t// `itemsDiff` will be `undefined` in case of a non-incremental items list change.\r\n\t\tif (itemsDiff) {\r\n\t\t\tconst {\r\n\t\t\t\tprependedItemsCount,\r\n\t\t\t\tappendedItemsCount\r\n\t\t\t} = itemsDiff\r\n\t\t\tif (prependedItemsCount === 0 && appendedItemsCount === 0) {\r\n\t\t\t\t// The items order hasn't changed.\r\n\t\t\t\t// No need to update them in `VirtualScroller` or to snapshot the Y scroll position.\r\n\t\t\t\tshouldUpdateItems = false\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (shouldUpdateItems) {\r\n\t\t\t// Request to update the `items` in `VirtualScroller`.\r\n\t\t\t// This will result in a `setState()` call.\r\n\t\t\t// The new items won't be rendered until that state update is applied.\r\n\t\t\tvirtualScroller.setItems(itemsProperty, {\r\n\t\t\t\t// `preserveScrollPosition` property name is deprecated,\r\n\t\t\t\t// use `preserveScrollPositionOnPrependItems` property instead.\r\n\t\t\t\tpreserveScrollPositionOnPrependItems: preserveScrollPositionOnPrependItems || preserveScrollPosition\r\n\t\t\t})\r\n\t\t}\r\n\t}\r\n}"],"mappings":";;;;;;;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAASA,4BAAT,CAAsCC,aAAtC,QAOZ;EAAA,IANFC,eAME,QANFA,eAME;EAAA,IAHFC,sBAGE,QAHFA,sBAGE;EAAA,IAFFC,oCAEE,QAFFA,oCAEE;EAAA,IADFC,SACE,QADFA,SACE;EACF;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA,IAAMC,qBAAqB,GAAG,IAAAC,aAAA,EAAON,aAAP,CAA9B;EACA,IAAMO,uBAAuB,GAAGP,aAAa,KAAKK,qBAAqB,CAACG,OAAxE;EACAH,qBAAqB,CAACG,OAAtB,GAAgCR,aAAhC;;EAEA,IAAIO,uBAAJ,EAA6B;IAC5B,IAAIE,iBAAiB,GAAG,IAAxB,CAD4B,CAG5B;;IACA,IAAMC,SAAS,GAAGT,eAAe,CAACU,YAAhB,CAA6BP,SAA7B,EAAwCJ,aAAxC,CAAlB,CAJ4B,CAM5B;;IACA,IAAIU,SAAJ,EAAe;MACd,IACCE,mBADD,GAGIF,SAHJ,CACCE,mBADD;MAAA,IAECC,kBAFD,GAGIH,SAHJ,CAECG,kBAFD;;MAIA,IAAID,mBAAmB,KAAK,CAAxB,IAA6BC,kBAAkB,KAAK,CAAxD,EAA2D;QAC1D;QACA;QACAJ,iBAAiB,GAAG,KAApB;MACA;IACD;;IAED,IAAIA,iBAAJ,EAAuB;MACtB;MACA;MACA;MACAR,eAAe,CAACa,QAAhB,CAAyBd,aAAzB,EAAwC;QACvC;QACA;QACAG,oCAAoC,EAAEA,oCAAoC,IAAID;MAHvC,CAAxC;IAKA;EACD;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useOnItemHeightChange.js","names":["useOnItemHeightChange","initialItemsCount","virtualScroller","initialCacheValue","useMemo","Array","cache","useRef","getOnItemHeightChange","useCallback","i","current","onItemHeightChange"],"sources":["../../source/react/useOnItemHeightChange.js"],"sourcesContent":["import { useMemo, useRef, useCallback } from 'react'\r\n\r\nexport default function useOnItemHeightChange({\r\n\tinitialItemsCount,\r\n\tvirtualScroller\r\n}) {\r\n\t// Only compute the initial cache value once.\r\n\tconst initialCacheValue = useMemo(() => {\r\n\t\treturn new Array(initialItemsCount)\r\n\t}, [])\r\n\r\n\t// Handler functions cache.\r\n\tconst cache = useRef(initialCacheValue)\r\n\r\n\t// Caches per-item `onItemHeightChange` functions' \"references\"\r\n\t// so that item components don't get re-rendered needlessly.\r\n\tconst getOnItemHeightChange = useCallback((i) => {\r\n\t\tif (!cache.current[i]) {\r\n\t\t\tcache.current[i] = () => virtualScroller.onItemHeightChange(i)\r\n\t\t}\r\n\t\treturn cache.current[i]\r\n\t}, [\r\n\t\tvirtualScroller,\r\n\t\tcache\r\n\t])\r\n\r\n\treturn getOnItemHeightChange\r\n}"],"mappings":";;;;;;;AAAA;;AAEe,SAASA,qBAAT,OAGZ;EAAA,IAFFC,iBAEE,QAFFA,iBAEE;EAAA,IADFC,eACE,QADFA,eACE;EACF;EACA,IAAMC,iBAAiB,GAAG,IAAAC,cAAA,EAAQ,YAAM;IACvC,OAAO,IAAIC,KAAJ,CAAUJ,iBAAV,CAAP;EACA,CAFyB,EAEvB,EAFuB,CAA1B,CAFE,CAMF;;EACA,IAAMK,KAAK,GAAG,IAAAC,aAAA,EAAOJ,iBAAP,CAAd,CAPE,CASF;EACA;;EACA,IAAMK,qBAAqB,GAAG,IAAAC,kBAAA,EAAY,UAACC,CAAD,EAAO;IAChD,IAAI,CAACJ,KAAK,CAACK,OAAN,CAAcD,CAAd,CAAL,EAAuB;MACtBJ,KAAK,CAACK,OAAN,CAAcD,CAAd,IAAmB;QAAA,OAAMR,eAAe,CAACU,kBAAhB,CAAmCF,CAAnC,CAAN;MAAA,CAAnB;IACA;;IACD,OAAOJ,KAAK,CAACK,OAAN,CAAcD,CAAd,CAAP;EACA,CAL6B,EAK3B,CACFR,eADE,EAEFI,KAFE,CAL2B,CAA9B;EAUA,OAAOE,qBAAP;AACA"}
@@ -1,45 +0,0 @@
1
- import { useRef } from 'react'; // If the order of the `items` changes, or new `items` get prepended resulting in a "shift":
2
- //
3
- // * Re-generate the React `key` prefix for item elements
4
- // so that all item components are re-rendered for the new `items` list.
5
- // That's because item components may have their own internal state,
6
- // and simply passing another `item` property for an item component
7
- // might result in bugs, which React would do with its "re-using" policy
8
- // if the unique `key` workaround hasn't been used.
9
- //
10
-
11
- export default function useHandleItemIndexesChange(_ref) {
12
- var virtualScroller = _ref.virtualScroller,
13
- itemsBeingRendered = _ref.itemsBeingRendered,
14
- updateItemKeysForNewItems = _ref.updateItemKeysForNewItems;
15
- var previousItemsBeingRenderedRef = useRef(itemsBeingRendered);
16
- var previousItemsBeingRendered = previousItemsBeingRenderedRef.current;
17
- var haveItemsChanged = itemsBeingRendered !== previousItemsBeingRendered;
18
- previousItemsBeingRenderedRef.current = itemsBeingRendered;
19
-
20
- if (haveItemsChanged) {
21
- var shouldUpdateItemKeys = true;
22
- var itemsDiff = virtualScroller.getItemsDiff(previousItemsBeingRendered, itemsBeingRendered); // `itemsDiff` will be `undefined` in case of a non-incremental items list change.
23
-
24
- if (itemsDiff) {
25
- var prependedItemsCount = itemsDiff.prependedItemsCount,
26
- appendedItemsCount = itemsDiff.appendedItemsCount;
27
-
28
- if (prependedItemsCount === 0 && appendedItemsCount === 0) {
29
- // The items order hasn't changed.
30
- // No need to re-generate the `key` prefix.
31
- shouldUpdateItemKeys = false;
32
- } else if (prependedItemsCount === 0 && appendedItemsCount > 0) {
33
- // The item order hasn't changed.
34
- // No need to re-generate the `key` prefix.
35
- shouldUpdateItemKeys = false;
36
- }
37
- } // Update React element `key`s for the new set of `items`.
38
-
39
-
40
- if (shouldUpdateItemKeys) {
41
- updateItemKeysForNewItems();
42
- }
43
- }
44
- }
45
- //# sourceMappingURL=useHandleItemIndexesChange.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useHandleItemIndexesChange.js","names":["useRef","useHandleItemIndexesChange","virtualScroller","itemsBeingRendered","updateItemKeysForNewItems","previousItemsBeingRenderedRef","previousItemsBeingRendered","current","haveItemsChanged","shouldUpdateItemKeys","itemsDiff","getItemsDiff","prependedItemsCount","appendedItemsCount"],"sources":["../../source/react/useHandleItemIndexesChange.js"],"sourcesContent":["import { useRef } from 'react'\r\n\r\n// If the order of the `items` changes, or new `items` get prepended resulting in a \"shift\":\r\n//\r\n// * Re-generate the React `key` prefix for item elements\r\n// so that all item components are re-rendered for the new `items` list.\r\n// That's because item components may have their own internal state,\r\n// and simply passing another `item` property for an item component\r\n// might result in bugs, which React would do with its \"re-using\" policy\r\n// if the unique `key` workaround hasn't been used.\r\n//\r\nexport default function useHandleItemIndexesChange({\r\n\tvirtualScroller,\r\n\titemsBeingRendered,\r\n\tupdateItemKeysForNewItems\r\n}) {\r\n\tconst previousItemsBeingRenderedRef = useRef(itemsBeingRendered)\r\n\tconst previousItemsBeingRendered = previousItemsBeingRenderedRef.current\r\n\tconst haveItemsChanged = itemsBeingRendered !== previousItemsBeingRendered\r\n\tpreviousItemsBeingRenderedRef.current = itemsBeingRendered\r\n\r\n\tif (haveItemsChanged) {\r\n\t\tlet shouldUpdateItemKeys = true\r\n\r\n\t\tconst itemsDiff = virtualScroller.getItemsDiff(previousItemsBeingRendered, itemsBeingRendered)\r\n\t\t// `itemsDiff` will be `undefined` in case of a non-incremental items list change.\r\n\t\tif (itemsDiff) {\r\n\t\t\tconst {\r\n\t\t\t\tprependedItemsCount,\r\n\t\t\t\tappendedItemsCount\r\n\t\t\t} = itemsDiff\r\n\t\t\tif (prependedItemsCount === 0 && appendedItemsCount === 0) {\r\n\t\t\t\t// The items order hasn't changed.\r\n\t\t\t\t// No need to re-generate the `key` prefix.\r\n\t\t\t\tshouldUpdateItemKeys = false\r\n\t\t\t}\r\n\t\t\telse if (prependedItemsCount === 0 && appendedItemsCount > 0) {\r\n\t\t\t\t// The item order hasn't changed.\r\n\t\t\t\t// No need to re-generate the `key` prefix.\r\n\t\t\t\tshouldUpdateItemKeys = false\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Update React element `key`s for the new set of `items`.\r\n\t\tif (shouldUpdateItemKeys) {\r\n\t\t\tupdateItemKeysForNewItems()\r\n\t\t}\r\n\t}\r\n}"],"mappings":"AAAA,SAASA,MAAT,QAAuB,OAAvB,C,CAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA,eAAe,SAASC,0BAAT,OAIZ;EAAA,IAHFC,eAGE,QAHFA,eAGE;EAAA,IAFFC,kBAEE,QAFFA,kBAEE;EAAA,IADFC,yBACE,QADFA,yBACE;EACF,IAAMC,6BAA6B,GAAGL,MAAM,CAACG,kBAAD,CAA5C;EACA,IAAMG,0BAA0B,GAAGD,6BAA6B,CAACE,OAAjE;EACA,IAAMC,gBAAgB,GAAGL,kBAAkB,KAAKG,0BAAhD;EACAD,6BAA6B,CAACE,OAA9B,GAAwCJ,kBAAxC;;EAEA,IAAIK,gBAAJ,EAAsB;IACrB,IAAIC,oBAAoB,GAAG,IAA3B;IAEA,IAAMC,SAAS,GAAGR,eAAe,CAACS,YAAhB,CAA6BL,0BAA7B,EAAyDH,kBAAzD,CAAlB,CAHqB,CAIrB;;IACA,IAAIO,SAAJ,EAAe;MACd,IACCE,mBADD,GAGIF,SAHJ,CACCE,mBADD;MAAA,IAECC,kBAFD,GAGIH,SAHJ,CAECG,kBAFD;;MAIA,IAAID,mBAAmB,KAAK,CAAxB,IAA6BC,kBAAkB,KAAK,CAAxD,EAA2D;QAC1D;QACA;QACAJ,oBAAoB,GAAG,KAAvB;MACA,CAJD,MAKK,IAAIG,mBAAmB,KAAK,CAAxB,IAA6BC,kBAAkB,GAAG,CAAtD,EAAyD;QAC7D;QACA;QACAJ,oBAAoB,GAAG,KAAvB;MACA;IACD,CApBoB,CAsBrB;;;IACA,IAAIA,oBAAJ,EAA0B;MACzBL,yBAAyB;IACzB;EACD;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useHandleItemsPropertyChange.js","names":["useRef","useHandleItemsPropertyChange","itemsProperty","virtualScroller","preserveScrollPosition","preserveScrollPositionOnPrependItems","nextItems","previousItemsProperty","hasItemsPropertyChanged","current","shouldUpdateItems","itemsDiff","getItemsDiff","prependedItemsCount","appendedItemsCount","setItems"],"sources":["../../source/react/useHandleItemsPropertyChange.js"],"sourcesContent":["import { useRef } from 'react'\r\n\r\n// If new `items` property is passed:\r\n//\r\n// * Store the scroll Y position for the first one of the current items\r\n// so that it could potentially (in some cases) be restored after the\r\n// new `items` are rendered.\r\n//\r\n// * Call `VirtualScroller.setItems()` function.\r\n//\r\nexport default function useHandleItemsPropertyChange(itemsProperty, {\r\n\tvirtualScroller,\r\n\t// `preserveScrollPosition` property name is deprecated,\r\n\t// use `preserveScrollPositionOnPrependItems` property instead.\r\n\tpreserveScrollPosition,\r\n\tpreserveScrollPositionOnPrependItems,\r\n\tnextItems\r\n}) {\r\n\t// During render, check if the `items` list has changed.\r\n\t// If it has, capture the Y scroll position and updated item element `key`s.\r\n\r\n\t// A long \"advanced\" sidenote on why capturing scroll Y position\r\n\t// is done during render instead of in an \"effect\":\r\n\t//\r\n\t// Previously, capturing scroll Y position was being done in `useLayoutEffect()`\r\n\t// but it was later found out that it wouldn't work for a \"Show previous\" button\r\n\t// scenario because that button would get hidden by the time `useLayoutEffect()`\r\n\t// gets called when there're no more \"previous\" items to show.\r\n\t//\r\n\t// Consider this code example:\r\n\t//\r\n\t// const { fromIndex, items } = this.state\r\n\t// const items = allItems.slice(fromIndex)\r\n\t// return (\r\n\t// \t{fromIndex > 0 &&\r\n\t// \t\t<button onClick={this.onShowPrevious}>\r\n\t// \t\t\tShow previous\r\n\t// \t\t</button>\r\n\t// \t}\r\n\t// \t<VirtualScroller\r\n\t// \t\titems={items}\r\n\t// \t\titemComponent={ItemComponent}/>\r\n\t// )\r\n\t//\r\n\t// Consider a user clicks \"Show previous\" to show the items from the start.\r\n\t// By the time `componentDidUpdate()` is called on `<VirtualScroller/>`,\r\n\t// the \"Show previous\" button has already been hidden\r\n\t// (because there're no more \"previous\" items)\r\n\t// which results in the scroll Y position jumping forward\r\n\t// by the height of that \"Show previous\" button.\r\n\t// This is because `<VirtualScroller/>` captures scroll Y\r\n\t// position when items are prepended via `.setItems()`\r\n\t// when the \"Show previous\" button is still being shown,\r\n\t// and then restores scroll Y position in `.onRender()`\r\n\t// when the \"Show previous\" button has already been hidden:\r\n\t// that's the reason for the scroll Y \"jump\".\r\n\t//\r\n\t// To prevent that, scroll Y position is captured at `render()`\r\n\t// time rather than later in `componentDidUpdate()`: this way,\r\n\t// scroll Y position is captured while the \"Show previous\" button\r\n\t// is still being shown.\r\n\r\n\tconst previousItemsProperty = useRef(itemsProperty)\r\n\tconst hasItemsPropertyChanged = itemsProperty !== previousItemsProperty.current\r\n\tpreviousItemsProperty.current = itemsProperty\r\n\r\n\tif (hasItemsPropertyChanged) {\r\n\t\tlet shouldUpdateItems = true\r\n\r\n\t\t// Analyze the upcoming `items` change.\r\n\t\tconst itemsDiff = virtualScroller.getItemsDiff(nextItems, itemsProperty)\r\n\r\n\t\t// `itemsDiff` will be `undefined` in case of a non-incremental items list change.\r\n\t\tif (itemsDiff) {\r\n\t\t\tconst {\r\n\t\t\t\tprependedItemsCount,\r\n\t\t\t\tappendedItemsCount\r\n\t\t\t} = itemsDiff\r\n\t\t\tif (prependedItemsCount === 0 && appendedItemsCount === 0) {\r\n\t\t\t\t// The items order hasn't changed.\r\n\t\t\t\t// No need to update them in `VirtualScroller` or to snapshot the Y scroll position.\r\n\t\t\t\tshouldUpdateItems = false\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (shouldUpdateItems) {\r\n\t\t\t// Request to update the `items` in `VirtualScroller`.\r\n\t\t\t// This will result in a `setState()` call.\r\n\t\t\t// The new items won't be rendered until that state update is applied.\r\n\t\t\tvirtualScroller.setItems(itemsProperty, {\r\n\t\t\t\t// `preserveScrollPosition` property name is deprecated,\r\n\t\t\t\t// use `preserveScrollPositionOnPrependItems` property instead.\r\n\t\t\t\tpreserveScrollPositionOnPrependItems: preserveScrollPositionOnPrependItems || preserveScrollPosition\r\n\t\t\t})\r\n\t\t}\r\n\t}\r\n}"],"mappings":"AAAA,SAASA,MAAT,QAAuB,OAAvB,C,CAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA,eAAe,SAASC,4BAAT,CAAsCC,aAAtC,QAOZ;EAAA,IANFC,eAME,QANFA,eAME;EAAA,IAHFC,sBAGE,QAHFA,sBAGE;EAAA,IAFFC,oCAEE,QAFFA,oCAEE;EAAA,IADFC,SACE,QADFA,SACE;EACF;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA,IAAMC,qBAAqB,GAAGP,MAAM,CAACE,aAAD,CAApC;EACA,IAAMM,uBAAuB,GAAGN,aAAa,KAAKK,qBAAqB,CAACE,OAAxE;EACAF,qBAAqB,CAACE,OAAtB,GAAgCP,aAAhC;;EAEA,IAAIM,uBAAJ,EAA6B;IAC5B,IAAIE,iBAAiB,GAAG,IAAxB,CAD4B,CAG5B;;IACA,IAAMC,SAAS,GAAGR,eAAe,CAACS,YAAhB,CAA6BN,SAA7B,EAAwCJ,aAAxC,CAAlB,CAJ4B,CAM5B;;IACA,IAAIS,SAAJ,EAAe;MACd,IACCE,mBADD,GAGIF,SAHJ,CACCE,mBADD;MAAA,IAECC,kBAFD,GAGIH,SAHJ,CAECG,kBAFD;;MAIA,IAAID,mBAAmB,KAAK,CAAxB,IAA6BC,kBAAkB,KAAK,CAAxD,EAA2D;QAC1D;QACA;QACAJ,iBAAiB,GAAG,KAApB;MACA;IACD;;IAED,IAAIA,iBAAJ,EAAuB;MACtB;MACA;MACA;MACAP,eAAe,CAACY,QAAhB,CAAyBb,aAAzB,EAAwC;QACvC;QACA;QACAG,oCAAoC,EAAEA,oCAAoC,IAAID;MAHvC,CAAxC;IAKA;EACD;AACD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"useOnItemHeightChange.js","names":["useMemo","useRef","useCallback","useOnItemHeightChange","initialItemsCount","virtualScroller","initialCacheValue","Array","cache","getOnItemHeightChange","i","current","onItemHeightChange"],"sources":["../../source/react/useOnItemHeightChange.js"],"sourcesContent":["import { useMemo, useRef, useCallback } from 'react'\r\n\r\nexport default function useOnItemHeightChange({\r\n\tinitialItemsCount,\r\n\tvirtualScroller\r\n}) {\r\n\t// Only compute the initial cache value once.\r\n\tconst initialCacheValue = useMemo(() => {\r\n\t\treturn new Array(initialItemsCount)\r\n\t}, [])\r\n\r\n\t// Handler functions cache.\r\n\tconst cache = useRef(initialCacheValue)\r\n\r\n\t// Caches per-item `onItemHeightChange` functions' \"references\"\r\n\t// so that item components don't get re-rendered needlessly.\r\n\tconst getOnItemHeightChange = useCallback((i) => {\r\n\t\tif (!cache.current[i]) {\r\n\t\t\tcache.current[i] = () => virtualScroller.onItemHeightChange(i)\r\n\t\t}\r\n\t\treturn cache.current[i]\r\n\t}, [\r\n\t\tvirtualScroller,\r\n\t\tcache\r\n\t])\r\n\r\n\treturn getOnItemHeightChange\r\n}"],"mappings":"AAAA,SAASA,OAAT,EAAkBC,MAAlB,EAA0BC,WAA1B,QAA6C,OAA7C;AAEA,eAAe,SAASC,qBAAT,OAGZ;EAAA,IAFFC,iBAEE,QAFFA,iBAEE;EAAA,IADFC,eACE,QADFA,eACE;EACF;EACA,IAAMC,iBAAiB,GAAGN,OAAO,CAAC,YAAM;IACvC,OAAO,IAAIO,KAAJ,CAAUH,iBAAV,CAAP;EACA,CAFgC,EAE9B,EAF8B,CAAjC,CAFE,CAMF;;EACA,IAAMI,KAAK,GAAGP,MAAM,CAACK,iBAAD,CAApB,CAPE,CASF;EACA;;EACA,IAAMG,qBAAqB,GAAGP,WAAW,CAAC,UAACQ,CAAD,EAAO;IAChD,IAAI,CAACF,KAAK,CAACG,OAAN,CAAcD,CAAd,CAAL,EAAuB;MACtBF,KAAK,CAACG,OAAN,CAAcD,CAAd,IAAmB;QAAA,OAAML,eAAe,CAACO,kBAAhB,CAAmCF,CAAnC,CAAN;MAAA,CAAnB;IACA;;IACD,OAAOF,KAAK,CAACG,OAAN,CAAcD,CAAd,CAAP;EACA,CALwC,EAKtC,CACFL,eADE,EAEFG,KAFE,CALsC,CAAzC;EAUA,OAAOC,qBAAP;AACA"}