@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.
- package/CHANGELOG.md +174 -0
- package/constants.d.ts +22 -0
- package/constants.js +26 -0
- package/esm/constants.d.ts +22 -0
- package/esm/constants.js +20 -0
- package/esm/features/colspan.d.ts +4 -4
- package/esm/features/dimensions.d.ts +14 -8
- package/esm/features/dimensions.js +26 -7
- package/esm/features/index.d.ts +1 -1
- package/esm/features/index.js +1 -1
- package/esm/features/keyboard.d.ts +3 -3
- package/esm/features/keyboard.js +1 -1
- package/esm/features/rowspan.d.ts +4 -4
- package/esm/features/virtualization/index.d.ts +2 -0
- package/esm/features/virtualization/index.js +2 -0
- package/esm/features/virtualization/layout.d.ts +129 -0
- package/esm/features/virtualization/layout.js +152 -0
- package/esm/features/{virtualization.d.ts → virtualization/virtualization.d.ts} +35 -58
- package/esm/features/{virtualization.js → virtualization/virtualization.js} +98 -136
- package/esm/index.d.ts +2 -1
- package/esm/index.js +3 -2
- package/esm/models/core.d.ts +7 -0
- package/esm/models/core.js +7 -0
- package/esm/models/dimensions.d.ts +8 -0
- package/esm/useVirtualizer.d.ts +25 -69
- package/esm/useVirtualizer.js +21 -4
- package/features/colspan.d.ts +4 -4
- package/features/dimensions.d.ts +14 -8
- package/features/dimensions.js +26 -7
- package/features/index.d.ts +1 -1
- package/features/keyboard.d.ts +3 -3
- package/features/rowspan.d.ts +4 -4
- package/features/virtualization/index.d.ts +2 -0
- package/features/virtualization/index.js +27 -0
- package/features/virtualization/layout.d.ts +129 -0
- package/features/virtualization/layout.js +163 -0
- package/features/{virtualization.d.ts → virtualization/virtualization.d.ts} +35 -58
- package/features/{virtualization.js → virtualization/virtualization.js} +98 -136
- package/index.d.ts +2 -1
- package/index.js +12 -1
- package/models/core.d.ts +7 -0
- package/models/core.js +8 -1
- package/models/dimensions.d.ts +8 -0
- package/package.json +2 -2
- package/useVirtualizer.d.ts +25 -69
- 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 "
|
|
5
|
-
import { Dimensions } from "
|
|
6
|
-
import type { BaseState,
|
|
7
|
-
import {
|
|
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:
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 "
|
|
15
|
-
import { Dimensions, observeRootNode } from "
|
|
16
|
-
import { ScrollDirection } from "
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
453
|
-
if (
|
|
454
|
-
|
|
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
|
|
459
|
-
|
|
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
|
-
|
|
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
|
|
489
|
-
|
|
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
|
-
|
|
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
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
522
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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:
|
|
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
|
}
|