@mui/x-virtualizer 0.2.9 → 0.2.11

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 (46) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/constants.d.ts +22 -0
  3. package/constants.js +26 -0
  4. package/esm/constants.d.ts +22 -0
  5. package/esm/constants.js +20 -0
  6. package/esm/features/colspan.d.ts +4 -4
  7. package/esm/features/dimensions.d.ts +14 -8
  8. package/esm/features/dimensions.js +26 -7
  9. package/esm/features/index.d.ts +1 -1
  10. package/esm/features/index.js +1 -1
  11. package/esm/features/keyboard.d.ts +3 -3
  12. package/esm/features/keyboard.js +1 -1
  13. package/esm/features/rowspan.d.ts +4 -4
  14. package/esm/features/virtualization/index.d.ts +2 -0
  15. package/esm/features/virtualization/index.js +2 -0
  16. package/esm/features/virtualization/layout.d.ts +129 -0
  17. package/esm/features/virtualization/layout.js +152 -0
  18. package/esm/features/{virtualization.d.ts → virtualization/virtualization.d.ts} +35 -58
  19. package/esm/features/{virtualization.js → virtualization/virtualization.js} +98 -136
  20. package/esm/index.d.ts +2 -1
  21. package/esm/index.js +3 -2
  22. package/esm/models/core.d.ts +7 -0
  23. package/esm/models/core.js +7 -0
  24. package/esm/models/dimensions.d.ts +8 -0
  25. package/esm/useVirtualizer.d.ts +25 -69
  26. package/esm/useVirtualizer.js +21 -4
  27. package/features/colspan.d.ts +4 -4
  28. package/features/dimensions.d.ts +14 -8
  29. package/features/dimensions.js +26 -7
  30. package/features/index.d.ts +1 -1
  31. package/features/keyboard.d.ts +3 -3
  32. package/features/rowspan.d.ts +4 -4
  33. package/features/virtualization/index.d.ts +2 -0
  34. package/features/virtualization/index.js +27 -0
  35. package/features/virtualization/layout.d.ts +129 -0
  36. package/features/virtualization/layout.js +163 -0
  37. package/features/{virtualization.d.ts → virtualization/virtualization.d.ts} +35 -58
  38. package/features/{virtualization.js → virtualization/virtualization.js} +98 -136
  39. package/index.d.ts +2 -1
  40. package/index.js +12 -1
  41. package/models/core.d.ts +7 -0
  42. package/models/core.js +8 -1
  43. package/models/dimensions.d.ts +8 -0
  44. package/package.json +2 -2
  45. package/useVirtualizer.d.ts +25 -69
  46. package/useVirtualizer.js +20 -3
@@ -0,0 +1,152 @@
1
+ import useForkRef from '@mui/utils/useForkRef';
2
+ import useEventCallback from '@mui/utils/useEventCallback';
3
+ import * as platform from '@mui/x-internals/platform';
4
+ import { createSelectorMemoized } from '@mui/x-internals/store';
5
+ import { Dimensions } from "../dimensions.js";
6
+ import { Virtualization } from "./virtualization.js";
7
+
8
+ /* eslint-disable react-hooks/rules-of-hooks */
9
+
10
+ export class Layout {
11
+ static elements = ['scroller', 'container'];
12
+ constructor(refs) {
13
+ this.refs = refs;
14
+ }
15
+ refSetter(name) {
16
+ return node => {
17
+ if (node && this.refs[name].current !== node) {
18
+ this.refs[name].current = node;
19
+ }
20
+ };
21
+ }
22
+ }
23
+ export class LayoutDataGrid extends Layout {
24
+ static elements = (() => ['scroller', 'container', 'content', 'positioner', 'scrollbarVertical', 'scrollbarHorizontal'])();
25
+ use(store, _params, _api, layoutParams) {
26
+ const {
27
+ scrollerRef,
28
+ containerRef
29
+ } = layoutParams;
30
+ const scrollbarVerticalRef = useEventCallback(this.refSetter('scrollbarVertical'));
31
+ const scrollbarHorizontalRef = useEventCallback(this.refSetter('scrollbarHorizontal'));
32
+ store.state.virtualization.context = {
33
+ scrollerRef,
34
+ containerRef,
35
+ scrollbarVerticalRef,
36
+ scrollbarHorizontalRef
37
+ };
38
+ }
39
+ static selectors = (() => ({
40
+ containerProps: createSelectorMemoized(Virtualization.selectors.context, context => ({
41
+ ref: context.containerRef
42
+ })),
43
+ scrollerProps: createSelectorMemoized(Virtualization.selectors.context, Dimensions.selectors.autoHeight, Dimensions.selectors.needsHorizontalScrollbar, (context, autoHeight, needsHorizontalScrollbar) => ({
44
+ ref: context.scrollerRef,
45
+ style: {
46
+ overflowX: !needsHorizontalScrollbar ? 'hidden' : undefined,
47
+ overflowY: autoHeight ? 'hidden' : undefined
48
+ },
49
+ role: 'presentation',
50
+ // `tabIndex` shouldn't be used along role=presentation, but it fixes a Firefox bug
51
+ // https://github.com/mui/mui-x/pull/13891#discussion_r1683416024
52
+ tabIndex: platform.isFirefox ? -1 : undefined
53
+ })),
54
+ contentProps: createSelectorMemoized(Dimensions.selectors.contentHeight, Dimensions.selectors.minimalContentHeight, Dimensions.selectors.columnsTotalWidth, Dimensions.selectors.needsHorizontalScrollbar, (contentHeight, minimalContentHeight, columnsTotalWidth, needsHorizontalScrollbar) => ({
55
+ style: {
56
+ width: needsHorizontalScrollbar ? columnsTotalWidth : 'auto',
57
+ flexBasis: contentHeight === 0 ? minimalContentHeight : contentHeight,
58
+ flexShrink: 0
59
+ },
60
+ role: 'presentation'
61
+ })),
62
+ positionerProps: createSelectorMemoized(Virtualization.selectors.offsetTop, offsetTop => ({
63
+ style: {
64
+ transform: `translate3d(0, ${offsetTop}px, 0)`
65
+ }
66
+ })),
67
+ scrollbarHorizontalProps: createSelectorMemoized(Virtualization.selectors.context, Virtualization.selectors.scrollPosition, (context, scrollPosition) => ({
68
+ ref: context.scrollbarHorizontalRef,
69
+ scrollPosition
70
+ })),
71
+ scrollbarVerticalProps: createSelectorMemoized(Virtualization.selectors.context, Virtualization.selectors.scrollPosition, (context, scrollPosition) => ({
72
+ ref: context.scrollbarVerticalRef,
73
+ scrollPosition
74
+ })),
75
+ scrollAreaProps: createSelectorMemoized(Virtualization.selectors.scrollPosition, scrollPosition => ({
76
+ scrollPosition
77
+ }))
78
+ }))();
79
+ }
80
+
81
+ // The current virtualizer API is exposed on one of the DataGrid slots, so we need to keep
82
+ // the old API for backward compatibility. This API prevents using fine-grained reactivity
83
+ // as all props are returned in a single object, so everything re-renders on any change.
84
+ //
85
+ // TODO(v9): Remove the legacy API.
86
+ export class LayoutDataGridLegacy extends LayoutDataGrid {
87
+ use(store, _params, _api, layoutParams) {
88
+ super.use(store, _params, _api, layoutParams);
89
+ const containerProps = store.use(LayoutDataGrid.selectors.containerProps);
90
+ const scrollerProps = store.use(LayoutDataGrid.selectors.scrollerProps);
91
+ const contentProps = store.use(LayoutDataGrid.selectors.contentProps);
92
+ const positionerProps = store.use(LayoutDataGrid.selectors.positionerProps);
93
+ const scrollbarVerticalProps = store.use(LayoutDataGrid.selectors.scrollbarVerticalProps);
94
+ const scrollbarHorizontalProps = store.use(LayoutDataGrid.selectors.scrollbarHorizontalProps);
95
+ const scrollAreaProps = store.use(LayoutDataGrid.selectors.scrollAreaProps);
96
+ return {
97
+ getContainerProps: () => containerProps,
98
+ getScrollerProps: () => scrollerProps,
99
+ getContentProps: () => contentProps,
100
+ getPositionerProps: () => positionerProps,
101
+ getScrollbarVerticalProps: () => scrollbarVerticalProps,
102
+ getScrollbarHorizontalProps: () => scrollbarHorizontalProps,
103
+ getScrollAreaProps: () => scrollAreaProps
104
+ };
105
+ }
106
+ }
107
+ export class LayoutList extends Layout {
108
+ static elements = (() => ['scroller', 'container', 'content', 'positioner'])();
109
+ use(store, _params, _api, layoutParams) {
110
+ const {
111
+ scrollerRef,
112
+ containerRef
113
+ } = layoutParams;
114
+ const mergedRef = useForkRef(scrollerRef, containerRef);
115
+ store.state.virtualization.context = {
116
+ mergedRef
117
+ };
118
+ }
119
+ static selectors = (() => ({
120
+ containerProps: createSelectorMemoized(Virtualization.selectors.context, Dimensions.selectors.autoHeight, Dimensions.selectors.needsHorizontalScrollbar, (context, autoHeight, needsHorizontalScrollbar) => ({
121
+ ref: context.mergedRef,
122
+ style: {
123
+ overflowX: !needsHorizontalScrollbar ? 'hidden' : undefined,
124
+ overflowY: autoHeight ? 'hidden' : undefined,
125
+ position: 'relative'
126
+ },
127
+ role: 'presentation',
128
+ // `tabIndex` shouldn't be used along role=presentation, but it fixes a Firefox bug
129
+ // https://github.com/mui/mui-x/pull/13891#discussion_r1683416024
130
+ tabIndex: platform.isFirefox ? -1 : undefined
131
+ })),
132
+ contentProps: createSelectorMemoized(Dimensions.selectors.contentHeight, contentHeight => {
133
+ return {
134
+ style: {
135
+ position: 'absolute',
136
+ display: 'inline-block',
137
+ width: '100%',
138
+ height: contentHeight,
139
+ top: 0,
140
+ left: 0,
141
+ zIndex: -1
142
+ },
143
+ role: 'presentation'
144
+ };
145
+ }),
146
+ positionerProps: createSelectorMemoized(Virtualization.selectors.offsetTop, offsetTop => ({
147
+ style: {
148
+ height: offsetTop
149
+ }
150
+ }))
151
+ }))();
152
+ }
@@ -1,10 +1,11 @@
1
1
  import * as React from 'react';
2
2
  import type { integer } from '@mui/x-internals/types';
3
3
  import { Store } from '@mui/x-internals/store';
4
- import type { CellColSpanInfo } from "../models/colspan.js";
5
- import { Dimensions } from "./dimensions.js";
6
- import type { BaseState, VirtualizerParams } from "../useVirtualizer.js";
7
- import { PinnedRowPosition, RenderContext, ColumnsRenderContext, ColumnWithWidth, RowId, RowEntry } from "../models/index.js";
4
+ import type { CellColSpanInfo } from "../../models/colspan.js";
5
+ import { Dimensions } from "../dimensions.js";
6
+ import type { BaseState, ParamsWithDefaults } from "../../useVirtualizer.js";
7
+ import type { Layout } from "./layout.js";
8
+ import { RenderContext, ColumnsRenderContext, ColumnWithWidth, RowId, ScrollPosition } from "../../models/index.js";
8
9
  export type VirtualizationParams = {
9
10
  /** @default false */
10
11
  isRtl?: boolean;
@@ -15,11 +16,16 @@ export type VirtualizationParams = {
15
16
  * @default 150 */
16
17
  columnBufferPx?: number;
17
18
  };
18
- export type VirtualizationState = {
19
+ export type VirtualizationState<K extends string = string> = {
19
20
  enabled: boolean;
20
21
  enabledForRows: boolean;
21
22
  enabledForColumns: boolean;
22
23
  renderContext: RenderContext;
24
+ props: Record<K, Record<string, any>>;
25
+ context: Record<string, any>;
26
+ scrollPosition: {
27
+ current: ScrollPosition;
28
+ };
23
29
  };
24
30
  export declare const EMPTY_RENDER_CONTEXT: {
25
31
  firstRowIndex: number;
@@ -31,20 +37,31 @@ export declare const Virtualization: {
31
37
  initialize: typeof initializeState;
32
38
  use: typeof useVirtualization;
33
39
  selectors: {
34
- store: (state: BaseState) => VirtualizationState;
40
+ store: (state: BaseState) => VirtualizationState<string>;
35
41
  renderContext: (state: BaseState) => RenderContext;
36
42
  enabledForRows: (state: BaseState) => boolean;
37
43
  enabledForColumns: (state: BaseState) => boolean;
44
+ offsetTop: (args_0: Virtualization.State<Layout<{
45
+ scroller: React.RefObject<HTMLElement | null>;
46
+ container: React.RefObject<HTMLElement | null>;
47
+ } & Record<string, React.RefObject<HTMLElement | null>>>> & Dimensions.State) => number;
48
+ context: (state: BaseState) => Record<string, any>;
49
+ scrollPosition: (state: BaseState) => {
50
+ current: ScrollPosition;
51
+ };
38
52
  };
39
53
  };
40
54
  export declare namespace Virtualization {
41
- type State = {
42
- virtualization: VirtualizationState;
55
+ type State<L extends Layout> = {
56
+ virtualization: VirtualizationState<L extends Layout<infer E> ? keyof E : string>;
43
57
  getters: ReturnType<typeof useVirtualization>['getters'];
44
58
  };
45
59
  type API = ReturnType<typeof useVirtualization>;
46
60
  }
47
- declare function initializeState(params: VirtualizerParams): Virtualization.State;
61
+ declare function initializeState(params: ParamsWithDefaults): Virtualization.State<Layout<{
62
+ scroller: React.RefObject<HTMLElement | null>;
63
+ container: React.RefObject<HTMLElement | null>;
64
+ } & Record<string, React.RefObject<HTMLElement | null>>>>;
48
65
  /** APIs to override for colspan/rowspan */
49
66
  type AbstractAPI = {
50
67
  getCellColSpanInfo: (rowId: RowId, columnIndex: integer) => CellColSpanInfo;
@@ -52,58 +69,18 @@ type AbstractAPI = {
52
69
  getHiddenCellsOrigin: () => Record<RowId, Record<number, number>>;
53
70
  };
54
71
  type RequiredAPI = Dimensions.API & AbstractAPI;
55
- declare function useVirtualization(store: Store<BaseState>, params: VirtualizerParams, api: RequiredAPI): {
56
- getters: {
57
- setPanels: React.Dispatch<React.SetStateAction<Readonly<Map<any, React.ReactNode>>>>;
58
- getOffsetTop: () => number;
59
- getRows: (rowParams?: {
60
- rows?: RowEntry[];
61
- position?: PinnedRowPosition;
62
- renderContext?: RenderContext;
63
- }, unstable_rowTree?: Record<RowId, any>) => React.ReactNode[];
64
- rows: RowEntry[];
65
- getContainerProps: () => {
66
- ref: (node: HTMLDivElement | null) => (() => void | undefined) | undefined;
67
- };
68
- getScrollerProps: () => {
69
- ref: (node: HTMLDivElement | null) => (() => void | undefined) | undefined;
70
- style: React.CSSProperties;
71
- role: string;
72
- tabIndex: number | undefined;
73
- };
74
- getContentProps: () => {
75
- ref: (node: HTMLDivElement | null) => void;
76
- style: React.CSSProperties;
77
- role: string;
78
- };
79
- getScrollbarVerticalProps: () => {
80
- ref: (node: HTMLDivElement | null) => void;
81
- scrollPosition: React.RefObject<{
82
- top: number;
83
- left: number;
84
- }>;
85
- };
86
- getScrollbarHorizontalProps: () => {
87
- ref: (node: HTMLDivElement | null) => void;
88
- scrollPosition: React.RefObject<{
89
- top: number;
90
- left: number;
91
- }>;
92
- };
93
- getScrollAreaProps: () => {
94
- scrollPosition: React.RefObject<{
95
- top: number;
96
- left: number;
97
- }>;
98
- };
99
- };
100
- useVirtualization: () => BaseState;
101
- setPanels: React.Dispatch<React.SetStateAction<Readonly<Map<any, React.ReactNode>>>>;
102
- forceUpdateRenderContext: () => void;
103
- scheduleUpdateRenderContext: () => void;
72
+ export type VirtualizationLayoutParams = {
73
+ containerRef: (node: HTMLDivElement | null) => void;
74
+ scrollerRef: (node: HTMLDivElement | null) => void;
75
+ };
76
+ declare function useVirtualization(store: Store<BaseState>, params: ParamsWithDefaults, api: RequiredAPI): {
104
77
  getCellColSpanInfo: (rowId: RowId, columnIndex: integer) => CellColSpanInfo;
105
78
  calculateColSpan: (rowId: RowId, minFirstColumn: integer, maxLastColumn: integer, columns: ColumnWithWidth[]) => void;
106
79
  getHiddenCellsOrigin: () => Record<RowId, Record<number, number>>;
80
+ getters: any;
81
+ setPanels: React.Dispatch<React.SetStateAction<Readonly<Map<any, React.ReactNode>>>>;
82
+ forceUpdateRenderContext: () => void;
83
+ scheduleUpdateRenderContext: () => void;
107
84
  };
108
85
  export declare function areRenderContextsEqual(context1: RenderContext, context2: RenderContext): boolean;
109
86
  export declare function computeOffsetLeft(columnPositions: number[], renderContext: ColumnsRenderContext, pinnedLeftLength: number): number;
@@ -11,9 +11,9 @@ import * as platform from '@mui/x-internals/platform';
11
11
  import { useRunOnce } from '@mui/x-internals/useRunOnce';
12
12
  import { createSelector, useStore, useStoreEffect } from '@mui/x-internals/store';
13
13
  import reactMajor from '@mui/x-internals/reactMajor';
14
- import { PinnedRows, PinnedColumns } from "../models/core.js";
15
- import { Dimensions, observeRootNode } from "./dimensions.js";
16
- import { ScrollDirection } from "../models/index.js";
14
+ import { PinnedRows, PinnedColumns } from "../../models/core.js";
15
+ import { Dimensions, observeRootNode } from "../dimensions.js";
16
+ import { ScrollPosition, ScrollDirection } from "../../models/index.js";
17
17
 
18
18
  /* eslint-disable import/export, @typescript-eslint/no-redeclare */
19
19
 
@@ -30,12 +30,18 @@ export const EMPTY_RENDER_CONTEXT = {
30
30
  firstColumnIndex: 0,
31
31
  lastColumnIndex: 0
32
32
  };
33
- const selectors = {
34
- store: createSelector(state => state.virtualization),
35
- renderContext: createSelector(state => state.virtualization.renderContext),
36
- enabledForRows: createSelector(state => state.virtualization.enabledForRows),
37
- enabledForColumns: createSelector(state => state.virtualization.enabledForColumns)
38
- };
33
+ const selectors = (() => {
34
+ const firstRowIndexSelector = createSelector(state => state.virtualization.renderContext.firstRowIndex);
35
+ return {
36
+ store: createSelector(state => state.virtualization),
37
+ renderContext: createSelector(state => state.virtualization.renderContext),
38
+ enabledForRows: createSelector(state => state.virtualization.enabledForRows),
39
+ enabledForColumns: createSelector(state => state.virtualization.enabledForColumns),
40
+ offsetTop: createSelector(Dimensions.selectors.rowPositions, firstRowIndexSelector, (rowPositions, firstRowIndex) => rowPositions[firstRowIndex] ?? 0),
41
+ context: createSelector(state => state.virtualization.context),
42
+ scrollPosition: createSelector(state => state.virtualization.scrollPosition)
43
+ };
44
+ })();
39
45
  export const Virtualization = {
40
46
  initialize: initializeState,
41
47
  use: useVirtualization,
@@ -47,7 +53,12 @@ function initializeState(params) {
47
53
  enabled: !platform.isJSDOM,
48
54
  enabledForRows: !platform.isJSDOM,
49
55
  enabledForColumns: !platform.isJSDOM,
50
- renderContext: EMPTY_RENDER_CONTEXT
56
+ renderContext: EMPTY_RENDER_CONTEXT,
57
+ props: params.layout.constructor.elements.reduce((acc, key) => (acc[key], acc), {}),
58
+ context: {},
59
+ scrollPosition: {
60
+ current: ScrollPosition.EMPTY
61
+ }
51
62
  }, params.initialState?.virtualization),
52
63
  // FIXME: refactor once the state shape is settled
53
64
  getters: null
@@ -59,10 +70,10 @@ function initializeState(params) {
59
70
 
60
71
  function useVirtualization(store, params, api) {
61
72
  const {
62
- refs,
73
+ layout,
63
74
  dimensions: {
64
75
  rowHeight,
65
- columnsTotalWidth
76
+ columnsTotalWidth = 0
66
77
  },
67
78
  virtualization: {
68
79
  isRtl = false,
@@ -76,8 +87,6 @@ function useVirtualization(store, params, api) {
76
87
  columns,
77
88
  pinnedRows = PinnedRows.EMPTY,
78
89
  pinnedColumns = PinnedColumns.EMPTY,
79
- minimalContentHeight,
80
- autoHeight,
81
90
  onWheel,
82
91
  onTouchMove,
83
92
  onRenderContextChange,
@@ -86,7 +95,6 @@ function useVirtualization(store, params, api) {
86
95
  renderRow,
87
96
  renderInfiniteLoadingTrigger
88
97
  } = params;
89
- const needsHorizontalScrollbar = useStore(store, Dimensions.selectors.needsHorizontalScrollbar);
90
98
  const hasBottomPinnedRows = pinnedRows.bottom.length > 0;
91
99
  const [panels, setPanels] = React.useState(EMPTY_DETAIL_PANELS);
92
100
  const isUpdateScheduled = React.useRef(false);
@@ -94,7 +102,6 @@ function useVirtualization(store, params, api) {
94
102
  const renderContext = useStore(store, selectors.renderContext);
95
103
  const enabledForRows = useStore(store, selectors.enabledForRows);
96
104
  const enabledForColumns = useStore(store, selectors.enabledForColumns);
97
- const rowsMeta = useStore(store, Dimensions.selectors.rowsMeta);
98
105
  const contentHeight = useStore(store, Dimensions.selectors.contentHeight);
99
106
 
100
107
  /*
@@ -122,7 +129,10 @@ function useVirtualization(store, params, api) {
122
129
  const updateRenderContext = React.useCallback(nextRenderContext => {
123
130
  if (!areRenderContextsEqual(nextRenderContext, store.state.virtualization.renderContext)) {
124
131
  store.set('virtualization', _extends({}, store.state.virtualization, {
125
- renderContext: nextRenderContext
132
+ renderContext: nextRenderContext,
133
+ scrollPosition: {
134
+ current: _extends({}, scrollPosition.current)
135
+ }
126
136
  }));
127
137
  }
128
138
 
@@ -138,7 +148,7 @@ function useVirtualization(store, params, api) {
138
148
  previousContextScrollPosition.current = scrollPosition.current;
139
149
  }, [store, onRenderContextChange]);
140
150
  const triggerUpdateRenderContext = useEventCallback(() => {
141
- const scroller = refs.scroller.current;
151
+ const scroller = layout.refs.scroller.current;
142
152
  if (!scroller) {
143
153
  return undefined;
144
154
  }
@@ -149,7 +159,7 @@ function useVirtualization(store, params, api) {
149
159
  // Clamp the scroll position to the viewport to avoid re-calculating the render context for scroll bounce
150
160
  const newScroll = {
151
161
  top: clamp(scroller.scrollTop, 0, maxScrollTop),
152
- left: isRtl ? clamp(scroller.scrollLeft, -maxScrollLeft, 0) : clamp(scroller.scrollLeft, 0, maxScrollLeft)
162
+ left: isRtl ? clamp(scroller.scrollLeft, -Math.abs(maxScrollLeft), 0) : clamp(scroller.scrollLeft, 0, maxScrollLeft)
153
163
  };
154
164
  const dx = newScroll.left - scrollPosition.current.left;
155
165
  const dy = newScroll.top - scrollPosition.current.top;
@@ -166,6 +176,11 @@ function useVirtualization(store, params, api) {
166
176
  const didChangeDirection = scrollCache.direction !== direction;
167
177
  const shouldUpdate = didCrossThreshold || didChangeDirection;
168
178
  if (!shouldUpdate) {
179
+ store.set('virtualization', _extends({}, store.state.virtualization, {
180
+ scrollPosition: {
181
+ current: _extends({}, scrollPosition.current)
182
+ }
183
+ }));
169
184
  return renderContext;
170
185
  }
171
186
 
@@ -232,9 +247,6 @@ function useVirtualization(store, params, api) {
232
247
  onScrollChange?.(scrollPosition.current, nextRenderContext);
233
248
  }
234
249
  });
235
- const getOffsetTop = () => {
236
- return rowsMeta.positions[renderContext.firstRowIndex] ?? 0;
237
- };
238
250
 
239
251
  /**
240
252
  * HACK: unstable_rowTree fixes the issue described below, but does it by tightly coupling this
@@ -363,34 +375,13 @@ function useVirtualization(store, params, api) {
363
375
  if (panel) {
364
376
  rowElements.push(panel);
365
377
  }
366
- if (rowParams.position === undefined && isLastVisibleInSection) {
378
+ if (rowParams.position === undefined && isLastVisibleInSection && renderInfiniteLoadingTrigger) {
367
379
  rowElements.push(renderInfiniteLoadingTrigger(id));
368
380
  }
369
381
  });
370
382
  return rowElements;
371
383
  };
372
- const scrollerStyle = React.useMemo(() => ({
373
- overflowX: !needsHorizontalScrollbar ? 'hidden' : undefined,
374
- overflowY: autoHeight ? 'hidden' : undefined
375
- }), [needsHorizontalScrollbar, autoHeight]);
376
- const contentSize = React.useMemo(() => {
377
- const size = {
378
- width: needsHorizontalScrollbar ? columnsTotalWidth : 'auto',
379
- flexBasis: contentHeight,
380
- flexShrink: 0
381
- };
382
- if (size.flexBasis === 0) {
383
- size.flexBasis = minimalContentHeight; // Give room to show the overlay when there no rows.
384
- }
385
- return size;
386
- }, [columnsTotalWidth, contentHeight, needsHorizontalScrollbar, minimalContentHeight]);
387
384
  const scrollRestoreCallback = React.useRef(null);
388
- const contentNodeRef = React.useCallback(node => {
389
- if (!node) {
390
- return;
391
- }
392
- scrollRestoreCallback.current?.(columnsTotalWidth, contentHeight);
393
- }, [columnsTotalWidth, contentHeight]);
394
385
  useEnhancedEffect(() => {
395
386
  if (!isRenderContextReady.current) {
396
387
  return;
@@ -398,15 +389,15 @@ function useVirtualization(store, params, api) {
398
389
  forceUpdateRenderContextCallback();
399
390
  }, [enabledForColumns, enabledForRows, forceUpdateRenderContextCallback]);
400
391
  useEnhancedEffect(() => {
401
- if (refs.scroller.current) {
402
- refs.scroller.current.scrollLeft = 0;
392
+ if (layout.refs.scroller.current) {
393
+ layout.refs.scroller.current.scrollLeft = 0;
403
394
  }
404
- }, [refs.scroller, scrollReset]);
395
+ }, [layout.refs.scroller, scrollReset]);
405
396
  useRunOnce(renderContext !== EMPTY_RENDER_CONTEXT, () => {
406
397
  onScrollChange?.(scrollPosition.current, renderContext);
407
398
  isRenderContextReady.current = true;
408
- if (initialState?.scroll && refs.scroller.current) {
409
- const scroller = refs.scroller.current;
399
+ if (initialState?.scroll && layout.refs.scroller.current) {
400
+ const scroller = layout.refs.scroller.current;
410
401
  const {
411
402
  top,
412
403
  left
@@ -449,20 +440,14 @@ function useVirtualization(store, params, api) {
449
440
  }
450
441
  });
451
442
  useStoreEffect(store, Dimensions.selectors.dimensions, forceUpdateRenderContext);
452
- const refSetter = name => node => {
453
- if (node && refs[name].current !== node) {
454
- refs[name].current = node;
443
+ useEnhancedEffect(() => {
444
+ if (layout.refs.scroller) {
445
+ scrollRestoreCallback.current?.(columnsTotalWidth, contentHeight);
455
446
  }
456
- };
447
+ }, [layout.refs.scroller, columnsTotalWidth, contentHeight]);
457
448
  const isFirstSizing = React.useRef(true);
458
- const containerCleanup = React.useRef(undefined);
459
- const containerRef = useEventCallback(node => {
460
- if (!node) {
461
- // Cleanup for R18
462
- containerCleanup.current?.();
463
- return;
464
- }
465
- refs.container.current = node;
449
+ const containerRef = useRefCallback(node => {
450
+ layout.refs.container.current = node;
466
451
  const unsubscribe = observeRootNode(node, store, rootSize => {
467
452
  if (rootSize.width === 0 && rootSize.height === 0 && store.state.rootSize.height !== 0 && store.state.rootSize.width !== 0) {
468
453
  return;
@@ -476,98 +461,58 @@ function useVirtualization(store, params, api) {
476
461
  api.debouncedUpdateDimensions();
477
462
  }
478
463
  });
479
- containerCleanup.current = () => {
464
+ return () => {
480
465
  unsubscribe?.();
481
- refs.container.current = null;
466
+ layout.refs.container.current = null;
482
467
  };
483
- if (reactMajor >= 19) {
484
- /* eslint-disable-next-line consistent-return */
485
- return containerCleanup.current;
486
- }
487
468
  });
488
- const scrollerCleanup = React.useRef(undefined);
489
- const scrollerRef = useEventCallback(node => {
490
- if (!node) {
491
- // Cleanup for R18
492
- scrollerCleanup.current?.();
493
- return;
494
- }
495
- scrollerCleanup.current?.();
496
- refs.scroller.current = node;
469
+ const scrollerRef = useRefCallback(node => {
470
+ layout.refs.scroller.current = node;
497
471
  const opts = {
498
472
  passive: true
499
473
  };
500
474
  node.addEventListener('scroll', handleScroll, opts);
501
475
  node.addEventListener('wheel', onWheel, opts);
502
476
  node.addEventListener('touchmove', onTouchMove, opts);
503
- scrollerCleanup.current = () => {
477
+ return () => {
504
478
  node.removeEventListener('scroll', handleScroll, opts);
505
479
  node.removeEventListener('wheel', onWheel, opts);
506
480
  node.removeEventListener('touchmove', onTouchMove, opts);
507
- refs.scroller.current = null;
481
+ layout.refs.scroller.current = null;
508
482
  };
509
- if (reactMajor >= 19) {
510
- /* eslint-disable-next-line consistent-return */
511
- return scrollerCleanup.current;
512
- }
513
483
  });
514
- const scrollbarVerticalRef = useEventCallback(refSetter('scrollbarVertical'));
515
- const scrollbarHorizontalRef = useEventCallback(refSetter('scrollbarHorizontal'));
516
- const getters = {
484
+ const layoutParams = {
485
+ containerRef,
486
+ scrollerRef
487
+ };
488
+ const layoutAPI = layout.use(store, params, api, layoutParams);
489
+ const getters = _extends({
517
490
  setPanels,
518
- getOffsetTop,
519
491
  getRows,
520
- rows: params.rows,
521
- getContainerProps: () => ({
522
- ref: containerRef
523
- }),
524
- getScrollerProps: () => ({
525
- ref: scrollerRef,
526
- style: scrollerStyle,
527
- role: 'presentation',
528
- // `tabIndex` shouldn't be used along role=presentation, but it fixes a Firefox bug
529
- // https://github.com/mui/mui-x/pull/13891#discussion_r1683416024
530
- tabIndex: platform.isFirefox ? -1 : undefined
531
- }),
532
- getContentProps: () => ({
533
- ref: contentNodeRef,
534
- style: contentSize,
535
- role: 'presentation'
536
- }),
537
- getScrollbarVerticalProps: () => ({
538
- ref: scrollbarVerticalRef,
539
- scrollPosition
540
- }),
541
- getScrollbarHorizontalProps: () => ({
542
- ref: scrollbarHorizontalRef,
543
- scrollPosition
544
- }),
545
- getScrollAreaProps: () => ({
546
- scrollPosition
547
- })
548
- };
549
-
550
- /* Placeholder API functions for colspan & rowspan to re-implement */
551
-
552
- const getCellColSpanInfo = () => {
553
- throw new Error('Unimplemented: colspan feature is required');
554
- };
555
- const calculateColSpan = () => {
556
- throw new Error('Unimplemented: colspan feature is required');
557
- };
558
- const getHiddenCellsOrigin = () => {
559
- throw new Error('Unimplemented: rowspan feature is required');
560
- };
561
- return {
492
+ rows: params.rows
493
+ }, layoutAPI);
494
+ return _extends({
562
495
  getters,
563
- useVirtualization: () => useStore(store, state => state),
564
496
  setPanels,
565
497
  forceUpdateRenderContext,
566
- scheduleUpdateRenderContext,
567
- getCellColSpanInfo,
568
- calculateColSpan,
569
- getHiddenCellsOrigin
570
- };
498
+ scheduleUpdateRenderContext
499
+ }, createSpanningAPI());
500
+ }
501
+ function useRefCallback(fn) {
502
+ const refCleanup = React.useRef(undefined);
503
+ const refCallback = useEventCallback(node => {
504
+ if (!node) {
505
+ // Cleanup for R18
506
+ refCleanup.current?.();
507
+ return;
508
+ }
509
+ refCleanup.current = fn(node);
510
+ if (reactMajor >= 19) {
511
+ /* eslint-disable-next-line consistent-return */
512
+ return refCleanup.current;
513
+ }
514
+ });
515
+ return refCallback;
571
516
  }
572
517
  function inputsSelector(store, params, api, enabledForRows, enabledForColumns) {
573
518
  const dimensions = Dimensions.selectors.dimensions(store.state);
@@ -581,7 +526,7 @@ function inputsSelector(store, params, api, enabledForRows, enabledForColumns) {
581
526
  api,
582
527
  enabledForRows,
583
528
  enabledForColumns,
584
- autoHeight: params.autoHeight,
529
+ autoHeight: dimensions.autoHeight,
585
530
  rowBufferPx: params.virtualization.rowBufferPx,
586
531
  columnBufferPx: params.virtualization.columnBufferPx,
587
532
  leftPinnedWidth: dimensions.leftPinnedWidth,
@@ -639,7 +584,6 @@ function computeRenderContext(inputs, scrollPosition, scrollCache) {
639
584
  // lastColumnIndex: 1,
640
585
  // };
641
586
  // }
642
-
643
587
  if (inputs.enabledForColumns) {
644
588
  let firstColumnIndex = 0;
645
589
  let lastColumnIndex = inputs.columnPositions.length;
@@ -887,6 +831,24 @@ function getFirstNonSpannedColumnToRender({
887
831
  }
888
832
  return firstNonSpannedColumnToRender;
889
833
  }
834
+
835
+ /** Placeholder API functions for colspan & rowspan to re-implement */
836
+ function createSpanningAPI() {
837
+ const getCellColSpanInfo = () => {
838
+ throw new Error('Unimplemented: colspan feature is required');
839
+ };
840
+ const calculateColSpan = () => {
841
+ throw new Error('Unimplemented: colspan feature is required');
842
+ };
843
+ const getHiddenCellsOrigin = () => {
844
+ throw new Error('Unimplemented: rowspan feature is required');
845
+ };
846
+ return {
847
+ getCellColSpanInfo,
848
+ calculateColSpan,
849
+ getHiddenCellsOrigin
850
+ };
851
+ }
890
852
  export function roundToDecimalPlaces(value, decimals) {
891
853
  return Math.round(value * 10 ** decimals) / 10 ** decimals;
892
854
  }