@mui/x-virtualizer 1.0.0-beta.0 → 1.0.0-rc.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.
- package/CHANGELOG.md +262 -2
- package/features/dimensions.d.mts +1 -0
- package/features/dimensions.d.ts +1 -0
- package/features/dimensions.js +48 -2
- package/features/dimensions.mjs +48 -2
- package/features/virtualization/layout.d.mts +32 -0
- package/features/virtualization/layout.d.ts +32 -0
- package/features/virtualization/layout.js +77 -19
- package/features/virtualization/layout.mjs +83 -25
- package/features/virtualization/virtualization.d.mts +18 -1
- package/features/virtualization/virtualization.d.ts +18 -1
- package/features/virtualization/virtualization.js +22 -7
- package/features/virtualization/virtualization.mjs +22 -7
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,265 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 9.0.0-rc.0
|
|
4
|
+
|
|
5
|
+
<!-- generated comparing v9.0.0-beta.0..master -->
|
|
6
|
+
|
|
7
|
+
_Apr 7, 2026_
|
|
8
|
+
|
|
9
|
+
We'd like to extend a big thank you to the 18 contributors who made this release possible.
|
|
10
|
+
|
|
11
|
+
Special thanks go out to these community members for their valuable contributions:
|
|
12
|
+
@mixelburg, @sibananda485, @youjin-hong
|
|
13
|
+
|
|
14
|
+
The following team members contributed to this release:
|
|
15
|
+
@aemartos, @alexfauquette, @arminmeh, @brijeshb42, @flaviendelangle, @JCQuintas, @LukasTy, @mapache-salvaje, @MBilalShafi, @michelengelen, @noraleonte, @rita-codes, @romgrk, @siriwatknp, @ZeeshanTamboli
|
|
16
|
+
|
|
17
|
+
### Data Grid
|
|
18
|
+
|
|
19
|
+
#### `@mui/x-data-grid@9.0.0-rc.0`
|
|
20
|
+
|
|
21
|
+
- [DataGrid] Rename filter panel `Columns` label to singular `Column` (#21935) @youjin-hong
|
|
22
|
+
- [DataGrid] Export `GridColumnUnsortedIconProps` for custom column icon slots (#21658) @mixelburg
|
|
23
|
+
- [DataGrid] Remove `x-virtualizer`'s `virtualScroller` from public API (#21936) @romgrk
|
|
24
|
+
- [DataGrid][virtualizer] Scrolling without render gaps (#21616) @romgrk
|
|
25
|
+
|
|
26
|
+
#### `@mui/x-data-grid-pro@9.0.0-rc.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
27
|
+
|
|
28
|
+
Same changes as in `@mui/x-data-grid@9.0.0-rc.0`, plus:
|
|
29
|
+
|
|
30
|
+
- [DataGridPro] Improve trigger for nested row reordering (#21642) @MBilalShafi
|
|
31
|
+
- [DataGridPro] Undeprecate `onRowsScrollEnd` prop (#21912) @MBilalShafi
|
|
32
|
+
|
|
33
|
+
#### `@mui/x-data-grid-premium@9.0.0-rc.0` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
34
|
+
|
|
35
|
+
Same changes as in `@mui/x-data-grid-pro@9.0.0-rc.0`, plus:
|
|
36
|
+
|
|
37
|
+
- [DataGridPremium] Fix clipboard paste issue in portal (#21931) @sibananda485
|
|
38
|
+
|
|
39
|
+
### Date and Time Pickers
|
|
40
|
+
|
|
41
|
+
#### Breaking changes
|
|
42
|
+
|
|
43
|
+
- Accessible DOM structure is now the only default. [Read more](https://next.mui.com/x/migration/migration-pickers-v8/#accessible-dom-structure-is-now-the-default)
|
|
44
|
+
- The `PickerDay2` and `DateRangePickerDay2` components were propagated to stable while removing the previous defaults. [Read more](https://next.mui.com/x/migration/migration-pickers-v8/#day-slot)
|
|
45
|
+
|
|
46
|
+
#### `@mui/x-date-pickers@9.0.0-rc.0`
|
|
47
|
+
|
|
48
|
+
- [pickers] Remove `PickersDay` and `DateRangePickerDay` and promote their `2` versions as replacements (#21739) @michelengelen
|
|
49
|
+
|
|
50
|
+
#### `@mui/x-date-pickers-pro@9.0.0-rc.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
51
|
+
|
|
52
|
+
Same changes as in `@mui/x-date-pickers@9.0.0-rc.0`.
|
|
53
|
+
|
|
54
|
+
### Charts
|
|
55
|
+
|
|
56
|
+
#### `@mui/x-charts@9.0.0-rc.0.0`
|
|
57
|
+
|
|
58
|
+
- [charts] Make line visibility toggle start from the baseline (#21893) @alexfauquette
|
|
59
|
+
- [charts] Remove the container overflow (#21955) @alexfauquette
|
|
60
|
+
- [charts] Revert `theme.alpha` for non-channel token (#21965) @siriwatknp
|
|
61
|
+
|
|
62
|
+
#### `@mui/x-charts-pro@9.0.0-rc.0.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
63
|
+
|
|
64
|
+
Same changes as in `@mui/x-charts@9.0.0-rc.0.0`, plus:
|
|
65
|
+
|
|
66
|
+
- [charts-pro] Zoom slider touch improvements (#21832) @JCQuintas
|
|
67
|
+
- [charts-pro] Add `seriesIds` filter to zoom slider preview (#21933) @JCQuintas
|
|
68
|
+
- [charts-pro] Fix zoom slider preview with discard filter mode (#21883) @JCQuintas
|
|
69
|
+
|
|
70
|
+
#### `@mui/x-charts-premium@9.0.0-rc.0.0` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
71
|
+
|
|
72
|
+
Same changes as in `@mui/x-charts-pro@9.0.0-rc.0.0`, plus:
|
|
73
|
+
|
|
74
|
+
- [charts-premium] Add series `valueFormatter` to candlestick chart (#21905) @JCQuintas
|
|
75
|
+
- [charts-premium] Add zoom slider preview support for candlestick charts (#21914) @JCQuintas
|
|
76
|
+
- [charts-premium] Allow color customization in `Candlestick` chart (#21838) @JCQuintas
|
|
77
|
+
- [charts-premium] Support hide/show for OHLC (candlestick) series (#21807) @Copilot
|
|
78
|
+
- [charts-premium] Add `dataset` support to `Candlestick` chart (#21872) @JCQuintas
|
|
79
|
+
- [charts-premium] Add candlestick page to sidebar navigation (#21834) @JCQuintas
|
|
80
|
+
|
|
81
|
+
### Tree View
|
|
82
|
+
|
|
83
|
+
#### `@mui/x-tree-view@9.0.0-rc.0`
|
|
84
|
+
|
|
85
|
+
Internal changes.
|
|
86
|
+
|
|
87
|
+
#### `@mui/x-tree-view-pro@9.0.0-rc.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
88
|
+
|
|
89
|
+
Same changes as in `@mui/x-tree-view@9.0.0-rc.0`, plus:
|
|
90
|
+
|
|
91
|
+
- [RichTreeViewPro] Allow to auto-expand lazy loaded items (#21759) @flaviendelangle
|
|
92
|
+
|
|
93
|
+
### Scheduler
|
|
94
|
+
|
|
95
|
+
#### `@mui/x-scheduler@9.0.0-alpha.0`
|
|
96
|
+
|
|
97
|
+
- [scheduler] Add locale files, adapt l10n scripts, and add localization table to docs (#21870) @rita-codes
|
|
98
|
+
- [scheduler] Add planned features to the docs (#21705) @rita-codes
|
|
99
|
+
- [scheduler] Add scheduler to docs introduction (#21845) @rita-codes
|
|
100
|
+
- [scheduler] Add wide docs to scheduler (#21860) @noraleonte
|
|
101
|
+
- [scheduler] All day event bugfixes (#21884) @noraleonte
|
|
102
|
+
- [scheduler] Autofocus title field (#21947) @noraleonte
|
|
103
|
+
- [scheduler] Change default event creation trigger to single click (#21979) @rita-codes
|
|
104
|
+
- [scheduler] Change order of the views on the view selector (#21904) @rita-codes
|
|
105
|
+
- [scheduler] Disabled border color for the repeat day picker in dark mode (#21987) @rita-codes
|
|
106
|
+
- [scheduler] Drop unused dependency (#21956) @flaviendelangle
|
|
107
|
+
- [scheduler] Fix all-day event shifting to previous day in negative UTC offsets (#21994) @rita-codes
|
|
108
|
+
- [scheduler] Fix dark theme localization demos (#21992) @noraleonte
|
|
109
|
+
- [scheduler] Fix licensing confusion in docs (#21939) @rita-codes
|
|
110
|
+
- [scheduler] Fix preferences menu width shift when toggling options + Improve preferences menu accessibility (#21902) @rita-codes
|
|
111
|
+
- [scheduler] Prepare for the alpha launch (#21859) @rita-codes
|
|
112
|
+
- [scheduler] Sync Base UI internals and apply good practices (#21946) @flaviendelangle
|
|
113
|
+
- [scheduler] Update close modal aria label translation (#21940) @rita-codes
|
|
114
|
+
- [scheduler] Add Spanish (es-ES) locale (#21900) @rita-codes
|
|
115
|
+
- [scheduler] Improve French (fr-FR) locale (#21941) @rita-codes
|
|
116
|
+
- [scheduler] Improve Romanian (ro-RO) locale (#21942) @rita-codes
|
|
117
|
+
|
|
118
|
+
#### `@mui/x-scheduler-premium@9.0.0-alpha.0` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
119
|
+
|
|
120
|
+
Same changes as in `@mui/x-scheduler@9.0.0-alpha.0`.
|
|
121
|
+
|
|
122
|
+
### Codemod
|
|
123
|
+
|
|
124
|
+
#### `@mui/x-codemod@9.0.0-rc.0`
|
|
125
|
+
|
|
126
|
+
Internal changes.
|
|
127
|
+
|
|
128
|
+
### Docs
|
|
129
|
+
|
|
130
|
+
- [docs] Fix JSDOM → jsdom casing (#21907) @JCQuintas
|
|
131
|
+
- [docs] Remove Joy UI references and dependency (#21937) @siriwatknp
|
|
132
|
+
- [docs] Remove none generated files (#21886) @alexfauquette
|
|
133
|
+
- [docs] Remove unused interactive demo code (#21945) @LukasTy
|
|
134
|
+
- [docs] Revise the Funnel doc (#21677) @mapache-salvaje
|
|
135
|
+
- [docs] Revise the Line chart docs (#21554) @mapache-salvaje
|
|
136
|
+
- [docs] Revise the Radar doc (#21674) @mapache-salvaje
|
|
137
|
+
- [docs] Revise the Sankey doc (#21678) @mapache-salvaje
|
|
138
|
+
- [docs] Revise the Scatter chart docs (#21564) @mapache-salvaje
|
|
139
|
+
|
|
140
|
+
### Core
|
|
141
|
+
|
|
142
|
+
- [docs-infra] Update to the latest monorepo (#21971) @brijeshb42
|
|
143
|
+
- [internal] Remove checks for `materialVersion >= 6` (#21975) @LukasTy
|
|
144
|
+
|
|
145
|
+
### Miscellaneous
|
|
146
|
+
|
|
147
|
+
- [core] Bump @mui/material to v9.0.0-beta.1 (#21858) @siriwatknp
|
|
148
|
+
- [core] Update browserslistrc (#21974) @siriwatknp
|
|
149
|
+
- [deps] Bump minimum core packages to 7.3.0 to adopt theme color manipulator (#21892) @siriwatknp
|
|
150
|
+
- [telemetry] Prefer upstream remote over origin for `projectId` (#21882) @aemartos
|
|
151
|
+
- [telemetry] Send `repoHash`, `[x]packageNameHash`, and `rootPathHash` alongside `projectId` (#21896) @aemartos
|
|
152
|
+
- [test] Exclude flaky `DataGrid` argos test (#21977) @MBilalShafi
|
|
153
|
+
- [test] Fix flaky `DataGrid` test (#22000) @arminmeh
|
|
154
|
+
- [test] Remove `componentsProp` test from `describeConformance` (#21897) @ZeeshanTamboli
|
|
155
|
+
- [x-license] Change `orderId` type from `number` to `string` (#21885) @aemartos
|
|
156
|
+
|
|
157
|
+
## 9.0.0-beta.0
|
|
158
|
+
|
|
159
|
+
<!-- generated comparing v9.0.0-alpha.4..master -->
|
|
160
|
+
|
|
161
|
+
_Mar 27, 2026_
|
|
162
|
+
|
|
163
|
+
We'd like to extend a big thank you to the 10 contributors who made this release possible. Here are some highlights ✨:
|
|
164
|
+
|
|
165
|
+
- 🔊 New Charts voiceover component for improved screen reader support
|
|
166
|
+
- ⌨️ Charts keyboard navigation improvements: axis tooltip now shows when navigating with the keyboard
|
|
167
|
+
- 📊 Charts axes now can be set to automatically resize to fit their content
|
|
168
|
+
- 📝 New `rowCheckbox` slot in Data Grid for easier checkbox column customization
|
|
169
|
+
- ⚡️ `fetchRows()` API in Data Grid Pro now defaults `start` and `end` based on scroll position with lazy loading
|
|
170
|
+
- 🐞 Bugfixes and internal improvements
|
|
171
|
+
|
|
172
|
+
The following team members contributed to this release:
|
|
173
|
+
@aemartos, @alexfauquette, @arminmeh, @cherniavskii, @Janpot, @JCQuintas, @mapache-salvaje, @michelengelen, @noraleonte, @rita-codes
|
|
174
|
+
|
|
175
|
+
### Data Grid
|
|
176
|
+
|
|
177
|
+
#### `@mui/x-data-grid@9.0.0-beta.0`
|
|
178
|
+
|
|
179
|
+
- [DataGrid] Add `rowCheckbox` slot for easier customization (#21797) @michelengelen
|
|
180
|
+
- [DataGrid] Prevent repeated `hasScrollbar` state updates (#21820) @arminmeh
|
|
181
|
+
|
|
182
|
+
#### `@mui/x-data-grid-pro@9.0.0-beta.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
183
|
+
|
|
184
|
+
Same changes as in `@mui/x-data-grid@9.0.0-beta.0`, plus:
|
|
185
|
+
|
|
186
|
+
- [DataGridPro] `fetchRows()` API's default `start` and `end` params based on scroll position with lazy loading (#21742) @arminmeh
|
|
187
|
+
|
|
188
|
+
#### `@mui/x-data-grid-premium@9.0.0-beta.0` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
189
|
+
|
|
190
|
+
Same changes as in `@mui/x-data-grid-pro@9.0.0-beta.0`.
|
|
191
|
+
|
|
192
|
+
### Date and Time Pickers
|
|
193
|
+
|
|
194
|
+
#### `@mui/x-date-pickers@9.0.0-beta.0`
|
|
195
|
+
|
|
196
|
+
Internal changes.
|
|
197
|
+
|
|
198
|
+
#### `@mui/x-date-pickers-pro@9.0.0-beta.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
199
|
+
|
|
200
|
+
Same changes as in `@mui/x-date-pickers@9.0.0-beta.0`.
|
|
201
|
+
|
|
202
|
+
### Charts
|
|
203
|
+
|
|
204
|
+
#### `@mui/x-charts@9.0.0-beta.0`
|
|
205
|
+
|
|
206
|
+
- [charts] Add `className` prop to Pro chart plot components (#21793) @JCQuintas
|
|
207
|
+
- [charts] Add experimental position-based pointer interaction for line series (#21809) @JCQuintas
|
|
208
|
+
- [charts] Add l10n to the bar accessibility (#21815) @alexfauquette
|
|
209
|
+
- [charts] Add localization for the basic charts (#21822) @alexfauquette
|
|
210
|
+
- [charts] Add voiceover component (#21344) @alexfauquette
|
|
211
|
+
- [charts] Allow axes to automatically resize to content (#21087) @JCQuintas
|
|
212
|
+
- [charts] Document multiple use-cases for references (#21768) @alexfauquette
|
|
213
|
+
- [charts] Remove compatibility layer for React vs native events (#21780) @JCQuintas
|
|
214
|
+
- [charts] Remove deprecated `barLabel` props (#21783) @alexfauquette
|
|
215
|
+
- [charts] Show axis tooltip when navigating with keyboard (#21689) @Copilot
|
|
216
|
+
|
|
217
|
+
#### `@mui/x-charts-pro@9.0.0-beta.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
218
|
+
|
|
219
|
+
Same changes as in `@mui/x-charts@9.0.0-beta.0`.
|
|
220
|
+
|
|
221
|
+
#### `@mui/x-charts-premium@9.0.0-beta.0` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
222
|
+
|
|
223
|
+
Same changes as in `@mui/x-charts-pro@9.0.0-beta.0`.
|
|
224
|
+
|
|
225
|
+
### Tree View
|
|
226
|
+
|
|
227
|
+
#### `@mui/x-tree-view@9.0.0-alpha.4`
|
|
228
|
+
|
|
229
|
+
Internal changes.
|
|
230
|
+
|
|
231
|
+
#### `@mui/x-tree-view-pro@9.0.0-alpha.4` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
232
|
+
|
|
233
|
+
Same changes as in `@mui/x-tree-view@9.0.0-alpha.4`.
|
|
234
|
+
|
|
235
|
+
### Codemod
|
|
236
|
+
|
|
237
|
+
#### `@mui/x-codemod@9.0.0-alpha.4`
|
|
238
|
+
|
|
239
|
+
Internal changes.
|
|
240
|
+
|
|
241
|
+
### Docs
|
|
242
|
+
|
|
243
|
+
- [docs] Document how to customize voiceover announcement (#21833) @alexfauquette
|
|
244
|
+
- [docs] Remove Discord mention from docs (#21855) @mapache-salvaje
|
|
245
|
+
- [docs] Remove stabilized experimental feature from demo (#21869) @JCQuintas
|
|
246
|
+
- [docs] Update telemetry guide to reflect pseudonymous data collection and license compliance (#21812) @aemartos
|
|
247
|
+
- [docs] Revise the Sparkline doc (#21614) @mapache-salvaje
|
|
248
|
+
- [docs] Revise the Gauge doc (#21673) @mapache-salvaje
|
|
249
|
+
- [docs] Revise the Heatmap doc (#21676) @mapache-salvaje
|
|
250
|
+
|
|
251
|
+
### Core
|
|
252
|
+
|
|
253
|
+
- [code-infra] Remove unused deps and unify es-toolkit via catalog (#21840) @Janpot
|
|
254
|
+
- [code-infra] Update @mui/internal-bundle-size-checker to canary.68 (#21836) @Janpot
|
|
255
|
+
- [code-infra] Update next (#21837) @Janpot
|
|
256
|
+
- [internal] Remove headless data grid packages (#21843) @cherniavskii
|
|
257
|
+
|
|
258
|
+
### Miscellaneous
|
|
259
|
+
|
|
260
|
+
- Add @romgrk to CODEOWNERS for `x-virtualizer` and `x-internals` (#21819) @Copilot
|
|
261
|
+
- [x-license] add 2022 plan version (#21814) @aemartos
|
|
262
|
+
|
|
3
263
|
## 9.0.0-alpha.4
|
|
4
264
|
|
|
5
265
|
_Mar 19, 2026_
|
|
@@ -59,7 +319,7 @@ Same changes as in `@mui/x-date-pickers@9.0.0-alpha.4`.
|
|
|
59
319
|
- [charts] Remove deprecated `useMouseTracker()` (#21787) @alexfauquette
|
|
60
320
|
- [charts] Remove deprecated classes (#21775) @alexfauquette
|
|
61
321
|
- [charts] Remove deprecated props from PieArcLabel animation (#21789) @alexfauquette
|
|
62
|
-
- [charts] Remove get
|
|
322
|
+
- [charts] Remove get\*UtilityClass from public exports (#21769) @JCQuintas
|
|
63
323
|
- [charts] Remove the deprecated `disableHover` property (#21785) @alexfauquette
|
|
64
324
|
- [charts] Remove the deprecated `message` prop (#21784) @alexfauquette
|
|
65
325
|
- [charts] Remove deprecated props about voronoi (#21796) @alexfauquette
|
|
@@ -207,7 +467,7 @@ Same changes as in `@mui/x-charts-pro@9.0.0-alpha.3`, plus:
|
|
|
207
467
|
- Remove deprecated CSS state classes from `treeItemClasses`: `expanded`, `selected`, `focused`, `disabled`, `editable`, `editing` (use `[data-expanded]`, `[data-selected]`, etc.)
|
|
208
468
|
- The `<RichTreeViewPro />` component has now virtualization enabled by default.
|
|
209
469
|
- The items used inside the `<RichTreeViewPro />` now have a default height of `32px`.
|
|
210
|
-
- The
|
|
470
|
+
- The items of the `<RichTreeViewPro />` are now rendered as a flat list instead of a nested tree.
|
|
211
471
|
|
|
212
472
|
#### `@mui/x-tree-view@9.0.0-alpha.3`
|
|
213
473
|
|
|
@@ -27,6 +27,7 @@ export declare const Dimensions: {
|
|
|
27
27
|
rowPositions: (state: BaseState) => number[];
|
|
28
28
|
columnPositions: (_: any, columns: ColumnWithWidth[]) => number[];
|
|
29
29
|
needsHorizontalScrollbar: (state: BaseState) => boolean;
|
|
30
|
+
needsVerticalScrollbar: (state: BaseState) => boolean;
|
|
30
31
|
};
|
|
31
32
|
};
|
|
32
33
|
export declare namespace Dimensions {
|
package/features/dimensions.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare const Dimensions: {
|
|
|
27
27
|
rowPositions: (state: BaseState) => number[];
|
|
28
28
|
columnPositions: (_: any, columns: ColumnWithWidth[]) => number[];
|
|
29
29
|
needsHorizontalScrollbar: (state: BaseState) => boolean;
|
|
30
|
+
needsVerticalScrollbar: (state: BaseState) => boolean;
|
|
30
31
|
};
|
|
31
32
|
};
|
|
32
33
|
export declare namespace Dimensions {
|
package/features/dimensions.js
CHANGED
|
@@ -61,7 +61,8 @@ const selectors = {
|
|
|
61
61
|
}
|
|
62
62
|
return positions;
|
|
63
63
|
}),
|
|
64
|
-
needsHorizontalScrollbar: state => state.dimensions.
|
|
64
|
+
needsHorizontalScrollbar: state => state.dimensions.viewportInnerSize.width > 0 && state.dimensions.columnsTotalWidth > state.dimensions.viewportInnerSize.width,
|
|
65
|
+
needsVerticalScrollbar: state => state.dimensions.viewportInnerSize.height > 0 && state.dimensions.contentSize.height > state.dimensions.viewportInnerSize.height
|
|
65
66
|
};
|
|
66
67
|
const Dimensions = exports.Dimensions = {
|
|
67
68
|
initialize: initializeState,
|
|
@@ -97,6 +98,21 @@ function initializeState(params) {
|
|
|
97
98
|
}
|
|
98
99
|
function useDimensions(store, params, _api) {
|
|
99
100
|
const isFirstSizing = React.useRef(true);
|
|
101
|
+
|
|
102
|
+
// Vertical scrollbar oscillation detector.
|
|
103
|
+
// Counts consecutive hasScrollY flips that happen with no row-height change.
|
|
104
|
+
// After 2 flips it is certainly a layout feedback loop, so every further flip
|
|
105
|
+
// is forced to false (no scrollbar). The counter resets when row heights change.
|
|
106
|
+
// Only vertical scrollbar can oscillate because column widths are never 'auto'.
|
|
107
|
+
// https://github.com/mui/mui-x/issues/20539
|
|
108
|
+
const scrollYOscillation = React.useRef({
|
|
109
|
+
counter: 0,
|
|
110
|
+
heights: {
|
|
111
|
+
content: 0,
|
|
112
|
+
pinnedTop: 0,
|
|
113
|
+
pinnedBottom: 0
|
|
114
|
+
}
|
|
115
|
+
});
|
|
100
116
|
const {
|
|
101
117
|
layout,
|
|
102
118
|
dimensions: {
|
|
@@ -130,6 +146,7 @@ function useDimensions(store, params, _api) {
|
|
|
130
146
|
width: columnsTotalWidth,
|
|
131
147
|
height: (0, _math.roundToDecimalPlaces)(rowsMeta.currentPageTotalHeight, 1)
|
|
132
148
|
};
|
|
149
|
+
const prevDimensions = store.state.dimensions;
|
|
133
150
|
let viewportOuterSize;
|
|
134
151
|
let viewportInnerSize;
|
|
135
152
|
let hasScrollX = false;
|
|
@@ -167,6 +184,36 @@ function useDimensions(store, params, _api) {
|
|
|
167
184
|
hasScrollY = content.height + scrollbarSize > container.height;
|
|
168
185
|
}
|
|
169
186
|
}
|
|
187
|
+
|
|
188
|
+
// Detect vertical scrollbar oscillation.
|
|
189
|
+
// Track consecutive hasScrollY flips with no row-height change.
|
|
190
|
+
// Once confirmed (≥ 2 flips), force hasScrollY off — the scrollbar is
|
|
191
|
+
// not genuinely needed, it is a layout feedback loop caused by stale
|
|
192
|
+
// rootSize or the horizontal scrollbar's height cascading.
|
|
193
|
+
{
|
|
194
|
+
const osc = scrollYOscillation.current;
|
|
195
|
+
const heightsChanged = rowsMeta.currentPageTotalHeight !== osc.heights.content || rowsMeta.pinnedTopRowsTotalHeight !== osc.heights.pinnedTop || rowsMeta.pinnedBottomRowsTotalHeight !== osc.heights.pinnedBottom;
|
|
196
|
+
if (heightsChanged) {
|
|
197
|
+
osc.counter = 0;
|
|
198
|
+
osc.heights = {
|
|
199
|
+
content: rowsMeta.currentPageTotalHeight,
|
|
200
|
+
pinnedTop: rowsMeta.pinnedTopRowsTotalHeight,
|
|
201
|
+
pinnedBottom: rowsMeta.pinnedBottomRowsTotalHeight
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (prevDimensions.isReady && hasScrollY !== prevDimensions.hasScrollY) {
|
|
205
|
+
if (!heightsChanged) {
|
|
206
|
+
osc.counter += 1;
|
|
207
|
+
}
|
|
208
|
+
if (osc.counter >= 2) {
|
|
209
|
+
hasScrollY = false;
|
|
210
|
+
// Recompute hasScrollX without the vertical scrollbar's width impact,
|
|
211
|
+
// otherwise the cascade (hasScrollY → narrower viewport → hasScrollX)
|
|
212
|
+
// keeps the horizontal scrollbar/filler alive and the root keeps resizing.
|
|
213
|
+
hasScrollX = hasScrollXIfNoYScrollBar;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
170
217
|
if (hasScrollY) {
|
|
171
218
|
viewportInnerSize.width -= scrollbarSize;
|
|
172
219
|
}
|
|
@@ -205,7 +252,6 @@ function useDimensions(store, params, _api) {
|
|
|
205
252
|
autoHeight: params.dimensions.autoHeight,
|
|
206
253
|
minimalContentHeight: params.dimensions.minimalContentHeight
|
|
207
254
|
};
|
|
208
|
-
const prevDimensions = store.state.dimensions;
|
|
209
255
|
if ((0, _isDeepEqual.isDeepEqual)(prevDimensions, newDimensions)) {
|
|
210
256
|
return;
|
|
211
257
|
}
|
package/features/dimensions.mjs
CHANGED
|
@@ -54,7 +54,8 @@ const selectors = {
|
|
|
54
54
|
}
|
|
55
55
|
return positions;
|
|
56
56
|
}),
|
|
57
|
-
needsHorizontalScrollbar: state => state.dimensions.
|
|
57
|
+
needsHorizontalScrollbar: state => state.dimensions.viewportInnerSize.width > 0 && state.dimensions.columnsTotalWidth > state.dimensions.viewportInnerSize.width,
|
|
58
|
+
needsVerticalScrollbar: state => state.dimensions.viewportInnerSize.height > 0 && state.dimensions.contentSize.height > state.dimensions.viewportInnerSize.height
|
|
58
59
|
};
|
|
59
60
|
export const Dimensions = {
|
|
60
61
|
initialize: initializeState,
|
|
@@ -90,6 +91,21 @@ function initializeState(params) {
|
|
|
90
91
|
}
|
|
91
92
|
function useDimensions(store, params, _api) {
|
|
92
93
|
const isFirstSizing = React.useRef(true);
|
|
94
|
+
|
|
95
|
+
// Vertical scrollbar oscillation detector.
|
|
96
|
+
// Counts consecutive hasScrollY flips that happen with no row-height change.
|
|
97
|
+
// After 2 flips it is certainly a layout feedback loop, so every further flip
|
|
98
|
+
// is forced to false (no scrollbar). The counter resets when row heights change.
|
|
99
|
+
// Only vertical scrollbar can oscillate because column widths are never 'auto'.
|
|
100
|
+
// https://github.com/mui/mui-x/issues/20539
|
|
101
|
+
const scrollYOscillation = React.useRef({
|
|
102
|
+
counter: 0,
|
|
103
|
+
heights: {
|
|
104
|
+
content: 0,
|
|
105
|
+
pinnedTop: 0,
|
|
106
|
+
pinnedBottom: 0
|
|
107
|
+
}
|
|
108
|
+
});
|
|
93
109
|
const {
|
|
94
110
|
layout,
|
|
95
111
|
dimensions: {
|
|
@@ -123,6 +139,7 @@ function useDimensions(store, params, _api) {
|
|
|
123
139
|
width: columnsTotalWidth,
|
|
124
140
|
height: roundToDecimalPlaces(rowsMeta.currentPageTotalHeight, 1)
|
|
125
141
|
};
|
|
142
|
+
const prevDimensions = store.state.dimensions;
|
|
126
143
|
let viewportOuterSize;
|
|
127
144
|
let viewportInnerSize;
|
|
128
145
|
let hasScrollX = false;
|
|
@@ -160,6 +177,36 @@ function useDimensions(store, params, _api) {
|
|
|
160
177
|
hasScrollY = content.height + scrollbarSize > container.height;
|
|
161
178
|
}
|
|
162
179
|
}
|
|
180
|
+
|
|
181
|
+
// Detect vertical scrollbar oscillation.
|
|
182
|
+
// Track consecutive hasScrollY flips with no row-height change.
|
|
183
|
+
// Once confirmed (≥ 2 flips), force hasScrollY off — the scrollbar is
|
|
184
|
+
// not genuinely needed, it is a layout feedback loop caused by stale
|
|
185
|
+
// rootSize or the horizontal scrollbar's height cascading.
|
|
186
|
+
{
|
|
187
|
+
const osc = scrollYOscillation.current;
|
|
188
|
+
const heightsChanged = rowsMeta.currentPageTotalHeight !== osc.heights.content || rowsMeta.pinnedTopRowsTotalHeight !== osc.heights.pinnedTop || rowsMeta.pinnedBottomRowsTotalHeight !== osc.heights.pinnedBottom;
|
|
189
|
+
if (heightsChanged) {
|
|
190
|
+
osc.counter = 0;
|
|
191
|
+
osc.heights = {
|
|
192
|
+
content: rowsMeta.currentPageTotalHeight,
|
|
193
|
+
pinnedTop: rowsMeta.pinnedTopRowsTotalHeight,
|
|
194
|
+
pinnedBottom: rowsMeta.pinnedBottomRowsTotalHeight
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (prevDimensions.isReady && hasScrollY !== prevDimensions.hasScrollY) {
|
|
198
|
+
if (!heightsChanged) {
|
|
199
|
+
osc.counter += 1;
|
|
200
|
+
}
|
|
201
|
+
if (osc.counter >= 2) {
|
|
202
|
+
hasScrollY = false;
|
|
203
|
+
// Recompute hasScrollX without the vertical scrollbar's width impact,
|
|
204
|
+
// otherwise the cascade (hasScrollY → narrower viewport → hasScrollX)
|
|
205
|
+
// keeps the horizontal scrollbar/filler alive and the root keeps resizing.
|
|
206
|
+
hasScrollX = hasScrollXIfNoYScrollBar;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
163
210
|
if (hasScrollY) {
|
|
164
211
|
viewportInnerSize.width -= scrollbarSize;
|
|
165
212
|
}
|
|
@@ -198,7 +245,6 @@ function useDimensions(store, params, _api) {
|
|
|
198
245
|
autoHeight: params.dimensions.autoHeight,
|
|
199
246
|
minimalContentHeight: params.dimensions.minimalContentHeight
|
|
200
247
|
};
|
|
201
|
-
const prevDimensions = store.state.dimensions;
|
|
202
248
|
if (isDeepEqual(prevDimensions, newDimensions)) {
|
|
203
249
|
return;
|
|
204
250
|
}
|
|
@@ -36,6 +36,17 @@ export declare class LayoutDataGrid extends Layout<DataGridElements> {
|
|
|
36
36
|
role: string;
|
|
37
37
|
tabIndex: number | undefined;
|
|
38
38
|
};
|
|
39
|
+
scrollerContentProps: (args_0: Virtualization.State<Layout<AnyElements>> & Dimensions.State) => {
|
|
40
|
+
style: React.CSSProperties | undefined;
|
|
41
|
+
role: string;
|
|
42
|
+
};
|
|
43
|
+
viewportProps: (args_0: Virtualization.State<Layout<AnyElements>> & Dimensions.State) => {
|
|
44
|
+
style: {
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
};
|
|
48
|
+
role: string;
|
|
49
|
+
};
|
|
39
50
|
contentProps: (args_0: Virtualization.State<Layout<AnyElements>> & Dimensions.State) => {
|
|
40
51
|
style: React.CSSProperties;
|
|
41
52
|
role: string;
|
|
@@ -45,6 +56,11 @@ export declare class LayoutDataGrid extends Layout<DataGridElements> {
|
|
|
45
56
|
transform: string;
|
|
46
57
|
};
|
|
47
58
|
};
|
|
59
|
+
containerVerticalProps: (args_0: Virtualization.State<Layout<AnyElements>> & Dimensions.State) => {
|
|
60
|
+
style: {
|
|
61
|
+
transform: string;
|
|
62
|
+
};
|
|
63
|
+
} | undefined;
|
|
48
64
|
scrollbarHorizontalProps: (args_0: Virtualization.State<Layout<AnyElements>> & Dimensions.State) => {
|
|
49
65
|
ref: any;
|
|
50
66
|
scrollPosition: {
|
|
@@ -78,6 +94,17 @@ export declare class LayoutDataGridLegacy extends LayoutDataGrid {
|
|
|
78
94
|
role: string;
|
|
79
95
|
tabIndex: number | undefined;
|
|
80
96
|
};
|
|
97
|
+
getScrollerContentProps: () => {
|
|
98
|
+
style: React.CSSProperties | undefined;
|
|
99
|
+
role: string;
|
|
100
|
+
};
|
|
101
|
+
getViewportProps: () => {
|
|
102
|
+
style: {
|
|
103
|
+
width: number;
|
|
104
|
+
height: number;
|
|
105
|
+
};
|
|
106
|
+
role: string;
|
|
107
|
+
};
|
|
81
108
|
getContentProps: () => {
|
|
82
109
|
style: React.CSSProperties;
|
|
83
110
|
role: string;
|
|
@@ -104,6 +131,11 @@ export declare class LayoutDataGridLegacy extends LayoutDataGrid {
|
|
|
104
131
|
current: import("../../models/index.mjs").ScrollPosition;
|
|
105
132
|
};
|
|
106
133
|
};
|
|
134
|
+
getContainerVerticalProps: () => {
|
|
135
|
+
style: {
|
|
136
|
+
transform: string;
|
|
137
|
+
};
|
|
138
|
+
} | undefined;
|
|
107
139
|
};
|
|
108
140
|
}
|
|
109
141
|
type ListElements = BaseElements;
|
|
@@ -36,6 +36,17 @@ export declare class LayoutDataGrid extends Layout<DataGridElements> {
|
|
|
36
36
|
role: string;
|
|
37
37
|
tabIndex: number | undefined;
|
|
38
38
|
};
|
|
39
|
+
scrollerContentProps: (args_0: Virtualization.State<Layout<AnyElements>> & Dimensions.State) => {
|
|
40
|
+
style: React.CSSProperties | undefined;
|
|
41
|
+
role: string;
|
|
42
|
+
};
|
|
43
|
+
viewportProps: (args_0: Virtualization.State<Layout<AnyElements>> & Dimensions.State) => {
|
|
44
|
+
style: {
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
};
|
|
48
|
+
role: string;
|
|
49
|
+
};
|
|
39
50
|
contentProps: (args_0: Virtualization.State<Layout<AnyElements>> & Dimensions.State) => {
|
|
40
51
|
style: React.CSSProperties;
|
|
41
52
|
role: string;
|
|
@@ -45,6 +56,11 @@ export declare class LayoutDataGrid extends Layout<DataGridElements> {
|
|
|
45
56
|
transform: string;
|
|
46
57
|
};
|
|
47
58
|
};
|
|
59
|
+
containerVerticalProps: (args_0: Virtualization.State<Layout<AnyElements>> & Dimensions.State) => {
|
|
60
|
+
style: {
|
|
61
|
+
transform: string;
|
|
62
|
+
};
|
|
63
|
+
} | undefined;
|
|
48
64
|
scrollbarHorizontalProps: (args_0: Virtualization.State<Layout<AnyElements>> & Dimensions.State) => {
|
|
49
65
|
ref: any;
|
|
50
66
|
scrollPosition: {
|
|
@@ -78,6 +94,17 @@ export declare class LayoutDataGridLegacy extends LayoutDataGrid {
|
|
|
78
94
|
role: string;
|
|
79
95
|
tabIndex: number | undefined;
|
|
80
96
|
};
|
|
97
|
+
getScrollerContentProps: () => {
|
|
98
|
+
style: React.CSSProperties | undefined;
|
|
99
|
+
role: string;
|
|
100
|
+
};
|
|
101
|
+
getViewportProps: () => {
|
|
102
|
+
style: {
|
|
103
|
+
width: number;
|
|
104
|
+
height: number;
|
|
105
|
+
};
|
|
106
|
+
role: string;
|
|
107
|
+
};
|
|
81
108
|
getContentProps: () => {
|
|
82
109
|
style: React.CSSProperties;
|
|
83
110
|
role: string;
|
|
@@ -104,6 +131,11 @@ export declare class LayoutDataGridLegacy extends LayoutDataGrid {
|
|
|
104
131
|
current: import("../../models/index.js").ScrollPosition;
|
|
105
132
|
};
|
|
106
133
|
};
|
|
134
|
+
getContainerVerticalProps: () => {
|
|
135
|
+
style: {
|
|
136
|
+
transform: string;
|
|
137
|
+
};
|
|
138
|
+
} | undefined;
|
|
107
139
|
};
|
|
108
140
|
}
|
|
109
141
|
type ListElements = BaseElements;
|
|
@@ -64,19 +64,58 @@ class LayoutDataGrid extends Layout {
|
|
|
64
64
|
// https://github.com/mui/mui-x/pull/13891#discussion_r1683416024
|
|
65
65
|
tabIndex: platform.isFirefox ? -1 : undefined
|
|
66
66
|
})),
|
|
67
|
+
scrollerContentProps: (0, _store.createSelectorMemoized)(_virtualization.Virtualization.selectors.layoutMode, _dimensions.Dimensions.selectors.dimensions, _dimensions.Dimensions.selectors.needsVerticalScrollbar, _dimensions.Dimensions.selectors.needsHorizontalScrollbar, (layoutMode, dimensions, needsVerticalScrollbar, needsHorizontalScrollbar) => {
|
|
68
|
+
let style;
|
|
69
|
+
if (layoutMode === 'controlled') {
|
|
70
|
+
const {
|
|
71
|
+
contentSize,
|
|
72
|
+
scrollbarSize,
|
|
73
|
+
topContainerHeight,
|
|
74
|
+
bottomContainerHeight,
|
|
75
|
+
minimalContentHeight,
|
|
76
|
+
columnsTotalWidth
|
|
77
|
+
} = dimensions;
|
|
78
|
+
const verticalScrollbarSize = needsVerticalScrollbar ? scrollbarSize : 0;
|
|
79
|
+
const horizontalScrollbarSize = needsHorizontalScrollbar ? scrollbarSize : 0;
|
|
80
|
+
const contentHeight = contentSize.height === 0 ? minimalContentHeight : contentSize.height;
|
|
81
|
+
const width = needsHorizontalScrollbar ? verticalScrollbarSize + columnsTotalWidth : 'auto';
|
|
82
|
+
const height = cssAdd(cssAdd(cssAdd(contentHeight, topContainerHeight), bottomContainerHeight), horizontalScrollbarSize);
|
|
83
|
+
style = {
|
|
84
|
+
width,
|
|
85
|
+
height,
|
|
86
|
+
flex: '0 0 auto'
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
style,
|
|
91
|
+
role: 'presentation'
|
|
92
|
+
};
|
|
93
|
+
}),
|
|
94
|
+
viewportProps: (0, _store.createSelectorMemoized)(_dimensions.Dimensions.selectors.dimensions, dimensions => ({
|
|
95
|
+
style: {
|
|
96
|
+
width: dimensions.viewportOuterSize.width,
|
|
97
|
+
height: dimensions.viewportOuterSize.height
|
|
98
|
+
},
|
|
99
|
+
role: 'presentation'
|
|
100
|
+
})),
|
|
67
101
|
contentProps: (0, _store.createSelectorMemoized)(_dimensions.Dimensions.selectors.contentHeight, _dimensions.Dimensions.selectors.minimalContentHeight, _dimensions.Dimensions.selectors.columnsTotalWidth, _dimensions.Dimensions.selectors.needsHorizontalScrollbar, (contentHeight, minimalContentHeight, columnsTotalWidth, needsHorizontalScrollbar) => ({
|
|
68
102
|
style: {
|
|
69
103
|
width: needsHorizontalScrollbar ? columnsTotalWidth : 'auto',
|
|
70
|
-
|
|
71
|
-
|
|
104
|
+
height: contentHeight === 0 ? minimalContentHeight : contentHeight,
|
|
105
|
+
flex: '0 0 auto'
|
|
72
106
|
},
|
|
73
107
|
role: 'presentation'
|
|
74
108
|
})),
|
|
75
|
-
positionerProps: (0, _store.createSelectorMemoized)(_virtualization.Virtualization.selectors.offsetTop, offsetTop => ({
|
|
109
|
+
positionerProps: (0, _store.createSelectorMemoized)(_virtualization.Virtualization.selectors.layoutMode, _virtualization.Virtualization.selectors.offsetTop, _virtualization.Virtualization.selectors.scrollPosition, (layoutMode, offsetTop, scrollPosition) => ({
|
|
76
110
|
style: {
|
|
77
|
-
transform: `translate3d(0, ${offsetTop}px, 0)`
|
|
111
|
+
transform: layoutMode === 'uncontrolled' ? `translate3d(0, ${offsetTop}px, 0)` : `translate3d(${-scrollPosition.current.left}px, ${offsetTop - scrollPosition.current.top}px, 0)`
|
|
78
112
|
}
|
|
79
113
|
})),
|
|
114
|
+
containerVerticalProps: (0, _store.createSelectorMemoized)(_virtualization.Virtualization.selectors.layoutMode, _virtualization.Virtualization.selectors.scrollPosition, (layoutMode, scrollPosition) => layoutMode === 'uncontrolled' ? undefined : {
|
|
115
|
+
style: {
|
|
116
|
+
transform: `translate3d(${-scrollPosition.current.left}px, 0, 0)`
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
80
119
|
scrollbarHorizontalProps: (0, _store.createSelectorMemoized)(_virtualization.Virtualization.selectors.context, _virtualization.Virtualization.selectors.scrollPosition, (context, scrollPosition) => ({
|
|
81
120
|
ref: context.scrollbarHorizontalRef,
|
|
82
121
|
scrollPosition
|
|
@@ -102,19 +141,25 @@ class LayoutDataGridLegacy extends LayoutDataGrid {
|
|
|
102
141
|
super.use(store, _params, _api, layoutParams);
|
|
103
142
|
const containerProps = store.use(LayoutDataGrid.selectors.containerProps);
|
|
104
143
|
const scrollerProps = store.use(LayoutDataGrid.selectors.scrollerProps);
|
|
144
|
+
const scrollerContentProps = store.use(LayoutDataGrid.selectors.scrollerContentProps);
|
|
145
|
+
const viewportProps = store.use(LayoutDataGrid.selectors.viewportProps);
|
|
105
146
|
const contentProps = store.use(LayoutDataGrid.selectors.contentProps);
|
|
106
147
|
const positionerProps = store.use(LayoutDataGrid.selectors.positionerProps);
|
|
107
148
|
const scrollbarVerticalProps = store.use(LayoutDataGrid.selectors.scrollbarVerticalProps);
|
|
108
149
|
const scrollbarHorizontalProps = store.use(LayoutDataGrid.selectors.scrollbarHorizontalProps);
|
|
109
150
|
const scrollAreaProps = store.use(LayoutDataGrid.selectors.scrollAreaProps);
|
|
151
|
+
const containerVerticalProps = store.use(LayoutDataGrid.selectors.containerVerticalProps);
|
|
110
152
|
return {
|
|
111
153
|
getContainerProps: () => containerProps,
|
|
112
154
|
getScrollerProps: () => scrollerProps,
|
|
155
|
+
getScrollerContentProps: () => scrollerContentProps,
|
|
156
|
+
getViewportProps: () => viewportProps,
|
|
113
157
|
getContentProps: () => contentProps,
|
|
114
158
|
getPositionerProps: () => positionerProps,
|
|
115
159
|
getScrollbarVerticalProps: () => scrollbarVerticalProps,
|
|
116
160
|
getScrollbarHorizontalProps: () => scrollbarHorizontalProps,
|
|
117
|
-
getScrollAreaProps: () => scrollAreaProps
|
|
161
|
+
getScrollAreaProps: () => scrollAreaProps,
|
|
162
|
+
getContainerVerticalProps: () => containerVerticalProps
|
|
118
163
|
};
|
|
119
164
|
}
|
|
120
165
|
}
|
|
@@ -144,20 +189,18 @@ class LayoutList extends Layout {
|
|
|
144
189
|
// https://github.com/mui/mui-x/pull/13891#discussion_r1683416024
|
|
145
190
|
tabIndex: platform.isFirefox ? -1 : undefined
|
|
146
191
|
})),
|
|
147
|
-
contentProps: (0, _store.createSelectorMemoized)(_dimensions.Dimensions.selectors.contentHeight, contentHeight => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
};
|
|
160
|
-
}),
|
|
192
|
+
contentProps: (0, _store.createSelectorMemoized)(_dimensions.Dimensions.selectors.contentHeight, contentHeight => ({
|
|
193
|
+
style: {
|
|
194
|
+
position: 'absolute',
|
|
195
|
+
display: 'inline-block',
|
|
196
|
+
width: '100%',
|
|
197
|
+
height: contentHeight,
|
|
198
|
+
top: 0,
|
|
199
|
+
left: 0,
|
|
200
|
+
zIndex: -1
|
|
201
|
+
},
|
|
202
|
+
role: 'presentation'
|
|
203
|
+
})),
|
|
161
204
|
positionerProps: (0, _store.createSelectorMemoized)(_virtualization.Virtualization.selectors.offsetTop, offsetTop => ({
|
|
162
205
|
style: {
|
|
163
206
|
height: offsetTop
|
|
@@ -216,4 +259,19 @@ function useScrollbarRefCallback(scrollerRef, refSetter, scrollProperty) {
|
|
|
216
259
|
scrollbar.removeEventListener('scroll', onScrollbarScroll);
|
|
217
260
|
};
|
|
218
261
|
});
|
|
262
|
+
}
|
|
263
|
+
function cssAdd(a, b) {
|
|
264
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
265
|
+
return a + b;
|
|
266
|
+
}
|
|
267
|
+
return `calc(${valueToCSSString(a)} + ${valueToCSSString(b)})`;
|
|
268
|
+
}
|
|
269
|
+
function valueToCSSString(value) {
|
|
270
|
+
if (typeof value === 'string') {
|
|
271
|
+
return value;
|
|
272
|
+
}
|
|
273
|
+
if (typeof value === 'undefined') {
|
|
274
|
+
return '0';
|
|
275
|
+
}
|
|
276
|
+
return `${value}px`;
|
|
219
277
|
}
|
|
@@ -25,7 +25,7 @@ export class Layout {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
export class LayoutDataGrid extends Layout {
|
|
28
|
-
static elements =
|
|
28
|
+
static elements = ['scroller', 'container', 'content', 'positioner', 'scrollbarVertical', 'scrollbarHorizontal'];
|
|
29
29
|
use(store, _params, _api, layoutParams) {
|
|
30
30
|
const {
|
|
31
31
|
scrollerRef,
|
|
@@ -40,7 +40,7 @@ export class LayoutDataGrid extends Layout {
|
|
|
40
40
|
scrollbarHorizontalRef
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
static selectors =
|
|
43
|
+
static selectors = {
|
|
44
44
|
containerProps: createSelectorMemoized(Virtualization.selectors.context, context => ({
|
|
45
45
|
ref: context.containerRef
|
|
46
46
|
})),
|
|
@@ -57,19 +57,58 @@ export class LayoutDataGrid extends Layout {
|
|
|
57
57
|
// https://github.com/mui/mui-x/pull/13891#discussion_r1683416024
|
|
58
58
|
tabIndex: platform.isFirefox ? -1 : undefined
|
|
59
59
|
})),
|
|
60
|
+
scrollerContentProps: createSelectorMemoized(Virtualization.selectors.layoutMode, Dimensions.selectors.dimensions, Dimensions.selectors.needsVerticalScrollbar, Dimensions.selectors.needsHorizontalScrollbar, (layoutMode, dimensions, needsVerticalScrollbar, needsHorizontalScrollbar) => {
|
|
61
|
+
let style;
|
|
62
|
+
if (layoutMode === 'controlled') {
|
|
63
|
+
const {
|
|
64
|
+
contentSize,
|
|
65
|
+
scrollbarSize,
|
|
66
|
+
topContainerHeight,
|
|
67
|
+
bottomContainerHeight,
|
|
68
|
+
minimalContentHeight,
|
|
69
|
+
columnsTotalWidth
|
|
70
|
+
} = dimensions;
|
|
71
|
+
const verticalScrollbarSize = needsVerticalScrollbar ? scrollbarSize : 0;
|
|
72
|
+
const horizontalScrollbarSize = needsHorizontalScrollbar ? scrollbarSize : 0;
|
|
73
|
+
const contentHeight = contentSize.height === 0 ? minimalContentHeight : contentSize.height;
|
|
74
|
+
const width = needsHorizontalScrollbar ? verticalScrollbarSize + columnsTotalWidth : 'auto';
|
|
75
|
+
const height = cssAdd(cssAdd(cssAdd(contentHeight, topContainerHeight), bottomContainerHeight), horizontalScrollbarSize);
|
|
76
|
+
style = {
|
|
77
|
+
width,
|
|
78
|
+
height,
|
|
79
|
+
flex: '0 0 auto'
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
style,
|
|
84
|
+
role: 'presentation'
|
|
85
|
+
};
|
|
86
|
+
}),
|
|
87
|
+
viewportProps: createSelectorMemoized(Dimensions.selectors.dimensions, dimensions => ({
|
|
88
|
+
style: {
|
|
89
|
+
width: dimensions.viewportOuterSize.width,
|
|
90
|
+
height: dimensions.viewportOuterSize.height
|
|
91
|
+
},
|
|
92
|
+
role: 'presentation'
|
|
93
|
+
})),
|
|
60
94
|
contentProps: createSelectorMemoized(Dimensions.selectors.contentHeight, Dimensions.selectors.minimalContentHeight, Dimensions.selectors.columnsTotalWidth, Dimensions.selectors.needsHorizontalScrollbar, (contentHeight, minimalContentHeight, columnsTotalWidth, needsHorizontalScrollbar) => ({
|
|
61
95
|
style: {
|
|
62
96
|
width: needsHorizontalScrollbar ? columnsTotalWidth : 'auto',
|
|
63
|
-
|
|
64
|
-
|
|
97
|
+
height: contentHeight === 0 ? minimalContentHeight : contentHeight,
|
|
98
|
+
flex: '0 0 auto'
|
|
65
99
|
},
|
|
66
100
|
role: 'presentation'
|
|
67
101
|
})),
|
|
68
|
-
positionerProps: createSelectorMemoized(Virtualization.selectors.offsetTop, offsetTop => ({
|
|
102
|
+
positionerProps: createSelectorMemoized(Virtualization.selectors.layoutMode, Virtualization.selectors.offsetTop, Virtualization.selectors.scrollPosition, (layoutMode, offsetTop, scrollPosition) => ({
|
|
69
103
|
style: {
|
|
70
|
-
transform: `translate3d(0, ${offsetTop}px, 0)`
|
|
104
|
+
transform: layoutMode === 'uncontrolled' ? `translate3d(0, ${offsetTop}px, 0)` : `translate3d(${-scrollPosition.current.left}px, ${offsetTop - scrollPosition.current.top}px, 0)`
|
|
71
105
|
}
|
|
72
106
|
})),
|
|
107
|
+
containerVerticalProps: createSelectorMemoized(Virtualization.selectors.layoutMode, Virtualization.selectors.scrollPosition, (layoutMode, scrollPosition) => layoutMode === 'uncontrolled' ? undefined : {
|
|
108
|
+
style: {
|
|
109
|
+
transform: `translate3d(${-scrollPosition.current.left}px, 0, 0)`
|
|
110
|
+
}
|
|
111
|
+
}),
|
|
73
112
|
scrollbarHorizontalProps: createSelectorMemoized(Virtualization.selectors.context, Virtualization.selectors.scrollPosition, (context, scrollPosition) => ({
|
|
74
113
|
ref: context.scrollbarHorizontalRef,
|
|
75
114
|
scrollPosition
|
|
@@ -81,7 +120,7 @@ export class LayoutDataGrid extends Layout {
|
|
|
81
120
|
scrollAreaProps: createSelectorMemoized(Virtualization.selectors.scrollPosition, scrollPosition => ({
|
|
82
121
|
scrollPosition
|
|
83
122
|
}))
|
|
84
|
-
}
|
|
123
|
+
};
|
|
85
124
|
}
|
|
86
125
|
|
|
87
126
|
// The current virtualizer API is exposed on one of the DataGrid slots, so we need to keep
|
|
@@ -94,24 +133,30 @@ export class LayoutDataGridLegacy extends LayoutDataGrid {
|
|
|
94
133
|
super.use(store, _params, _api, layoutParams);
|
|
95
134
|
const containerProps = store.use(LayoutDataGrid.selectors.containerProps);
|
|
96
135
|
const scrollerProps = store.use(LayoutDataGrid.selectors.scrollerProps);
|
|
136
|
+
const scrollerContentProps = store.use(LayoutDataGrid.selectors.scrollerContentProps);
|
|
137
|
+
const viewportProps = store.use(LayoutDataGrid.selectors.viewportProps);
|
|
97
138
|
const contentProps = store.use(LayoutDataGrid.selectors.contentProps);
|
|
98
139
|
const positionerProps = store.use(LayoutDataGrid.selectors.positionerProps);
|
|
99
140
|
const scrollbarVerticalProps = store.use(LayoutDataGrid.selectors.scrollbarVerticalProps);
|
|
100
141
|
const scrollbarHorizontalProps = store.use(LayoutDataGrid.selectors.scrollbarHorizontalProps);
|
|
101
142
|
const scrollAreaProps = store.use(LayoutDataGrid.selectors.scrollAreaProps);
|
|
143
|
+
const containerVerticalProps = store.use(LayoutDataGrid.selectors.containerVerticalProps);
|
|
102
144
|
return {
|
|
103
145
|
getContainerProps: () => containerProps,
|
|
104
146
|
getScrollerProps: () => scrollerProps,
|
|
147
|
+
getScrollerContentProps: () => scrollerContentProps,
|
|
148
|
+
getViewportProps: () => viewportProps,
|
|
105
149
|
getContentProps: () => contentProps,
|
|
106
150
|
getPositionerProps: () => positionerProps,
|
|
107
151
|
getScrollbarVerticalProps: () => scrollbarVerticalProps,
|
|
108
152
|
getScrollbarHorizontalProps: () => scrollbarHorizontalProps,
|
|
109
|
-
getScrollAreaProps: () => scrollAreaProps
|
|
153
|
+
getScrollAreaProps: () => scrollAreaProps,
|
|
154
|
+
getContainerVerticalProps: () => containerVerticalProps
|
|
110
155
|
};
|
|
111
156
|
}
|
|
112
157
|
}
|
|
113
158
|
export class LayoutList extends Layout {
|
|
114
|
-
static elements =
|
|
159
|
+
static elements = ['scroller', 'container', 'content', 'positioner'];
|
|
115
160
|
use(store, _params, _api, layoutParams) {
|
|
116
161
|
const {
|
|
117
162
|
scrollerRef,
|
|
@@ -122,7 +167,7 @@ export class LayoutList extends Layout {
|
|
|
122
167
|
mergedRef
|
|
123
168
|
};
|
|
124
169
|
}
|
|
125
|
-
static selectors =
|
|
170
|
+
static selectors = {
|
|
126
171
|
containerProps: createSelectorMemoized(Virtualization.selectors.context, Dimensions.selectors.autoHeight, Dimensions.selectors.needsHorizontalScrollbar, (context, autoHeight, needsHorizontalScrollbar) => ({
|
|
127
172
|
ref: context.mergedRef,
|
|
128
173
|
style: {
|
|
@@ -135,26 +180,24 @@ export class LayoutList extends Layout {
|
|
|
135
180
|
// https://github.com/mui/mui-x/pull/13891#discussion_r1683416024
|
|
136
181
|
tabIndex: platform.isFirefox ? -1 : undefined
|
|
137
182
|
})),
|
|
138
|
-
contentProps: createSelectorMemoized(Dimensions.selectors.contentHeight, contentHeight => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
};
|
|
151
|
-
}),
|
|
183
|
+
contentProps: createSelectorMemoized(Dimensions.selectors.contentHeight, contentHeight => ({
|
|
184
|
+
style: {
|
|
185
|
+
position: 'absolute',
|
|
186
|
+
display: 'inline-block',
|
|
187
|
+
width: '100%',
|
|
188
|
+
height: contentHeight,
|
|
189
|
+
top: 0,
|
|
190
|
+
left: 0,
|
|
191
|
+
zIndex: -1
|
|
192
|
+
},
|
|
193
|
+
role: 'presentation'
|
|
194
|
+
})),
|
|
152
195
|
positionerProps: createSelectorMemoized(Virtualization.selectors.offsetTop, offsetTop => ({
|
|
153
196
|
style: {
|
|
154
197
|
height: offsetTop
|
|
155
198
|
}
|
|
156
199
|
}))
|
|
157
|
-
}
|
|
200
|
+
};
|
|
158
201
|
}
|
|
159
202
|
function useScrollbarRefCallback(scrollerRef, refSetter, scrollProperty) {
|
|
160
203
|
const isLocked = React.useRef(false);
|
|
@@ -206,4 +249,19 @@ function useScrollbarRefCallback(scrollerRef, refSetter, scrollProperty) {
|
|
|
206
249
|
scrollbar.removeEventListener('scroll', onScrollbarScroll);
|
|
207
250
|
};
|
|
208
251
|
});
|
|
252
|
+
}
|
|
253
|
+
function cssAdd(a, b) {
|
|
254
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
255
|
+
return a + b;
|
|
256
|
+
}
|
|
257
|
+
return `calc(${valueToCSSString(a)} + ${valueToCSSString(b)})`;
|
|
258
|
+
}
|
|
259
|
+
function valueToCSSString(value) {
|
|
260
|
+
if (typeof value === 'string') {
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
if (typeof value === 'undefined') {
|
|
264
|
+
return '0';
|
|
265
|
+
}
|
|
266
|
+
return `${value}px`;
|
|
209
267
|
}
|
|
@@ -15,6 +15,13 @@ export type VirtualizationParams = {
|
|
|
15
15
|
/** The column buffer in pixels to render before and after the viewport.
|
|
16
16
|
* @default 150 */
|
|
17
17
|
columnBufferPx?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Controls how the container and render zones are positioned:
|
|
20
|
+
* - 'uncontrolled': uses CSS sticky positioning (default)
|
|
21
|
+
* - 'controlled': uses CSS absolute positioning with JS-computed offsets
|
|
22
|
+
* @default 'uncontrolled'
|
|
23
|
+
*/
|
|
24
|
+
layoutMode?: 'controlled' | 'uncontrolled';
|
|
18
25
|
};
|
|
19
26
|
export type VirtualizationState<K extends string = string> = {
|
|
20
27
|
enabled: boolean;
|
|
@@ -26,6 +33,7 @@ export type VirtualizationState<K extends string = string> = {
|
|
|
26
33
|
scrollPosition: {
|
|
27
34
|
current: ScrollPosition;
|
|
28
35
|
};
|
|
36
|
+
layoutMode: 'controlled' | 'uncontrolled';
|
|
29
37
|
};
|
|
30
38
|
export declare const EMPTY_RENDER_CONTEXT: {
|
|
31
39
|
firstRowIndex: number;
|
|
@@ -46,9 +54,18 @@ export declare const Virtualization: {
|
|
|
46
54
|
container: React.RefObject<HTMLElement | null>;
|
|
47
55
|
} & Record<string, React.RefObject<HTMLElement | null>>>> & Dimensions.State) => number;
|
|
48
56
|
context: (state: BaseState) => Record<string, any>;
|
|
57
|
+
layoutMode: (state: BaseState) => "controlled" | "uncontrolled";
|
|
49
58
|
scrollPosition: (state: BaseState) => {
|
|
50
59
|
current: ScrollPosition;
|
|
51
60
|
};
|
|
61
|
+
pinnedLeftOffsetSelector: (args_0: Virtualization.State<Layout<{
|
|
62
|
+
scroller: React.RefObject<HTMLElement | null>;
|
|
63
|
+
container: React.RefObject<HTMLElement | null>;
|
|
64
|
+
} & Record<string, React.RefObject<HTMLElement | null>>>> & Dimensions.State) => number;
|
|
65
|
+
pinnedRightOffsetSelector: (args_0: Virtualization.State<Layout<{
|
|
66
|
+
scroller: React.RefObject<HTMLElement | null>;
|
|
67
|
+
container: React.RefObject<HTMLElement | null>;
|
|
68
|
+
} & Record<string, React.RefObject<HTMLElement | null>>>> & Dimensions.State) => number;
|
|
52
69
|
};
|
|
53
70
|
};
|
|
54
71
|
export declare namespace Virtualization {
|
|
@@ -83,6 +100,6 @@ declare function useVirtualization(store: Store<BaseState>, params: ParamsWithDe
|
|
|
83
100
|
scheduleUpdateRenderContext: () => void;
|
|
84
101
|
};
|
|
85
102
|
export declare function areRenderContextsEqual(context1: RenderContext, context2: RenderContext): boolean;
|
|
86
|
-
export declare function computeOffsetLeft(columnPositions: number[], renderContext: ColumnsRenderContext, pinnedLeftLength: number): number;
|
|
103
|
+
export declare function computeOffsetLeft(columnPositions: number[], renderContext: ColumnsRenderContext, pinnedLeftLength: number, layoutMode?: VirtualizationState['layoutMode']): number;
|
|
87
104
|
export declare function roundToDecimalPlaces(value: number, decimals: number): number;
|
|
88
105
|
export {};
|
|
@@ -15,6 +15,13 @@ export type VirtualizationParams = {
|
|
|
15
15
|
/** The column buffer in pixels to render before and after the viewport.
|
|
16
16
|
* @default 150 */
|
|
17
17
|
columnBufferPx?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Controls how the container and render zones are positioned:
|
|
20
|
+
* - 'uncontrolled': uses CSS sticky positioning (default)
|
|
21
|
+
* - 'controlled': uses CSS absolute positioning with JS-computed offsets
|
|
22
|
+
* @default 'uncontrolled'
|
|
23
|
+
*/
|
|
24
|
+
layoutMode?: 'controlled' | 'uncontrolled';
|
|
18
25
|
};
|
|
19
26
|
export type VirtualizationState<K extends string = string> = {
|
|
20
27
|
enabled: boolean;
|
|
@@ -26,6 +33,7 @@ export type VirtualizationState<K extends string = string> = {
|
|
|
26
33
|
scrollPosition: {
|
|
27
34
|
current: ScrollPosition;
|
|
28
35
|
};
|
|
36
|
+
layoutMode: 'controlled' | 'uncontrolled';
|
|
29
37
|
};
|
|
30
38
|
export declare const EMPTY_RENDER_CONTEXT: {
|
|
31
39
|
firstRowIndex: number;
|
|
@@ -46,9 +54,18 @@ export declare const Virtualization: {
|
|
|
46
54
|
container: React.RefObject<HTMLElement | null>;
|
|
47
55
|
} & Record<string, React.RefObject<HTMLElement | null>>>> & Dimensions.State) => number;
|
|
48
56
|
context: (state: BaseState) => Record<string, any>;
|
|
57
|
+
layoutMode: (state: BaseState) => "controlled" | "uncontrolled";
|
|
49
58
|
scrollPosition: (state: BaseState) => {
|
|
50
59
|
current: ScrollPosition;
|
|
51
60
|
};
|
|
61
|
+
pinnedLeftOffsetSelector: (args_0: Virtualization.State<Layout<{
|
|
62
|
+
scroller: React.RefObject<HTMLElement | null>;
|
|
63
|
+
container: React.RefObject<HTMLElement | null>;
|
|
64
|
+
} & Record<string, React.RefObject<HTMLElement | null>>>> & Dimensions.State) => number;
|
|
65
|
+
pinnedRightOffsetSelector: (args_0: Virtualization.State<Layout<{
|
|
66
|
+
scroller: React.RefObject<HTMLElement | null>;
|
|
67
|
+
container: React.RefObject<HTMLElement | null>;
|
|
68
|
+
} & Record<string, React.RefObject<HTMLElement | null>>>> & Dimensions.State) => number;
|
|
52
69
|
};
|
|
53
70
|
};
|
|
54
71
|
export declare namespace Virtualization {
|
|
@@ -83,6 +100,6 @@ declare function useVirtualization(store: Store<BaseState>, params: ParamsWithDe
|
|
|
83
100
|
scheduleUpdateRenderContext: () => void;
|
|
84
101
|
};
|
|
85
102
|
export declare function areRenderContextsEqual(context1: RenderContext, context2: RenderContext): boolean;
|
|
86
|
-
export declare function computeOffsetLeft(columnPositions: number[], renderContext: ColumnsRenderContext, pinnedLeftLength: number): number;
|
|
103
|
+
export declare function computeOffsetLeft(columnPositions: number[], renderContext: ColumnsRenderContext, pinnedLeftLength: number, layoutMode?: VirtualizationState['layoutMode']): number;
|
|
87
104
|
export declare function roundToDecimalPlaces(value: number, decimals: number): number;
|
|
88
105
|
export {};
|
|
@@ -42,14 +42,23 @@ const EMPTY_RENDER_CONTEXT = exports.EMPTY_RENDER_CONTEXT = {
|
|
|
42
42
|
};
|
|
43
43
|
const selectors = (() => {
|
|
44
44
|
const firstRowIndexSelector = (0, _store.createSelector)(state => state.virtualization.renderContext.firstRowIndex);
|
|
45
|
+
const scrollPositionSelector = (0, _store.createSelector)(state => state.virtualization.scrollPosition);
|
|
46
|
+
const layoutModeSelector = (0, _store.createSelector)(state => state.virtualization.layoutMode);
|
|
45
47
|
return {
|
|
46
48
|
store: (0, _store.createSelector)(state => state.virtualization),
|
|
47
49
|
renderContext: (0, _store.createSelector)(state => state.virtualization.renderContext),
|
|
48
50
|
enabledForRows: (0, _store.createSelector)(state => state.virtualization.enabledForRows),
|
|
49
51
|
enabledForColumns: (0, _store.createSelector)(state => state.virtualization.enabledForColumns),
|
|
50
|
-
offsetTop: (0, _store.createSelector)(_dimensions.Dimensions.selectors.rowPositions, firstRowIndexSelector, (rowPositions, firstRowIndex) =>
|
|
52
|
+
offsetTop: (0, _store.createSelector)(layoutModeSelector, _dimensions.Dimensions.selectors.dimensions, _dimensions.Dimensions.selectors.rowPositions, firstRowIndexSelector, (layoutMode, dimensions, rowPositions, firstRowIndex) => {
|
|
53
|
+
return (layoutMode === 'uncontrolled' ? dimensions.topContainerHeight : 0) + (rowPositions[firstRowIndex] ?? 0);
|
|
54
|
+
}),
|
|
51
55
|
context: (0, _store.createSelector)(state => state.virtualization.context),
|
|
52
|
-
|
|
56
|
+
layoutMode: layoutModeSelector,
|
|
57
|
+
scrollPosition: scrollPositionSelector,
|
|
58
|
+
pinnedLeftOffsetSelector: (0, _store.createSelector)(scrollPositionSelector, scrollPosition => scrollPosition.current.left),
|
|
59
|
+
pinnedRightOffsetSelector: (0, _store.createSelector)(scrollPositionSelector, _dimensions.Dimensions.selectors.dimensions, _dimensions.Dimensions.selectors.columnsTotalWidth, _dimensions.Dimensions.selectors.needsVerticalScrollbar, (scrollPosition, dimensions, columnsTotalWidth, needsVerticalScrollbar) => {
|
|
60
|
+
return Math.max(columnsTotalWidth, dimensions.viewportOuterSize.width) - dimensions.viewportOuterSize.width - scrollPosition.current.left + (needsVerticalScrollbar ? dimensions.scrollbarSize : 0);
|
|
61
|
+
})
|
|
53
62
|
};
|
|
54
63
|
})();
|
|
55
64
|
const Virtualization = exports.Virtualization = {
|
|
@@ -68,7 +77,8 @@ function initializeState(params) {
|
|
|
68
77
|
context: {},
|
|
69
78
|
scrollPosition: {
|
|
70
79
|
current: _models.ScrollPosition.EMPTY
|
|
71
|
-
}
|
|
80
|
+
},
|
|
81
|
+
layoutMode: params.virtualization.layoutMode ?? 'uncontrolled'
|
|
72
82
|
}, params.initialState?.virtualization),
|
|
73
83
|
// FIXME: refactor once the state shape is settled
|
|
74
84
|
getters: null
|
|
@@ -365,7 +375,7 @@ function useVirtualization(store, params, api) {
|
|
|
365
375
|
}
|
|
366
376
|
const isVirtualFocusRow = rowIndexInPage === virtualRowIndex;
|
|
367
377
|
const isVirtualFocusColumn = focusedVirtualCell?.rowIndex === rowIndex;
|
|
368
|
-
const offsetLeft = computeOffsetLeft(columnPositions, currentRenderContext, pinnedColumns.left.length);
|
|
378
|
+
const offsetLeft = computeOffsetLeft(columnPositions, currentRenderContext, pinnedColumns.left.length, store.state.virtualization.layoutMode);
|
|
369
379
|
const showBottomBorder = isLastVisibleInSection && rowParams.position === 'top';
|
|
370
380
|
const firstColumnIndex = currentRenderContext.firstColumnIndex;
|
|
371
381
|
const lastColumnIndex = currentRenderContext.lastColumnIndex;
|
|
@@ -735,9 +745,14 @@ function areRenderContextsEqual(context1, context2) {
|
|
|
735
745
|
}
|
|
736
746
|
return context1.firstRowIndex === context2.firstRowIndex && context1.lastRowIndex === context2.lastRowIndex && context1.firstColumnIndex === context2.firstColumnIndex && context1.lastColumnIndex === context2.lastColumnIndex;
|
|
737
747
|
}
|
|
738
|
-
function computeOffsetLeft(columnPositions, renderContext, pinnedLeftLength) {
|
|
739
|
-
|
|
740
|
-
|
|
748
|
+
function computeOffsetLeft(columnPositions, renderContext, pinnedLeftLength, layoutMode = 'uncontrolled') {
|
|
749
|
+
let offset = columnPositions[renderContext.firstColumnIndex] ?? 0;
|
|
750
|
+
/* CSS sticky leaves elements in the normal flow of the DOM, so we
|
|
751
|
+
* don't need to add the offset of the pinned columns. */
|
|
752
|
+
if (layoutMode === 'uncontrolled') {
|
|
753
|
+
offset -= columnPositions[pinnedLeftLength] ?? 0;
|
|
754
|
+
}
|
|
755
|
+
return Math.abs(offset);
|
|
741
756
|
}
|
|
742
757
|
function bufferForDirection(isRtl, direction, rowBufferPx, columnBufferPx, verticalBuffer, horizontalBuffer) {
|
|
743
758
|
if (isRtl) {
|
|
@@ -33,14 +33,23 @@ export const EMPTY_RENDER_CONTEXT = {
|
|
|
33
33
|
};
|
|
34
34
|
const selectors = (() => {
|
|
35
35
|
const firstRowIndexSelector = createSelector(state => state.virtualization.renderContext.firstRowIndex);
|
|
36
|
+
const scrollPositionSelector = createSelector(state => state.virtualization.scrollPosition);
|
|
37
|
+
const layoutModeSelector = createSelector(state => state.virtualization.layoutMode);
|
|
36
38
|
return {
|
|
37
39
|
store: createSelector(state => state.virtualization),
|
|
38
40
|
renderContext: createSelector(state => state.virtualization.renderContext),
|
|
39
41
|
enabledForRows: createSelector(state => state.virtualization.enabledForRows),
|
|
40
42
|
enabledForColumns: createSelector(state => state.virtualization.enabledForColumns),
|
|
41
|
-
offsetTop: createSelector(Dimensions.selectors.rowPositions, firstRowIndexSelector, (rowPositions, firstRowIndex) =>
|
|
43
|
+
offsetTop: createSelector(layoutModeSelector, Dimensions.selectors.dimensions, Dimensions.selectors.rowPositions, firstRowIndexSelector, (layoutMode, dimensions, rowPositions, firstRowIndex) => {
|
|
44
|
+
return (layoutMode === 'uncontrolled' ? dimensions.topContainerHeight : 0) + (rowPositions[firstRowIndex] ?? 0);
|
|
45
|
+
}),
|
|
42
46
|
context: createSelector(state => state.virtualization.context),
|
|
43
|
-
|
|
47
|
+
layoutMode: layoutModeSelector,
|
|
48
|
+
scrollPosition: scrollPositionSelector,
|
|
49
|
+
pinnedLeftOffsetSelector: createSelector(scrollPositionSelector, scrollPosition => scrollPosition.current.left),
|
|
50
|
+
pinnedRightOffsetSelector: createSelector(scrollPositionSelector, Dimensions.selectors.dimensions, Dimensions.selectors.columnsTotalWidth, Dimensions.selectors.needsVerticalScrollbar, (scrollPosition, dimensions, columnsTotalWidth, needsVerticalScrollbar) => {
|
|
51
|
+
return Math.max(columnsTotalWidth, dimensions.viewportOuterSize.width) - dimensions.viewportOuterSize.width - scrollPosition.current.left + (needsVerticalScrollbar ? dimensions.scrollbarSize : 0);
|
|
52
|
+
})
|
|
44
53
|
};
|
|
45
54
|
})();
|
|
46
55
|
export const Virtualization = {
|
|
@@ -59,7 +68,8 @@ function initializeState(params) {
|
|
|
59
68
|
context: {},
|
|
60
69
|
scrollPosition: {
|
|
61
70
|
current: ScrollPosition.EMPTY
|
|
62
|
-
}
|
|
71
|
+
},
|
|
72
|
+
layoutMode: params.virtualization.layoutMode ?? 'uncontrolled'
|
|
63
73
|
}, params.initialState?.virtualization),
|
|
64
74
|
// FIXME: refactor once the state shape is settled
|
|
65
75
|
getters: null
|
|
@@ -356,7 +366,7 @@ function useVirtualization(store, params, api) {
|
|
|
356
366
|
}
|
|
357
367
|
const isVirtualFocusRow = rowIndexInPage === virtualRowIndex;
|
|
358
368
|
const isVirtualFocusColumn = focusedVirtualCell?.rowIndex === rowIndex;
|
|
359
|
-
const offsetLeft = computeOffsetLeft(columnPositions, currentRenderContext, pinnedColumns.left.length);
|
|
369
|
+
const offsetLeft = computeOffsetLeft(columnPositions, currentRenderContext, pinnedColumns.left.length, store.state.virtualization.layoutMode);
|
|
360
370
|
const showBottomBorder = isLastVisibleInSection && rowParams.position === 'top';
|
|
361
371
|
const firstColumnIndex = currentRenderContext.firstColumnIndex;
|
|
362
372
|
const lastColumnIndex = currentRenderContext.lastColumnIndex;
|
|
@@ -726,9 +736,14 @@ export function areRenderContextsEqual(context1, context2) {
|
|
|
726
736
|
}
|
|
727
737
|
return context1.firstRowIndex === context2.firstRowIndex && context1.lastRowIndex === context2.lastRowIndex && context1.firstColumnIndex === context2.firstColumnIndex && context1.lastColumnIndex === context2.lastColumnIndex;
|
|
728
738
|
}
|
|
729
|
-
export function computeOffsetLeft(columnPositions, renderContext, pinnedLeftLength) {
|
|
730
|
-
|
|
731
|
-
|
|
739
|
+
export function computeOffsetLeft(columnPositions, renderContext, pinnedLeftLength, layoutMode = 'uncontrolled') {
|
|
740
|
+
let offset = columnPositions[renderContext.firstColumnIndex] ?? 0;
|
|
741
|
+
/* CSS sticky leaves elements in the normal flow of the DOM, so we
|
|
742
|
+
* don't need to add the offset of the pinned columns. */
|
|
743
|
+
if (layoutMode === 'uncontrolled') {
|
|
744
|
+
offset -= columnPositions[pinnedLeftLength] ?? 0;
|
|
745
|
+
}
|
|
746
|
+
return Math.abs(offset);
|
|
732
747
|
}
|
|
733
748
|
function bufferForDirection(isRtl, direction, rowBufferPx, columnBufferPx, verticalBuffer, horizontalBuffer) {
|
|
734
749
|
if (isRtl) {
|
package/index.js
CHANGED
package/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/x-virtualizer",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-rc.0",
|
|
4
4
|
"author": "MUI Team",
|
|
5
5
|
"description": "MUI virtualization library",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@babel/runtime": "^7.28.6",
|
|
31
|
-
"@mui/utils": "
|
|
32
|
-
"@mui/x-internals": "9.0.0-
|
|
31
|
+
"@mui/utils": "9.0.0-beta.1",
|
|
32
|
+
"@mui/x-internals": "9.0.0-rc.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|