@noya-app/noya-designsystem 0.1.71 → 0.1.73
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +17 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +58 -5
- package/dist/index.d.ts +58 -5
- package/dist/index.js +990 -780
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +970 -759
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -7
- package/src/components/BaseToolbar.tsx +1 -1
- package/src/components/Breadcrumbs.tsx +1 -0
- package/src/components/Collection.tsx +1 -2
- package/src/components/ComboboxMenu.tsx +1 -1
- package/src/components/Message.tsx +3 -2
- package/src/components/ScrollArea2.tsx +22 -2
- package/src/components/Virtualized.tsx +439 -0
- package/src/components/__tests__/Virtualized.math.test.ts +153 -0
- package/src/components/__tests__/Virtualized.test.tsx +85 -0
- package/src/components/listView/ListViewContexts.tsx +1 -6
- package/src/components/listView/ListViewRoot.tsx +18 -152
- package/src/index.tsx +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-designsystem",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.73",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@noya-app/noya-geometry": "0.1.16",
|
|
29
29
|
"@noya-app/noya-icons": "0.1.16",
|
|
30
30
|
"@noya-app/noya-keymap": "0.1.4",
|
|
31
|
-
"@noya-app/noya-tailwind-config": "0.1.
|
|
31
|
+
"@noya-app/noya-tailwind-config": "0.1.9",
|
|
32
32
|
"radix-ui": "1.4.2",
|
|
33
33
|
"@radix-ui/react-compose-refs": "^1.0.0",
|
|
34
34
|
"@radix-ui/primitive": "^1.0.1",
|
|
@@ -38,17 +38,13 @@
|
|
|
38
38
|
"immer": "9.0.5",
|
|
39
39
|
"kiwi.js": "^1.1.3",
|
|
40
40
|
"react-resizable-panels": "3.0.3",
|
|
41
|
-
"
|
|
42
|
-
"vscode-fuzzy-scorer": "^0.0.4",
|
|
43
|
-
"react-virtualized": "9.22.6"
|
|
41
|
+
"vscode-fuzzy-scorer": "^0.0.4"
|
|
44
42
|
},
|
|
45
43
|
"devDependencies": {
|
|
46
44
|
"@tailwindcss/container-queries": "^0.1.1",
|
|
47
45
|
"@tailwindcss/typography": "^0.5.19",
|
|
48
46
|
"@types/react": "*",
|
|
49
47
|
"@types/react-dom": "*",
|
|
50
|
-
"@types/react-virtualized": "^9.21.30",
|
|
51
|
-
"@types/react-window": "^1.8.5",
|
|
52
48
|
"tailwindcss": "^3.4.17",
|
|
53
49
|
"postcss": "^8.5.3",
|
|
54
50
|
"autoprefixer": "^10.4.21",
|
|
@@ -85,7 +85,7 @@ export function BaseToolbar({
|
|
|
85
85
|
{children && (
|
|
86
86
|
<div
|
|
87
87
|
className={
|
|
88
|
-
"n-flex n-items-center n-justify-center n-text-text-muted n-pointer-events-none @
|
|
88
|
+
"n-flex n-items-center n-justify-center n-text-text-muted n-pointer-events-none @xl/toolbar:!n-absolute n-inset-0"
|
|
89
89
|
}
|
|
90
90
|
>
|
|
91
91
|
<div className="n-flex n-items-center n-justify-center n-pointer-events-auto">
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Size } from "@noya-app/noya-geometry";
|
|
4
3
|
import { forwardRefGeneric } from "@noya-app/react-utils";
|
|
5
4
|
import React, { CSSProperties } from "react";
|
|
6
5
|
import { Grid } from "./Grid";
|
|
@@ -34,7 +33,7 @@ export type CollectionRenderActionProps<T> = {
|
|
|
34
33
|
export interface CollectionProps<T, M extends string = string> {
|
|
35
34
|
className?: string;
|
|
36
35
|
items: T[];
|
|
37
|
-
virtualized?:
|
|
36
|
+
virtualized?: number;
|
|
38
37
|
getId: (item: T) => string;
|
|
39
38
|
getName: (item: T) => string;
|
|
40
39
|
getHref?: (item: T) => string;
|
|
@@ -62,7 +62,7 @@ export const ComboboxMenu = memoGeneric(
|
|
|
62
62
|
return item.value;
|
|
63
63
|
}}
|
|
64
64
|
data={items}
|
|
65
|
-
virtualized={listSize}
|
|
65
|
+
virtualized={listSize.height}
|
|
66
66
|
{...(itemHeight ? { itemHeight } : {})}
|
|
67
67
|
{...(getRowHeightForItem
|
|
68
68
|
? { getItemHeightForItem: getRowHeightForItem as any }
|
|
@@ -23,7 +23,8 @@ export const Message = memo(
|
|
|
23
23
|
ref: React.ForwardedRef<HTMLDivElement>
|
|
24
24
|
) {
|
|
25
25
|
const randomId = uuid();
|
|
26
|
-
const
|
|
26
|
+
const resolvedUserId = user?.authId ?? user?.connectionId ?? randomId;
|
|
27
|
+
const userMessageBackgroundColor = colorFromString(resolvedUserId);
|
|
27
28
|
const styles = useMemo(
|
|
28
29
|
() =>
|
|
29
30
|
role === "user"
|
|
@@ -50,7 +51,7 @@ export const Message = memo(
|
|
|
50
51
|
</Avatar>
|
|
51
52
|
) : (
|
|
52
53
|
<Avatar
|
|
53
|
-
userId={
|
|
54
|
+
userId={resolvedUserId}
|
|
54
55
|
name={user?.name ?? "Anonymous"}
|
|
55
56
|
image={user?.image}
|
|
56
57
|
size={avatarSize}
|
|
@@ -17,21 +17,41 @@ const SCROLLBAR_THUMB_VERTICAL =
|
|
|
17
17
|
const SCROLLBAR_THUMB_HORIZONTAL =
|
|
18
18
|
"scroll-component n-h-full n-rounded n-bg-scrollbar";
|
|
19
19
|
|
|
20
|
+
type ScrollAreaViewportProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
21
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
22
|
+
};
|
|
23
|
+
|
|
20
24
|
export function ScrollArea2({
|
|
21
25
|
children,
|
|
22
26
|
className,
|
|
23
27
|
viewportClassName,
|
|
24
28
|
orientation = "vertical",
|
|
29
|
+
style,
|
|
30
|
+
viewportProps,
|
|
25
31
|
}: {
|
|
26
32
|
children: React.ReactNode;
|
|
27
33
|
className?: string;
|
|
28
34
|
viewportClassName?: string;
|
|
29
35
|
orientation?: "vertical" | "horizontal" | "both";
|
|
36
|
+
style?: React.CSSProperties;
|
|
37
|
+
viewportProps?: ScrollAreaViewportProps;
|
|
30
38
|
}) {
|
|
39
|
+
const {
|
|
40
|
+
className: viewportPropsClassName,
|
|
41
|
+
ref: viewportRef,
|
|
42
|
+
...viewportRest
|
|
43
|
+
} = viewportProps ?? {};
|
|
44
|
+
|
|
31
45
|
return (
|
|
32
|
-
<ScrollArea.Root className={cx("n-relative", className)}>
|
|
46
|
+
<ScrollArea.Root className={cx("n-relative", className)} style={style}>
|
|
33
47
|
<ScrollArea.Viewport
|
|
34
|
-
|
|
48
|
+
ref={viewportRef}
|
|
49
|
+
className={cx(
|
|
50
|
+
"n-h-full n-overscroll-contain",
|
|
51
|
+
viewportClassName,
|
|
52
|
+
viewportPropsClassName
|
|
53
|
+
)}
|
|
54
|
+
{...viewportRest}
|
|
35
55
|
>
|
|
36
56
|
{children}
|
|
37
57
|
</ScrollArea.Viewport>
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, {
|
|
4
|
+
CSSProperties,
|
|
5
|
+
ForwardedRef,
|
|
6
|
+
ReactNode,
|
|
7
|
+
UIEventHandler,
|
|
8
|
+
useCallback,
|
|
9
|
+
useImperativeHandle,
|
|
10
|
+
useLayoutEffect,
|
|
11
|
+
useMemo,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
} from "react";
|
|
15
|
+
import { flushSync } from "react-dom";
|
|
16
|
+
import { IVirtualizedList } from "./IVirtualizedList";
|
|
17
|
+
import { ScrollArea2 } from "./ScrollArea2";
|
|
18
|
+
|
|
19
|
+
export type ItemMeasurement = {
|
|
20
|
+
index: number;
|
|
21
|
+
size: number;
|
|
22
|
+
start: number;
|
|
23
|
+
end: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type VirtualizedRenderItem = (index: number) => ReactNode;
|
|
27
|
+
|
|
28
|
+
type VirtualizedPadding =
|
|
29
|
+
| number
|
|
30
|
+
| {
|
|
31
|
+
top?: number;
|
|
32
|
+
bottom?: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type PaddingValues = {
|
|
36
|
+
top: number;
|
|
37
|
+
bottom: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type MeasurementResult = {
|
|
41
|
+
measurements: ItemMeasurement[];
|
|
42
|
+
totalHeight: number;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type VisibleRange = {
|
|
46
|
+
start: number;
|
|
47
|
+
end: number;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function calculateScrollOffsetForIndex({
|
|
51
|
+
measurements,
|
|
52
|
+
height,
|
|
53
|
+
totalHeight,
|
|
54
|
+
targetIndex,
|
|
55
|
+
currentScrollTop,
|
|
56
|
+
}: {
|
|
57
|
+
measurements: ItemMeasurement[];
|
|
58
|
+
height: number;
|
|
59
|
+
totalHeight: number;
|
|
60
|
+
targetIndex: number;
|
|
61
|
+
currentScrollTop: number;
|
|
62
|
+
}): number | undefined {
|
|
63
|
+
if (!measurements.length) return;
|
|
64
|
+
|
|
65
|
+
const index = Math.min(Math.max(targetIndex, 0), measurements.length - 1);
|
|
66
|
+
const measurement = measurements[index];
|
|
67
|
+
|
|
68
|
+
if (!measurement) return;
|
|
69
|
+
|
|
70
|
+
const viewportTop = currentScrollTop;
|
|
71
|
+
const viewportBottom = viewportTop + height;
|
|
72
|
+
const itemTop = measurement.start;
|
|
73
|
+
const itemBottom = measurement.end;
|
|
74
|
+
|
|
75
|
+
let nextScrollTop = viewportTop;
|
|
76
|
+
|
|
77
|
+
if (itemTop < viewportTop) {
|
|
78
|
+
nextScrollTop = itemTop;
|
|
79
|
+
} else if (itemBottom > viewportBottom) {
|
|
80
|
+
const canAlignTop = height >= measurement.size;
|
|
81
|
+
|
|
82
|
+
nextScrollTop = canAlignTop ? itemTop : itemBottom - height;
|
|
83
|
+
} else {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const maxScrollTop = Math.max(0, totalHeight - height);
|
|
88
|
+
nextScrollTop = Math.max(0, Math.min(nextScrollTop, maxScrollTop));
|
|
89
|
+
|
|
90
|
+
if (nextScrollTop === viewportTop) return;
|
|
91
|
+
|
|
92
|
+
return nextScrollTop;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function resolvePaddingValues(
|
|
96
|
+
padding?: VirtualizedPadding
|
|
97
|
+
): PaddingValues {
|
|
98
|
+
if (typeof padding === "number") {
|
|
99
|
+
return { top: padding, bottom: padding };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
top: padding?.top ?? 0,
|
|
104
|
+
bottom: padding?.bottom ?? 0,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function clampGapValue(gap: number | undefined): number {
|
|
109
|
+
return Math.max(0, gap ?? 0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function buildMeasurements({
|
|
113
|
+
itemCount,
|
|
114
|
+
getItemHeight,
|
|
115
|
+
paddingTop,
|
|
116
|
+
paddingBottom,
|
|
117
|
+
gap,
|
|
118
|
+
}: {
|
|
119
|
+
itemCount: number;
|
|
120
|
+
getItemHeight: (index: number) => number;
|
|
121
|
+
paddingTop: number;
|
|
122
|
+
paddingBottom: number;
|
|
123
|
+
gap: number;
|
|
124
|
+
}): MeasurementResult {
|
|
125
|
+
const measurements: ItemMeasurement[] = [];
|
|
126
|
+
let offset = paddingTop;
|
|
127
|
+
|
|
128
|
+
for (let index = 0; index < itemCount; index += 1) {
|
|
129
|
+
const size = Math.max(0, getItemHeight(index));
|
|
130
|
+
const start = offset;
|
|
131
|
+
const end = start + size;
|
|
132
|
+
measurements.push({ index, size, start, end });
|
|
133
|
+
offset = end + gap;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const totalHeight = measurements.length
|
|
137
|
+
? measurements[measurements.length - 1].end + paddingBottom
|
|
138
|
+
: paddingTop + paddingBottom;
|
|
139
|
+
|
|
140
|
+
return { measurements, totalHeight };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function findFirstVisibleIndex(
|
|
144
|
+
measurements: ItemMeasurement[],
|
|
145
|
+
offset: number
|
|
146
|
+
): number {
|
|
147
|
+
if (!measurements.length) return 0;
|
|
148
|
+
|
|
149
|
+
let low = 0;
|
|
150
|
+
let high = measurements.length - 1;
|
|
151
|
+
let match = measurements.length - 1;
|
|
152
|
+
|
|
153
|
+
while (low <= high) {
|
|
154
|
+
const middle = Math.floor((low + high) / 2);
|
|
155
|
+
const measurement = measurements[middle];
|
|
156
|
+
|
|
157
|
+
if (measurement.end > offset) {
|
|
158
|
+
match = middle;
|
|
159
|
+
high = middle - 1;
|
|
160
|
+
} else {
|
|
161
|
+
low = middle + 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return match;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function calculateVisibleRange({
|
|
169
|
+
measurements,
|
|
170
|
+
scrollTop,
|
|
171
|
+
height,
|
|
172
|
+
overscan,
|
|
173
|
+
}: {
|
|
174
|
+
measurements: ItemMeasurement[];
|
|
175
|
+
scrollTop: number;
|
|
176
|
+
height: number;
|
|
177
|
+
overscan: number;
|
|
178
|
+
}): VisibleRange {
|
|
179
|
+
if (!measurements.length) {
|
|
180
|
+
return { start: 0, end: -1 };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const viewBottom = scrollTop + height;
|
|
184
|
+
const firstVisible = findFirstVisibleIndex(measurements, scrollTop);
|
|
185
|
+
let lastVisibleExclusive = firstVisible;
|
|
186
|
+
|
|
187
|
+
while (
|
|
188
|
+
lastVisibleExclusive < measurements.length &&
|
|
189
|
+
measurements[lastVisibleExclusive].start < viewBottom
|
|
190
|
+
) {
|
|
191
|
+
lastVisibleExclusive += 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const lastVisible = Math.max(
|
|
195
|
+
firstVisible,
|
|
196
|
+
Math.min(measurements.length - 1, lastVisibleExclusive - 1)
|
|
197
|
+
);
|
|
198
|
+
const overscanCount = Math.max(0, Math.floor(overscan));
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
start: Math.max(0, firstVisible - overscanCount),
|
|
202
|
+
end: Math.min(measurements.length - 1, lastVisible + overscanCount),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export type VirtualizedProps = {
|
|
207
|
+
height: number;
|
|
208
|
+
itemCount: number;
|
|
209
|
+
getItemHeight: (index: number) => number;
|
|
210
|
+
renderItem: VirtualizedRenderItem;
|
|
211
|
+
overscan?: number;
|
|
212
|
+
className?: string;
|
|
213
|
+
style?: CSSProperties;
|
|
214
|
+
innerClassName?: string;
|
|
215
|
+
innerStyle?: CSSProperties;
|
|
216
|
+
onScroll?: (scrollTop: number) => void;
|
|
217
|
+
itemKey?: (index: number) => string | number;
|
|
218
|
+
initialScrollOffset?: number;
|
|
219
|
+
/**
|
|
220
|
+
* When true (default) we flush scroll updates with `flushSync`
|
|
221
|
+
* to avoid visual gaps during fast scrolling.
|
|
222
|
+
*/
|
|
223
|
+
syncScrollUpdates?: boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Optional padding applied above and below the content. Numbers apply to both.
|
|
226
|
+
*/
|
|
227
|
+
padding?: VirtualizedPadding;
|
|
228
|
+
/**
|
|
229
|
+
* Optional constant gap inserted between items.
|
|
230
|
+
*/
|
|
231
|
+
gap?: number;
|
|
232
|
+
/**
|
|
233
|
+
* Render content inside ScrollArea2 instead of a plain div.
|
|
234
|
+
*/
|
|
235
|
+
useScrollArea?: boolean;
|
|
236
|
+
/**
|
|
237
|
+
* Optional class applied to the ScrollArea viewport when `useScrollArea` is true.
|
|
238
|
+
*/
|
|
239
|
+
scrollAreaViewportClassName?: string;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const DEFAULT_OVERSCAN = 2;
|
|
243
|
+
|
|
244
|
+
const VirtualizedInner = React.forwardRef(function Virtualized(
|
|
245
|
+
{
|
|
246
|
+
height,
|
|
247
|
+
itemCount,
|
|
248
|
+
getItemHeight,
|
|
249
|
+
renderItem,
|
|
250
|
+
overscan = DEFAULT_OVERSCAN,
|
|
251
|
+
className,
|
|
252
|
+
style,
|
|
253
|
+
innerClassName,
|
|
254
|
+
innerStyle,
|
|
255
|
+
onScroll,
|
|
256
|
+
itemKey,
|
|
257
|
+
initialScrollOffset = 0,
|
|
258
|
+
syncScrollUpdates = true,
|
|
259
|
+
padding,
|
|
260
|
+
gap = 0,
|
|
261
|
+
useScrollArea = false,
|
|
262
|
+
scrollAreaViewportClassName,
|
|
263
|
+
}: VirtualizedProps,
|
|
264
|
+
forwardedRef: ForwardedRef<IVirtualizedList>
|
|
265
|
+
) {
|
|
266
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
267
|
+
const setScrollElement = useCallback((element: HTMLDivElement | null) => {
|
|
268
|
+
containerRef.current = element;
|
|
269
|
+
}, []);
|
|
270
|
+
const [scrollTop, setScrollTop] = useState(initialScrollOffset);
|
|
271
|
+
const pendingInitialOffset = useRef<number | null>(initialScrollOffset);
|
|
272
|
+
|
|
273
|
+
useLayoutEffect(() => {
|
|
274
|
+
if (pendingInitialOffset.current == null) return;
|
|
275
|
+
if (containerRef.current) {
|
|
276
|
+
containerRef.current.scrollTop = pendingInitialOffset.current;
|
|
277
|
+
setScrollTop(pendingInitialOffset.current);
|
|
278
|
+
}
|
|
279
|
+
pendingInitialOffset.current = null;
|
|
280
|
+
}, []);
|
|
281
|
+
|
|
282
|
+
const paddingValues = useMemo(() => resolvePaddingValues(padding), [padding]);
|
|
283
|
+
|
|
284
|
+
const gapValue = useMemo(() => clampGapValue(gap), [gap]);
|
|
285
|
+
|
|
286
|
+
const { measurements, totalHeight } = useMemo(
|
|
287
|
+
() =>
|
|
288
|
+
buildMeasurements({
|
|
289
|
+
itemCount,
|
|
290
|
+
getItemHeight,
|
|
291
|
+
paddingTop: paddingValues.top,
|
|
292
|
+
paddingBottom: paddingValues.bottom,
|
|
293
|
+
gap: gapValue,
|
|
294
|
+
}),
|
|
295
|
+
[
|
|
296
|
+
gapValue,
|
|
297
|
+
getItemHeight,
|
|
298
|
+
itemCount,
|
|
299
|
+
paddingValues.bottom,
|
|
300
|
+
paddingValues.top,
|
|
301
|
+
]
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const visibleRange = useMemo(
|
|
305
|
+
() =>
|
|
306
|
+
calculateVisibleRange({
|
|
307
|
+
measurements,
|
|
308
|
+
scrollTop,
|
|
309
|
+
height,
|
|
310
|
+
overscan,
|
|
311
|
+
}),
|
|
312
|
+
[height, measurements, overscan, scrollTop]
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const handleScroll = useCallback<UIEventHandler<HTMLDivElement>>(
|
|
316
|
+
(event) => {
|
|
317
|
+
const nextOffset = event.currentTarget.scrollTop;
|
|
318
|
+
|
|
319
|
+
if (nextOffset === scrollTop) return;
|
|
320
|
+
|
|
321
|
+
if (syncScrollUpdates) {
|
|
322
|
+
flushSync(() => {
|
|
323
|
+
setScrollTop(nextOffset);
|
|
324
|
+
});
|
|
325
|
+
} else {
|
|
326
|
+
setScrollTop(nextOffset);
|
|
327
|
+
}
|
|
328
|
+
onScroll?.(nextOffset);
|
|
329
|
+
},
|
|
330
|
+
[onScroll, scrollTop, syncScrollUpdates]
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
useImperativeHandle(
|
|
334
|
+
forwardedRef,
|
|
335
|
+
() => ({
|
|
336
|
+
scrollToIndex(index: number) {
|
|
337
|
+
const container = containerRef.current;
|
|
338
|
+
if (!container) return;
|
|
339
|
+
|
|
340
|
+
const nextScrollTop = calculateScrollOffsetForIndex({
|
|
341
|
+
measurements,
|
|
342
|
+
height,
|
|
343
|
+
totalHeight,
|
|
344
|
+
targetIndex: index,
|
|
345
|
+
currentScrollTop: container.scrollTop,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (nextScrollTop == null) return;
|
|
349
|
+
|
|
350
|
+
container.scrollTop = nextScrollTop;
|
|
351
|
+
setScrollTop(nextScrollTop);
|
|
352
|
+
},
|
|
353
|
+
}),
|
|
354
|
+
[height, measurements, totalHeight]
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const containerStyle = useMemo<CSSProperties>(
|
|
358
|
+
() => ({
|
|
359
|
+
height,
|
|
360
|
+
overflowY: "auto",
|
|
361
|
+
overflowX: "hidden",
|
|
362
|
+
...style,
|
|
363
|
+
}),
|
|
364
|
+
[height, style]
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const scrollAreaStyle = useMemo<CSSProperties>(
|
|
368
|
+
() => ({
|
|
369
|
+
height,
|
|
370
|
+
...style,
|
|
371
|
+
}),
|
|
372
|
+
[height, style]
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const totalContent = (
|
|
376
|
+
<div
|
|
377
|
+
className={innerClassName}
|
|
378
|
+
style={{
|
|
379
|
+
position: "relative",
|
|
380
|
+
width: "100%",
|
|
381
|
+
height: totalHeight,
|
|
382
|
+
...innerStyle,
|
|
383
|
+
}}
|
|
384
|
+
>
|
|
385
|
+
{visibleRange.end >= visibleRange.start &&
|
|
386
|
+
Array.from(
|
|
387
|
+
{ length: visibleRange.end - visibleRange.start + 1 },
|
|
388
|
+
(_, offset) => {
|
|
389
|
+
const index = visibleRange.start + offset;
|
|
390
|
+
const measurement = measurements[index];
|
|
391
|
+
const key = itemKey ? itemKey(index) : index;
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<div
|
|
395
|
+
key={key}
|
|
396
|
+
style={{
|
|
397
|
+
position: "absolute",
|
|
398
|
+
top: measurement.start,
|
|
399
|
+
height: measurement.size,
|
|
400
|
+
width: "100%",
|
|
401
|
+
}}
|
|
402
|
+
>
|
|
403
|
+
{renderItem(index)}
|
|
404
|
+
</div>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
)}
|
|
408
|
+
</div>
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
if (useScrollArea) {
|
|
412
|
+
return (
|
|
413
|
+
<ScrollArea2
|
|
414
|
+
className={className}
|
|
415
|
+
style={scrollAreaStyle}
|
|
416
|
+
viewportClassName={scrollAreaViewportClassName}
|
|
417
|
+
viewportProps={{
|
|
418
|
+
ref: setScrollElement,
|
|
419
|
+
onScroll: handleScroll,
|
|
420
|
+
}}
|
|
421
|
+
>
|
|
422
|
+
{totalContent}
|
|
423
|
+
</ScrollArea2>
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return (
|
|
428
|
+
<div
|
|
429
|
+
ref={setScrollElement}
|
|
430
|
+
className={className}
|
|
431
|
+
style={containerStyle}
|
|
432
|
+
onScroll={handleScroll}
|
|
433
|
+
>
|
|
434
|
+
{totalContent}
|
|
435
|
+
</div>
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
export const Virtualized = React.memo(VirtualizedInner);
|