@mui/x-virtualizer 0.1.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 (56) hide show
  1. package/CHANGELOG.md +10645 -0
  2. package/README.md +3 -0
  3. package/esm/features/colspan.d.ts +28 -0
  4. package/esm/features/colspan.js +88 -0
  5. package/esm/features/dimensions.d.ts +41 -0
  6. package/esm/features/dimensions.js +465 -0
  7. package/esm/features/index.d.ts +5 -0
  8. package/esm/features/index.js +5 -0
  9. package/esm/features/keyboard.d.ts +16 -0
  10. package/esm/features/keyboard.js +35 -0
  11. package/esm/features/rowspan.d.ts +25 -0
  12. package/esm/features/rowspan.js +36 -0
  13. package/esm/features/virtualization.d.ts +103 -0
  14. package/esm/features/virtualization.js +844 -0
  15. package/esm/index.d.ts +2 -0
  16. package/esm/index.js +9 -0
  17. package/esm/models/colspan.d.ts +13 -0
  18. package/esm/models/colspan.js +1 -0
  19. package/esm/models/core.d.ts +72 -0
  20. package/esm/models/core.js +43 -0
  21. package/esm/models/dimensions.d.ts +132 -0
  22. package/esm/models/dimensions.js +1 -0
  23. package/esm/models/index.d.ts +4 -0
  24. package/esm/models/index.js +4 -0
  25. package/esm/models/rowspan.d.ts +17 -0
  26. package/esm/models/rowspan.js +1 -0
  27. package/esm/package.json +1 -0
  28. package/esm/useVirtualizer.d.ts +193 -0
  29. package/esm/useVirtualizer.js +26 -0
  30. package/features/colspan.d.ts +28 -0
  31. package/features/colspan.js +94 -0
  32. package/features/dimensions.d.ts +41 -0
  33. package/features/dimensions.js +472 -0
  34. package/features/index.d.ts +5 -0
  35. package/features/index.js +60 -0
  36. package/features/keyboard.d.ts +16 -0
  37. package/features/keyboard.js +40 -0
  38. package/features/rowspan.d.ts +25 -0
  39. package/features/rowspan.js +42 -0
  40. package/features/virtualization.d.ts +103 -0
  41. package/features/virtualization.js +853 -0
  42. package/index.d.ts +2 -0
  43. package/index.js +34 -0
  44. package/models/colspan.d.ts +13 -0
  45. package/models/colspan.js +5 -0
  46. package/models/core.d.ts +72 -0
  47. package/models/core.js +49 -0
  48. package/models/dimensions.d.ts +132 -0
  49. package/models/dimensions.js +5 -0
  50. package/models/index.d.ts +4 -0
  51. package/models/index.js +49 -0
  52. package/models/rowspan.d.ts +17 -0
  53. package/models/rowspan.js +5 -0
  54. package/package.json +67 -0
  55. package/useVirtualizer.d.ts +193 -0
  56. package/useVirtualizer.js +33 -0
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # x-virtualizer
2
+
3
+ Virtualization engine
@@ -0,0 +1,28 @@
1
+ import { Store } from '@mui/x-internals/store';
2
+ import type { integer } from '@mui/x-internals/types';
3
+ import type { BaseState, VirtualizerParams } from "../useVirtualizer.js";
4
+ import type { ColumnWithWidth, RowId } from "../models/index.js";
5
+ import type { CellColSpanInfo } from "../models/colspan.js";
6
+ import { Virtualization } from "./virtualization.js";
7
+ type ColumnIndex = number;
8
+ type ColspanMap = Map<RowId, Record<ColumnIndex, CellColSpanInfo>>;
9
+ export declare const Colspan: {
10
+ initialize: typeof initializeState;
11
+ use: typeof useColspan;
12
+ selectors: {};
13
+ };
14
+ export declare namespace Colspan {
15
+ type State = {
16
+ colspanMap: ColspanMap;
17
+ };
18
+ type API = ReturnType<typeof useColspan>;
19
+ }
20
+ declare function initializeState(_params: VirtualizerParams): {
21
+ colspanMap: Map<any, any>;
22
+ };
23
+ declare function useColspan(store: Store<BaseState & Colspan.State>, params: VirtualizerParams, api: Virtualization.API): {
24
+ resetColSpan: () => void;
25
+ getCellColSpanInfo: (rowId: RowId, columnIndex: integer) => CellColSpanInfo | undefined;
26
+ calculateColSpan: (rowId: RowId, minFirstColumn: integer, maxLastColumn: integer, columns: ColumnWithWidth[]) => void;
27
+ };
28
+ export {};
@@ -0,0 +1,88 @@
1
+ import useEventCallback from '@mui/utils/useEventCallback';
2
+
3
+ /* eslint-disable import/export, @typescript-eslint/no-redeclare */
4
+
5
+ const selectors = {};
6
+ export const Colspan = {
7
+ initialize: initializeState,
8
+ use: useColspan,
9
+ selectors
10
+ };
11
+ function initializeState(_params) {
12
+ return {
13
+ colspanMap: new Map()
14
+ };
15
+ }
16
+ function useColspan(store, params, api) {
17
+ const resetColSpan = () => {
18
+ store.state.colspanMap = new Map();
19
+ };
20
+ const getCellColSpanInfo = (rowId, columnIndex) => {
21
+ return store.state.colspanMap.get(rowId)?.[columnIndex];
22
+ };
23
+
24
+ // Calculate `colSpan` for each cell in the row
25
+ const calculateColSpan = useEventCallback((rowId, minFirstColumn, maxLastColumn, columns) => {
26
+ for (let i = minFirstColumn; i < maxLastColumn; i += 1) {
27
+ const cellProps = calculateCellColSpan(store.state.colspanMap, i, rowId, minFirstColumn, maxLastColumn, columns, params.getColspan);
28
+ if (cellProps.colSpan > 1) {
29
+ i += cellProps.colSpan - 1;
30
+ }
31
+ }
32
+ });
33
+ api.calculateColSpan = calculateColSpan;
34
+ return {
35
+ resetColSpan,
36
+ getCellColSpanInfo,
37
+ calculateColSpan
38
+ };
39
+ }
40
+ function calculateCellColSpan(lookup, columnIndex, rowId, minFirstColumnIndex, maxLastColumnIndex, columns, getColspan) {
41
+ const columnsLength = columns.length;
42
+ const column = columns[columnIndex];
43
+ const colSpan = getColspan(rowId, column, columnIndex);
44
+ if (!colSpan || colSpan === 1) {
45
+ setCellColSpanInfo(lookup, rowId, columnIndex, {
46
+ spannedByColSpan: false,
47
+ cellProps: {
48
+ colSpan: 1,
49
+ width: column.computedWidth
50
+ }
51
+ });
52
+ return {
53
+ colSpan: 1
54
+ };
55
+ }
56
+ let width = column.computedWidth;
57
+ for (let j = 1; j < colSpan; j += 1) {
58
+ const nextColumnIndex = columnIndex + j;
59
+ // Cells should be spanned only within their column section (left-pinned, right-pinned and unpinned).
60
+ if (nextColumnIndex >= minFirstColumnIndex && nextColumnIndex < maxLastColumnIndex) {
61
+ const nextColumn = columns[nextColumnIndex];
62
+ width += nextColumn.computedWidth;
63
+ setCellColSpanInfo(lookup, rowId, columnIndex + j, {
64
+ spannedByColSpan: true,
65
+ rightVisibleCellIndex: Math.min(columnIndex + colSpan, columnsLength - 1),
66
+ leftVisibleCellIndex: columnIndex
67
+ });
68
+ }
69
+ setCellColSpanInfo(lookup, rowId, columnIndex, {
70
+ spannedByColSpan: false,
71
+ cellProps: {
72
+ colSpan,
73
+ width
74
+ }
75
+ });
76
+ }
77
+ return {
78
+ colSpan
79
+ };
80
+ }
81
+ function setCellColSpanInfo(colspanMap, rowId, columnIndex, cellColSpanInfo) {
82
+ let columnInfo = colspanMap.get(rowId);
83
+ if (!columnInfo) {
84
+ columnInfo = {};
85
+ colspanMap.set(rowId, columnInfo);
86
+ }
87
+ columnInfo[columnIndex] = cellColSpanInfo;
88
+ }
@@ -0,0 +1,41 @@
1
+ import { Store } from '@mui/x-internals/store';
2
+ import { ColumnWithWidth, DimensionsState, RowId, RowsMetaState, Size } from "../models/index.js";
3
+ import type { BaseState, VirtualizerParams } from "../useVirtualizer.js";
4
+ export declare const Dimensions: {
5
+ initialize: typeof initializeState;
6
+ use: typeof useDimensions;
7
+ selectors: {
8
+ rootSize: (state: BaseState) => Size;
9
+ dimensions: (state: BaseState) => DimensionsState;
10
+ rowHeight: (state: BaseState) => number;
11
+ rowsMeta: (state: BaseState) => RowsMetaState;
12
+ columnPositions: (_: any, columns: ColumnWithWidth[]) => number[];
13
+ needsHorizontalScrollbar: (state: BaseState) => boolean;
14
+ };
15
+ };
16
+ export declare namespace Dimensions {
17
+ type State = {
18
+ rootSize: Size;
19
+ dimensions: DimensionsState;
20
+ rowsMeta: RowsMetaState;
21
+ rowHeights: Map<any, any>;
22
+ };
23
+ type API = ReturnType<typeof useDimensions>;
24
+ }
25
+ declare function initializeState(params: VirtualizerParams): Dimensions.State;
26
+ declare function useDimensions(store: Store<BaseState>, params: VirtualizerParams, _api: {}): {
27
+ updateDimensions: () => void;
28
+ debouncedUpdateDimensions: ((() => void) & import("@mui/x-internals/throttle/throttle").Cancelable) | undefined;
29
+ rowsMeta: {
30
+ getRowHeight: (rowId: RowId) => any;
31
+ setLastMeasuredRowIndex: (index: number) => void;
32
+ storeRowHeightMeasurement: (id: RowId, height: number) => void;
33
+ hydrateRowsMeta: () => void;
34
+ observeRowHeight: (element: Element, rowId: RowId) => () => void | undefined;
35
+ rowHasAutoHeight: (id: RowId) => any;
36
+ getRowHeightEntry: (rowId: RowId) => any;
37
+ getLastMeasuredRowIndex: () => number;
38
+ resetRowHeights: () => void;
39
+ };
40
+ };
41
+ export {};
@@ -0,0 +1,465 @@
1
+ 'use client';
2
+
3
+ import _extends from "@babel/runtime/helpers/esm/extends";
4
+ import * as React from 'react';
5
+ import ownerDocument from '@mui/utils/ownerDocument';
6
+ import useLazyRef from '@mui/utils/useLazyRef';
7
+ import useLayoutEffect from '@mui/utils/useEnhancedEffect';
8
+ import useEventCallback from '@mui/utils/useEventCallback';
9
+ import { throttle } from '@mui/x-internals/throttle';
10
+ import { isDeepEqual } from '@mui/x-internals/isDeepEqual';
11
+ import { roundToDecimalPlaces } from '@mui/x-internals/math';
12
+ import { useStore, useStoreEffect, createSelectorMemoized } from '@mui/x-internals/store';
13
+ import { Size } from "../models/index.js";
14
+ /* eslint-disable import/export, @typescript-eslint/no-redeclare */
15
+ /* eslint-disable no-underscore-dangle */
16
+
17
+ const EMPTY_DIMENSIONS = {
18
+ isReady: false,
19
+ root: Size.EMPTY,
20
+ viewportOuterSize: Size.EMPTY,
21
+ viewportInnerSize: Size.EMPTY,
22
+ contentSize: Size.EMPTY,
23
+ minimumSize: Size.EMPTY,
24
+ hasScrollX: false,
25
+ hasScrollY: false,
26
+ scrollbarSize: 0,
27
+ headerHeight: 0,
28
+ groupHeaderHeight: 0,
29
+ headerFilterHeight: 0,
30
+ rowWidth: 0,
31
+ rowHeight: 0,
32
+ columnsTotalWidth: 0,
33
+ leftPinnedWidth: 0,
34
+ rightPinnedWidth: 0,
35
+ headersTotalHeight: 0,
36
+ topContainerHeight: 0,
37
+ bottomContainerHeight: 0
38
+ };
39
+ const selectors = {
40
+ rootSize: state => state.rootSize,
41
+ dimensions: state => state.dimensions,
42
+ rowHeight: state => state.dimensions.rowHeight,
43
+ rowsMeta: state => state.rowsMeta,
44
+ columnPositions: createSelectorMemoized((_, columns) => {
45
+ const positions = [];
46
+ let currentPosition = 0;
47
+ for (let i = 0; i < columns.length; i += 1) {
48
+ positions.push(currentPosition);
49
+ currentPosition += columns[i].computedWidth;
50
+ }
51
+ return positions;
52
+ }),
53
+ needsHorizontalScrollbar: state => state.dimensions.viewportOuterSize.width > 0 && state.dimensions.columnsTotalWidth > state.dimensions.viewportOuterSize.width
54
+ };
55
+ export const Dimensions = {
56
+ initialize: initializeState,
57
+ use: useDimensions,
58
+ selectors
59
+ };
60
+ function initializeState(params) {
61
+ const dimensions = _extends({}, EMPTY_DIMENSIONS, params.dimensions);
62
+ const {
63
+ rowCount
64
+ } = params;
65
+ const {
66
+ rowHeight
67
+ } = dimensions;
68
+ const rowsMeta = {
69
+ currentPageTotalHeight: rowCount * rowHeight,
70
+ positions: Array.from({
71
+ length: rowCount
72
+ }, (_, i) => i * rowHeight),
73
+ pinnedTopRowsTotalHeight: 0,
74
+ pinnedBottomRowsTotalHeight: 0
75
+ };
76
+ const rowHeights = new Map();
77
+ return {
78
+ rootSize: Size.EMPTY,
79
+ dimensions,
80
+ rowsMeta,
81
+ rowHeights
82
+ };
83
+ }
84
+ function useDimensions(store, params, _api) {
85
+ const isFirstSizing = React.useRef(true);
86
+ const {
87
+ refs,
88
+ dimensions: {
89
+ rowHeight,
90
+ headerHeight,
91
+ columnsTotalWidth,
92
+ groupHeaderHeight,
93
+ headerFilterHeight,
94
+ headersTotalHeight,
95
+ leftPinnedWidth,
96
+ rightPinnedWidth
97
+ }
98
+ } = params;
99
+ const updateDimensions = React.useCallback(() => {
100
+ if (isFirstSizing.current) {
101
+ return;
102
+ }
103
+ const rootSize = selectors.rootSize(store.state);
104
+ const rowsMeta = selectors.rowsMeta(store.state);
105
+
106
+ // All the floating point dimensions should be rounded to .1 decimal places to avoid subpixel rendering issues
107
+ // https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477
108
+ // https://github.com/mui/mui-x/issues/15721
109
+ const scrollbarSize = measureScrollbarSize(params.refs.container.current, params.scrollbarSize);
110
+ const topContainerHeight = headersTotalHeight + rowsMeta.pinnedTopRowsTotalHeight;
111
+ const bottomContainerHeight = rowsMeta.pinnedBottomRowsTotalHeight;
112
+ const contentSize = {
113
+ width: columnsTotalWidth,
114
+ height: roundToDecimalPlaces(rowsMeta.currentPageTotalHeight, 1)
115
+ };
116
+ let viewportOuterSize;
117
+ let viewportInnerSize;
118
+ let hasScrollX = false;
119
+ let hasScrollY = false;
120
+ if (params.autoHeight) {
121
+ hasScrollY = false;
122
+ hasScrollX = Math.round(columnsTotalWidth) > Math.round(rootSize.width);
123
+ viewportOuterSize = {
124
+ width: rootSize.width,
125
+ height: topContainerHeight + bottomContainerHeight + contentSize.height
126
+ };
127
+ viewportInnerSize = {
128
+ width: Math.max(0, viewportOuterSize.width - (hasScrollY ? scrollbarSize : 0)),
129
+ height: Math.max(0, viewportOuterSize.height - (hasScrollX ? scrollbarSize : 0))
130
+ };
131
+ } else {
132
+ viewportOuterSize = {
133
+ width: rootSize.width,
134
+ height: rootSize.height
135
+ };
136
+ viewportInnerSize = {
137
+ width: Math.max(0, viewportOuterSize.width),
138
+ height: Math.max(0, viewportOuterSize.height - topContainerHeight - bottomContainerHeight)
139
+ };
140
+ const content = contentSize;
141
+ const container = viewportInnerSize;
142
+ const hasScrollXIfNoYScrollBar = content.width > container.width;
143
+ const hasScrollYIfNoXScrollBar = content.height > container.height;
144
+ if (hasScrollXIfNoYScrollBar || hasScrollYIfNoXScrollBar) {
145
+ hasScrollY = hasScrollYIfNoXScrollBar;
146
+ hasScrollX = content.width + (hasScrollY ? scrollbarSize : 0) > container.width;
147
+
148
+ // We recalculate the scroll y to consider the size of the x scrollbar.
149
+ if (hasScrollX) {
150
+ hasScrollY = content.height + scrollbarSize > container.height;
151
+ }
152
+ }
153
+ if (hasScrollY) {
154
+ viewportInnerSize.width -= scrollbarSize;
155
+ }
156
+ if (hasScrollX) {
157
+ viewportInnerSize.height -= scrollbarSize;
158
+ }
159
+ }
160
+ const rowWidth = Math.max(viewportOuterSize.width, columnsTotalWidth + (hasScrollY ? scrollbarSize : 0));
161
+ const minimumSize = {
162
+ width: columnsTotalWidth,
163
+ height: topContainerHeight + contentSize.height + bottomContainerHeight
164
+ };
165
+ const newDimensions = {
166
+ isReady: true,
167
+ root: rootSize,
168
+ viewportOuterSize,
169
+ viewportInnerSize,
170
+ contentSize,
171
+ minimumSize,
172
+ hasScrollX,
173
+ hasScrollY,
174
+ scrollbarSize,
175
+ headerHeight,
176
+ groupHeaderHeight,
177
+ headerFilterHeight,
178
+ rowWidth,
179
+ rowHeight,
180
+ columnsTotalWidth,
181
+ leftPinnedWidth,
182
+ rightPinnedWidth,
183
+ headersTotalHeight,
184
+ topContainerHeight,
185
+ bottomContainerHeight
186
+ };
187
+ const prevDimensions = store.state.dimensions;
188
+ if (isDeepEqual(prevDimensions, newDimensions)) {
189
+ return;
190
+ }
191
+ store.update({
192
+ dimensions: newDimensions
193
+ });
194
+ }, [store, params.refs.container, params.scrollbarSize, params.autoHeight, rowHeight, headerHeight, groupHeaderHeight, headerFilterHeight, columnsTotalWidth, headersTotalHeight, leftPinnedWidth, rightPinnedWidth]);
195
+ const {
196
+ resizeThrottleMs,
197
+ onResize
198
+ } = params;
199
+ const updateDimensionCallback = useEventCallback(updateDimensions);
200
+ const debouncedUpdateDimensions = React.useMemo(() => resizeThrottleMs > 0 ? throttle(() => {
201
+ updateDimensionCallback();
202
+ onResize?.(store.state.rootSize);
203
+ }, resizeThrottleMs) : undefined, [resizeThrottleMs, onResize, store, updateDimensionCallback]);
204
+ React.useEffect(() => debouncedUpdateDimensions?.clear, [debouncedUpdateDimensions]);
205
+ useLayoutEffect(() => observeRootNode(refs.container.current, store), [refs, store]);
206
+ useLayoutEffect(updateDimensions, [updateDimensions]);
207
+ useStoreEffect(store, selectors.rootSize, (_, size) => {
208
+ params.onResize?.(size);
209
+ if (isFirstSizing.current || !debouncedUpdateDimensions) {
210
+ // We want to initialize the grid dimensions as soon as possible to avoid flickering
211
+ isFirstSizing.current = false;
212
+ updateDimensions();
213
+ } else {
214
+ debouncedUpdateDimensions();
215
+ }
216
+ });
217
+ const rowsMeta = useRowsMeta(store, params, updateDimensions);
218
+ return {
219
+ updateDimensions,
220
+ debouncedUpdateDimensions,
221
+ rowsMeta
222
+ };
223
+ }
224
+ function useRowsMeta(store, params, updateDimensions) {
225
+ const heightCache = store.state.rowHeights;
226
+ const {
227
+ rows,
228
+ getRowHeight: getRowHeightProp,
229
+ getRowSpacing,
230
+ getEstimatedRowHeight
231
+ } = params;
232
+ const lastMeasuredRowIndex = React.useRef(-1);
233
+ const hasRowWithAutoHeight = React.useRef(false);
234
+ const isHeightMetaValid = React.useRef(false);
235
+ const pinnedRows = params.pinnedRows;
236
+ const rowHeight = useStore(store, selectors.rowHeight);
237
+ const getRowHeightEntry = useEventCallback(rowId => {
238
+ let entry = heightCache.get(rowId);
239
+ if (entry === undefined) {
240
+ entry = {
241
+ content: store.state.dimensions.rowHeight,
242
+ spacingTop: 0,
243
+ spacingBottom: 0,
244
+ detail: 0,
245
+ autoHeight: false,
246
+ needsFirstMeasurement: true
247
+ };
248
+ heightCache.set(rowId, entry);
249
+ }
250
+ return entry;
251
+ });
252
+ const {
253
+ rowIdToIndexMap,
254
+ applyRowHeight
255
+ } = params;
256
+ const processHeightEntry = React.useCallback(row => {
257
+ // HACK: rowHeight trails behind the most up-to-date value just enough to
258
+ // mess the initial rowsMeta hydration :/
259
+ eslintUseValue(rowHeight);
260
+ const dimensions = selectors.dimensions(store.state);
261
+ const baseRowHeight = dimensions.rowHeight;
262
+ const entry = getRowHeightEntry(row.id);
263
+ if (!getRowHeightProp) {
264
+ entry.content = baseRowHeight;
265
+ entry.needsFirstMeasurement = false;
266
+ } else {
267
+ const rowHeightFromUser = getRowHeightProp(row);
268
+ if (rowHeightFromUser === 'auto') {
269
+ if (entry.needsFirstMeasurement) {
270
+ const estimatedRowHeight = getEstimatedRowHeight ? getEstimatedRowHeight(row) : baseRowHeight;
271
+
272
+ // If the row was not measured yet use the estimated row height
273
+ entry.content = estimatedRowHeight ?? baseRowHeight;
274
+ }
275
+ hasRowWithAutoHeight.current = true;
276
+ entry.autoHeight = true;
277
+ } else {
278
+ // Default back to base rowHeight if getRowHeight returns null value.
279
+ entry.content = rowHeightFromUser ?? dimensions.rowHeight;
280
+ entry.needsFirstMeasurement = false;
281
+ entry.autoHeight = false;
282
+ }
283
+ }
284
+ if (getRowSpacing) {
285
+ const indexRelativeToCurrentPage = rowIdToIndexMap.get(row.id) ?? -1;
286
+ const spacing = getRowSpacing(row, {
287
+ isFirstVisible: indexRelativeToCurrentPage === 0,
288
+ isLastVisible: indexRelativeToCurrentPage === rows.length - 1,
289
+ indexRelativeToCurrentPage
290
+ });
291
+ entry.spacingTop = spacing.top ?? 0;
292
+ entry.spacingBottom = spacing.bottom ?? 0;
293
+ } else {
294
+ entry.spacingTop = 0;
295
+ entry.spacingBottom = 0;
296
+ }
297
+ applyRowHeight?.(entry, row);
298
+ return entry;
299
+ }, [store, rows, getRowHeightProp, getRowHeightEntry, getEstimatedRowHeight, rowHeight, getRowSpacing, rowIdToIndexMap, applyRowHeight]);
300
+ const hydrateRowsMeta = React.useCallback(() => {
301
+ hasRowWithAutoHeight.current = false;
302
+ const pinnedTopRowsTotalHeight = pinnedRows.top.reduce((acc, row) => {
303
+ const entry = processHeightEntry(row);
304
+ return acc + entry.content + entry.spacingTop + entry.spacingBottom + entry.detail;
305
+ }, 0);
306
+ const pinnedBottomRowsTotalHeight = pinnedRows.bottom.reduce((acc, row) => {
307
+ const entry = processHeightEntry(row);
308
+ return acc + entry.content + entry.spacingTop + entry.spacingBottom + entry.detail;
309
+ }, 0);
310
+ const positions = [];
311
+ const currentPageTotalHeight = rows.reduce((acc, row) => {
312
+ positions.push(acc);
313
+ const entry = processHeightEntry(row);
314
+ const total = entry.content + entry.spacingTop + entry.spacingBottom + entry.detail;
315
+ return acc + total;
316
+ }, 0);
317
+ if (!hasRowWithAutoHeight.current) {
318
+ // No row has height=auto, so all rows are already measured
319
+ lastMeasuredRowIndex.current = Infinity;
320
+ }
321
+ const didHeightsChange = pinnedTopRowsTotalHeight !== store.state.rowsMeta.pinnedTopRowsTotalHeight || pinnedBottomRowsTotalHeight !== store.state.rowsMeta.pinnedBottomRowsTotalHeight || currentPageTotalHeight !== store.state.rowsMeta.currentPageTotalHeight;
322
+ const rowsMeta = {
323
+ currentPageTotalHeight,
324
+ positions,
325
+ pinnedTopRowsTotalHeight,
326
+ pinnedBottomRowsTotalHeight
327
+ };
328
+ store.set('rowsMeta', rowsMeta);
329
+ if (didHeightsChange) {
330
+ updateDimensions();
331
+ }
332
+ isHeightMetaValid.current = true;
333
+ }, [store, pinnedRows, rows, processHeightEntry, updateDimensions]);
334
+ const hydrateRowsMetaLatest = useEventCallback(hydrateRowsMeta);
335
+ const getRowHeight = rowId => {
336
+ return heightCache.get(rowId)?.content ?? selectors.rowHeight(store.state);
337
+ };
338
+ const storeRowHeightMeasurement = (id, height) => {
339
+ const entry = getRowHeightEntry(id);
340
+ const didChange = entry.content !== height;
341
+ entry.needsFirstMeasurement = false;
342
+ entry.content = height;
343
+ isHeightMetaValid.current &&= !didChange;
344
+ };
345
+ const rowHasAutoHeight = id => {
346
+ return heightCache.get(id)?.autoHeight ?? false;
347
+ };
348
+ const getLastMeasuredRowIndex = () => {
349
+ return lastMeasuredRowIndex.current;
350
+ };
351
+ const setLastMeasuredRowIndex = index => {
352
+ if (hasRowWithAutoHeight.current && index > lastMeasuredRowIndex.current) {
353
+ lastMeasuredRowIndex.current = index;
354
+ }
355
+ };
356
+ const resetRowHeights = () => {
357
+ heightCache.clear();
358
+ hydrateRowsMeta();
359
+ };
360
+ const resizeObserver = useLazyRef(() => typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(entries => {
361
+ for (let i = 0; i < entries.length; i += 1) {
362
+ const entry = entries[i];
363
+ const height = entry.borderBoxSize && entry.borderBoxSize.length > 0 ? entry.borderBoxSize[0].blockSize : entry.contentRect.height;
364
+ const rowId = entry.target.__mui_id;
365
+ const focusedVirtualRowId = params.focusedVirtualCell()?.id;
366
+ if (focusedVirtualRowId === rowId && height === 0) {
367
+ // Focused virtual row has 0 height.
368
+ // We don't want to store it to avoid scroll jumping.
369
+ // https://github.com/mui/mui-x/issues/14726
370
+ return;
371
+ }
372
+ storeRowHeightMeasurement(rowId, height);
373
+ }
374
+ if (!isHeightMetaValid.current) {
375
+ // Avoids "ResizeObserver loop completed with undelivered notifications" error
376
+ requestAnimationFrame(() => {
377
+ hydrateRowsMetaLatest();
378
+ });
379
+ }
380
+ })).current;
381
+ const observeRowHeight = (element, rowId) => {
382
+ element.__mui_id = rowId;
383
+ resizeObserver?.observe(element);
384
+ return () => resizeObserver?.unobserve(element);
385
+ };
386
+
387
+ // The effect is used to build the rows meta data - currentPageTotalHeight and positions.
388
+ // Because of variable row height this is needed for the virtualization
389
+ useLayoutEffect(() => {
390
+ hydrateRowsMeta();
391
+ }, [hydrateRowsMeta]);
392
+ return {
393
+ getRowHeight,
394
+ setLastMeasuredRowIndex,
395
+ storeRowHeightMeasurement,
396
+ hydrateRowsMeta,
397
+ observeRowHeight,
398
+ rowHasAutoHeight,
399
+ getRowHeightEntry,
400
+ getLastMeasuredRowIndex,
401
+ resetRowHeights
402
+ };
403
+ }
404
+ function observeRootNode(node, store) {
405
+ if (!node) {
406
+ return undefined;
407
+ }
408
+ const bounds = node.getBoundingClientRect();
409
+ const initialSize = {
410
+ width: roundToDecimalPlaces(bounds.width, 1),
411
+ height: roundToDecimalPlaces(bounds.height, 1)
412
+ };
413
+ if (store.state.rootSize === Size.EMPTY || !Size.equals(initialSize, store.state.rootSize)) {
414
+ store.update({
415
+ rootSize: initialSize
416
+ });
417
+ }
418
+ if (typeof ResizeObserver === 'undefined') {
419
+ return undefined;
420
+ }
421
+ const observer = new ResizeObserver(([entry]) => {
422
+ if (!entry) {
423
+ return;
424
+ }
425
+ const rootSize = {
426
+ width: roundToDecimalPlaces(entry.contentRect.width, 1),
427
+ height: roundToDecimalPlaces(entry.contentRect.height, 1)
428
+ };
429
+ if (!Size.equals(rootSize, store.state.rootSize)) {
430
+ store.update({
431
+ rootSize
432
+ });
433
+ }
434
+ });
435
+ observer.observe(node);
436
+ return () => {
437
+ observer.disconnect();
438
+ };
439
+ }
440
+ const scrollbarSizeCache = new WeakMap();
441
+ function measureScrollbarSize(element, scrollbarSize) {
442
+ if (scrollbarSize !== undefined) {
443
+ return scrollbarSize;
444
+ }
445
+ if (element === null) {
446
+ return 0;
447
+ }
448
+ const cachedSize = scrollbarSizeCache.get(element);
449
+ if (cachedSize !== undefined) {
450
+ return cachedSize;
451
+ }
452
+ const doc = ownerDocument(element);
453
+ const scrollDiv = doc.createElement('div');
454
+ scrollDiv.style.width = '99px';
455
+ scrollDiv.style.height = '99px';
456
+ scrollDiv.style.position = 'absolute';
457
+ scrollDiv.style.overflow = 'scroll';
458
+ scrollDiv.className = 'scrollDiv';
459
+ element.appendChild(scrollDiv);
460
+ const size = scrollDiv.offsetWidth - scrollDiv.clientWidth;
461
+ element.removeChild(scrollDiv);
462
+ scrollbarSizeCache.set(element, size);
463
+ return size;
464
+ }
465
+ function eslintUseValue(_) {}
@@ -0,0 +1,5 @@
1
+ export * from "./colspan.js";
2
+ export * from "./dimensions.js";
3
+ export * from "./keyboard.js";
4
+ export * from "./rowspan.js";
5
+ export * from "./virtualization.js";
@@ -0,0 +1,5 @@
1
+ export * from "./colspan.js";
2
+ export * from "./dimensions.js";
3
+ export * from "./keyboard.js";
4
+ export * from "./rowspan.js";
5
+ export * from "./virtualization.js";
@@ -0,0 +1,16 @@
1
+ import { Store } from '@mui/x-internals/store';
2
+ import type { BaseState, VirtualizerParams } from "../useVirtualizer.js";
3
+ export declare const Keyboard: {
4
+ initialize: typeof initializeState;
5
+ use: typeof useKeyboard;
6
+ selectors: {};
7
+ };
8
+ export declare namespace Keyboard {
9
+ type State = {};
10
+ type API = ReturnType<typeof useKeyboard>;
11
+ }
12
+ declare function initializeState(_params: VirtualizerParams): Keyboard.State;
13
+ declare function useKeyboard(store: Store<BaseState & Keyboard.State>, params: VirtualizerParams, _api: {}): {
14
+ getViewportPageSize: () => number;
15
+ };
16
+ export {};
@@ -0,0 +1,35 @@
1
+ import { Dimensions } from "./dimensions.js";
2
+ import { Virtualization } from "./virtualization.js";
3
+
4
+ /* eslint-disable import/export, @typescript-eslint/no-redeclare */
5
+
6
+ const selectors = {};
7
+ export const Keyboard = {
8
+ initialize: initializeState,
9
+ use: useKeyboard,
10
+ selectors
11
+ };
12
+ function initializeState(_params) {
13
+ return {};
14
+ }
15
+ function useKeyboard(store, params, _api) {
16
+ const getViewportPageSize = () => {
17
+ const dimensions = Dimensions.selectors.dimensions(store.state);
18
+ if (!dimensions.isReady) {
19
+ return 0;
20
+ }
21
+
22
+ // TODO: Use a combination of scrollTop, dimensions.viewportInnerSize.height and rowsMeta.possitions
23
+ // to find out the maximum number of rows that can fit in the visible part of the grid
24
+ if (params.getRowHeight) {
25
+ const renderContext = Virtualization.selectors.renderContext(store.state);
26
+ const viewportPageSize = renderContext.lastRowIndex - renderContext.firstRowIndex;
27
+ return Math.min(viewportPageSize - 1, params.rows.length);
28
+ }
29
+ const maximumPageSizeWithoutScrollBar = Math.floor(dimensions.viewportInnerSize.height / dimensions.rowHeight);
30
+ return Math.min(maximumPageSizeWithoutScrollBar, params.rows.length);
31
+ };
32
+ return {
33
+ getViewportPageSize
34
+ };
35
+ }