@difizen/libro-virtualized 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/es/auto-sizer/auto-sizer.d.ts +56 -0
  4. package/es/auto-sizer/auto-sizer.d.ts.map +1 -0
  5. package/es/auto-sizer/auto-sizer.js +157 -0
  6. package/es/auto-sizer/index.d.ts +3 -0
  7. package/es/auto-sizer/index.d.ts.map +1 -0
  8. package/es/auto-sizer/index.js +2 -0
  9. package/es/cell-measurer/cell-measurer-cache.d.ts +52 -0
  10. package/es/cell-measurer/cell-measurer-cache.d.ts.map +1 -0
  11. package/es/cell-measurer/cell-measurer-cache.js +176 -0
  12. package/es/cell-measurer/cell-measurer.d.ts +39 -0
  13. package/es/cell-measurer/cell-measurer.d.ts.map +1 -0
  14. package/es/cell-measurer/cell-measurer.js +154 -0
  15. package/es/cell-measurer/index.d.ts +5 -0
  16. package/es/cell-measurer/index.d.ts.map +1 -0
  17. package/es/cell-measurer/index.js +4 -0
  18. package/es/cell-measurer/types.d.ts +9 -0
  19. package/es/cell-measurer/types.d.ts.map +1 -0
  20. package/es/cell-measurer/types.js +0 -0
  21. package/es/grid/accessibility-overscanIndices-getter.d.ts +11 -0
  22. package/es/grid/accessibility-overscanIndices-getter.d.ts.map +1 -0
  23. package/es/grid/accessibility-overscanIndices-getter.js +33 -0
  24. package/es/grid/default-cell-range-renderer.d.ts +10 -0
  25. package/es/grid/default-cell-range-renderer.d.ts.map +1 -0
  26. package/es/grid/default-cell-range-renderer.js +135 -0
  27. package/es/grid/default-overscanIndices-getter.d.ts +11 -0
  28. package/es/grid/default-overscanIndices-getter.d.ts.map +1 -0
  29. package/es/grid/default-overscanIndices-getter.js +28 -0
  30. package/es/grid/grid.d.ts +359 -0
  31. package/es/grid/grid.d.ts.map +1 -0
  32. package/es/grid/grid.js +1287 -0
  33. package/es/grid/index.d.ts +6 -0
  34. package/es/grid/index.d.ts.map +1 -0
  35. package/es/grid/index.js +4 -0
  36. package/es/grid/types.d.ts +87 -0
  37. package/es/grid/types.d.ts.map +1 -0
  38. package/es/grid/types.js +1 -0
  39. package/es/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.d.ts +17 -0
  40. package/es/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.d.ts.map +1 -0
  41. package/es/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.js +25 -0
  42. package/es/grid/utils/cell-size-and-position-manager-row.d.ts +97 -0
  43. package/es/grid/utils/cell-size-and-position-manager-row.d.ts.map +1 -0
  44. package/es/grid/utils/cell-size-and-position-manager-row.js +297 -0
  45. package/es/grid/utils/cell-size-and-position-manager.d.ts +85 -0
  46. package/es/grid/utils/cell-size-and-position-manager.d.ts.map +1 -0
  47. package/es/grid/utils/cell-size-and-position-manager.js +268 -0
  48. package/es/grid/utils/max-element-size.d.ts +2 -0
  49. package/es/grid/utils/max-element-size.d.ts.map +1 -0
  50. package/es/grid/utils/max-element-size.js +17 -0
  51. package/es/grid/utils/scaling-cell-size-and-position-manager-row.d.ts +78 -0
  52. package/es/grid/utils/scaling-cell-size-and-position-manager-row.d.ts.map +1 -0
  53. package/es/grid/utils/scaling-cell-size-and-position-manager-row.js +187 -0
  54. package/es/grid/utils/scaling-cell-size-and-position-manager.d.ts +70 -0
  55. package/es/grid/utils/scaling-cell-size-and-position-manager.d.ts.map +1 -0
  56. package/es/grid/utils/scaling-cell-size-and-position-manager.js +187 -0
  57. package/es/grid/utils/update-scroll-index-helper.d.ts +24 -0
  58. package/es/grid/utils/update-scroll-index-helper.d.ts.map +1 -0
  59. package/es/grid/utils/update-scroll-index-helper.js +40 -0
  60. package/es/index.d.ts +5 -0
  61. package/es/index.d.ts.map +1 -0
  62. package/es/index.js +4 -0
  63. package/es/list/index.d.ts +3 -0
  64. package/es/list/index.d.ts.map +1 -0
  65. package/es/list/index.js +1 -0
  66. package/es/list/list.d.ts +109 -0
  67. package/es/list/list.d.ts.map +1 -0
  68. package/es/list/list.js +261 -0
  69. package/es/list/types.d.ts +22 -0
  70. package/es/list/types.d.ts.map +1 -0
  71. package/es/list/types.js +1 -0
  72. package/es/utils/animation-frame.d.ts +7 -0
  73. package/es/utils/animation-frame.d.ts.map +1 -0
  74. package/es/utils/animation-frame.js +20 -0
  75. package/es/utils/create-callback-memoizer.d.ts +5 -0
  76. package/es/utils/create-callback-memoizer.d.ts.map +1 -0
  77. package/es/utils/create-callback-memoizer.js +25 -0
  78. package/es/utils/get-updated-offset-for-index.d.ts +14 -0
  79. package/es/utils/get-updated-offset-for-index.d.ts.map +1 -0
  80. package/es/utils/get-updated-offset-for-index.js +32 -0
  81. package/es/utils/init-cell-metadata.d.ts +13 -0
  82. package/es/utils/init-cell-metadata.d.ts.map +1 -0
  83. package/es/utils/init-cell-metadata.js +32 -0
  84. package/es/utils/request-animation-timeout.d.ts +12 -0
  85. package/es/utils/request-animation-timeout.d.ts.map +1 -0
  86. package/es/utils/request-animation-timeout.js +33 -0
  87. package/es/utils/test-helper.d.ts +5 -0
  88. package/es/utils/test-helper.d.ts.map +1 -0
  89. package/es/utils/test-helper.js +31 -0
  90. package/es/vendor/binary-search-bounds.d.ts +22 -0
  91. package/es/vendor/binary-search-bounds.d.ts.map +1 -0
  92. package/es/vendor/binary-search-bounds.js +198 -0
  93. package/es/vendor/detect-element-resize.d.ts +16 -0
  94. package/es/vendor/detect-element-resize.d.ts.map +1 -0
  95. package/es/vendor/detect-element-resize.js +184 -0
  96. package/es/vendor/interval-tree.d.ts +10 -0
  97. package/es/vendor/interval-tree.d.ts.map +1 -0
  98. package/es/vendor/interval-tree.js +359 -0
  99. package/package.json +59 -0
  100. package/src/auto-sizer/auto-sizer.tsx +187 -0
  101. package/src/auto-sizer/index.ts +4 -0
  102. package/src/cell-measurer/cell-measurer-cache.ts +220 -0
  103. package/src/cell-measurer/cell-measurer.ts +151 -0
  104. package/src/cell-measurer/index.ts +5 -0
  105. package/src/cell-measurer/types.ts +8 -0
  106. package/src/grid/accessibility-overscanIndices-getter.ts +38 -0
  107. package/src/grid/default-cell-range-renderer.ts +166 -0
  108. package/src/grid/default-overscanIndices-getter.ts +32 -0
  109. package/src/grid/grid.tsx +1672 -0
  110. package/src/grid/index.ts +14 -0
  111. package/src/grid/types.ts +112 -0
  112. package/src/grid/utils/calculate-size-and-position-data-and-update-scroll-offset.ts +62 -0
  113. package/src/grid/utils/cell-size-and-position-manager-row.ts +365 -0
  114. package/src/grid/utils/cell-size-and-position-manager.ts +309 -0
  115. package/src/grid/utils/max-element-size.ts +18 -0
  116. package/src/grid/utils/scaling-cell-size-and-position-manager-row.ts +206 -0
  117. package/src/grid/utils/scaling-cell-size-and-position-manager.ts +198 -0
  118. package/src/grid/utils/update-scroll-index-helper.ts +96 -0
  119. package/src/index.spec.ts +10 -0
  120. package/src/index.ts +4 -0
  121. package/src/list/index.ts +2 -0
  122. package/src/list/list.tsx +292 -0
  123. package/src/list/types.ts +25 -0
  124. package/src/utils/animation-frame.ts +38 -0
  125. package/src/utils/create-callback-memoizer.ts +32 -0
  126. package/src/utils/get-updated-offset-for-index.ts +33 -0
  127. package/src/utils/init-cell-metadata.ts +32 -0
  128. package/src/utils/request-animation-timeout.ts +44 -0
  129. package/src/utils/test-helper.ts +20 -0
  130. package/src/vendor/binary-search-bounds.ts +203 -0
  131. package/src/vendor/detect-element-resize.ts +241 -0
  132. package/src/vendor/interval-tree.ts +406 -0
@@ -0,0 +1,187 @@
1
+ import * as React from 'react';
2
+
3
+ import createDetectElementResize from '../vendor/detect-element-resize.js';
4
+
5
+ type Size = {
6
+ height: number;
7
+ width: number;
8
+ };
9
+
10
+ type Props = {
11
+ /** Function responsible for rendering children.*/
12
+ children: (param: Size) => React.ReactNode;
13
+
14
+ /** Optional custom CSS class name to attach to root AutoSizer element. */
15
+ className?: string;
16
+
17
+ /** Default height to use for initial render; useful for SSR */
18
+ defaultHeight?: number;
19
+
20
+ /** Default width to use for initial render; useful for SSR */
21
+ defaultWidth?: number;
22
+
23
+ /** Disable dynamic :height property */
24
+ disableHeight: boolean;
25
+
26
+ /** Disable dynamic :width property */
27
+ disableWidth: boolean;
28
+
29
+ /** Nonce of the inlined stylesheet for Content Security Policy */
30
+ nonce?: string;
31
+
32
+ /** Callback to be invoked on-resize */
33
+ onResize: (param: Size) => void;
34
+
35
+ /** Optional inline style */
36
+ style?: object;
37
+ };
38
+
39
+ type State = {
40
+ height: number;
41
+ width: number;
42
+ };
43
+
44
+ type ResizeHandler = (element: HTMLElement, onResize: () => void) => void;
45
+
46
+ type DetectElementResize = {
47
+ addResizeListener: ResizeHandler;
48
+ removeResizeListener: ResizeHandler;
49
+ };
50
+
51
+ export default class AutoSizer extends React.PureComponent<Props, State> {
52
+ static defaultProps = {
53
+ onResize: () => {
54
+ //
55
+ },
56
+ disableHeight: false,
57
+ disableWidth: false,
58
+ style: {},
59
+ };
60
+
61
+ override state = {
62
+ height: this.props.defaultHeight || 0,
63
+ width: this.props.defaultWidth || 0,
64
+ };
65
+
66
+ _parentNode?: HTMLElement;
67
+ _autoSizer: HTMLElement | null;
68
+ _detectElementResize: DetectElementResize;
69
+
70
+ override componentDidMount() {
71
+ const { nonce } = this.props;
72
+ if (
73
+ this._autoSizer &&
74
+ this._autoSizer.parentNode &&
75
+ this._autoSizer.parentNode.ownerDocument &&
76
+ this._autoSizer.parentNode.ownerDocument.defaultView &&
77
+ this._autoSizer.parentNode instanceof
78
+ this._autoSizer.parentNode.ownerDocument.defaultView.HTMLElement
79
+ ) {
80
+ // Delay access of parentNode until mount.
81
+ // This handles edge-cases where the component has already been unmounted before its ref has been set,
82
+ // As well as libraries like react-lite which have a slightly different lifecycle.
83
+ this._parentNode = this._autoSizer.parentNode;
84
+
85
+ // Defer requiring resize handler in order to support server-side rendering.
86
+ // See issue #41
87
+ this._detectElementResize = createDetectElementResize(nonce);
88
+ this._detectElementResize.addResizeListener(this._parentNode, this._onResize);
89
+
90
+ this._onResize();
91
+ }
92
+ }
93
+
94
+ override componentWillUnmount() {
95
+ if (this._detectElementResize && this._parentNode) {
96
+ this._detectElementResize.removeResizeListener(this._parentNode, this._onResize);
97
+ }
98
+ }
99
+
100
+ override render() {
101
+ const { children, className, disableHeight, disableWidth, style } = this.props;
102
+ const { height, width } = this.state;
103
+
104
+ // Outer div should not force width/height since that may prevent containers from shrinking.
105
+ // Inner component should overflow and use calculated width/height.
106
+ // See issue #68 for more information.
107
+ const outerStyle: Record<any, any> = { overflow: 'visible' };
108
+ const childParams: { height: number; width: number; [index: string]: any } = {
109
+ height: 0,
110
+ width: 0,
111
+ };
112
+
113
+ if (!disableHeight) {
114
+ outerStyle['height'] = 0;
115
+ childParams['height'] = height;
116
+ }
117
+
118
+ if (!disableWidth) {
119
+ outerStyle['width'] = 0;
120
+ childParams['width'] = width;
121
+ }
122
+
123
+ /**
124
+ * TODO: Avoid rendering children before the initial measurements have been collected.
125
+ * At best this would just be wasting cycles.
126
+ * Add this check into version 10 though as it could break too many ref callbacks in version 9.
127
+ * Note that if default width/height props were provided this would still work with SSR.
128
+ if (
129
+ height !== 0 &&
130
+ width !== 0
131
+ ) {
132
+ child = children({ height, width })
133
+ }
134
+ */
135
+
136
+ return (
137
+ <div
138
+ className={className}
139
+ ref={this._setRef}
140
+ style={{
141
+ ...outerStyle,
142
+ ...style,
143
+ }}
144
+ >
145
+ {children(childParams)}
146
+ </div>
147
+ );
148
+ }
149
+
150
+ _onResize = () => {
151
+ const { disableHeight, disableWidth, onResize } = this.props;
152
+
153
+ if (this._parentNode) {
154
+ // Guard against AutoSizer component being removed from the DOM immediately after being added.
155
+ // This can result in invalid style values which can result in NaN values if we don't handle them.
156
+ // See issue #150 for more context.
157
+
158
+ const height = this._parentNode.offsetHeight || 0;
159
+ const width = this._parentNode.offsetWidth || 0;
160
+
161
+ const style = window.getComputedStyle(this._parentNode) || {};
162
+ const paddingLeft = parseInt(style.paddingLeft, 10) || 0;
163
+ const paddingRight = parseInt(style.paddingRight, 10) || 0;
164
+ const paddingTop = parseInt(style.paddingTop, 10) || 0;
165
+ const paddingBottom = parseInt(style.paddingBottom, 10) || 0;
166
+
167
+ const newHeight = height - paddingTop - paddingBottom;
168
+ const newWidth = width - paddingLeft - paddingRight;
169
+
170
+ if (
171
+ (!disableHeight && this.state.height !== newHeight) ||
172
+ (!disableWidth && this.state.width !== newWidth)
173
+ ) {
174
+ this.setState({
175
+ height: height - paddingTop - paddingBottom,
176
+ width: width - paddingLeft - paddingRight,
177
+ });
178
+
179
+ onResize({ height, width });
180
+ }
181
+ }
182
+ };
183
+
184
+ _setRef = (autoSizer: HTMLElement | null) => {
185
+ this._autoSizer = autoSizer;
186
+ };
187
+ }
@@ -0,0 +1,4 @@
1
+ // @flow
2
+
3
+ export { default } from './auto-sizer.js';
4
+ export { default as AutoSizer } from './auto-sizer.js';
@@ -0,0 +1,220 @@
1
+ /** @flow */
2
+
3
+ import type { CellMeasureCache } from './types.js';
4
+
5
+ export const DEFAULT_HEIGHT = 30;
6
+ export const DEFAULT_WIDTH = 100;
7
+
8
+ // Enables more intelligent mapping of a given column and row index to an item ID.
9
+ // This prevents a cell cache from being invalidated when its parent collection is modified.
10
+ type KeyMapper = (rowIndex: number, columnIndex: number) => any;
11
+
12
+ type CellMeasurerCacheParams = {
13
+ defaultHeight?: number;
14
+ defaultWidth?: number;
15
+ fixedHeight?: boolean;
16
+ fixedWidth?: boolean;
17
+ minHeight?: number;
18
+ minWidth?: number;
19
+ keyMapper?: KeyMapper;
20
+ };
21
+
22
+ type Cache = Record<any, number>;
23
+
24
+ type IndexParam = {
25
+ index: number;
26
+ };
27
+
28
+ /**
29
+ * Caches measurements for a given cell.
30
+ */
31
+ export default class CellMeasurerCache implements CellMeasureCache {
32
+ _cellHeightCache: Cache = {};
33
+ _cellWidthCache: Cache = {};
34
+ _columnWidthCache: Cache = {};
35
+ _rowHeightCache: Cache = {};
36
+ _defaultHeight: number;
37
+ _defaultWidth: number;
38
+ _minHeight: number;
39
+ _minWidth: number;
40
+ _keyMapper: KeyMapper;
41
+ _hasFixedHeight: boolean;
42
+ _hasFixedWidth: boolean;
43
+ _columnCount = 0;
44
+ _rowCount = 0;
45
+
46
+ constructor(params: CellMeasurerCacheParams = {}) {
47
+ const {
48
+ defaultHeight,
49
+ defaultWidth,
50
+ fixedHeight,
51
+ fixedWidth,
52
+ keyMapper,
53
+ minHeight,
54
+ minWidth,
55
+ } = params;
56
+
57
+ this._hasFixedHeight = fixedHeight === true;
58
+ this._hasFixedWidth = fixedWidth === true;
59
+ this._minHeight = minHeight || 0;
60
+ this._minWidth = minWidth || 0;
61
+ this._keyMapper = keyMapper || defaultKeyMapper;
62
+
63
+ this._defaultHeight = Math.max(
64
+ this._minHeight,
65
+ typeof defaultHeight === 'number' ? defaultHeight : DEFAULT_HEIGHT,
66
+ );
67
+ this._defaultWidth = Math.max(
68
+ this._minWidth,
69
+ typeof defaultWidth === 'number' ? defaultWidth : DEFAULT_WIDTH,
70
+ );
71
+
72
+ if (process.env['NODE_ENV'] !== 'production') {
73
+ if (this._hasFixedHeight === false && this._hasFixedWidth === false) {
74
+ console.warn(
75
+ "CellMeasurerCache should only measure a cell's width or height. " +
76
+ 'You have configured CellMeasurerCache to measure both. ' +
77
+ 'This will result in poor performance.',
78
+ );
79
+ }
80
+
81
+ if (this._hasFixedHeight === false && this._defaultHeight === 0) {
82
+ console.warn(
83
+ 'Fixed height CellMeasurerCache should specify a :defaultHeight greater than 0. ' +
84
+ 'Failing to do so will lead to unnecessary layout and poor performance.',
85
+ );
86
+ }
87
+
88
+ if (this._hasFixedWidth === false && this._defaultWidth === 0) {
89
+ console.warn(
90
+ 'Fixed width CellMeasurerCache should specify a :defaultWidth greater than 0. ' +
91
+ 'Failing to do so will lead to unnecessary layout and poor performance.',
92
+ );
93
+ }
94
+ }
95
+ }
96
+
97
+ clear(rowIndex: number, columnIndex = 0) {
98
+ const key = this._keyMapper(rowIndex, columnIndex);
99
+
100
+ delete this._cellHeightCache[key];
101
+ delete this._cellWidthCache[key];
102
+
103
+ this._updateCachedColumnAndRowSizes(rowIndex, columnIndex);
104
+ }
105
+
106
+ clearAll() {
107
+ this._cellHeightCache = {};
108
+ this._cellWidthCache = {};
109
+ this._columnWidthCache = {};
110
+ this._rowHeightCache = {};
111
+ this._rowCount = 0;
112
+ this._columnCount = 0;
113
+ }
114
+
115
+ columnWidth = ({ index }: IndexParam) => {
116
+ const key = this._keyMapper(0, index);
117
+
118
+ return Object.prototype.hasOwnProperty.call(this._columnWidthCache, key)
119
+ ? this._columnWidthCache[key]
120
+ : this._defaultWidth;
121
+ };
122
+
123
+ get defaultHeight(): number {
124
+ return this._defaultHeight;
125
+ }
126
+
127
+ get defaultWidth(): number {
128
+ return this._defaultWidth;
129
+ }
130
+
131
+ hasFixedHeight(): boolean {
132
+ return this._hasFixedHeight;
133
+ }
134
+
135
+ hasFixedWidth(): boolean {
136
+ return this._hasFixedWidth;
137
+ }
138
+
139
+ getHeight(rowIndex: number, columnIndex = 0): number {
140
+ if (this._hasFixedHeight) {
141
+ return this._defaultHeight;
142
+ } else {
143
+ const key = this._keyMapper(rowIndex, columnIndex);
144
+
145
+ return Object.prototype.hasOwnProperty.call(this._cellHeightCache, key)
146
+ ? Math.max(this._minHeight, this._cellHeightCache[key])
147
+ : this._defaultHeight;
148
+ }
149
+ }
150
+
151
+ getWidth(rowIndex: number, columnIndex = 0): number {
152
+ if (this._hasFixedWidth) {
153
+ return this._defaultWidth;
154
+ } else {
155
+ const key = this._keyMapper(rowIndex, columnIndex);
156
+
157
+ return Object.prototype.hasOwnProperty.call(this._cellWidthCache, key)
158
+ ? Math.max(this._minWidth, this._cellWidthCache[key])
159
+ : this._defaultWidth;
160
+ }
161
+ }
162
+
163
+ has(rowIndex: number, columnIndex = 0): boolean {
164
+ const key = this._keyMapper(rowIndex, columnIndex);
165
+
166
+ return Object.prototype.hasOwnProperty.call(this._cellHeightCache, key);
167
+ }
168
+
169
+ rowHeight = ({ index }: IndexParam) => {
170
+ const key = this._keyMapper(index, 0);
171
+
172
+ return Object.prototype.hasOwnProperty.call(this._rowHeightCache, key)
173
+ ? this._rowHeightCache[key]
174
+ : this._defaultHeight;
175
+ };
176
+
177
+ set(rowIndex: number, columnIndex: number, width: number, height: number): void {
178
+ const key = this._keyMapper(rowIndex, columnIndex);
179
+
180
+ if (columnIndex >= this._columnCount) {
181
+ this._columnCount = columnIndex + 1;
182
+ }
183
+ if (rowIndex >= this._rowCount) {
184
+ this._rowCount = rowIndex + 1;
185
+ }
186
+
187
+ // Size is cached per cell so we don't have to re-measure if cells are re-ordered.
188
+ this._cellHeightCache[key] = height;
189
+ this._cellWidthCache[key] = width;
190
+
191
+ this._updateCachedColumnAndRowSizes(rowIndex, columnIndex);
192
+ }
193
+
194
+ _updateCachedColumnAndRowSizes(rowIndex: number, columnIndex: number) {
195
+ // :columnWidth and :rowHeight are derived based on all cells in a column/row.
196
+ // Pre-cache these derived values for faster lookup later.
197
+ // Reads are expected to occur more frequently than writes in this case.
198
+ // Only update non-fixed dimensions though to avoid doing unnecessary work.
199
+ if (!this._hasFixedWidth) {
200
+ let columnWidth = 0;
201
+ for (let i = 0; i < this._rowCount; i++) {
202
+ columnWidth = Math.max(columnWidth, this.getWidth(i, columnIndex));
203
+ }
204
+ const columnKey = this._keyMapper(0, columnIndex);
205
+ this._columnWidthCache[columnKey] = columnWidth;
206
+ }
207
+ if (!this._hasFixedHeight) {
208
+ let rowHeight = 0;
209
+ for (let i = 0; i < this._columnCount; i++) {
210
+ rowHeight = Math.max(rowHeight, this.getHeight(rowIndex, i));
211
+ }
212
+ const rowKey = this._keyMapper(rowIndex, 0);
213
+ this._rowHeightCache[rowKey] = rowHeight;
214
+ }
215
+ }
216
+ }
217
+
218
+ function defaultKeyMapper(rowIndex: number, columnIndex: number) {
219
+ return `${rowIndex}-${columnIndex}`;
220
+ }
@@ -0,0 +1,151 @@
1
+ import * as React from 'react';
2
+ import { findDOMNode } from 'react-dom';
3
+
4
+ import type { CellMeasureCache } from './types.js';
5
+
6
+ type Children = (params: { measure: () => void }) => React.ReactNode;
7
+
8
+ type Cell = {
9
+ columnIndex: number;
10
+ rowIndex: number;
11
+ };
12
+
13
+ type Props = {
14
+ cache: CellMeasureCache;
15
+ children: Children | React.ReactNode;
16
+ columnIndex?: number;
17
+ index?: number;
18
+ parent: {
19
+ invalidateCellSizeAfterRender?: (cell: Cell) => void;
20
+ recomputeGridSize?: (cell: Cell) => void;
21
+ };
22
+ rowIndex?: number;
23
+ };
24
+
25
+ /**
26
+ * Wraps a cell and measures its rendered content.
27
+ * Measurements are stored in a per-cell cache.
28
+ * Cached-content is not be re-measured.
29
+ */
30
+ export default class CellMeasurer extends React.PureComponent<Props> {
31
+ static __internalCellMeasurerFlag = false;
32
+
33
+ override componentDidMount() {
34
+ this._maybeMeasureCell();
35
+ }
36
+
37
+ override componentDidUpdate() {
38
+ this._maybeMeasureCell();
39
+ }
40
+
41
+ override render() {
42
+ const { children } = this.props;
43
+
44
+ return typeof children === 'function'
45
+ ? children({ measure: this._measure })
46
+ : children;
47
+ }
48
+
49
+ _getCellMeasurements() {
50
+ const { cache } = this.props;
51
+
52
+ // eslint-disable-next-line react/no-find-dom-node
53
+ const node = findDOMNode(this);
54
+
55
+ // TODO Check for a bad combination of fixedWidth and missing numeric width or vice versa with height
56
+
57
+ if (
58
+ node &&
59
+ node.ownerDocument &&
60
+ node.ownerDocument.defaultView &&
61
+ node instanceof node.ownerDocument.defaultView.HTMLElement
62
+ ) {
63
+ const styleWidth = node.style.width;
64
+ const styleHeight = node.style.height;
65
+
66
+ // If we are re-measuring a cell that has already been measured,
67
+ // It will have a hard-coded width/height from the previous measurement.
68
+ // The fact that we are measuring indicates this measurement is probably stale,
69
+ // So explicitly clear it out (eg set to "auto") so we can recalculate.
70
+ // See issue #593 for more info.
71
+ // Even if we are measuring initially- if we're inside of a MultiGrid component,
72
+ // Explicitly clear width/height before measuring to avoid being tainted by another Grid.
73
+ // eg top/left Grid renders before bottom/right Grid
74
+ // Since the CellMeasurerCache is shared between them this taints derived cell size values.
75
+ if (!cache.hasFixedWidth()) {
76
+ node.style.width = 'auto';
77
+ }
78
+ if (!cache.hasFixedHeight()) {
79
+ node.style.height = 'auto';
80
+ }
81
+
82
+ const height = Math.ceil(node.offsetHeight);
83
+ const width = Math.ceil(node.offsetWidth);
84
+
85
+ // Reset after measuring to avoid breaking styles; see #660
86
+ if (styleWidth) {
87
+ node.style.width = styleWidth;
88
+ }
89
+ if (styleHeight) {
90
+ node.style.height = styleHeight;
91
+ }
92
+
93
+ return { height, width };
94
+ } else {
95
+ return { height: 0, width: 0 };
96
+ }
97
+ }
98
+
99
+ _maybeMeasureCell() {
100
+ const {
101
+ cache,
102
+ columnIndex = 0,
103
+ parent,
104
+ rowIndex = this.props.index || 0,
105
+ } = this.props;
106
+
107
+ if (!cache.has(rowIndex, columnIndex)) {
108
+ const { height, width } = this._getCellMeasurements();
109
+
110
+ cache.set(rowIndex, columnIndex, width, height);
111
+
112
+ // If size has changed, let Grid know to re-render.
113
+ if (parent && typeof parent.invalidateCellSizeAfterRender === 'function') {
114
+ parent.invalidateCellSizeAfterRender({
115
+ columnIndex,
116
+ rowIndex,
117
+ });
118
+ }
119
+ }
120
+ }
121
+
122
+ _measure = () => {
123
+ const {
124
+ cache,
125
+ columnIndex = 0,
126
+ parent,
127
+ rowIndex = this.props.index || 0,
128
+ } = this.props;
129
+
130
+ const { height, width } = this._getCellMeasurements();
131
+
132
+ if (
133
+ height !== cache.getHeight(rowIndex, columnIndex) ||
134
+ width !== cache.getWidth(rowIndex, columnIndex)
135
+ ) {
136
+ cache.set(rowIndex, columnIndex, width, height);
137
+
138
+ if (parent && typeof parent.recomputeGridSize === 'function') {
139
+ parent.recomputeGridSize({
140
+ columnIndex,
141
+ rowIndex,
142
+ });
143
+ }
144
+ }
145
+ };
146
+ }
147
+
148
+ // Used for DEV mode warning check
149
+ if (process.env['NODE_ENV'] !== 'production') {
150
+ CellMeasurer.__internalCellMeasurerFlag = true;
151
+ }
@@ -0,0 +1,5 @@
1
+ import CellMeasurerCache from './cell-measurer-cache.js';
2
+ import CellMeasurer from './cell-measurer.js';
3
+
4
+ export default CellMeasurer;
5
+ export { CellMeasurer, CellMeasurerCache };
@@ -0,0 +1,8 @@
1
+ export interface CellMeasureCache {
2
+ hasFixedWidth(): boolean;
3
+ hasFixedHeight(): boolean;
4
+ has(rowIndex: number, columnIndex: number): boolean;
5
+ set(rowIndex: number, columnIndex: number, width: number, height: number): void;
6
+ getHeight(rowIndex: number, columnIndex?: number): number;
7
+ getWidth(rowIndex: number, columnIndex?: number): number;
8
+ }
@@ -0,0 +1,38 @@
1
+ import type { OverscanIndices, OverscanIndicesGetterParams } from './types.js';
2
+
3
+ export const SCROLL_DIRECTION_BACKWARD = -1;
4
+ export const SCROLL_DIRECTION_FORWARD = 1;
5
+
6
+ export const SCROLL_DIRECTION_HORIZONTAL = 'horizontal';
7
+ export const SCROLL_DIRECTION_VERTICAL = 'vertical';
8
+
9
+ /**
10
+ * Calculates the number of cells to overscan before and after a specified range.
11
+ * This function ensures that overscanning doesn't exceed the available cells.
12
+ */
13
+
14
+ export default function defaultOverscanIndicesGetter({
15
+ cellCount,
16
+ overscanCellsCount,
17
+ scrollDirection,
18
+ startIndex,
19
+ stopIndex,
20
+ }: OverscanIndicesGetterParams): OverscanIndices {
21
+ // Make sure we render at least 1 cell extra before and after (except near boundaries)
22
+ // This is necessary in order to support keyboard navigation (TAB/SHIFT+TAB) in some cases
23
+ // For more info see issues #625
24
+ // eslint-disable-next-line no-param-reassign
25
+ overscanCellsCount = Math.max(1, overscanCellsCount);
26
+
27
+ if (scrollDirection === SCROLL_DIRECTION_FORWARD) {
28
+ return {
29
+ overscanStartIndex: Math.max(0, startIndex - 1),
30
+ overscanStopIndex: Math.min(cellCount - 1, stopIndex + overscanCellsCount),
31
+ };
32
+ } else {
33
+ return {
34
+ overscanStartIndex: Math.max(0, startIndex - overscanCellsCount),
35
+ overscanStopIndex: Math.min(cellCount - 1, stopIndex + 1),
36
+ };
37
+ }
38
+ }