virtual-scroller 1.12.2 → 1.12.4

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 (104) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/CODE_OF_CONDUCT.md +78 -0
  3. package/README.md +1 -1
  4. package/bundle/index-bypass.html +2 -2
  5. package/bundle/index-grid.html +2 -2
  6. package/bundle/index-scrollableContainer.html +2 -2
  7. package/bundle/virtual-scroller-dom.js +1 -1
  8. package/bundle/virtual-scroller-dom.js.map +1 -1
  9. package/bundle/virtual-scroller-react.js +1 -1
  10. package/bundle/virtual-scroller-react.js.map +1 -1
  11. package/bundle/virtual-scroller.js +1 -1
  12. package/bundle/virtual-scroller.js.map +1 -1
  13. package/commonjs/Layout.js +10 -13
  14. package/commonjs/Layout.js.map +1 -1
  15. package/commonjs/Layout.test.js +20 -10
  16. package/commonjs/Layout.test.js.map +1 -1
  17. package/commonjs/Scroll.js +2 -1
  18. package/commonjs/Scroll.js.map +1 -1
  19. package/commonjs/{Resize.js → ScrollableContainerResizeHandler.js} +7 -7
  20. package/commonjs/ScrollableContainerResizeHandler.js.map +1 -0
  21. package/commonjs/VirtualScroller.constructor.js +5 -5
  22. package/commonjs/VirtualScroller.constructor.js.map +1 -1
  23. package/commonjs/VirtualScroller.items.js +1 -1
  24. package/commonjs/VirtualScroller.items.js.map +1 -1
  25. package/commonjs/VirtualScroller.js +67 -28
  26. package/commonjs/VirtualScroller.js.map +1 -1
  27. package/commonjs/VirtualScroller.layout.js +24 -22
  28. package/commonjs/VirtualScroller.layout.js.map +1 -1
  29. package/commonjs/{VirtualScroller.resize.js → VirtualScroller.onContainerResize.js} +2 -2
  30. package/commonjs/VirtualScroller.onContainerResize.js.map +1 -0
  31. package/commonjs/VirtualScroller.onRender.js +2 -2
  32. package/commonjs/VirtualScroller.onRender.js.map +1 -1
  33. package/commonjs/react/VirtualScroller.js +3 -0
  34. package/commonjs/react/VirtualScroller.js.map +1 -1
  35. package/commonjs/react/useEffectDontMountTwiceInStrictMode.js +83 -0
  36. package/commonjs/react/useEffectDontMountTwiceInStrictMode.js.map +1 -0
  37. package/commonjs/react/useInsertionEffectDontMountTwiceInStrictMode.js +20 -0
  38. package/commonjs/react/useInsertionEffectDontMountTwiceInStrictMode.js.map +1 -0
  39. package/commonjs/react/useLayoutEffectDontMountTwiceInStrictMode.js +20 -0
  40. package/commonjs/react/useLayoutEffectDontMountTwiceInStrictMode.js.map +1 -0
  41. package/commonjs/react/useState.js +13 -7
  42. package/commonjs/react/useState.js.map +1 -1
  43. package/commonjs/react/useStateNoStaleBug.js +59 -0
  44. package/commonjs/react/useStateNoStaleBug.js.map +1 -0
  45. package/modules/Layout.js +10 -13
  46. package/modules/Layout.js.map +1 -1
  47. package/modules/Layout.test.js +20 -10
  48. package/modules/Layout.test.js.map +1 -1
  49. package/modules/Scroll.js +2 -1
  50. package/modules/Scroll.js.map +1 -1
  51. package/modules/{Resize.js → ScrollableContainerResizeHandler.js} +7 -7
  52. package/modules/ScrollableContainerResizeHandler.js.map +1 -0
  53. package/modules/VirtualScroller.constructor.js +5 -5
  54. package/modules/VirtualScroller.constructor.js.map +1 -1
  55. package/modules/VirtualScroller.items.js +1 -1
  56. package/modules/VirtualScroller.items.js.map +1 -1
  57. package/modules/VirtualScroller.js +67 -28
  58. package/modules/VirtualScroller.js.map +1 -1
  59. package/modules/VirtualScroller.layout.js +24 -22
  60. package/modules/VirtualScroller.layout.js.map +1 -1
  61. package/modules/{VirtualScroller.resize.js → VirtualScroller.onContainerResize.js} +2 -2
  62. package/modules/VirtualScroller.onContainerResize.js.map +1 -0
  63. package/modules/VirtualScroller.onRender.js +2 -2
  64. package/modules/VirtualScroller.onRender.js.map +1 -1
  65. package/modules/react/VirtualScroller.js +3 -1
  66. package/modules/react/VirtualScroller.js.map +1 -1
  67. package/modules/react/useEffectDontMountTwiceInStrictMode.js +75 -0
  68. package/modules/react/useEffectDontMountTwiceInStrictMode.js.map +1 -0
  69. package/modules/react/useInsertionEffectDontMountTwiceInStrictMode.js +9 -0
  70. package/modules/react/useInsertionEffectDontMountTwiceInStrictMode.js.map +1 -0
  71. package/modules/react/useLayoutEffectDontMountTwiceInStrictMode.js +9 -0
  72. package/modules/react/useLayoutEffectDontMountTwiceInStrictMode.js.map +1 -0
  73. package/modules/react/useState.js +11 -8
  74. package/modules/react/useState.js.map +1 -1
  75. package/modules/react/useStateNoStaleBug.js +51 -0
  76. package/modules/react/useStateNoStaleBug.js.map +1 -0
  77. package/package.json +1 -1
  78. package/source/Layout.js +10 -13
  79. package/source/Layout.test.js +20 -10
  80. package/source/Scroll.js +1 -0
  81. package/source/{Resize.js → ScrollableContainerResizeHandler.js} +1 -1
  82. package/source/VirtualScroller.constructor.js +5 -5
  83. package/source/VirtualScroller.items.js +1 -1
  84. package/source/VirtualScroller.js +65 -25
  85. package/source/VirtualScroller.layout.js +22 -20
  86. package/source/{VirtualScroller.resize.js → VirtualScroller.onContainerResize.js} +1 -1
  87. package/source/VirtualScroller.onRender.js +2 -2
  88. package/source/react/VirtualScroller.js +3 -0
  89. package/source/react/useEffectDontMountTwiceInStrictMode.js +68 -0
  90. package/source/react/useInsertionEffectDontMountTwiceInStrictMode.js +10 -0
  91. package/source/react/useLayoutEffectDontMountTwiceInStrictMode.js +10 -0
  92. package/source/react/useState.js +8 -5
  93. package/source/react/useStateNoStaleBug.js +35 -0
  94. package/website/index-bypass.html +4 -14
  95. package/website/index-dom.html +1 -1
  96. package/website/index-grid.html +4 -4
  97. package/website/index-scrollableContainer.html +4 -4
  98. package/website/index-tbody-scrollableContainer.html +2 -0
  99. package/website/index-tbody.html +2 -0
  100. package/website/index.html +3 -3
  101. package/commonjs/Resize.js.map +0 -1
  102. package/commonjs/VirtualScroller.resize.js.map +0 -1
  103. package/modules/Resize.js.map +0 -1
  104. package/modules/VirtualScroller.resize.js.map +0 -1
@@ -49,7 +49,11 @@ export default class VirtualScroller {
49
49
  }
50
50
  }
51
51
 
52
- log('~ Start ~')
52
+ if (isRestart) {
53
+ log('~ Start (restart) ~')
54
+ } else {
55
+ log('~ Start ~')
56
+ }
53
57
 
54
58
  // `this._isActive = true` should be placed somewhere at the start of this function.
55
59
  this._isActive = true
@@ -71,18 +75,13 @@ export default class VirtualScroller {
71
75
  }
72
76
  }
73
77
 
74
- // If there was a pending state update that didn't get applied
75
- // because of stopping the `VirtualScroller`, apply that state update now.
76
- //
77
- // The pending state update won't get applied if the scrollable container width
78
- // has changed but that's ok because that state update currently could only contain:
79
- // * `scrollableContainerWidth`
80
- // * `verticalSpacing`
81
- // * `beforeResize`
82
- // All of those get rewritten in `onResize()` anyway.
83
- //
84
- let stateUpdate = this._stoppedStateUpdate
85
- this._stoppedStateUpdate = undefined
78
+ // If there was a pending "after render" state update that didn't get applied
79
+ // because the `VirtualScroller` got stopped, then apply that pending "after render"
80
+ // state update now. Such state update could include properties like:
81
+ // * A `verticalSpacing` that has been measured in `onRender()`.
82
+ // * A cleaned-up `beforeResize` object that was cleaned-up in `onRender()`.
83
+ let stateUpdate = this._afterRenderStateUpdateThatWasStopped
84
+ this._afterRenderStateUpdateThatWasStopped = undefined
86
85
 
87
86
  // Reset `this.verticalSpacing` so that it re-measures it in cases when
88
87
  // the `VirtualScroller` was previously stopped and is now being restarted.
@@ -103,7 +102,7 @@ export default class VirtualScroller {
103
102
  }
104
103
  }
105
104
 
106
- this.resize.start()
105
+ this.scrollableContainerResizeHandler.start()
107
106
  this.scroll.start()
108
107
 
109
108
  // If `scrollableContainerWidth` hasn't been measured yet,
@@ -122,13 +121,11 @@ export default class VirtualScroller {
122
121
  const prevWidth = this.getState().scrollableContainerWidth
123
122
  if (newWidth !== prevWidth) {
124
123
  log('~ Scrollable container width changed from', prevWidth, 'to', newWidth, '~')
125
- // `stateUpdate` doesn't get passed to `this.onResize()`, and, therefore,
126
- // won't be applied. But that's ok because currently it could only contain:
127
- // * `scrollableContainerWidth`
128
- // * `verticalSpacing`
129
- // * `beforeResize`
130
- // All of those get rewritten in `onResize()` anyway.
131
- return this.onResize()
124
+ // The pending state update (if present) won't be applied in this case.
125
+ // That's ok because such state update could currently only originate in
126
+ // `this.onContainerResize()` function. Therefore, alling `this.onContainerResize()` again
127
+ // would rewrite all those `stateUpdate` properties anyway, so they're not passed.
128
+ return this.onContainerResize()
132
129
  }
133
130
  }
134
131
 
@@ -139,7 +136,7 @@ export default class VirtualScroller {
139
136
  const columnsCount = this.getActualColumnsCount()
140
137
  const columnsCountFromState = this.getState().columnsCount || 1
141
138
  if (columnsCount !== columnsCountFromState) {
142
- return this.onResize()
139
+ return this.onContainerResize()
143
140
  }
144
141
  }
145
142
 
@@ -161,7 +158,7 @@ export default class VirtualScroller {
161
158
 
162
159
  log('~ Stop ~')
163
160
 
164
- this.resize.stop()
161
+ this.scrollableContainerResizeHandler.stop()
165
162
  this.scroll.stop()
166
163
 
167
164
  // Stop `ListTopOffsetWatcher` if it has been started.
@@ -221,7 +218,9 @@ export default class VirtualScroller {
221
218
  * @param {number} i — Item index
222
219
  */
223
220
  onItemHeightDidChange(i) {
224
- this.hasToBeStarted()
221
+ // See the comments in the `setItemState()` function below for the rationale
222
+ // on why the `hasToBeStarted()` check was commented out.
223
+ // this.hasToBeStarted()
225
224
  this._onItemHeightDidChange(i)
226
225
  }
227
226
 
@@ -231,7 +230,48 @@ export default class VirtualScroller {
231
230
  * @param {any} i — Item's new state
232
231
  */
233
232
  setItemState(i, newItemState) {
234
- this.hasToBeStarted()
233
+ // There is an issue in React 18.2.0 when `useInsertionEffect()` doesn't run twice
234
+ // on mount unlike `useLayoutEffect()` in "strict" mode. That causes a bug in a React
235
+ // implementation of the `virtual-scroller`.
236
+ // https://gitlab.com/catamphetamine/virtual-scroller/-/issues/33
237
+ // https://github.com/facebook/react/issues/26320
238
+ // A workaround for that bug is ignoring the second-initial run of the effects at mount.
239
+ //
240
+ // But in that case, if an `ItemComponent` calls `setItemState()` in `useLayoutEffect()`,
241
+ // it could result in a bug.
242
+ //
243
+ // Consider a type of `useLayoutEffect()` that skips the initial mount:
244
+ // `useLayoutEffectSkipInitialMount()`.
245
+ // Suppose that effect is written in such a way that it only skips the first call of itself.
246
+ // In that case, if React is run in "strict" mode, the effect will no longer work as expected
247
+ // and it won't actually skip the initial mount and will be executed during the second initial run.
248
+ // But the `VirtualScroller` itself has already implemented a workaround that prevents
249
+ // its hooks from running twice on mount. This means that `useVirtualScrollerStartStop()`
250
+ // of the React component would have already stopped the `VirtualScroller` by the time
251
+ // `ItemComponent`'s incorrectly-behaving `useLayoutEffectSkipInitialMount()` effect is run,
252
+ // resulting in an error: "`VirtualScroller` hasn't been started".
253
+ //
254
+ // The log when not in "strict" mode would be:
255
+ //
256
+ // * `useLayoutEffect()` is run in `ItemComponent` — skips the initial run.
257
+ // * `useLayoutEffect()` is run in `useVirtualScrollerStartStop()`. It starts the `VirtualScroller`.
258
+ // * Some dependency property gets updated inside `ItemComponent`.
259
+ // * `useLayoutEffect()` is run in `ItemComponent` — no longer skips. Calls `setItemState()`.
260
+ // * The `VirtualScroller` is started so it handles `setState()` correctly.
261
+ //
262
+ // The log when in "strict" mode would be:
263
+ //
264
+ // * `useLayoutEffect()` is run in `ItemComponent` — skips the initial run.
265
+ // * `useLayoutEffect()` is run in `useVirtualScrollerStartStop()`. It starts the `VirtualScroller`.
266
+ // * `useLayoutEffect()` is unmounted in `useVirtualScrollerStartStop()`. It stops the `VirtualScroller`.
267
+ // * `useLayoutEffect()` is unmounted in `ItemComponent` — does nothing.
268
+ // * `useLayoutEffect()` is run the second time in `ItemComponent` — no longer skips. Calls `setItemState()`.
269
+ // * The `VirtualScroller` is stopped so it throws an error: "`VirtualScroller` hasn't been started".
270
+ //
271
+ // For that reason, the requirement of the `VirtualScroller` to be started was commented out.
272
+ // Commenting it out wouldn't result in any potential bugs because the code would work correctly
273
+ // in both cases.
274
+ // this.hasToBeStarted()
235
275
  this._setItemState(i, newItemState)
236
276
  }
237
277
 
@@ -217,25 +217,22 @@ export default function() {
217
217
  })
218
218
  }
219
219
 
220
- function getVisibleArea() {
221
- const visibleArea = this.scroll.getVisibleAreaBounds()
222
- this.latestLayoutVisibleArea = visibleArea
220
+ function getCoordinatesOfVisibleAreaInsideTheList() {
221
+ const visibleAreaBounds = this.scroll.getVisibleAreaBounds()
222
+ this.latestLayoutVisibleArea = visibleAreaBounds
223
223
 
224
224
  // Subtract the top offset of the list inside the scrollable container.
225
225
  const listTopOffsetInsideScrollableContainer = this.getListTopOffsetInsideScrollableContainer()
226
226
  return {
227
- top: visibleArea.top - listTopOffsetInsideScrollableContainer,
228
- bottom: visibleArea.bottom - listTopOffsetInsideScrollableContainer
227
+ top: visibleAreaBounds.top - listTopOffsetInsideScrollableContainer,
228
+ bottom: visibleAreaBounds.bottom - listTopOffsetInsideScrollableContainer
229
229
  }
230
230
  }
231
231
 
232
232
  function getShownItemIndexes() {
233
233
  const itemsCount = this.getItemsCount()
234
234
 
235
- const {
236
- top: visibleAreaTop,
237
- bottom: visibleAreaBottom
238
- } = getVisibleArea.call(this)
235
+ const visibleAreaInsideTheList = getCoordinatesOfVisibleAreaInsideTheList.call(this)
239
236
 
240
237
  if (this.bypass) {
241
238
  return {
@@ -254,7 +251,7 @@ export default function() {
254
251
  // then it would also require listening for "scroll" events on the screen.
255
252
  // Overall, I suppose that such "actual visibility" feature would be
256
253
  // a very minor optimization and not something I'd deal with.
257
- const isVisible = visibleAreaTop < this.itemsContainer.getHeight() && visibleAreaBottom > 0
254
+ const isVisible = visibleAreaInsideTheList.top < this.itemsContainer.getHeight() + this.layout.getPrerenderMargin() && visibleAreaInsideTheList.bottom > 0 - this.layout.getPrerenderMargin()
258
255
  if (!isVisible) {
259
256
  log('The entire list is off-screen. No items are visible.')
260
257
  return this.layout.getNonVisibleListShownItemIndexes()
@@ -263,8 +260,7 @@ export default function() {
263
260
  // Get shown item indexes.
264
261
  return this.layout.getShownItemIndexes({
265
262
  itemsCount: this.getItemsCount(),
266
- visibleAreaTop,
267
- visibleAreaBottom
263
+ visibleAreaInsideTheList
268
264
  })
269
265
  }
270
266
 
@@ -441,9 +437,13 @@ export default function() {
441
437
  if (previousHeight !== newHeight) {
442
438
  log('~ Item height has changed. Should update layout. ~')
443
439
 
444
- // Update or reset a previously calculated layout
445
- // so that the "diff"s based on that layout in the future
446
- // produce correct results.
440
+ // Update or reset a previously calculated layout with the new item height
441
+ // so that the potential future "diff"s based on that "previously calculated" layout
442
+ // would be correct.
443
+ //
444
+ // The "previously calculated layout" feature is not currently used
445
+ // so this function call doesn't really affect anything.
446
+ //
447
447
  updatePreviouslyCalculatedLayoutOnItemHeightChange.call(this, i, previousHeight, newHeight)
448
448
 
449
449
  // Recalculate layout.
@@ -455,11 +455,13 @@ export default function() {
455
455
  // that might happen in the middle of the currently pending `setState()` operation
456
456
  // being applied, resulting in weird "race condition" bugs.
457
457
  //
458
- if (this.waitingForRender) {
459
- log('~ Another state update is already waiting to be rendered. Delay the layout update until then. ~')
460
- this.updateLayoutAfterRenderBecauseItemHeightChanged = true
461
- } else {
462
- this.onUpdateShownItemIndexes({ reason: LAYOUT_REASON.ITEM_HEIGHT_CHANGED })
458
+ if (this._isActive) {
459
+ if (this.waitingForRender) {
460
+ log('~ Another state update is already waiting to be rendered. Delay the layout update until then. ~')
461
+ this.updateLayoutAfterRenderBecauseItemHeightChanged = true
462
+ } else {
463
+ this.onUpdateShownItemIndexes({ reason: LAYOUT_REASON.ITEM_HEIGHT_CHANGED })
464
+ }
463
465
  }
464
466
 
465
467
  // If there was a request for `setState()` with new `items`, then the changes
@@ -1,7 +1,7 @@
1
1
  import log from './utility/debug.js'
2
2
 
3
3
  export default function() {
4
- this.onResize = () => {
4
+ this.onContainerResize = () => {
5
5
  // Reset "previously calculated layout".
6
6
  //
7
7
  // The "previously calculated layout" feature is not currently used.
@@ -63,7 +63,7 @@ export default function() {
63
63
  // would construct its own state object.
64
64
  if (!shallowEqual(newState, this.mostRecentSetStateValue)) {
65
65
  warn('The most recent state that was set', getStateSnapshot(this.mostRecentSetStateValue))
66
- reportError('The state that has been rendered is not the most recent one that was set')
66
+ reportError('`VirtualScroller` has been rendered with a `state` that is not equal to the most recently set one')
67
67
  }
68
68
  }
69
69
 
@@ -199,7 +199,7 @@ export default function() {
199
199
  }
200
200
 
201
201
  if (!this._isActive) {
202
- this._stoppedStateUpdate = stateUpdate
202
+ this._afterRenderStateUpdateThatWasStopped = stateUpdate
203
203
  return
204
204
  }
205
205
 
@@ -13,6 +13,8 @@ import useUpdateItemKeysOnItemsChange from './useUpdateItemKeysOnItemsChange.js'
13
13
  import useClassName from './useClassName.js'
14
14
  import useStyle from './useStyle.js'
15
15
 
16
+ import { warn } from '../utility/debug.js'
17
+
16
18
  // When `items` property changes:
17
19
  // * A new `items` property is supplied to the React component.
18
20
  // * The React component re-renders itself.
@@ -172,6 +174,7 @@ function VirtualScroller({
172
174
  // `onMount()` option is deprecated due to no longer being used.
173
175
  // If someone thinks there's a valid use case for it, create an issue.
174
176
  if (onMount) {
177
+ warn('`onMount` property is deprecated')
175
178
  onMount()
176
179
  }
177
180
  }, [])
@@ -0,0 +1,68 @@
1
+ import { useRef, useCallback } from 'react'
2
+
3
+ // A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
4
+ // in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
5
+ // https://github.com/facebook/react/issues/26320
6
+ export default function useEffectDontMountTwiceInStrictMode(useEffect, handler, dependencies) {
7
+ if (!Array.isArray(dependencies)) {
8
+ throw new Error('Dependencies argument must be an array')
9
+ }
10
+
11
+ const { onEffect } = useEffectStatus()
12
+ const { onChange } = usePrevousValue(dependencies)
13
+
14
+ useEffect(() => {
15
+ const { isInitialRun } = onEffect()
16
+ const previousDependencies = onChange(dependencies)
17
+ if (isInitialRun || !isShallowEqualArrays(previousDependencies, dependencies)) {
18
+ const cleanUpFunction = handler()
19
+ if (typeof cleanUpFunction === 'function') {
20
+ throw new Error('An effect can\'t return a clean-up function when used with `useEffectDontMountTwiceInStrictMode()` because the clean-up function won\'t behave correctly in that case')
21
+ }
22
+ }
23
+ }, dependencies)
24
+ }
25
+
26
+ function useEffectStatus() {
27
+ const hasMounted = useRef(false)
28
+
29
+ const onEffect = useCallback(() => {
30
+ const wasAlreadyMounted = hasMounted.current
31
+ hasMounted.current = true
32
+ return {
33
+ isInitialRun: !wasAlreadyMounted
34
+ }
35
+ }, [])
36
+
37
+ return {
38
+ onEffect
39
+ }
40
+ }
41
+
42
+ function usePrevousValue(value) {
43
+ const prevValue = useRef(value)
44
+
45
+ const onChange = useCallback((value) => {
46
+ const previousValue = prevValue.current
47
+ prevValue.current = value
48
+ return previousValue
49
+ }, [])
50
+
51
+ return {
52
+ onChange
53
+ }
54
+ }
55
+
56
+ function isShallowEqualArrays(a, b) {
57
+ if (a.length !== b.length) {
58
+ return false
59
+ }
60
+ let i = 0
61
+ while (i < a.length) {
62
+ if (a[i] !== b[i]) {
63
+ return false
64
+ }
65
+ i++
66
+ }
67
+ return true
68
+ }
@@ -0,0 +1,10 @@
1
+ import { useInsertionEffect } from 'react'
2
+
3
+ import useEffectDontMountTwiceInStrictMode from './useEffectDontMountTwiceInStrictMode.js'
4
+
5
+ // A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
6
+ // in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
7
+ // https://github.com/facebook/react/issues/26320
8
+ export default function useInsertionEffectDontMountTwiceInStrictMode(handler, dependencies) {
9
+ return useEffectDontMountTwiceInStrictMode(useInsertionEffect, handler, dependencies)
10
+ }
@@ -0,0 +1,10 @@
1
+ import { useLayoutEffect } from 'react'
2
+
3
+ import useEffectDontMountTwiceInStrictMode from './useEffectDontMountTwiceInStrictMode.js'
4
+
5
+ // A workaround for a React bug when `useInsertionEffect()` doesn't run twice on mount
6
+ // in "strict" mode unlike `useEffect()` and `useLayoutEffect()` do.
7
+ // https://github.com/facebook/react/issues/26320
8
+ export default function useLayoutEffectDontMountTwiceInStrictMode(handler, dependencies) {
9
+ return useEffectDontMountTwiceInStrictMode(useLayoutEffect, handler, dependencies)
10
+ }
@@ -1,7 +1,10 @@
1
1
  import log, { isDebug } from '../utility/debug.js'
2
2
  import getStateSnapshot from '../utility/getStateSnapshot.js'
3
3
 
4
- import { useState, useRef, useCallback, useLayoutEffect, useInsertionEffect } from 'react'
4
+ import { useRef, useCallback } from 'react'
5
+ import useStateNoStaleBug from './useStateNoStaleBug.js'
6
+ import useInsertionEffectDontMountTwiceInStrictMode from './useInsertionEffectDontMountTwiceInStrictMode.js'
7
+ import useLayoutEffectDontMountTwiceInStrictMode from './useLayoutEffectDontMountTwiceInStrictMode.js'
5
8
 
6
9
  // Creates state management functions.
7
10
  export default function _useState({
@@ -14,7 +17,7 @@ export default function _useState({
14
17
  // `VirtualScroller` state gets updated from this variable.
15
18
  // The reason for that is that `VirtualScroller` state must always
16
19
  // correspond exactly to what's currently rendered on the screen.
17
- const [_newState, _setNewState] = useState(initialState)
20
+ const [_newState, _setNewState] = useStateNoStaleBug(initialState)
18
21
 
19
22
  // This `state` reference is what `VirtualScroller` uses internally.
20
23
  // It's the "source of truth" on the actual `VirtualScroller` state.
@@ -137,7 +140,7 @@ export default function _useState({
137
140
  // So if `useInsertionEffect()` gets removed from React in some hypothetical future,
138
141
  // it could be replaced with using `ref`s on `ItemComponent`s to measure the DOM element heights.
139
142
  //
140
- useInsertionEffect(() => {
143
+ useInsertionEffectDontMountTwiceInStrictMode(() => {
141
144
  // Update the actual `VirtualScroller` state right before the DOM changes
142
145
  // are going to be applied for the requested state update.
143
146
  //
@@ -160,13 +163,13 @@ export default function _useState({
160
163
  // This hook doesn't do anything at the initial render.
161
164
  //
162
165
  if (isDebug()) {
163
- log('React: ~ The requested state is about to be applied in DOM. Set it as the `VirtualScroller` state. ~')
166
+ log('React: ~ The requested state is about to be applied in DOM. Setting it as the `VirtualScroller` state. ~')
164
167
  log(getStateSnapshot(_newState))
165
168
  }
166
169
  setState(_newState)
167
170
  }, [_newState])
168
171
 
169
- useLayoutEffect(() => {
172
+ useLayoutEffectDontMountTwiceInStrictMode(() => {
170
173
  // Call `onRender()` right after a requested state update has been applied,
171
174
  // and also right after the initial render.
172
175
  onRender()
@@ -0,0 +1,35 @@
1
+ import { useRef, useState, useCallback } from 'react'
2
+
3
+ // This hook fixes any weird intermediate inconsistent/invalid/stale state values.
4
+ // https://github.com/facebook/react/issues/25023#issuecomment-1480463544
5
+ export default function useStateNoStaleBug(initialState) {
6
+ // const latestValidState = useRef(initialState)
7
+ const latestWrittenState = useRef(initialState)
8
+ const [_state, _setState] = useState(initialState)
9
+
10
+ // Instead of dealing with a potentially out-of-sync (stale) state value,
11
+ // simply use the correct latest one.
12
+ const state = latestWrittenState.current
13
+
14
+ /*
15
+ let state
16
+ if (_state === latestWrittenState.current) {
17
+ state = _state
18
+ latestValidState.current = _state
19
+ } else {
20
+ // React bug detected: an out-of-sync (stale) state value received.
21
+ // Ignore the out-of-sync (stale) state value.
22
+ state = latestValidState.current
23
+ }
24
+ */
25
+
26
+ const setState = useCallback((newState) => {
27
+ if (typeof newState === 'function') {
28
+ throw new Error('Function argument of `setState()` function is not supported by this hook')
29
+ }
30
+ latestWrittenState.current = newState
31
+ _setState(newState)
32
+ }, [])
33
+
34
+ return [state, setState]
35
+ }
@@ -8,31 +8,21 @@
8
8
 
9
9
  <title>React VirtualScroller Demo</title>
10
10
 
11
- <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
12
- <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
11
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
12
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
13
13
  <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
14
14
  <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
15
15
 
16
16
  <script src="./virtual-scroller-react.js"></script>
17
- <script src="./on-scroll-to-react.js"></script>
17
+ <script src="./lib/on-scroll-to-react.js"></script>
18
18
  <script src="./messages.js"></script>
19
19
 
20
20
  <link rel="stylesheet" href="./style.css"/>
21
-
22
- <!--
23
- <style>
24
- #messages {
25
- border-top: 2px solid rgb(230, 236, 240);
26
- display: grid;
27
- grid-gap: 2em;
28
- }
29
- </style>
30
- -->
31
21
  </head>
32
22
 
33
23
  <body>
34
24
  <!-- http://tholman.com/github-corners/ -->
35
- <a title="Go to GitHub repo" href="https://github.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
25
+ <a title="Go to GitLab repo" href="https://gitlab.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitLab"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
36
26
 
37
27
  <div id="root"></div>
38
28
 
@@ -19,7 +19,7 @@
19
19
 
20
20
  <body>
21
21
  <!-- http://tholman.com/github-corners/ -->
22
- <a title="Go to GitHub repo" href="https://github.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
22
+ <a title="Go to GitLab repo" href="https://gitlab.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitLab"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
23
23
 
24
24
  <section class="container">
25
25
  <h1>
@@ -8,13 +8,13 @@
8
8
 
9
9
  <title>React VirtualScroller Demo (Grid Layout)</title>
10
10
 
11
- <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
12
- <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
11
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
12
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
13
13
  <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
14
14
  <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
15
15
 
16
16
  <script src="./virtual-scroller-react.js"></script>
17
- <script src="./on-scroll-to-react.js"></script>
17
+ <script src="./lib/on-scroll-to-react.js"></script>
18
18
  <script src="./messages.js"></script>
19
19
 
20
20
  <link rel="stylesheet" href="./style.css"/>
@@ -45,7 +45,7 @@
45
45
 
46
46
  <body>
47
47
  <!-- http://tholman.com/github-corners/ -->
48
- <a title="Go to GitHub repo" href="https://github.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
48
+ <a title="Go to GitLab repo" href="https://gitlab.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitLab"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
49
49
 
50
50
  <div id="root"></div>
51
51
 
@@ -8,13 +8,13 @@
8
8
 
9
9
  <title>React VirtualScroller Demo (Scrollable Container)</title>
10
10
 
11
- <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
12
- <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
11
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
12
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
13
13
  <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
14
14
  <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
15
15
 
16
16
  <script src="./virtual-scroller-react.js"></script>
17
- <script src="./on-scroll-to-react.js"></script>
17
+ <script src="./lib/on-scroll-to-react.js"></script>
18
18
  <script src="./messages.js"></script>
19
19
 
20
20
  <link rel="stylesheet" href="./style.css"/>
@@ -22,7 +22,7 @@
22
22
 
23
23
  <body>
24
24
  <!-- http://tholman.com/github-corners/ -->
25
- <a title="Go to GitHub repo" href="https://github.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
25
+ <a title="Go to GitLab repo" href="https://gitlab.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitLab"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
26
26
 
27
27
  <div id="root"></div>
28
28
 
@@ -2,6 +2,8 @@
2
2
  <html>
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
+ <!-- Fix document width for mobile devices. -->
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
7
  <title>
6
8
  VirtualScroller scrollable container test
7
9
  </title>
@@ -2,6 +2,8 @@
2
2
  <html>
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
+ <!-- Fix document width for mobile devices. -->
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
7
  <title>
6
8
  VirtualScroller scrollable container test
7
9
  </title>
@@ -8,8 +8,8 @@
8
8
 
9
9
  <title>React VirtualScroller Demo</title>
10
10
 
11
- <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
12
- <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
11
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
12
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
13
13
  <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.7.2/prop-types.min.js"></script>
14
14
  <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
15
15
 
@@ -22,7 +22,7 @@
22
22
 
23
23
  <body>
24
24
  <!-- http://tholman.com/github-corners/ -->
25
- <a title="Go to GitHub repo" href="https://github.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
25
+ <a title="Go to GitLab repo" href="https://gitlab.com/catamphetamine/virtual-scroller" class="github-corner" aria-label="View source on GitLab"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
26
26
 
27
27
  <div id="root"></div>
28
28