-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathGridVirtualScrollbar.tsx
171 lines (146 loc) · 5.85 KB
/
GridVirtualScrollbar.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import * as React from 'react';
import { styled } from '@mui/system';
import {
unstable_composeClasses as composeClasses,
unstable_useForkRef as useForkRef,
unstable_useEventCallback as useEventCallback,
} from '@mui/utils';
import { useOnMount } from '../../hooks/utils/useOnMount';
import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext';
import { gridDimensionsSelector, useGridSelector } from '../../hooks';
import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
import { getDataGridUtilityClass } from '../../constants/gridClasses';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
type Position = 'vertical' | 'horizontal';
type OwnerState = DataGridProcessedProps;
type GridVirtualScrollbarProps = { position: Position };
const useUtilityClasses = (ownerState: OwnerState, position: Position) => {
const { classes } = ownerState;
const slots = {
root: ['scrollbar', `scrollbar--${position}`],
content: ['scrollbarContent'],
};
return composeClasses(slots, getDataGridUtilityClass, classes);
};
const Scrollbar = styled('div')({
position: 'absolute',
display: 'inline-block',
zIndex: 6,
// In macOS Safari and Gnome Web, scrollbars are overlaid and don't affect the layout. So we consider
// their size to be 0px throughout all the calculations, but the floating scrollbar container does need
// to appear and have a real size. We set it to 14px because it seems like an acceptable value and we
// don't have a method to find the required size for scrollbars on those platforms.
'--size': 'calc(max(var(--DataGrid-scrollbarSize), 14px))',
});
const ScrollbarVertical = styled(Scrollbar)({
width: 'var(--size)',
height:
'calc(var(--DataGrid-hasScrollY) * (100% - var(--DataGrid-topContainerHeight) - var(--DataGrid-bottomContainerHeight) - var(--DataGrid-hasScrollX) * var(--DataGrid-scrollbarSize)))',
overflowY: 'auto',
overflowX: 'hidden',
// Disable focus-visible style, it's a scrollbar.
outline: 0,
'& > div': {
width: 'var(--size)',
},
top: 'var(--DataGrid-topContainerHeight)',
right: '0px',
});
const ScrollbarHorizontal = styled(Scrollbar)({
width: '100%',
height: 'var(--size)',
overflowY: 'hidden',
overflowX: 'auto',
// Disable focus-visible style, it's a scrollbar.
outline: 0,
'& > div': {
height: 'var(--size)',
},
bottom: '0px',
});
const GridVirtualScrollbar = React.forwardRef<HTMLDivElement, GridVirtualScrollbarProps>(
function GridVirtualScrollbar(props, ref) {
const apiRef = useGridPrivateApiContext();
const rootProps = useGridRootProps();
const isLocked = React.useRef(false);
const lastPosition = React.useRef(0);
const scrollbarRef = React.useRef<HTMLDivElement>(null);
const contentRef = React.useRef<HTMLDivElement>(null);
const classes = useUtilityClasses(rootProps, props.position);
const dimensions = useGridSelector(apiRef, gridDimensionsSelector);
const propertyDimension = props.position === 'vertical' ? 'height' : 'width';
const propertyScroll = props.position === 'vertical' ? 'scrollTop' : 'scrollLeft';
const hasScroll = props.position === 'vertical' ? dimensions.hasScrollX : dimensions.hasScrollY;
const contentSize =
dimensions.minimumSize[propertyDimension] + (hasScroll ? dimensions.scrollbarSize : 0);
const scrollbarSize =
props.position === 'vertical'
? dimensions.viewportInnerSize.height
: dimensions.viewportOuterSize.width;
const scrollbarInnerSize =
scrollbarSize * (contentSize / dimensions.viewportOuterSize[propertyDimension]);
const onScrollerScroll = useEventCallback(() => {
const scroller = apiRef.current.virtualScrollerRef.current!;
const scrollbar = scrollbarRef.current;
if (!scrollbar) {
return;
}
if (scroller[propertyScroll] === lastPosition.current) {
return;
}
lastPosition.current = scroller[propertyScroll];
if (isLocked.current) {
isLocked.current = false;
return;
}
isLocked.current = true;
const value = scroller[propertyScroll] / contentSize;
scrollbar[propertyScroll] = value * scrollbarInnerSize;
});
const onScrollbarScroll = useEventCallback(() => {
const scroller = apiRef.current.virtualScrollerRef.current!;
const scrollbar = scrollbarRef.current;
if (!scrollbar) {
return;
}
if (isLocked.current) {
isLocked.current = false;
return;
}
isLocked.current = true;
const value = scrollbar[propertyScroll] / scrollbarInnerSize;
scroller[propertyScroll] = value * contentSize;
});
useOnMount(() => {
const scroller = apiRef.current.virtualScrollerRef.current!;
const scrollbar = scrollbarRef.current!;
scroller.addEventListener('scroll', onScrollerScroll, { capture: true });
scrollbar.addEventListener('scroll', onScrollbarScroll, { capture: true });
return () => {
scroller.removeEventListener('scroll', onScrollerScroll, { capture: true });
scrollbar.removeEventListener('scroll', onScrollbarScroll, { capture: true });
};
});
React.useEffect(() => {
const content = contentRef.current!;
content.style.setProperty(propertyDimension, `${scrollbarInnerSize}px`);
}, [scrollbarInnerSize, propertyDimension]);
const Container = props.position === 'vertical' ? ScrollbarVertical : ScrollbarHorizontal;
return (
<Container
ref={useForkRef(ref, scrollbarRef)}
className={classes.root}
style={
props.position === 'vertical' && rootProps.unstable_listView
? { height: '100%', top: 0 }
: undefined
}
tabIndex={-1}
aria-hidden="true"
>
<div ref={contentRef} className={classes.content} />
</Container>
);
},
);
export { GridVirtualScrollbar };