virtual-scroller 1.14.0 → 1.15.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 (216) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +309 -250
  3. package/bundle/index-dom-bypass.html +198 -0
  4. package/bundle/index-dom-grid.html +204 -0
  5. package/bundle/index-dom-scrollableContainer.html +215 -0
  6. package/bundle/index-dom-tbody-scrollableContainer.html +81 -0
  7. package/bundle/index-dom-tbody.html +65 -0
  8. package/bundle/index-dom.html +115 -84
  9. package/bundle/{index-bypass.html → index-react-bypass.html} +83 -78
  10. package/bundle/{index-grid.html → index-react-grid.html} +78 -91
  11. package/bundle/{index-scrollableContainer.html → index-react-scrollableContainer.html} +96 -91
  12. package/bundle/index-react-strictMode.html +199 -0
  13. package/bundle/index-react-tbody-scrollableContainer.html +96 -0
  14. package/bundle/index-react-tbody.html +80 -0
  15. package/bundle/{messages.js → items.js} +1 -1
  16. package/bundle/lib/babel.min.js +25 -0
  17. package/bundle/lib/prop-types.min.js +1 -0
  18. package/bundle/lib/react-dom.development.js +29924 -0
  19. package/bundle/lib/react-dom.production.min.js +267 -0
  20. package/bundle/lib/react.development.js +3343 -0
  21. package/bundle/lib/react.production.min.js +31 -0
  22. package/bundle/style.base.css +33 -0
  23. package/{website/style.css → bundle/style.list.css} +10 -43
  24. package/bundle/style.list.grid.css +23 -0
  25. package/bundle/virtual-scroller-dom.js +1 -1
  26. package/bundle/virtual-scroller-dom.js.map +1 -1
  27. package/bundle/virtual-scroller-react.js +1 -1
  28. package/bundle/virtual-scroller-react.js.map +1 -1
  29. package/bundle/virtual-scroller.js +1 -1
  30. package/bundle/virtual-scroller.js.map +1 -1
  31. package/commonjs/BeforeResize.js +1 -2
  32. package/commonjs/BeforeResize.js.map +1 -1
  33. package/commonjs/DOM/VirtualScroller.js +7 -13
  34. package/commonjs/DOM/VirtualScroller.js.map +1 -1
  35. package/commonjs/DOM/tbody.js +6 -6
  36. package/commonjs/DOM/tbody.js.map +1 -1
  37. package/commonjs/ItemHeights.js +10 -13
  38. package/commonjs/ItemHeights.js.map +1 -1
  39. package/commonjs/Layout.defaults.js +21 -0
  40. package/commonjs/Layout.defaults.js.map +1 -0
  41. package/commonjs/Layout.js +75 -64
  42. package/commonjs/Layout.js.map +1 -1
  43. package/commonjs/Layout.test.js +8 -4
  44. package/commonjs/Layout.test.js.map +1 -1
  45. package/commonjs/VirtualScroller.constructor.js +38 -4
  46. package/commonjs/VirtualScroller.constructor.js.map +1 -1
  47. package/commonjs/VirtualScroller.items.js +50 -4
  48. package/commonjs/VirtualScroller.items.js.map +1 -1
  49. package/commonjs/VirtualScroller.js +23 -14
  50. package/commonjs/VirtualScroller.js.map +1 -1
  51. package/commonjs/VirtualScroller.layout.js +40 -29
  52. package/commonjs/VirtualScroller.layout.js.map +1 -1
  53. package/commonjs/VirtualScroller.onContainerResize.js +1 -2
  54. package/commonjs/VirtualScroller.onContainerResize.js.map +1 -1
  55. package/commonjs/VirtualScroller.state.js +10 -9
  56. package/commonjs/VirtualScroller.state.js.map +1 -1
  57. package/commonjs/VirtualScroller.verticalSpacing.js +39 -6
  58. package/commonjs/VirtualScroller.verticalSpacing.js.map +1 -1
  59. package/commonjs/react/VirtualScroller.js +85 -34
  60. package/commonjs/react/VirtualScroller.js.map +1 -1
  61. package/commonjs/react/useClassName.js +2 -2
  62. package/commonjs/react/useClassName.js.map +1 -1
  63. package/commonjs/react/useForwardedRef.js +50 -0
  64. package/commonjs/react/useForwardedRef.js.map +1 -0
  65. package/commonjs/react/useInstanceMethods.js +4 -4
  66. package/commonjs/react/useInstanceMethods.js.map +1 -1
  67. package/commonjs/react/useItemKeys.js +28 -5
  68. package/commonjs/react/useItemKeys.js.map +1 -1
  69. package/commonjs/react/useOnItemHeightDidChange.js +28 -12
  70. package/commonjs/react/useOnItemHeightDidChange.js.map +1 -1
  71. package/commonjs/react/useSetItemState.js +31 -12
  72. package/commonjs/react/useSetItemState.js.map +1 -1
  73. package/commonjs/react/useState.js +9 -9
  74. package/commonjs/react/useState.js.map +1 -1
  75. package/commonjs/react/{useStateNoStaleBug.js → useStateWithRepeatableRead.js} +3 -3
  76. package/commonjs/react/useStateWithRepeatableRead.js.map +1 -0
  77. package/commonjs/react/useStyle.js +10 -4
  78. package/commonjs/react/useStyle.js.map +1 -1
  79. package/commonjs/react/useValidateTableBodyItemsContainer.js +24 -0
  80. package/commonjs/react/useValidateTableBodyItemsContainer.js.map +1 -0
  81. package/commonjs/react/useVirtualScroller.js +4 -3
  82. package/commonjs/react/useVirtualScroller.js.map +1 -1
  83. package/commonjs/test/ItemsContainer.js +10 -10
  84. package/commonjs/test/ItemsContainer.js.map +1 -1
  85. package/commonjs/test/VirtualScroller.js +25 -10
  86. package/commonjs/test/VirtualScroller.js.map +1 -1
  87. package/dom/index.d.ts +6 -5
  88. package/index.d.ts +19 -8
  89. package/modules/BeforeResize.js +1 -2
  90. package/modules/BeforeResize.js.map +1 -1
  91. package/modules/DOM/VirtualScroller.js +7 -13
  92. package/modules/DOM/VirtualScroller.js.map +1 -1
  93. package/modules/DOM/tbody.js +4 -4
  94. package/modules/DOM/tbody.js.map +1 -1
  95. package/modules/ItemHeights.js +11 -14
  96. package/modules/ItemHeights.js.map +1 -1
  97. package/modules/Layout.defaults.js +11 -0
  98. package/modules/Layout.defaults.js.map +1 -0
  99. package/modules/Layout.js +74 -64
  100. package/modules/Layout.js.map +1 -1
  101. package/modules/Layout.test.js +8 -4
  102. package/modules/Layout.test.js.map +1 -1
  103. package/modules/VirtualScroller.constructor.js +37 -4
  104. package/modules/VirtualScroller.constructor.js.map +1 -1
  105. package/modules/VirtualScroller.items.js +51 -5
  106. package/modules/VirtualScroller.items.js.map +1 -1
  107. package/modules/VirtualScroller.js +23 -14
  108. package/modules/VirtualScroller.js.map +1 -1
  109. package/modules/VirtualScroller.layout.js +40 -29
  110. package/modules/VirtualScroller.layout.js.map +1 -1
  111. package/modules/VirtualScroller.onContainerResize.js +1 -2
  112. package/modules/VirtualScroller.onContainerResize.js.map +1 -1
  113. package/modules/VirtualScroller.state.js +10 -9
  114. package/modules/VirtualScroller.state.js.map +1 -1
  115. package/modules/VirtualScroller.verticalSpacing.js +38 -6
  116. package/modules/VirtualScroller.verticalSpacing.js.map +1 -1
  117. package/modules/react/VirtualScroller.js +84 -35
  118. package/modules/react/VirtualScroller.js.map +1 -1
  119. package/modules/react/useClassName.js +3 -3
  120. package/modules/react/useClassName.js.map +1 -1
  121. package/modules/react/useForwardedRef.js +42 -0
  122. package/modules/react/useForwardedRef.js.map +1 -0
  123. package/modules/react/useInstanceMethods.js +4 -4
  124. package/modules/react/useInstanceMethods.js.map +1 -1
  125. package/modules/react/useItemKeys.js +28 -5
  126. package/modules/react/useItemKeys.js.map +1 -1
  127. package/modules/react/useOnItemHeightDidChange.js +28 -12
  128. package/modules/react/useOnItemHeightDidChange.js.map +1 -1
  129. package/modules/react/useSetItemState.js +31 -12
  130. package/modules/react/useSetItemState.js.map +1 -1
  131. package/modules/react/useState.js +9 -9
  132. package/modules/react/useState.js.map +1 -1
  133. package/modules/react/{useStateNoStaleBug.js → useStateWithRepeatableRead.js} +2 -2
  134. package/modules/react/useStateWithRepeatableRead.js.map +1 -0
  135. package/modules/react/useStyle.js +10 -4
  136. package/modules/react/useStyle.js.map +1 -1
  137. package/modules/react/useValidateTableBodyItemsContainer.js +16 -0
  138. package/modules/react/useValidateTableBodyItemsContainer.js.map +1 -0
  139. package/modules/react/useVirtualScroller.js +4 -3
  140. package/modules/react/useVirtualScroller.js.map +1 -1
  141. package/modules/test/ItemsContainer.js +10 -10
  142. package/modules/test/ItemsContainer.js.map +1 -1
  143. package/modules/test/VirtualScroller.js +25 -10
  144. package/modules/test/VirtualScroller.js.map +1 -1
  145. package/package.json +1 -1
  146. package/react/as.d.ts +42 -0
  147. package/react/index.d.ts +204 -63
  148. package/source/BeforeResize.js +1 -2
  149. package/source/DOM/VirtualScroller.js +7 -13
  150. package/source/DOM/tbody.js +5 -5
  151. package/source/ItemHeights.js +11 -12
  152. package/source/Layout.defaults.js +8 -0
  153. package/source/Layout.js +66 -53
  154. package/source/Layout.test.js +7 -2
  155. package/source/VirtualScroller.constructor.js +27 -4
  156. package/source/VirtualScroller.items.js +47 -2
  157. package/source/VirtualScroller.js +23 -14
  158. package/source/VirtualScroller.layout.js +41 -28
  159. package/source/VirtualScroller.onContainerResize.js +1 -2
  160. package/source/VirtualScroller.state.js +10 -11
  161. package/source/VirtualScroller.verticalSpacing.js +32 -6
  162. package/source/react/VirtualScroller.js +96 -33
  163. package/source/react/useClassName.js +3 -3
  164. package/source/react/useForwardedRef.js +39 -0
  165. package/source/react/useInstanceMethods.js +12 -4
  166. package/source/react/useItemKeys.js +22 -4
  167. package/source/react/useOnItemHeightDidChange.js +29 -10
  168. package/source/react/useSetItemState.js +32 -10
  169. package/source/react/useState.js +6 -6
  170. package/source/react/{useStateNoStaleBug.js → useStateWithRepeatableRead.js} +1 -1
  171. package/source/react/useStyle.js +3 -2
  172. package/source/react/useValidateTableBodyItemsContainer.js +16 -0
  173. package/source/react/useVirtualScroller.js +4 -3
  174. package/source/test/ItemsContainer.js +10 -10
  175. package/source/test/VirtualScroller.js +16 -10
  176. package/website/index-dom-bypass.html +198 -0
  177. package/website/index-dom-grid.html +204 -0
  178. package/website/index-dom-scrollableContainer.html +215 -0
  179. package/website/index-dom-tbody-scrollableContainer.html +81 -0
  180. package/website/index-dom-tbody.html +65 -0
  181. package/website/index-dom.html +117 -84
  182. package/website/index-react-bypass.html +200 -0
  183. package/website/{index-grid.html → index-react-grid.html} +79 -92
  184. package/website/index-react-scrollableContainer.html +213 -0
  185. package/website/index-react-strictMode.html +199 -0
  186. package/website/index-react-tbody-scrollableContainer.html +96 -0
  187. package/website/index-react-tbody.html +80 -0
  188. package/website/{index-bypass.html → index-react.html} +84 -70
  189. package/website/index.html +84 -69
  190. package/website/{messages.js → items.js} +1 -1
  191. package/website/lib/babel.min.js +25 -0
  192. package/website/lib/prop-types.min.js +1 -0
  193. package/website/lib/react-dom.development.js +29924 -0
  194. package/website/lib/react-dom.production.min.js +267 -0
  195. package/website/lib/react.development.js +3343 -0
  196. package/website/lib/react.production.min.js +31 -0
  197. package/website/style.base.css +33 -0
  198. package/website/style.list.css +92 -0
  199. package/website/style.list.grid.css +23 -0
  200. package/bundle/index-tbody-scrollableContainer.html +0 -70
  201. package/bundle/index-tbody.html +0 -57
  202. package/bundle/on-scroll-to-dom.js +0 -2
  203. package/bundle/on-scroll-to-dom.js.map +0 -1
  204. package/bundle/on-scroll-to-react.js +0 -2
  205. package/bundle/on-scroll-to-react.js.map +0 -1
  206. package/bundle/on-scroll-to.js +0 -2
  207. package/bundle/on-scroll-to.js.map +0 -1
  208. package/commonjs/react/useStateNoStaleBug.js.map +0 -1
  209. package/modules/react/useStateNoStaleBug.js.map +0 -1
  210. package/website/index-scrollableContainer.html +0 -208
  211. package/website/index-tbody-scrollableContainer.html +0 -70
  212. package/website/index-tbody.html +0 -57
  213. package/website/lib/on-scroll-to-dom.js +0 -2
  214. package/website/lib/on-scroll-to-dom.js.map +0 -1
  215. package/website/lib/on-scroll-to-react.js +0 -2
  216. package/website/lib/on-scroll-to-react.js.map +0 -1
@@ -37,7 +37,16 @@ export default function createStateHelpers({
37
37
 
38
38
  this.getInitialItemState = getInitialItemState
39
39
 
40
- this._setItemState = (i, newItemState) => {
40
+ this._setItemState = (itemOrIndex, newItemState) => {
41
+ // Item index.
42
+ const i = this._getItemIndexByItemOrIndex(itemOrIndex)
43
+
44
+ // If the item wasn't found, the error was already reported,
45
+ // so just return some "sensible" default value.
46
+ if (i === undefined) {
47
+ return
48
+ }
49
+
41
50
  if (isDebug()) {
42
51
  log('~ Item state changed ~')
43
52
  log('Item index', i)
@@ -284,16 +293,6 @@ export default function createStateHelpers({
284
293
  function getInitialLayoutState(items, { beforeStart }) {
285
294
  const itemsCount = items.length
286
295
 
287
- const getColumnsCount = () => this.getActualColumnsCount()
288
-
289
- const columnsCount = beforeStart
290
- ? this.layout.getInitialLayoutValueWithFallback(
291
- 'columnsCount',
292
- getColumnsCount,
293
- 1
294
- )
295
- : getColumnsCount()
296
-
297
296
  const {
298
297
  firstShownItemIndex,
299
298
  lastShownItemIndex,
@@ -1,20 +1,46 @@
1
1
  import log from './utility/debug.js'
2
2
  import getVerticalSpacing from './getVerticalSpacing.js'
3
+ import { DEFAULT_INTER_ITEM_VERTICAL_SPACING } from './Layout.defaults.js'
3
4
 
4
- export default function createVerticalSpacingHelpers() {
5
+ export default function createVerticalSpacingHelpers({
6
+ getEstimatedInterItemVerticalSpacing
7
+ }) {
5
8
  // Bind to `this` in order to prevent bugs when this function is passed by reference
6
9
  // and then called with its `this` being unintentionally `window` resulting in
7
10
  // the `if` condition being "falsy".
8
11
  this.getVerticalSpacing = () => {
9
- return this.verticalSpacing || 0
12
+ const { verticalSpacing } = this
13
+ if (typeof verticalSpacing === 'number') {
14
+ return verticalSpacing
15
+ }
16
+ return this.getEstimatedInterItemVerticalSpacing()
10
17
  }
11
18
 
12
19
  this.getVerticalSpacingBeforeResize = () => {
13
- // `beforeResize.verticalSpacing` can be `undefined`.
14
- // For example, if `this.updateState({ verticalSpacing })` call hasn't been applied
15
- // before the resize happened (in case of an "asynchronous" state update).
16
20
  const { beforeResize } = this.getState()
17
- return beforeResize && beforeResize.verticalSpacing || 0
21
+ if (beforeResize) {
22
+ const { verticalSpacing } = beforeResize
23
+ // `beforeResize.verticalSpacing` can be `undefined`.
24
+ // For example, if `this.updateState({ verticalSpacing })` call hasn't been applied
25
+ // before the resize happened (in case of an "asynchronous" state update).
26
+ if (typeof verticalSpacing === 'number') {
27
+ return verticalSpacing
28
+ }
29
+ return this.getEstimatedInterItemVerticalSpacing()
30
+ }
31
+ }
32
+
33
+ this.getEstimatedInterItemVerticalSpacing = () => {
34
+ if (getEstimatedInterItemVerticalSpacing) {
35
+ const estimatedVerticalSpacing = getEstimatedInterItemVerticalSpacing()
36
+ if (typeof estimatedVerticalSpacing === 'number') {
37
+ return estimatedVerticalSpacing
38
+ }
39
+ throw new Error('[virtual-scroller] `getEstimatedInterItemVerticalSpacing()` must return a number')
40
+ }
41
+ // `DEFAULT_INTER_ITEM_VERTICAL_SPACING` will be used in server-side render
42
+ // unless `getEstimatedInterItemVerticalSpacing()` parameter is specified.
43
+ return DEFAULT_INTER_ITEM_VERTICAL_SPACING
18
44
  }
19
45
 
20
46
  /**
@@ -1,4 +1,4 @@
1
- import React, { useRef, useMemo, useLayoutEffect } from 'react'
1
+ import React, { useMemo, useLayoutEffect } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
4
  import useState from './useState.js'
@@ -10,6 +10,8 @@ import useSetItemState from './useSetItemState.js'
10
10
  import useOnItemHeightDidChange from './useOnItemHeightDidChange.js'
11
11
  import useSetNewItemsOnItemsPropertyChange from './useSetNewItemsOnItemsPropertyChange.js'
12
12
  import useUpdateItemKeysOnItemsChange from './useUpdateItemKeysOnItemsChange.js'
13
+ import useValidateTableBodyItemsContainer from './useValidateTableBodyItemsContainer.js'
14
+ import useForwardedRef from './useForwardedRef.js'
13
15
  import useClassName from './useClassName.js'
14
16
  import useStyle from './useStyle.js'
15
17
 
@@ -27,19 +29,32 @@ import { warn } from '../utility/debug.js'
27
29
  // * The React component re-renders itself the second time.
28
30
 
29
31
  function VirtualScroller({
30
- as: AsComponent = 'div',
32
+ // The following are `<VirtualScroller/>` properties.
33
+ //
34
+ // `as` property is deprecated, use `itemsContainerComponent` property instead.
35
+ as,
31
36
  items: itemsProperty,
32
- itemComponent: Component,
37
+ itemComponent: ItemComponent,
33
38
  itemComponentProps,
39
+ itemsContainerComponent: ItemsContainerComponent,
40
+ itemsContainerComponentProps,
41
+ itemsContainerRef,
34
42
  // `estimatedItemHeight` property name is deprecated,
35
43
  // use `getEstimatedItemHeight` property instead.
36
44
  estimatedItemHeight,
37
45
  getEstimatedItemHeight,
38
46
  getEstimatedVisibleItemRowsCount,
39
- bypass,
47
+ getEstimatedInterItemVerticalSpacing,
48
+ onMount,
40
49
  // `tbody` property is deprecated.
41
50
  // Pass `as: "tbody"` property instead.
42
51
  tbody,
52
+ readyToStart,
53
+ className: classNameProperty,
54
+
55
+ // The following are the "core" component options.
56
+ //
57
+ bypass,
43
58
  // `preserveScrollPosition` property name is deprecated,
44
59
  // use `preserveScrollPositionOnPrependItems` property instead.
45
60
  preserveScrollPosition,
@@ -51,19 +66,26 @@ function VirtualScroller({
51
66
  getScrollableContainer,
52
67
  getColumnsCount,
53
68
  getItemId,
54
- className,
55
- readyToStart,
56
- onMount,
57
69
  // `onItemFirstRender(i)` is deprecated, use `onItemInitialRender(item)` instead.
58
70
  onItemFirstRender,
59
71
  onItemInitialRender,
60
72
  initialScrollPosition,
61
73
  onScrollPositionChange,
62
- onStateChange,
63
74
  initialState,
64
75
  getInitialItemState,
76
+ onStateChange,
77
+
78
+ // "Rest" properties that will be passed through to the `itemsContainerComponent`.
65
79
  ...rest
66
80
  }, ref) {
81
+ // Previously, `as` property was being used instead of `itemsContainerComponent`,
82
+ // and the default `as` property value was a generic `<div/>`.
83
+ // Starting from version `1.14.1`, it is recommended to explicitly specify the `itemsContainerComponent`.
84
+ // The default `"div"` fallback value is just a legacy compatibility relic, and so is the `as` property.
85
+ if (!ItemsContainerComponent) {
86
+ ItemsContainerComponent = as || 'div'
87
+ }
88
+
67
89
  // It turns out that since May 2022, `useVirtualScroller()` hook completely ignored the `tbody` property.
68
90
  // Instead, it always derived `tbody` property value from `as` property value by comparing it to `"tbody"` string.
69
91
  // As a result, it seemed like the explicit passing of `tbody` property didn't really work as intended.
@@ -71,11 +93,22 @@ function VirtualScroller({
71
93
  // without a developer having to manually specify it. So the `tbody` property was deprecated.
72
94
  // It still exists though for backwards compatibility with the older versions of the package.
73
95
  if (tbody === undefined) {
74
- tbody = AsComponent === 'tbody'
96
+ // `tbody` should be somehow detected before any DOM Elements have been mounted.
97
+ // This is because during Server-Side Render there's no DOM Elements tree at all.
98
+ // And server-sider render result is required to be exactly the same as client-side render result.
99
+ // This means that `tbody` detection for the purposes of getting the initial
100
+ // `className` or `style` property values must not rely on any DOM Elements at all,
101
+ // and should use some other means such as explicitly passing a `tbody: true` property
102
+ // (as it used to be in the past) or detecting `<tbody/>` tag usage from the
103
+ // `itemsContainerCompoent` property value.
104
+ tbody = ItemsContainerComponent === 'tbody'
75
105
  }
76
106
 
77
107
  // List items "container" DOM Element reference.
78
- const container = useRef()
108
+ const {
109
+ setRef: setItemsContainerRef,
110
+ internalRef: itemsContainer
111
+ } = useForwardedRef(itemsContainerRef)
79
112
 
80
113
  // Create a `VirtualScroller` instance.
81
114
  const virtualScroller = useVirtualScroller({
@@ -85,6 +118,7 @@ function VirtualScroller({
85
118
  estimatedItemHeight,
86
119
  getEstimatedItemHeight,
87
120
  getEstimatedVisibleItemRowsCount,
121
+ getEstimatedInterItemVerticalSpacing,
88
122
  bypass,
89
123
  // bypassBatchSize,
90
124
  onItemInitialRender,
@@ -99,12 +133,11 @@ function VirtualScroller({
99
133
  getScrollableContainer,
100
134
  getColumnsCount,
101
135
  getItemId,
102
- AsComponent,
103
136
  initialState,
104
137
  getInitialItemState,
105
138
  onStateChange
106
139
  }, {
107
- container
140
+ itemsContainer
108
141
  })
109
142
 
110
143
  // Only compute the initial state once.
@@ -139,6 +172,7 @@ function VirtualScroller({
139
172
  // "reuse" `itemComponent`s in cases when `items` are changed.
140
173
  const {
141
174
  getItemKey,
175
+ onItemKeysReset,
142
176
  usesAutogeneratedItemKeys,
143
177
  updateItemKeysForNewItems
144
178
  } = useItemKeys({
@@ -148,14 +182,16 @@ function VirtualScroller({
148
182
  // Cache per-item `setItemState` functions' "references"
149
183
  // so that item components don't get re-rendered needlessly.
150
184
  const getSetItemState = useSetItemState({
151
- initialItemsCount: itemsProperty.length,
185
+ getItemKey,
186
+ onItemKeysReset,
152
187
  virtualScroller
153
188
  })
154
189
 
155
190
  // Cache per-item `onItemHeightDidChange` functions' "references"
156
191
  // so that item components don't get re-rendered needlessly.
157
192
  const getOnItemHeightDidChange = useOnItemHeightDidChange({
158
- initialItemsCount: itemsProperty.length,
193
+ getItemKey,
194
+ onItemKeysReset,
159
195
  virtualScroller
160
196
  })
161
197
 
@@ -190,6 +226,14 @@ function VirtualScroller({
190
226
  }
191
227
  }, [])
192
228
 
229
+ // A developer might "forget" to pass `itemsContainerComponent="tbody"` property
230
+ // when using a `<tbody/>` as a container for list items.
231
+ // This hook validates that the developer didn't "forget" to do that in such case.
232
+ useValidateTableBodyItemsContainer({
233
+ virtualScroller,
234
+ tbody
235
+ })
236
+
193
237
  // `willRender()` function is no longer used.
194
238
  //
195
239
  // // `getSnapshotBeforeUpdate()` is called right before `componentDidUpdate()`.
@@ -205,11 +249,15 @@ function VirtualScroller({
205
249
  // return null
206
250
  // }
207
251
 
208
- className = useClassName(className, {
252
+ const classNamePassThrough = classNameProperty || itemsContainerComponentProps && itemsContainerComponentProps.className
253
+
254
+ const className = useClassName(classNamePassThrough, {
209
255
  tbody
210
256
  })
211
257
 
212
- const style = useStyle({
258
+ const stylePassThrough = itemsContainerComponentProps && itemsContainerComponentProps.style
259
+
260
+ const style = useStyle(stylePassThrough, {
213
261
  tbody,
214
262
  state: stateToRender
215
263
  })
@@ -222,34 +270,39 @@ function VirtualScroller({
222
270
  } = stateToRender
223
271
 
224
272
  return (
225
- <AsComponent
273
+ <ItemsContainerComponent
274
+ {...itemsContainerComponentProps}
226
275
  {...rest}
227
- ref={container}
276
+ ref={setItemsContainerRef}
228
277
  className={className}
229
278
  style={style}>
230
279
  {currentItems.map((item, i) => {
231
280
  if (i >= firstShownItemIndex && i <= lastShownItemIndex) {
232
- // * Passing `item` as `children` property is legacy and is deprecated.
281
+ // * Passing the `item` as `children` property is legacy and is deprecated.
233
282
  // If version `2.x` is published in some hypothetical future,
234
- // the `item` and `itemIndex` properties should be moved below
235
- // `{...itemComponentProps}`.
283
+ // the `item` property should be moved below `{...itemComponentProps}`.
236
284
  //
237
- // * Passing `itemIndex` property is legacy and is deprecated.
238
- // The rationale is that setting new `items` on a React component
239
- // is an asynchronous operation, so when a user obtains `itemIndex`,
240
- // they don't know which `items` list does that index correspond to,
241
- // therefore making it useless, or even buggy if used incorreclty.
285
+ // * Passing `itemIndex` property is legacy and is deprecated
286
+ // and could be removed in some future.
287
+ // The rationale for deprecation is that the `items` property
288
+ // is not constant and could change, in which case the `itemIndex` value
289
+ // would be of no use because the application wouldn't know
290
+ // which exact `items` it corresponds to at any given moment in time.
291
+ // Having just the `itemIndex` and no actual `item` is therefore considered useless.
292
+ // Instead, a developer could simply use `getItemKey(item)` function.
242
293
  //
243
- // * Passing `onStateChange` property for legacy reasons.
294
+ // * `onStateChange` property is passed here for legacy reasons.
244
295
  // The new property name is `setState`.
245
- // The old property name `onStateChange` is deprecated.
296
+ // The old property name `onStateChange` is deprecated
297
+ // and could be removed in some future.
246
298
  //
247
- // * Passing `onHeightChange` property for legacy reasons.
299
+ // * `onHeightChange` property is passed here for legacy reasons.
248
300
  // The new property name is `onHeightDidChange`.
249
- // The old property name `onHeightChange` is deprecated.
301
+ // The old property name `onHeightChange` is deprecated
302
+ // and could be removed in some future.
250
303
  //
251
304
  return (
252
- <Component
305
+ <ItemComponent
253
306
  item={item}
254
307
  itemIndex={i}
255
308
  {...itemComponentProps}
@@ -260,12 +313,12 @@ function VirtualScroller({
260
313
  onHeightChange={getOnItemHeightDidChange(i)}
261
314
  onHeightDidChange={getOnItemHeightDidChange(i)}>
262
315
  {item}
263
- </Component>
316
+ </ItemComponent>
264
317
  )
265
318
  }
266
319
  return null
267
320
  })}
268
- </AsComponent>
321
+ </ItemsContainerComponent>
269
322
  )
270
323
  }
271
324
 
@@ -282,15 +335,25 @@ const elementType = PropTypes.elementType || PropTypes.oneOfType([
282
335
  ])
283
336
 
284
337
  VirtualScroller.propTypes = {
338
+ // `as` property is deprecated, use `itemsContainerComponent` property instead.
285
339
  as: elementType,
286
340
  items: PropTypes.arrayOf(PropTypes.any).isRequired,
287
341
  itemComponent: elementType.isRequired,
288
342
  itemComponentProps: PropTypes.object,
343
+ // `itemsContainerComponent` property is not required just for legacy compatibility reasons.
344
+ // Any new applications should explicitly specify it.
345
+ itemsContainerComponent: elementType,
346
+ itemsContainerComponentProps: PropTypes.object,
347
+ itemsContainerRef: PropTypes.oneOfType([
348
+ PropTypes.func,
349
+ PropTypes.shape({ current: PropTypes.object })
350
+ ]),
289
351
  // `estimatedItemHeight` property name is deprecated,
290
352
  // use `getEstimatedItemHeight` property instead.
291
353
  estimatedItemHeight: PropTypes.number,
292
354
  getEstimatedItemHeight: PropTypes.func,
293
355
  getEstimatedVisibleItemRowsCount: PropTypes.func,
356
+ getEstimatedInterItemVerticalSpacing: PropTypes.func,
294
357
  bypass: PropTypes.bool,
295
358
  // bypassBatchSize: PropTypes.number,
296
359
  // `tbody` property is deprecated.
@@ -1,4 +1,4 @@
1
- import { TBODY_CLASS_NAME } from '../DOM/tbody.js'
1
+ import { CLASS_NAME_FOR_TBODY_WORKAROUND } from '../DOM/tbody.js'
2
2
 
3
3
  export default function useClassName(className, { tbody }) {
4
4
  // For `<tbody/>`, a workaround is used which uses CSS variables
@@ -6,9 +6,9 @@ export default function useClassName(className, { tbody }) {
6
6
  // See `addTbodyStyles()` function in `../DOM/tbody.js` for more details.
7
7
  if (tbody) {
8
8
  if (className) {
9
- return className + ' ' + TBODY_CLASS_NAME
9
+ return className + ' ' + CLASS_NAME_FOR_TBODY_WORKAROUND
10
10
  }
11
- return TBODY_CLASS_NAME
11
+ return CLASS_NAME_FOR_TBODY_WORKAROUND
12
12
  }
13
13
  return className
14
14
  }
@@ -0,0 +1,39 @@
1
+ // import type { MutableRefObject } from 'react'
2
+ import { useCallback, useRef } from 'react'
3
+
4
+ // When a React component receives a `ref` which it is supposed to "forward"
5
+ // and when it would like to also read that `ref` in its internal implementation,
6
+ // this `useForwardedRef()` hook could be used to get read access to such "forwarded" ref
7
+ // inside the component's internal implementation.
8
+ //
9
+ // ```js
10
+ // const FormWithAutoFocus = forwardRef((props, ref) => {
11
+ // const { setRef, internalRef } = useForwardedRef<RefValueType>(ref)
12
+ //
13
+ // useEffect(() => {
14
+ // internalRef.current.focus()
15
+ // }, [])
16
+ //
17
+ // return (
18
+ // <Form ref={setRef} {...props}/>
19
+ // )
20
+ // })
21
+ // ```
22
+ //
23
+ // export default function useForwardedRef<T extends MutableRefObject<any>>(ref) {
24
+ export default function useForwardedRef(ref) {
25
+ const internalRef = useRef() // as T
26
+
27
+ const setRef = useCallback((instance) => {
28
+ internalRef.current = instance
29
+ if (ref) {
30
+ if (typeof ref === 'function') {
31
+ ref(instance)
32
+ } else {
33
+ ref.current = instance
34
+ }
35
+ }
36
+ }, [ref])
37
+
38
+ return { setRef, internalRef }
39
+ }
@@ -8,17 +8,25 @@ export default function useInstanceMethods(ref, {
8
8
  }) {
9
9
  useImperativeHandle(ref, () => ({
10
10
  // This is a proxy for `VirtualScroller`'s `.updateLayout` instance method.
11
- updateLayout: () => virtualScroller.updateLayout(),
11
+ updateLayout: () => {
12
+ virtualScroller.updateLayout()
13
+ },
12
14
 
13
15
  // (deprecated)
14
16
  // `.layout()` method name is deprecated, use `.updateLayout()` instead.
15
- layout: () => virtualScroller.updateLayout(),
17
+ layout: () => {
18
+ virtualScroller.updateLayout()
19
+ },
16
20
 
17
21
  // (deprecated)
18
- updateItem: (i) => reportError(`[virtual-scroller] ".updateItem(i)" method of React <VirtualScroller/> has been removed`),
22
+ updateItem: (i) => {
23
+ reportError(`[virtual-scroller] ".updateItem(i)" method of React <VirtualScroller/> has been removed`)
24
+ },
19
25
 
20
26
  // (deprecated)
21
- renderItem: (i) => reportError(`[virtual-scroller] ".renderItem(i)" method of React <VirtualScroller/> has been removed`)
27
+ renderItem: (i) => {
28
+ reportError(`[virtual-scroller] ".renderItem(i)" method of React <VirtualScroller/> has been removed`)
29
+ }
22
30
  }), [
23
31
  virtualScroller
24
32
  ])
@@ -3,15 +3,31 @@ import log from '../utility/debug.js'
3
3
  import { useRef, useMemo, useCallback } from 'react'
4
4
 
5
5
  export default function useItemKeys({ getItemId }) {
6
+ // These "listeners" will be called in case the auto-generated item keys are reset
7
+ // due to the prefix counter number reaching its maximum allowed value.
8
+ const itemKeysResetEventListeners = useMemo(() => {
9
+ return []
10
+ }, [])
11
+
12
+ // Adds an "on item keys reset" listener.
13
+ const onItemKeysReset = useCallback((listener) => {
14
+ itemKeysResetEventListeners.push(listener)
15
+ }, [])
16
+
6
17
  // List items are rendered with `key`s so that React doesn't
7
18
  // "reuse" `itemComponent`s in cases when `items` are changed.
8
19
  const itemKeyPrefix = useRef()
9
20
 
10
- // Generates a unique `key` prefix for list item components.
21
+ // When no `getItemId()` function is specified, it creates an item key
22
+ // from an auto-generated unique prefix and the item's index in the `items` array.
23
+ // This way, when the `items` change, their keys are also changed.
11
24
  const generateItemKeyPrefix = useMemo(() => {
12
25
  let counter = 0
13
26
  function getNextCounter() {
14
27
  if (counter === Number.MAX_SAFE_INTEGER) {
28
+ for (const listener of itemKeysResetEventListeners) {
29
+ listener()
30
+ }
15
31
  counter = 0
16
32
  }
17
33
  counter++
@@ -21,11 +37,12 @@ export default function useItemKeys({ getItemId }) {
21
37
  itemKeyPrefix.current = String(getNextCounter())
22
38
  }
23
39
  }, [
24
- itemKeyPrefix
40
+ itemKeyPrefix,
41
+ itemKeysResetEventListeners
25
42
  ])
26
43
 
27
44
  useMemo(() => {
28
- // Generate an initial unique `key` prefix for list item components.
45
+ // Generate an initial unique `key` prefix for list items.
29
46
  generateItemKeyPrefix()
30
47
  }, [])
31
48
 
@@ -50,7 +67,7 @@ export default function useItemKeys({ getItemId }) {
50
67
  */
51
68
  const getItemKey = useCallback((item, i) => {
52
69
  if (getItemId) {
53
- return getItemId(item)
70
+ return String(getItemId(item))
54
71
  }
55
72
  return `${itemKeyPrefix.current}:${i}`
56
73
  }, [
@@ -60,6 +77,7 @@ export default function useItemKeys({ getItemId }) {
60
77
 
61
78
  return {
62
79
  getItemKey,
80
+ onItemKeysReset,
63
81
  usesAutogeneratedItemKeys,
64
82
  updateItemKeysForNewItems: generateItemKeyPrefixIfNotUsingItemIds
65
83
  }
@@ -1,28 +1,47 @@
1
1
  import { useMemo, useRef, useCallback } from 'react'
2
2
 
3
3
  export default function useOnItemHeightDidChange({
4
- initialItemsCount,
4
+ getItemKey,
5
+ onItemKeysReset,
5
6
  virtualScroller
6
7
  }) {
7
- // Only compute the initial cache value once.
8
- const initialCacheValue = useMemo(() => {
9
- return new Array(initialItemsCount)
8
+ // Only create the initial cache once.
9
+ const initialCache = useMemo(() => {
10
+ return createCache()
10
11
  }, [])
11
12
 
12
- // Handler functions cache.
13
- const cache = useRef(initialCacheValue)
13
+ // A cache of `onItemHeightDidChange()` functions.
14
+ const cache = useRef(initialCache)
15
+
16
+ // Adds an "on item keys reset" listener that clears the cache when item keys are reset.
17
+ useMemo(() => {
18
+ onItemKeysReset(() => {
19
+ cache.current = createCache()
20
+ })
21
+ }, [])
14
22
 
15
23
  // Caches per-item `onItemHeightDidChange` functions' "references"
16
24
  // so that item components don't get re-rendered needlessly.
17
- const getOnItemHeightDidChange = useCallback((i) => {
18
- if (!cache.current[i]) {
19
- cache.current[i] = () => virtualScroller.onItemHeightDidChange(i)
25
+ const getOnItemHeightDidChange = useCallback((item) => {
26
+ const itemKey = getItemKey(item)
27
+ if (!cache.current[itemKey]) {
28
+ cache.current[itemKey] = () => {
29
+ virtualScroller.onItemHeightDidChange(item)
30
+ }
20
31
  }
21
- return cache.current[i]
32
+ return cache.current[itemKey]
22
33
  }, [
23
34
  virtualScroller,
35
+ getItemKey,
24
36
  cache
25
37
  ])
26
38
 
27
39
  return getOnItemHeightDidChange
40
+ }
41
+
42
+ function createCache() {
43
+ // It could also use a `new Map()` here and then use `item` as a key.
44
+ // Although, sometimes an `item` "reference" might change while it still being
45
+ // the same item, i.e. having the same `getItemId(item)` value.
46
+ return {}
28
47
  }
@@ -1,28 +1,50 @@
1
1
  import { useMemo, useRef, useCallback } from 'react'
2
2
 
3
3
  export default function useSetItemState({
4
- initialItemsCount,
4
+ getItemKey,
5
+ onItemKeysReset,
5
6
  virtualScroller
6
7
  }) {
7
- // Only compute the initial cache value once.
8
- const initialCacheValue = useMemo(() => {
9
- return new Array(initialItemsCount)
8
+ // Only create the initial cache once.
9
+ const initialCache = useMemo(() => {
10
+ return createCache()
10
11
  }, [])
11
12
 
12
- // Handler functions cache.
13
- const cache = useRef(initialCacheValue)
13
+ // A cache of `setItemState()` functions.
14
+ const cache = useRef(initialCache)
15
+
16
+ // Adds an "on item keys reset" listener that clears the cache when item keys are reset.
17
+ useMemo(() => {
18
+ onItemKeysReset(() => {
19
+ cache.current = createCache()
20
+ })
21
+ }, [])
14
22
 
15
23
  // Caches per-item `setItemState` functions' "references"
16
24
  // so that item components don't get re-rendered needlessly.
17
- const getSetItemState = useCallback((i) => {
18
- if (!cache.current[i]) {
19
- cache.current[i] = (itemState) => virtualScroller.setItemState(i, itemState)
25
+ // I.e. it could just re-create this function every time
26
+ // but that would also make React re-render the item component every time
27
+ // which wouldn't be efficient.
28
+ const getSetItemState = useCallback((item) => {
29
+ const itemKey = getItemKey(item)
30
+ if (!cache.current[itemKey]) {
31
+ cache.current[itemKey] = (itemState) => {
32
+ virtualScroller.setItemState(item, itemState)
33
+ }
20
34
  }
21
- return cache.current[i]
35
+ return cache.current[itemKey]
22
36
  }, [
23
37
  virtualScroller,
38
+ getItemKey,
24
39
  cache
25
40
  ])
26
41
 
27
42
  return getSetItemState
43
+ }
44
+
45
+ function createCache() {
46
+ // It could also use a `new Map()` here and then use `item` as a key.
47
+ // Although, sometimes an `item` "reference" might change while it still being
48
+ // the same item, i.e. having the same `getItemId(item)` value.
49
+ return {}
28
50
  }