@react-fabric/data 1.0.0-beta.5 → 1.0.0-beta.51
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/{src/histogram/Histogram.module.css → css/_histogram.css} +16 -13
- package/css/styles.css +24 -0
- package/dist/cjs/checklist/CheckList.js +25 -16
- package/dist/cjs/histogram/Histogram.js +20 -10
- package/dist/cjs/hooks/usePagination.js +7 -4
- package/dist/cjs/json/JsonViewer.js +40 -27
- package/dist/cjs/table/AddColumn.js +1 -1
- package/dist/cjs/table/BodyCell.js +38 -5
- package/dist/cjs/table/BodyRow.js +107 -0
- package/dist/cjs/table/CheckboxCell.js +1 -1
- package/dist/cjs/table/ColumnFilters.js +97 -0
- package/dist/cjs/table/HeaderCell.js +70 -37
- package/dist/cjs/table/Table.js +113 -71
- package/dist/cjs/table/useResizer.js +4 -4
- package/dist/cjs/table/useVirtualTable.js +122 -18
- package/dist/cjs/tree/TreeNode.js +45 -25
- package/dist/cjs/tree/TreePanel.js +28 -10
- package/dist/cjs/tree/useTree.js +97 -15
- package/dist/cjs/tree/utils.js +20 -5
- package/dist/cjs/virtual/Gallery.js +51 -35
- package/dist/cjs/virtual/List.js +44 -31
- package/dist/esm/checklist/CheckList.js +27 -18
- package/dist/esm/histogram/Histogram.js +21 -11
- package/dist/esm/hooks/usePagination.js +7 -4
- package/dist/esm/json/JsonViewer.js +41 -28
- package/dist/esm/table/AddColumn.js +1 -1
- package/dist/esm/table/BodyCell.js +38 -5
- package/dist/esm/table/BodyRow.js +107 -0
- package/dist/esm/table/CheckboxCell.js +1 -1
- package/dist/esm/table/ColumnFilters.js +97 -0
- package/dist/esm/table/HeaderCell.js +71 -38
- package/dist/esm/table/Table.js +115 -73
- package/dist/esm/table/useResizer.js +4 -4
- package/dist/esm/table/useVirtualTable.js +124 -20
- package/dist/esm/tree/TreeNode.js +47 -27
- package/dist/esm/tree/TreePanel.js +28 -10
- package/dist/esm/tree/useTree.js +99 -17
- package/dist/esm/tree/utils.js +20 -5
- package/dist/esm/virtual/Gallery.js +51 -35
- package/dist/esm/virtual/List.js +44 -31
- package/dist/types/checklist/CheckList.d.ts +2 -2
- package/dist/types/histogram/Histogram.d.ts +3 -4
- package/dist/types/index.d.ts +1 -1
- package/dist/types/json/JsonViewer.d.ts +0 -1
- package/dist/types/table/AddColumn.d.ts +2 -2
- package/dist/types/table/BodyCell.d.ts +4 -3
- package/dist/types/table/BodyRow.d.ts +20 -0
- package/dist/types/table/ColumnFilters.d.ts +9 -0
- package/dist/types/table/EmptyCell.d.ts +2 -2
- package/dist/types/table/HeaderCell.d.ts +2 -2
- package/dist/types/table/Table.d.ts +1 -1
- package/dist/types/table/types.d.ts +20 -7
- package/dist/types/table/useTableColumns.d.ts +6 -6
- package/dist/types/table/useVirtualTable.d.ts +23 -7
- package/dist/types/tree/TreeNode.d.ts +1 -1
- package/dist/types/tree/TreePanel.d.ts +1 -1
- package/dist/types/tree/types.d.ts +25 -5
- package/dist/types/tree/useTree.d.ts +2 -9
- package/dist/types/tree/utils.d.ts +4 -1
- package/dist/types/virtual/Gallery.d.ts +12 -4
- package/dist/types/virtual/List.d.ts +8 -4
- package/package.json +13 -10
- package/src/checklist/CheckList.tsx +81 -77
- package/src/histogram/Histogram.tsx +17 -11
- package/src/hooks/usePagination.tsx +13 -7
- package/src/index.ts +1 -1
- package/src/json/JsonViewer.tsx +45 -35
- package/src/table/AddColumn.tsx +3 -3
- package/src/table/BodyCell.tsx +34 -5
- package/src/table/BodyRow.tsx +143 -0
- package/src/table/CheckboxCell.tsx +1 -1
- package/src/table/ColumnFilters.tsx +105 -0
- package/src/table/EmptyCell.tsx +2 -2
- package/src/table/HeaderCell.tsx +72 -14
- package/src/table/Table.tsx +164 -77
- package/src/table/types.ts +24 -6
- package/src/table/useResizer.ts +5 -3
- package/src/table/useTableColumns.ts +7 -7
- package/src/table/useVirtualTable.ts +146 -24
- package/src/tree/TreeNode.tsx +98 -56
- package/src/tree/TreePanel.tsx +23 -3
- package/src/tree/types.ts +32 -4
- package/src/tree/useTree.ts +109 -26
- package/src/tree/utils.ts +23 -8
- package/src/virtual/Gallery.tsx +63 -42
- package/src/virtual/List.tsx +44 -32
- package/dist/cjs/histogram/Histogram.module.css +0 -1
- package/dist/esm/histogram/Histogram.module.css +0 -1
package/src/tree/useTree.ts
CHANGED
|
@@ -25,7 +25,11 @@ import { useDebounce } from "@react-fabric/core";
|
|
|
25
25
|
import { EMPTY_ARRAY } from "@react-fabric/utilities";
|
|
26
26
|
import memoizeOne from "memoize-one";
|
|
27
27
|
import { useCallback, useEffect, useReducer, useRef } from "react";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
type InternalNode,
|
|
30
|
+
type TreeNodeType,
|
|
31
|
+
type TreePanelProps,
|
|
32
|
+
} from "./types";
|
|
29
33
|
import {
|
|
30
34
|
filterTree,
|
|
31
35
|
flattenTree,
|
|
@@ -71,7 +75,11 @@ type TreeActions =
|
|
|
71
75
|
/**
|
|
72
76
|
* collapse all nodes
|
|
73
77
|
*/
|
|
74
|
-
| { type: "collapseAll" }
|
|
78
|
+
| { type: "collapseAll" }
|
|
79
|
+
/**
|
|
80
|
+
* loaded dynamic nodes
|
|
81
|
+
*/
|
|
82
|
+
| { type: "loaded" };
|
|
75
83
|
|
|
76
84
|
interface TreeState {
|
|
77
85
|
items: InternalNode[];
|
|
@@ -87,47 +95,104 @@ export const useTree = <T extends KeyValue>({
|
|
|
87
95
|
items = EMPTY_ARRAY,
|
|
88
96
|
checked = EMPTY_ARRAY,
|
|
89
97
|
selected,
|
|
98
|
+
sorter,
|
|
90
99
|
matcher,
|
|
100
|
+
onLoad,
|
|
101
|
+
onQuery,
|
|
91
102
|
onSelect,
|
|
92
103
|
onChecked,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
checked?: string[];
|
|
97
|
-
onSelect?: (id: string) => void;
|
|
98
|
-
onChecked?: (leafs: string[], nodes: string[]) => void;
|
|
99
|
-
matcher?: (data: T, query: string) => boolean;
|
|
100
|
-
}) => {
|
|
104
|
+
defaultExpanded,
|
|
105
|
+
onExpandToggle,
|
|
106
|
+
}: Partial<TreePanelProps<T>>) => {
|
|
101
107
|
const componentEvents = useRef({ onSelect, onChecked });
|
|
102
|
-
|
|
108
|
+
const intialExpand = useRef(defaultExpanded ?? EMPTY_ARRAY);
|
|
103
109
|
const itemList = createItemList(items);
|
|
104
110
|
|
|
111
|
+
const fireExpandToggle = useCallback(
|
|
112
|
+
(itemMap: InternalNode[]) => {
|
|
113
|
+
const expandedList = itemMap
|
|
114
|
+
.filter((node) => node.open)
|
|
115
|
+
.map((node) => node.id);
|
|
116
|
+
setTimeout(() => onExpandToggle?.(expandedList), 100);
|
|
117
|
+
},
|
|
118
|
+
[onExpandToggle],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const loadChildren = useCallback(
|
|
122
|
+
(node: InternalNode) => {
|
|
123
|
+
if (node.open && !node.leaf && !node.children?.length) {
|
|
124
|
+
node.loading = true;
|
|
125
|
+
void Promise.resolve(onLoad?.(node.id))
|
|
126
|
+
.then((resp = EMPTY_ARRAY) => {
|
|
127
|
+
node.children = refactorTree(
|
|
128
|
+
resp,
|
|
129
|
+
{ sorter, defaultExpanded },
|
|
130
|
+
{ level: node.level + 1, parent: node.id },
|
|
131
|
+
);
|
|
132
|
+
})
|
|
133
|
+
.finally(() => {
|
|
134
|
+
node.loading = false;
|
|
135
|
+
dispatch({ type: "loaded" });
|
|
136
|
+
intialExpand.current?.length > 0 &&
|
|
137
|
+
dispatch({
|
|
138
|
+
type: "expand",
|
|
139
|
+
id: intialExpand.current.shift() as unknown as string,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
} else {
|
|
143
|
+
dispatch({ type: "loaded" });
|
|
144
|
+
intialExpand.current?.length > 0 &&
|
|
145
|
+
dispatch({
|
|
146
|
+
type: "expand",
|
|
147
|
+
id: intialExpand.current.shift() as unknown as string,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
[onLoad],
|
|
152
|
+
);
|
|
153
|
+
|
|
105
154
|
const [state, dispatch] = useReducer(
|
|
106
155
|
(state: TreeState, action: TreeActions) => {
|
|
107
156
|
if (action.type === "load") {
|
|
108
|
-
state.items = refactorTree(action.items);
|
|
157
|
+
state.items = refactorTree(action.items, { sorter, defaultExpanded });
|
|
109
158
|
state.itemMap = makeTreeMap(state.items);
|
|
110
159
|
state.tree = flattenTree(state.items);
|
|
111
160
|
return { ...state };
|
|
112
161
|
}
|
|
113
162
|
if (action.type === "toggleExpand") {
|
|
114
163
|
const node = state.itemMap.get(action.id);
|
|
115
|
-
|
|
164
|
+
if (node && !node.leaf) {
|
|
165
|
+
node && (node.open = !node.open);
|
|
166
|
+
loadChildren(node);
|
|
167
|
+
return { ...state };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (action.type === "loaded") {
|
|
171
|
+
state.itemMap = makeTreeMap(state.items);
|
|
172
|
+
fireExpandToggle(Array.from(state.itemMap.values()));
|
|
116
173
|
state.tree = flattenTree(state.items);
|
|
174
|
+
state.selected && updateSelection(state.itemMap, state.selected, true);
|
|
117
175
|
return { ...state };
|
|
118
176
|
}
|
|
119
177
|
if (action.type === "expand") {
|
|
120
|
-
// TODO: check if children available, if not call onLoad to fetch node children
|
|
121
178
|
const node = state.itemMap.get(action.id);
|
|
122
|
-
node &&
|
|
123
|
-
|
|
124
|
-
|
|
179
|
+
if (node && !node.leaf) {
|
|
180
|
+
node.open = true;
|
|
181
|
+
let parentId = node.parent;
|
|
182
|
+
while (parentId) {
|
|
183
|
+
const node = state.itemMap.get(parentId);
|
|
184
|
+
node && (node.open = true);
|
|
185
|
+
parentId = node?.parent;
|
|
186
|
+
}
|
|
187
|
+
loadChildren(node);
|
|
188
|
+
return { ...state };
|
|
189
|
+
}
|
|
125
190
|
}
|
|
126
191
|
if (action.type === "expandAll") {
|
|
127
|
-
// TODO: prevent opening dynamic nodes that dont have children
|
|
128
192
|
state.itemMap.forEach((node) => {
|
|
129
|
-
!node.leaf && (node.open = true);
|
|
193
|
+
!node.leaf && node.children?.length && (node.open = true);
|
|
130
194
|
});
|
|
195
|
+
fireExpandToggle(Array.from(state.itemMap.values()));
|
|
131
196
|
state.tree = flattenTree(state.items);
|
|
132
197
|
return { ...state };
|
|
133
198
|
}
|
|
@@ -135,13 +200,18 @@ export const useTree = <T extends KeyValue>({
|
|
|
135
200
|
state.itemMap.forEach((node) => {
|
|
136
201
|
!node.leaf && (node.open = false);
|
|
137
202
|
});
|
|
203
|
+
fireExpandToggle([]);
|
|
138
204
|
state.tree = flattenTree(state.items);
|
|
139
205
|
return { ...state };
|
|
140
206
|
}
|
|
141
207
|
if (action.type === "filter") {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
208
|
+
if (onQuery) {
|
|
209
|
+
onQuery(action.query ?? "");
|
|
210
|
+
} else {
|
|
211
|
+
filterTree(state.itemMap, action.query, matcher);
|
|
212
|
+
state.tree = flattenTree(state.items);
|
|
213
|
+
return { ...state };
|
|
214
|
+
}
|
|
145
215
|
}
|
|
146
216
|
if (action.type === "select") {
|
|
147
217
|
if (state.selected) {
|
|
@@ -149,7 +219,10 @@ export const useTree = <T extends KeyValue>({
|
|
|
149
219
|
}
|
|
150
220
|
updateSelection(state.itemMap, action.id, true);
|
|
151
221
|
state.tree = flattenTree(state.items);
|
|
152
|
-
componentEvents.current.onSelect?.(
|
|
222
|
+
componentEvents.current.onSelect?.(
|
|
223
|
+
action.id,
|
|
224
|
+
state.itemMap.get(action.id)?.data,
|
|
225
|
+
);
|
|
153
226
|
return { ...state, selected: action.id };
|
|
154
227
|
}
|
|
155
228
|
if (action.type === "toggleCheck") {
|
|
@@ -181,6 +254,12 @@ export const useTree = <T extends KeyValue>({
|
|
|
181
254
|
|
|
182
255
|
useEffect(() => {
|
|
183
256
|
dispatch({ type: "load", items: itemList });
|
|
257
|
+
itemList.length &&
|
|
258
|
+
intialExpand.current?.length > 0 &&
|
|
259
|
+
dispatch({
|
|
260
|
+
type: "expand",
|
|
261
|
+
id: intialExpand.current.shift() as unknown as string,
|
|
262
|
+
});
|
|
184
263
|
}, [itemList]);
|
|
185
264
|
|
|
186
265
|
useEffect(() => {
|
|
@@ -215,9 +294,13 @@ export const useTree = <T extends KeyValue>({
|
|
|
215
294
|
dispatch({ type: "collapseAll" });
|
|
216
295
|
}, []);
|
|
217
296
|
|
|
218
|
-
const onFilter = useDebounce(
|
|
219
|
-
|
|
220
|
-
|
|
297
|
+
const onFilter = useDebounce(
|
|
298
|
+
(query: string) => {
|
|
299
|
+
dispatch({ type: "filter", query });
|
|
300
|
+
},
|
|
301
|
+
[],
|
|
302
|
+
300,
|
|
303
|
+
);
|
|
221
304
|
|
|
222
305
|
return {
|
|
223
306
|
tree: state.tree,
|
package/src/tree/utils.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import { compareValues, isString, matchString } from "@react-fabric/utilities";
|
|
25
25
|
import { type InternalNode, type TreeNodeType } from "./types";
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const defaultSorter = (a: TreeNodeType, b: TreeNodeType) => {
|
|
28
28
|
if (!!a.leaf !== !!b.leaf) return a.leaf ? 1 : -1;
|
|
29
29
|
return compareValues()(a.label, b.label);
|
|
30
30
|
};
|
|
@@ -48,13 +48,17 @@ const getIntermediate = (list: AnyObject[] = []) => {
|
|
|
48
48
|
// convert node item to internal node used by the tree
|
|
49
49
|
export const refactorTree = (
|
|
50
50
|
nodes: TreeNodeType[],
|
|
51
|
+
{
|
|
52
|
+
sorter = defaultSorter,
|
|
53
|
+
defaultExpanded = [],
|
|
54
|
+
}: { sorter?: any; defaultExpanded?: string[] },
|
|
51
55
|
options: { level: number; parent?: string } = { level: 0, parent: undefined },
|
|
52
56
|
) => {
|
|
53
57
|
const list: InternalNode[] = [];
|
|
54
|
-
|
|
55
|
-
nodes.
|
|
58
|
+
if (sorter !== false) nodes = nodes.sort(sorter);
|
|
59
|
+
nodes.forEach((node) => {
|
|
56
60
|
list.push({
|
|
57
|
-
open:
|
|
61
|
+
open: defaultExpanded.includes(node.id) && !!node.children?.length,
|
|
58
62
|
level: options.level,
|
|
59
63
|
parent: options.parent,
|
|
60
64
|
checked: 0,
|
|
@@ -66,10 +70,14 @@ export const refactorTree = (
|
|
|
66
70
|
leaf: !!node.leaf,
|
|
67
71
|
children:
|
|
68
72
|
node.children &&
|
|
69
|
-
refactorTree(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
refactorTree(
|
|
74
|
+
node.children,
|
|
75
|
+
{ sorter, defaultExpanded },
|
|
76
|
+
{
|
|
77
|
+
level: options.level + 1,
|
|
78
|
+
parent: node.id,
|
|
79
|
+
},
|
|
80
|
+
),
|
|
73
81
|
});
|
|
74
82
|
});
|
|
75
83
|
|
|
@@ -106,6 +114,13 @@ export const flattenTree = (
|
|
|
106
114
|
[...lines, idx === filtered.length - 1 ? 0 : 1],
|
|
107
115
|
),
|
|
108
116
|
);
|
|
117
|
+
node.open &&
|
|
118
|
+
node.children?.length === 0 &&
|
|
119
|
+
list.push({
|
|
120
|
+
empty: true,
|
|
121
|
+
leaf: true,
|
|
122
|
+
lines: [...lines, idx === filtered.length - 1 ? 0 : 1, 2],
|
|
123
|
+
} as any);
|
|
109
124
|
});
|
|
110
125
|
return list;
|
|
111
126
|
};
|
package/src/virtual/Gallery.tsx
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { Skeleton, useDebounce, useEffectDebugger } from "@react-fabric/core";
|
|
25
|
+
import { type TestProps } from "@react-fabric/core/dist/types/types";
|
|
25
26
|
import { debounce } from "@react-fabric/utilities";
|
|
26
27
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
27
28
|
import classNames from "classnames";
|
|
@@ -47,9 +48,9 @@ export interface VirtualGalleryRef {
|
|
|
47
48
|
unhilight: () => void;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
export interface VirtualGalleryProps<T> {
|
|
51
|
+
export interface VirtualGalleryProps<T> extends TestProps {
|
|
51
52
|
children: (props: {
|
|
52
|
-
|
|
53
|
+
item: T;
|
|
53
54
|
index: number;
|
|
54
55
|
isLast: boolean;
|
|
55
56
|
}) => ReactElement;
|
|
@@ -66,6 +67,12 @@ export interface VirtualGalleryProps<T> {
|
|
|
66
67
|
* default item width
|
|
67
68
|
*/
|
|
68
69
|
defaultWidth?: number;
|
|
70
|
+
/**
|
|
71
|
+
* full width list
|
|
72
|
+
*/
|
|
73
|
+
fullWidth?: boolean;
|
|
74
|
+
maxWidth?: string | number;
|
|
75
|
+
minWidth?: string | number;
|
|
69
76
|
/**
|
|
70
77
|
* padding
|
|
71
78
|
*/
|
|
@@ -74,6 +81,7 @@ export interface VirtualGalleryProps<T> {
|
|
|
74
81
|
* data list
|
|
75
82
|
*/
|
|
76
83
|
items: T[];
|
|
84
|
+
total?: number;
|
|
77
85
|
/**
|
|
78
86
|
* loading state
|
|
79
87
|
*/
|
|
@@ -86,7 +94,7 @@ export interface VirtualGalleryProps<T> {
|
|
|
86
94
|
/**
|
|
87
95
|
* load more callback
|
|
88
96
|
*/
|
|
89
|
-
onLoadMore?: () => void;
|
|
97
|
+
onLoadMore?: (lastIndex?: number) => void;
|
|
90
98
|
/**
|
|
91
99
|
* scroll handler
|
|
92
100
|
*/
|
|
@@ -95,10 +103,14 @@ export interface VirtualGalleryProps<T> {
|
|
|
95
103
|
|
|
96
104
|
const createItemList = memoize((items) => items);
|
|
97
105
|
|
|
98
|
-
const _VirtualGallery = <T extends
|
|
106
|
+
const _VirtualGallery = <T extends AnyObject>({
|
|
99
107
|
items,
|
|
100
108
|
children,
|
|
101
109
|
columns,
|
|
110
|
+
fullWidth,
|
|
111
|
+
maxWidth,
|
|
112
|
+
minWidth,
|
|
113
|
+
total = 0,
|
|
102
114
|
padding = "md",
|
|
103
115
|
defaultHeight: height = 48,
|
|
104
116
|
defaultWidth: width = 48,
|
|
@@ -108,20 +120,21 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
108
120
|
onLoadMore,
|
|
109
121
|
onScroll,
|
|
110
122
|
galleryRef,
|
|
123
|
+
...aria
|
|
111
124
|
}: VirtualGalleryProps<T>) => {
|
|
112
125
|
const count = useDeferredValue(items.length);
|
|
113
|
-
const extraSize = useMemo(() => (onLoadMore ?
|
|
126
|
+
const extraSize = useMemo(() => (onLoadMore ? 8 : 0), [onLoadMore]);
|
|
114
127
|
const itemList: Array<{
|
|
115
128
|
index: number;
|
|
116
|
-
|
|
129
|
+
item: T;
|
|
117
130
|
isLast: boolean;
|
|
118
131
|
isSticky: boolean;
|
|
119
132
|
}> = createItemList(
|
|
120
|
-
items.map((item: AnyObject,
|
|
133
|
+
items.map((item: AnyObject, index) => {
|
|
121
134
|
return {
|
|
122
|
-
index
|
|
123
|
-
|
|
124
|
-
isLast:
|
|
135
|
+
index,
|
|
136
|
+
item,
|
|
137
|
+
isLast: index + 1 === items.length,
|
|
125
138
|
};
|
|
126
139
|
}),
|
|
127
140
|
);
|
|
@@ -137,9 +150,19 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
137
150
|
if (scrollerRef.current) {
|
|
138
151
|
const el = scrollerRef.current;
|
|
139
152
|
const handler = () => {
|
|
153
|
+
const pad = parseInt(
|
|
154
|
+
getComputedStyle(el.firstElementChild?.firstElementChild as any)
|
|
155
|
+
.padding ?? 8,
|
|
156
|
+
);
|
|
140
157
|
setColumnCount(
|
|
141
158
|
columns ??
|
|
142
|
-
Math.max(
|
|
159
|
+
Math.max(
|
|
160
|
+
1,
|
|
161
|
+
Math.floor(
|
|
162
|
+
((el?.offsetWidth ?? 800) - 32 - (isNaN(pad) ? 8 : pad) * 2) /
|
|
163
|
+
width,
|
|
164
|
+
),
|
|
165
|
+
),
|
|
143
166
|
);
|
|
144
167
|
};
|
|
145
168
|
const ob = new ResizeObserver(handler);
|
|
@@ -159,6 +182,7 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
159
182
|
count: Math.ceil(count / columnCount),
|
|
160
183
|
getScrollElement: () => scrollerRef.current,
|
|
161
184
|
estimateSize: () => height,
|
|
185
|
+
overscan: 2,
|
|
162
186
|
});
|
|
163
187
|
|
|
164
188
|
const virtualItems = virtualizer.getVirtualItems();
|
|
@@ -203,31 +227,24 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
203
227
|
}, [count, calculateActiveScroll]);
|
|
204
228
|
|
|
205
229
|
useEffect(() => {
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
db();
|
|
216
|
-
return () => db.cancel();
|
|
217
|
-
}, [onLoadMore, loading, itemList, virtualItems, count, columnCount]);
|
|
218
|
-
|
|
219
|
-
useEffect(() => {
|
|
220
|
-
const firstItem = virtualItems[0]?.index;
|
|
221
|
-
if (
|
|
222
|
-
onLoadMore &&
|
|
223
|
-
firstItem !== undefined &&
|
|
224
|
-
items[firstItem * columnCount] === null
|
|
225
|
-
) {
|
|
230
|
+
if (onLoadMore) {
|
|
231
|
+
const firstItem = virtualItems[0]?.index;
|
|
232
|
+
const lastItem = virtualItems?.slice(-1).pop()?.index;
|
|
233
|
+
const firstIndex = firstItem * columnCount;
|
|
234
|
+
const lastIndex = (lastItem ?? 0) * columnCount + columnCount;
|
|
235
|
+
const canLoadPrevPage =
|
|
236
|
+
firstItem !== undefined && items[firstIndex] === null;
|
|
237
|
+
const canLoadNextPage =
|
|
238
|
+
lastItem !== undefined && lastIndex >= count - 1 && count < total;
|
|
226
239
|
const db = debounce(onLoadMore);
|
|
227
|
-
|
|
240
|
+
if (canLoadPrevPage) {
|
|
241
|
+
db(firstIndex);
|
|
242
|
+
} else if (!loading && canLoadNextPage) {
|
|
243
|
+
db(lastIndex + 1);
|
|
244
|
+
}
|
|
228
245
|
return () => db.cancel();
|
|
229
246
|
}
|
|
230
|
-
}, [onLoadMore,
|
|
247
|
+
}, [onLoadMore, virtualItems]);
|
|
231
248
|
|
|
232
249
|
useLayoutEffect(() => {
|
|
233
250
|
setTimeout(() => {
|
|
@@ -243,7 +260,7 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
243
260
|
() => ({
|
|
244
261
|
scrollToItem(index) {
|
|
245
262
|
virtualizer.scrollToIndex(Math.floor(index / columnCount), {
|
|
246
|
-
align: "
|
|
263
|
+
align: "auto",
|
|
247
264
|
});
|
|
248
265
|
},
|
|
249
266
|
hilight(index) {
|
|
@@ -251,7 +268,7 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
251
268
|
?.querySelector(".hilight")
|
|
252
269
|
?.classList.remove("hilight", "ring-1");
|
|
253
270
|
virtualizer.scrollToIndex(Math.floor(index / columnCount), {
|
|
254
|
-
align: "
|
|
271
|
+
align: "auto",
|
|
255
272
|
});
|
|
256
273
|
setTimeout(() => {
|
|
257
274
|
scrollerRef.current
|
|
@@ -272,9 +289,8 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
272
289
|
<div
|
|
273
290
|
ref={scrollerRef}
|
|
274
291
|
onScroll={handleScroll}
|
|
275
|
-
className={classNames(
|
|
276
|
-
|
|
277
|
-
)}
|
|
292
|
+
className={classNames("area-content bg-base overflow-auto")}
|
|
293
|
+
{...aria}
|
|
278
294
|
>
|
|
279
295
|
<div
|
|
280
296
|
className={classNames(
|
|
@@ -284,23 +300,28 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
284
300
|
<div
|
|
285
301
|
className={classNames(
|
|
286
302
|
"box-content",
|
|
303
|
+
fullWidth && "flex-1",
|
|
287
304
|
padding === "sm" && "p-1",
|
|
288
305
|
padding === "md" && "p-2",
|
|
289
306
|
padding === "lg" && "p-4",
|
|
290
307
|
)}
|
|
308
|
+
style={{
|
|
309
|
+
minWidth,
|
|
310
|
+
maxWidth,
|
|
311
|
+
}}
|
|
291
312
|
>
|
|
292
313
|
<div
|
|
293
314
|
style={{
|
|
294
|
-
height: virtualizer.getTotalSize(),
|
|
315
|
+
height: virtualizer.getTotalSize() + extraSize,
|
|
295
316
|
}}
|
|
296
317
|
>
|
|
297
318
|
<div style={{ height: virtualItems[0]?.start }} />
|
|
298
|
-
{virtualItems.map(({ key, index
|
|
319
|
+
{virtualItems.map(({ key, index }) => (
|
|
299
320
|
<div
|
|
300
|
-
className="flex"
|
|
321
|
+
className="flex justify-center"
|
|
301
322
|
key={key}
|
|
302
323
|
data-index={index}
|
|
303
|
-
ref={measureElement}
|
|
324
|
+
ref={virtualizer.measureElement}
|
|
304
325
|
>
|
|
305
326
|
{columnMap.map((col) => (
|
|
306
327
|
<div
|
package/src/virtual/List.tsx
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { Skeleton, useDebounce } from "@react-fabric/core";
|
|
32
|
+
import { type TestProps } from "@react-fabric/core/dist/types/types";
|
|
32
33
|
import { debounce } from "@react-fabric/utilities";
|
|
33
34
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
34
35
|
import classNames from "classnames";
|
|
@@ -54,9 +55,9 @@ export interface VirtualListRef {
|
|
|
54
55
|
unhilight: () => void;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
export interface VirtualListProps<T> {
|
|
58
|
+
export interface VirtualListProps<T> extends TestProps {
|
|
58
59
|
children: (props: {
|
|
59
|
-
|
|
60
|
+
item: T;
|
|
60
61
|
index: number;
|
|
61
62
|
isLast: boolean;
|
|
62
63
|
}) => ReactElement;
|
|
@@ -73,6 +74,8 @@ export interface VirtualListProps<T> {
|
|
|
73
74
|
* full width list
|
|
74
75
|
*/
|
|
75
76
|
fullWidth?: boolean;
|
|
77
|
+
maxWidth?: string | number;
|
|
78
|
+
minWidth?: string | number;
|
|
76
79
|
/**
|
|
77
80
|
* padding
|
|
78
81
|
*/
|
|
@@ -85,6 +88,7 @@ export interface VirtualListProps<T> {
|
|
|
85
88
|
* data list
|
|
86
89
|
*/
|
|
87
90
|
items: T[];
|
|
91
|
+
total?: number;
|
|
88
92
|
initialScroll?: number;
|
|
89
93
|
/**
|
|
90
94
|
* loading state
|
|
@@ -97,7 +101,7 @@ export interface VirtualListProps<T> {
|
|
|
97
101
|
/**
|
|
98
102
|
* load more callback
|
|
99
103
|
*/
|
|
100
|
-
onLoadMore?: (
|
|
104
|
+
onLoadMore?: (lastIndex?: number) => void;
|
|
101
105
|
/**
|
|
102
106
|
* scroll handler
|
|
103
107
|
*/
|
|
@@ -106,10 +110,13 @@ export interface VirtualListProps<T> {
|
|
|
106
110
|
|
|
107
111
|
const createItemList = memoizeOne((items) => items);
|
|
108
112
|
|
|
109
|
-
const _VirtualList = <T extends
|
|
113
|
+
const _VirtualList = <T extends AnyObject>({
|
|
110
114
|
items,
|
|
115
|
+
total = 0,
|
|
111
116
|
children,
|
|
112
117
|
fullWidth,
|
|
118
|
+
maxWidth,
|
|
119
|
+
minWidth,
|
|
113
120
|
padding = "md",
|
|
114
121
|
orientation = "vertical",
|
|
115
122
|
defaultHeight: height = 48,
|
|
@@ -120,20 +127,21 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
120
127
|
onLoadMore,
|
|
121
128
|
onScroll,
|
|
122
129
|
listRef,
|
|
130
|
+
...aria
|
|
123
131
|
}: VirtualListProps<T>) => {
|
|
124
132
|
const count = useDeferredValue(items.length);
|
|
125
|
-
const extraSize = useMemo(() => (onLoadMore ?
|
|
133
|
+
const extraSize = useMemo(() => (onLoadMore ? 8 : 0), [onLoadMore]);
|
|
126
134
|
const itemList: Array<{
|
|
127
135
|
index: number;
|
|
128
136
|
data: T;
|
|
129
137
|
isLast: boolean;
|
|
130
138
|
isSticky: boolean;
|
|
131
139
|
}> = createItemList(
|
|
132
|
-
items.map((item: AnyObject,
|
|
140
|
+
items.map((item: AnyObject, index) => {
|
|
133
141
|
return {
|
|
134
|
-
index
|
|
135
|
-
|
|
136
|
-
isLast:
|
|
142
|
+
index,
|
|
143
|
+
item,
|
|
144
|
+
isLast: index + 1 === items.length,
|
|
137
145
|
};
|
|
138
146
|
}),
|
|
139
147
|
);
|
|
@@ -141,6 +149,7 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
141
149
|
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
142
150
|
const virtualizer = useVirtualizer({
|
|
143
151
|
count,
|
|
152
|
+
overscan: 2,
|
|
144
153
|
horizontal: orientation === "horizontal",
|
|
145
154
|
getScrollElement: () => scrollerRef.current,
|
|
146
155
|
estimateSize: () => (orientation === "horizontal" ? width : height),
|
|
@@ -199,22 +208,22 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
199
208
|
}, [itemList, calculateActiveScroll]);
|
|
200
209
|
|
|
201
210
|
useEffect(() => {
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
useEffect(() => {
|
|
211
|
-
const firstItem = virtualItems[0]?.index;
|
|
212
|
-
if (onLoadMore && firstItem !== undefined && items[firstItem] === null) {
|
|
211
|
+
if (onLoadMore) {
|
|
212
|
+
const firstItem = virtualItems[0]?.index;
|
|
213
|
+
const lastItem = virtualItems?.slice(-1).pop()?.index;
|
|
214
|
+
const canLoadPrevPage =
|
|
215
|
+
firstItem !== undefined && items[firstItem] === null;
|
|
216
|
+
const canLoadNextPage =
|
|
217
|
+
lastItem !== undefined && lastItem === count - 1 && count < total;
|
|
213
218
|
const db = debounce(onLoadMore);
|
|
214
|
-
|
|
219
|
+
if (canLoadPrevPage) {
|
|
220
|
+
db(firstItem);
|
|
221
|
+
} else if (!loading && canLoadNextPage) {
|
|
222
|
+
db(lastItem + 1);
|
|
223
|
+
}
|
|
215
224
|
return () => db.cancel();
|
|
216
225
|
}
|
|
217
|
-
}, [onLoadMore,
|
|
226
|
+
}, [onLoadMore, virtualItems]);
|
|
218
227
|
|
|
219
228
|
useLayoutEffect(() => {
|
|
220
229
|
setTimeout(() => {
|
|
@@ -227,14 +236,14 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
227
236
|
listRef,
|
|
228
237
|
() => ({
|
|
229
238
|
scrollToItem(index) {
|
|
230
|
-
virtualizer.scrollToIndex(index, { align: "
|
|
239
|
+
virtualizer.scrollToIndex(index, { align: "auto" });
|
|
231
240
|
},
|
|
232
241
|
hilight(index) {
|
|
233
242
|
scrollerRef.current
|
|
234
243
|
?.querySelector(".hilight")
|
|
235
244
|
?.classList.remove("hilight", "ring-1");
|
|
236
245
|
virtualizer.scrollToIndex(Math.floor(index), {
|
|
237
|
-
align: "
|
|
246
|
+
align: "auto",
|
|
238
247
|
});
|
|
239
248
|
setTimeout(() => {
|
|
240
249
|
scrollerRef.current
|
|
@@ -255,9 +264,8 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
255
264
|
<div
|
|
256
265
|
ref={scrollerRef}
|
|
257
266
|
onScroll={handleScroll}
|
|
258
|
-
className={classNames(
|
|
259
|
-
|
|
260
|
-
)}
|
|
267
|
+
className={classNames("area-content bg-base overflow-auto")}
|
|
268
|
+
{...aria}
|
|
261
269
|
>
|
|
262
270
|
<div
|
|
263
271
|
className={classNames(
|
|
@@ -275,12 +283,16 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
275
283
|
padding === "md" && "p-2",
|
|
276
284
|
padding === "lg" && "p-4",
|
|
277
285
|
)}
|
|
286
|
+
style={{
|
|
287
|
+
minWidth,
|
|
288
|
+
maxWidth,
|
|
289
|
+
}}
|
|
278
290
|
>
|
|
279
291
|
<div
|
|
280
292
|
style={
|
|
281
293
|
orientation === "vertical"
|
|
282
|
-
? { height: virtualizer.getTotalSize() }
|
|
283
|
-
: { width: virtualizer.getTotalSize() }
|
|
294
|
+
? { height: virtualizer.getTotalSize() + extraSize }
|
|
295
|
+
: { width: virtualizer.getTotalSize() + extraSize }
|
|
284
296
|
}
|
|
285
297
|
>
|
|
286
298
|
<div
|
|
@@ -304,9 +316,9 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
304
316
|
</div>
|
|
305
317
|
))}
|
|
306
318
|
</div>
|
|
307
|
-
{
|
|
308
|
-
<div className={orientation === "horizontal" ? "min-w-16" : "
|
|
309
|
-
|
|
319
|
+
{loading && (
|
|
320
|
+
<div className={orientation === "horizontal" ? "min-w-16" : ""}>
|
|
321
|
+
<Skeleton />
|
|
310
322
|
</div>
|
|
311
323
|
)}
|
|
312
324
|
</div>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer comp{.histogram{.histoLabel{--tw-gradient-from:var(--color) var(--tw-gradient-from-position);--tw-gradient-to:color-mix(in lab,var(--color) 20%,var(--fabric-bg)) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);background-color:color(from var(--color) srgb r g b/5%);&:hover{background-color:color(from var(--color) srgb r g b/15%)}&[data-disabled=true]{--color:theme(colors.tint.300);color:theme(colors.tint.600)}}.histoBar{box-shadow:-2px 0 0 var(--color) inset;:global([dir=rtl]) &{box-shadow:2px 0 0 var(--color) inset}}.histoBadge{background-color:color(from var(--color) srgb r g b/15%)}}}
|