@mui/x-virtualizer 0.1.1 → 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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,102 @@
5
5
  All notable changes to this project will be documented in this file.
6
6
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
7
7
 
8
+ ## 8.10.1
9
+
10
+ _Aug 15, 2025_
11
+
12
+ We'd like to extend a big thank you to the 8 contributors who made this release possible. Here are some highlights ✨:
13
+
14
+ - 📊 Y-axes can now be grouped by category when using `band` and `point` scales.
15
+ - 📚 Documentation improvements
16
+
17
+ The following are all team members who have contributed to this release:
18
+ @alexfauquette, @bernardobelchior, @Janpot, @JCQuintas, @mnajdova, @oliviertassinari, @prakhargupta1, @romgrk
19
+
20
+ ### Data Grid
21
+
22
+ #### `@mui/x-data-grid@8.10.1`
23
+
24
+ - [DataGrid] Fix scroll jumping (#19156) @romgrk
25
+ - [DataGrid] Fix scroll restoration (#19182) @romgrk
26
+ - [DataGrid] Fix "no row with id" error (#19193) @romgrk
27
+
28
+ #### `@mui/x-data-grid-pro@8.10.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link "Pro plan")
29
+
30
+ Same changes as in `@mui/x-data-grid@8.10.1`.
31
+
32
+ #### `@mui/x-data-grid-premium@8.10.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link "Premium plan")
33
+
34
+ Same changes as in `@mui/x-data-grid-pro@8.10.1`.
35
+
36
+ ### Date and Time Pickers
37
+
38
+ #### `@mui/x-date-pickers@8.10.0`
39
+
40
+ Internal changes.
41
+
42
+ #### `@mui/x-date-pickers-pro@8.10.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link "Pro plan")
43
+
44
+ Same changes as in `@mui/x-date-pickers@8.10.0`.
45
+
46
+ ### Charts
47
+
48
+ - Axes can now be grouped by category when using `band` and `point` scales
49
+
50
+ <img width="643" height="455" alt="Bar chart with y-axis grouped per categories" src="https://github.com/user-attachments/assets/59044afe-bcc5-4152-8bf1-225db0635025" />
51
+
52
+ #### `@mui/x-charts@8.10.1`
53
+
54
+ - [charts] Allow y-axis to be grouped (#19081) @JCQuintas
55
+ - [charts] Fix default axis highlight for axes without data attribute (#18985) @alexfauquette
56
+ - [charts] Fix tooltip mark unexpected wrapping in mobile (#19122) @bernardobelchior
57
+ - [charts] Prevent overflow on charts tooltip (#19123) @bernardobelchior
58
+
59
+ #### `@mui/x-charts-pro@8.10.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link "Pro plan")
60
+
61
+ Same changes as in `@mui/x-charts@8.10.1`.
62
+
63
+ ### Tree View
64
+
65
+ #### `@mui/x-tree-view@8.10.1`
66
+
67
+ Internal changes.
68
+
69
+ #### `@mui/x-tree-view-pro@8.10.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link "Pro plan")
70
+
71
+ Same changes as in `@mui/x-tree-view@8.10.1`.
72
+
73
+ ### Codemod
74
+
75
+ #### `@mui/x-codemod@8.10.1`
76
+
77
+ Internal changes.
78
+
79
+ ### Docs
80
+
81
+ - [docs] Add all planned charts on the overview page (#19077) @prakhargupta1
82
+ - [docs] Add future charts components link in the sidebar (#19140) @prakhargupta1
83
+ - [docs] Fix Heatmap docs duplicate text (#19138) @JCQuintas
84
+ - [docs] Remove preview from Toolbar & Funnel (#19131) @mnajdova
85
+ - [docs] Reproduce npm sparkline (#19089) @alexfauquette
86
+
87
+ ### Core
88
+
89
+ - [core] Fix licensing model name (#19025) @oliviertassinari
90
+ - [core] Fix usage of `:catalog` for `@babel/runtime` (#19028) @oliviertassinari
91
+ - [core] Refactor virtualizer API (#18995) @romgrk
92
+
93
+ ### Miscellaneous
94
+
95
+ - [code-infra] Remove namespaces (#19071) @Janpot
96
+ - [code-infra] Fix `fs-extra` removal from `formattedTSDemos` script (#19132) @bernardobelchior
97
+ - [code-infra] Remove unused code and dependency (#19139) @bernardobelchior
98
+ - [code-infra] Replace `fs-extra` with `node:fs` where possible (#19127) @bernardobelchior
99
+ - [internal] Edit, keep `lockFileMaintenance` simple @oliviertassinari
100
+ - [internal] Fix writing style action name @oliviertassinari
101
+ - [internal] Make it clear that `lockFileMaintenance` is enabled @oliviertassinari
102
+ - [support-infra] Remove default issue label (#19104) @oliviertassinari
103
+
8
104
  ## 8.10.0
9
105
 
10
106
  _Aug 8, 2025_
@@ -6,6 +6,10 @@ import type { CellColSpanInfo } from "../models/colspan.js";
6
6
  import { Virtualization } from "./virtualization.js";
7
7
  type ColumnIndex = number;
8
8
  type ColspanMap = Map<RowId, Record<ColumnIndex, CellColSpanInfo>>;
9
+ export type ColspanParams = {
10
+ enabled: boolean;
11
+ getColspan: (rowId: RowId, column: ColumnWithWidth, columnIndex: integer) => integer;
12
+ };
9
13
  export declare const Colspan: {
10
14
  initialize: typeof initializeState;
11
15
  use: typeof useColspan;
@@ -14,6 +14,7 @@ function initializeState(_params) {
14
14
  };
15
15
  }
16
16
  function useColspan(store, params, api) {
17
+ const getColspan = params.colspan?.getColspan;
17
18
  const resetColSpan = () => {
18
19
  store.state.colspanMap = new Map();
19
20
  };
@@ -22,14 +23,14 @@ function useColspan(store, params, api) {
22
23
  };
23
24
 
24
25
  // Calculate `colSpan` for each cell in the row
25
- const calculateColSpan = useEventCallback((rowId, minFirstColumn, maxLastColumn, columns) => {
26
+ const calculateColSpan = useEventCallback(getColspan ? (rowId, minFirstColumn, maxLastColumn, columns) => {
26
27
  for (let i = minFirstColumn; i < maxLastColumn; i += 1) {
27
- const cellProps = calculateCellColSpan(store.state.colspanMap, i, rowId, minFirstColumn, maxLastColumn, columns, params.getColspan);
28
+ const cellProps = calculateCellColSpan(store.state.colspanMap, i, rowId, minFirstColumn, maxLastColumn, columns, getColspan);
28
29
  if (cellProps.colSpan > 1) {
29
30
  i += cellProps.colSpan - 1;
30
31
  }
31
32
  }
32
- });
33
+ } : () => {});
33
34
  api.calculateColSpan = calculateColSpan;
34
35
  return {
35
36
  resetColSpan,
@@ -1,6 +1,15 @@
1
1
  import { Store } from '@mui/x-internals/store';
2
2
  import { ColumnWithWidth, DimensionsState, RowId, RowsMetaState, Size } from "../models/index.js";
3
3
  import type { BaseState, VirtualizerParams } from "../useVirtualizer.js";
4
+ export type DimensionsParams = {
5
+ rowHeight: number;
6
+ columnsTotalWidth: number;
7
+ leftPinnedWidth: number;
8
+ rightPinnedWidth: number;
9
+ topPinnedHeight: number;
10
+ bottomPinnedHeight: number;
11
+ scrollbarSize?: number;
12
+ };
4
13
  export declare const Dimensions: {
5
14
  initialize: typeof initializeState;
6
15
  use: typeof useDimensions;
@@ -8,6 +17,7 @@ export declare const Dimensions: {
8
17
  rootSize: (state: BaseState) => Size;
9
18
  dimensions: (state: BaseState) => DimensionsState;
10
19
  rowHeight: (state: BaseState) => number;
20
+ contentHeight: (state: BaseState) => number;
11
21
  rowsMeta: (state: BaseState) => RowsMetaState;
12
22
  columnPositions: (_: any, columns: ColumnWithWidth[]) => number[];
13
23
  needsHorizontalScrollbar: (state: BaseState) => boolean;
@@ -9,8 +9,9 @@ import useEventCallback from '@mui/utils/useEventCallback';
9
9
  import { throttle } from '@mui/x-internals/throttle';
10
10
  import { isDeepEqual } from '@mui/x-internals/isDeepEqual';
11
11
  import { roundToDecimalPlaces } from '@mui/x-internals/math';
12
- import { useStore, useStoreEffect, createSelectorMemoized } from '@mui/x-internals/store';
12
+ import { useStore, createSelectorMemoized } from '@mui/x-internals/store';
13
13
  import { Size } from "../models/index.js";
14
+
14
15
  /* eslint-disable import/export, @typescript-eslint/no-redeclare */
15
16
  /* eslint-disable no-underscore-dangle */
16
17
 
@@ -24,15 +25,11 @@ const EMPTY_DIMENSIONS = {
24
25
  hasScrollX: false,
25
26
  hasScrollY: false,
26
27
  scrollbarSize: 0,
27
- headerHeight: 0,
28
- groupHeaderHeight: 0,
29
- headerFilterHeight: 0,
30
28
  rowWidth: 0,
31
29
  rowHeight: 0,
32
30
  columnsTotalWidth: 0,
33
31
  leftPinnedWidth: 0,
34
32
  rightPinnedWidth: 0,
35
- headersTotalHeight: 0,
36
33
  topContainerHeight: 0,
37
34
  bottomContainerHeight: 0
38
35
  };
@@ -40,6 +37,7 @@ const selectors = {
40
37
  rootSize: state => state.rootSize,
41
38
  dimensions: state => state.dimensions,
42
39
  rowHeight: state => state.dimensions.rowHeight,
40
+ contentHeight: state => state.dimensions.contentSize.height,
43
41
  rowsMeta: state => state.rowsMeta,
44
42
  columnPositions: createSelectorMemoized((_, columns) => {
45
43
  const positions = [];
@@ -87,15 +85,15 @@ function useDimensions(store, params, _api) {
87
85
  refs,
88
86
  dimensions: {
89
87
  rowHeight,
90
- headerHeight,
91
88
  columnsTotalWidth,
92
- groupHeaderHeight,
93
- headerFilterHeight,
94
- headersTotalHeight,
95
89
  leftPinnedWidth,
96
- rightPinnedWidth
97
- }
90
+ rightPinnedWidth,
91
+ topPinnedHeight,
92
+ bottomPinnedHeight
93
+ },
94
+ onResize
98
95
  } = params;
96
+ const containerNode = refs.container.current;
99
97
  const updateDimensions = React.useCallback(() => {
100
98
  if (isFirstSizing.current) {
101
99
  return;
@@ -106,9 +104,9 @@ function useDimensions(store, params, _api) {
106
104
  // All the floating point dimensions should be rounded to .1 decimal places to avoid subpixel rendering issues
107
105
  // https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477
108
106
  // 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;
107
+ const scrollbarSize = measureScrollbarSize(containerNode, params.dimensions.scrollbarSize);
108
+ const topContainerHeight = topPinnedHeight + rowsMeta.pinnedTopRowsTotalHeight;
109
+ const bottomContainerHeight = bottomPinnedHeight + rowsMeta.pinnedBottomRowsTotalHeight;
112
110
  const contentSize = {
113
111
  width: columnsTotalWidth,
114
112
  height: roundToDecimalPlaces(rowsMeta.currentPageTotalHeight, 1)
@@ -172,15 +170,11 @@ function useDimensions(store, params, _api) {
172
170
  hasScrollX,
173
171
  hasScrollY,
174
172
  scrollbarSize,
175
- headerHeight,
176
- groupHeaderHeight,
177
- headerFilterHeight,
178
173
  rowWidth,
179
174
  rowHeight,
180
175
  columnsTotalWidth,
181
176
  leftPinnedWidth,
182
177
  rightPinnedWidth,
183
- headersTotalHeight,
184
178
  topContainerHeight,
185
179
  bottomContainerHeight
186
180
  };
@@ -191,21 +185,16 @@ function useDimensions(store, params, _api) {
191
185
  store.update({
192
186
  dimensions: newDimensions
193
187
  });
194
- }, [store, params.refs.container, params.scrollbarSize, params.autoHeight, rowHeight, headerHeight, groupHeaderHeight, headerFilterHeight, columnsTotalWidth, headersTotalHeight, leftPinnedWidth, rightPinnedWidth]);
188
+ onResize?.(newDimensions.root);
189
+ }, [store, containerNode, params.dimensions.scrollbarSize, params.autoHeight, onResize, rowHeight, columnsTotalWidth, leftPinnedWidth, rightPinnedWidth, topPinnedHeight, bottomPinnedHeight]);
195
190
  const {
196
- resizeThrottleMs,
197
- onResize
191
+ resizeThrottleMs
198
192
  } = params;
199
193
  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]);
194
+ const debouncedUpdateDimensions = React.useMemo(() => resizeThrottleMs > 0 ? throttle(updateDimensionCallback, resizeThrottleMs) : undefined, [resizeThrottleMs, updateDimensionCallback]);
204
195
  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);
196
+ const setRootSize = useEventCallback(rootSize => {
197
+ store.state.rootSize = rootSize;
209
198
  if (isFirstSizing.current || !debouncedUpdateDimensions) {
210
199
  // We want to initialize the grid dimensions as soon as possible to avoid flickering
211
200
  isFirstSizing.current = false;
@@ -214,6 +203,8 @@ function useDimensions(store, params, _api) {
214
203
  debouncedUpdateDimensions();
215
204
  }
216
205
  });
206
+ useLayoutEffect(() => observeRootNode(containerNode, store, setRootSize), [containerNode, store, setRootSize]);
207
+ useLayoutEffect(updateDimensions, [updateDimensions]);
217
208
  const rowsMeta = useRowsMeta(store, params, updateDimensions);
218
209
  return {
219
210
  updateDimensions,
@@ -250,7 +241,6 @@ function useRowsMeta(store, params, updateDimensions) {
250
241
  return entry;
251
242
  });
252
243
  const {
253
- rowIdToIndexMap,
254
244
  applyRowHeight
255
245
  } = params;
256
246
  const processHeightEntry = React.useCallback(row => {
@@ -282,12 +272,7 @@ function useRowsMeta(store, params, updateDimensions) {
282
272
  }
283
273
  }
284
274
  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
- });
275
+ const spacing = getRowSpacing(row);
291
276
  entry.spacingTop = spacing.top ?? 0;
292
277
  entry.spacingBottom = spacing.bottom ?? 0;
293
278
  } else {
@@ -296,17 +281,17 @@ function useRowsMeta(store, params, updateDimensions) {
296
281
  }
297
282
  applyRowHeight?.(entry, row);
298
283
  return entry;
299
- }, [store, rows, getRowHeightProp, getRowHeightEntry, getEstimatedRowHeight, rowHeight, getRowSpacing, rowIdToIndexMap, applyRowHeight]);
284
+ }, [store, getRowHeightProp, getRowHeightEntry, getEstimatedRowHeight, rowHeight, getRowSpacing, applyRowHeight]);
300
285
  const hydrateRowsMeta = React.useCallback(() => {
301
286
  hasRowWithAutoHeight.current = false;
302
- const pinnedTopRowsTotalHeight = pinnedRows.top.reduce((acc, row) => {
287
+ const pinnedTopRowsTotalHeight = pinnedRows?.top.reduce((acc, row) => {
303
288
  const entry = processHeightEntry(row);
304
289
  return acc + entry.content + entry.spacingTop + entry.spacingBottom + entry.detail;
305
- }, 0);
306
- const pinnedBottomRowsTotalHeight = pinnedRows.bottom.reduce((acc, row) => {
290
+ }, 0) ?? 0;
291
+ const pinnedBottomRowsTotalHeight = pinnedRows?.bottom.reduce((acc, row) => {
307
292
  const entry = processHeightEntry(row);
308
293
  return acc + entry.content + entry.spacingTop + entry.spacingBottom + entry.detail;
309
- }, 0);
294
+ }, 0) ?? 0;
310
295
  const positions = [];
311
296
  const currentPageTotalHeight = rows.reduce((acc, row) => {
312
297
  positions.push(acc);
@@ -362,7 +347,7 @@ function useRowsMeta(store, params, updateDimensions) {
362
347
  const entry = entries[i];
363
348
  const height = entry.borderBoxSize && entry.borderBoxSize.length > 0 ? entry.borderBoxSize[0].blockSize : entry.contentRect.height;
364
349
  const rowId = entry.target.__mui_id;
365
- const focusedVirtualRowId = params.focusedVirtualCell()?.id;
350
+ const focusedVirtualRowId = params.focusedVirtualCell?.()?.id;
366
351
  if (focusedVirtualRowId === rowId && height === 0) {
367
352
  // Focused virtual row has 0 height.
368
353
  // We don't want to store it to avoid scroll jumping.
@@ -401,7 +386,7 @@ function useRowsMeta(store, params, updateDimensions) {
401
386
  resetRowHeights
402
387
  };
403
388
  }
404
- function observeRootNode(node, store) {
389
+ function observeRootNode(node, store, setRootSize) {
405
390
  if (!node) {
406
391
  return undefined;
407
392
  }
@@ -411,9 +396,7 @@ function observeRootNode(node, store) {
411
396
  height: roundToDecimalPlaces(bounds.height, 1)
412
397
  };
413
398
  if (store.state.rootSize === Size.EMPTY || !Size.equals(initialSize, store.state.rootSize)) {
414
- store.update({
415
- rootSize: initialSize
416
- });
399
+ setRootSize(initialSize);
417
400
  }
418
401
  if (typeof ResizeObserver === 'undefined') {
419
402
  return undefined;
@@ -427,9 +410,7 @@ function observeRootNode(node, store) {
427
410
  height: roundToDecimalPlaces(entry.contentRect.height, 1)
428
411
  };
429
412
  if (!Size.equals(rootSize, store.state.rootSize)) {
430
- store.update({
431
- rootSize
432
- });
413
+ setRootSize(rootSize);
433
414
  }
434
415
  });
435
416
  observer.observe(node);
@@ -5,6 +5,16 @@ import type { CellColSpanInfo } from "../models/colspan.js";
5
5
  import { Dimensions } from "./dimensions.js";
6
6
  import type { BaseState, VirtualizerParams } from "../useVirtualizer.js";
7
7
  import { PinnedRowPosition, RenderContext, ColumnsRenderContext, ColumnWithWidth, RowId, RowEntry } from "../models/index.js";
8
+ export type VirtualizationParams = {
9
+ /** @default false */
10
+ isRtl?: boolean;
11
+ /** The row buffer in pixels to render before and after the viewport.
12
+ * @default 150 */
13
+ rowBufferPx?: number;
14
+ /** The column buffer in pixels to render before and after the viewport.
15
+ * @default 150 */
16
+ columnBufferPx?: number;
17
+ };
8
18
  export type VirtualizationState = {
9
19
  enabled: boolean;
10
20
  enabledForRows: boolean;
@@ -44,16 +54,16 @@ type RequiredAPI = Dimensions.API & AbstractAPI;
44
54
  declare function useVirtualization(store: Store<BaseState>, params: VirtualizerParams, api: RequiredAPI): {
45
55
  getters: {
46
56
  setPanels: React.Dispatch<React.SetStateAction<Readonly<Map<any, React.ReactNode>>>>;
47
- getRows: (rowParams: {
57
+ getRows: (rowParams?: {
48
58
  rows?: RowEntry[];
49
59
  position?: PinnedRowPosition;
50
60
  renderContext?: RenderContext;
51
- } | undefined, unstable_rowTree: Record<RowId, any>) => React.ReactNode[];
61
+ }, unstable_rowTree?: Record<RowId, any>) => React.ReactNode[];
52
62
  getContainerProps: () => {
53
- ref: React.RefObject<HTMLDivElement | null>;
63
+ ref: (node: HTMLDivElement | null) => void;
54
64
  };
55
65
  getScrollerProps: () => {
56
- ref: React.RefObject<HTMLDivElement | null>;
66
+ ref: (node: HTMLDivElement | null) => void;
57
67
  onScroll: () => void;
58
68
  onWheel: ((event: React.WheelEvent) => void) | undefined;
59
69
  onTouchMove: ((event: React.TouchEvent) => void) | undefined;
@@ -62,22 +72,19 @@ declare function useVirtualization(store: Store<BaseState>, params: VirtualizerP
62
72
  tabIndex: number | undefined;
63
73
  };
64
74
  getContentProps: () => {
65
- style: React.CSSProperties;
66
- role: string;
67
75
  ref: (node: HTMLDivElement | null) => void;
68
- };
69
- getRenderZoneProps: () => {
76
+ style: React.CSSProperties;
70
77
  role: string;
71
78
  };
72
79
  getScrollbarVerticalProps: () => {
73
- ref: React.RefObject<HTMLDivElement | null>;
80
+ ref: (node: HTMLDivElement | null) => void;
74
81
  scrollPosition: React.RefObject<{
75
82
  top: number;
76
83
  left: number;
77
84
  }>;
78
85
  };
79
86
  getScrollbarHorizontalProps: () => {
80
- ref: React.RefObject<HTMLDivElement | null>;
87
+ ref: (node: HTMLDivElement | null) => void;
81
88
  scrollPosition: React.RefObject<{
82
89
  top: number;
83
90
  left: number;