@react-fabric/data 1.0.0-beta.4 → 1.0.0-beta.40
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/BodyCell.js +38 -5
- package/dist/cjs/table/BodyRow.js +107 -0
- package/dist/cjs/table/CheckboxCell.js +1 -1
- package/dist/cjs/table/HeaderCell.js +29 -36
- package/dist/cjs/table/Table.js +105 -67
- package/dist/cjs/table/useResizer.js +4 -4
- package/dist/cjs/table/useVirtualTable.js +118 -17
- package/dist/cjs/tree/TreeNode.js +44 -24
- package/dist/cjs/tree/TreePanel.js +28 -10
- package/dist/cjs/tree/useTree.js +96 -14
- package/dist/cjs/tree/utils.js +20 -5
- package/dist/cjs/virtual/Gallery.js +43 -29
- package/dist/cjs/virtual/List.js +43 -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/BodyCell.js +38 -5
- package/dist/esm/table/BodyRow.js +107 -0
- package/dist/esm/table/CheckboxCell.js +1 -1
- package/dist/esm/table/HeaderCell.js +30 -37
- package/dist/esm/table/Table.js +107 -69
- package/dist/esm/table/useResizer.js +4 -4
- package/dist/esm/table/useVirtualTable.js +120 -19
- package/dist/esm/tree/TreeNode.js +46 -26
- package/dist/esm/tree/TreePanel.js +28 -10
- package/dist/esm/tree/useTree.js +98 -16
- package/dist/esm/tree/utils.js +20 -5
- package/dist/esm/virtual/Gallery.js +43 -29
- package/dist/esm/virtual/List.js +43 -31
- package/dist/types/checklist/CheckList.d.ts +2 -2
- package/dist/types/histogram/Histogram.d.ts +3 -3
- package/dist/types/index.d.ts +1 -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/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 +17 -6
- package/dist/types/table/useTableColumns.d.ts +6 -6
- package/dist/types/table/useVirtualTable.d.ts +20 -5
- 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 +7 -3
- package/package.json +10 -7
- 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 +2 -2
- package/src/table/BodyCell.tsx +34 -5
- package/src/table/BodyRow.tsx +143 -0
- package/src/table/CheckboxCell.tsx +1 -1
- package/src/table/EmptyCell.tsx +2 -2
- package/src/table/HeaderCell.tsx +27 -13
- package/src/table/Table.tsx +141 -73
- package/src/table/types.ts +20 -5
- package/src/table/useResizer.ts +5 -3
- package/src/table/useTableColumns.ts +7 -7
- package/src/table/useVirtualTable.ts +143 -28
- package/src/tree/TreeNode.tsx +90 -56
- package/src/tree/TreePanel.tsx +23 -3
- package/src/tree/types.ts +32 -4
- package/src/tree/useTree.ts +102 -23
- package/src/tree/utils.ts +23 -8
- package/src/virtual/Gallery.tsx +46 -35
- package/src/virtual/List.tsx +40 -27
- 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(() => {
|
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?: (page?: number) => void;
|
|
90
98
|
/**
|
|
91
99
|
* scroll handler
|
|
92
100
|
*/
|
|
@@ -99,6 +107,10 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
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
|
);
|
|
@@ -203,31 +216,23 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
203
216
|
}, [count, calculateActiveScroll]);
|
|
204
217
|
|
|
205
218
|
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
|
-
) {
|
|
219
|
+
if (onLoadMore) {
|
|
220
|
+
const firstItem = virtualItems[0]?.index;
|
|
221
|
+
const lastItem = virtualItems?.slice(-1).pop()?.index;
|
|
222
|
+
const lastIndex = (lastItem ?? 0) * columnCount + columnCount;
|
|
223
|
+
const canLoadPrevPage =
|
|
224
|
+
firstItem !== undefined && items[firstItem * columnCount] === null;
|
|
225
|
+
const canLoadNextPage =
|
|
226
|
+
lastItem !== undefined && lastIndex >= count - 1 && count < total;
|
|
226
227
|
const db = debounce(onLoadMore);
|
|
227
|
-
|
|
228
|
+
if (canLoadPrevPage) {
|
|
229
|
+
db(firstItem);
|
|
230
|
+
} else if (!loading && canLoadNextPage) {
|
|
231
|
+
db(lastItem + 1);
|
|
232
|
+
}
|
|
228
233
|
return () => db.cancel();
|
|
229
234
|
}
|
|
230
|
-
}, [onLoadMore,
|
|
235
|
+
}, [onLoadMore, virtualItems]);
|
|
231
236
|
|
|
232
237
|
useLayoutEffect(() => {
|
|
233
238
|
setTimeout(() => {
|
|
@@ -243,7 +248,7 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
243
248
|
() => ({
|
|
244
249
|
scrollToItem(index) {
|
|
245
250
|
virtualizer.scrollToIndex(Math.floor(index / columnCount), {
|
|
246
|
-
align: "
|
|
251
|
+
align: "auto",
|
|
247
252
|
});
|
|
248
253
|
},
|
|
249
254
|
hilight(index) {
|
|
@@ -251,7 +256,7 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
251
256
|
?.querySelector(".hilight")
|
|
252
257
|
?.classList.remove("hilight", "ring-1");
|
|
253
258
|
virtualizer.scrollToIndex(Math.floor(index / columnCount), {
|
|
254
|
-
align: "
|
|
259
|
+
align: "auto",
|
|
255
260
|
});
|
|
256
261
|
setTimeout(() => {
|
|
257
262
|
scrollerRef.current
|
|
@@ -275,6 +280,7 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
275
280
|
className={classNames(
|
|
276
281
|
"area-content bg-base overflow-auto contain-strict",
|
|
277
282
|
)}
|
|
283
|
+
{...aria}
|
|
278
284
|
>
|
|
279
285
|
<div
|
|
280
286
|
className={classNames(
|
|
@@ -284,20 +290,25 @@ const _VirtualGallery = <T extends KeyValue>({
|
|
|
284
290
|
<div
|
|
285
291
|
className={classNames(
|
|
286
292
|
"box-content",
|
|
293
|
+
fullWidth && "flex-1",
|
|
287
294
|
padding === "sm" && "p-1",
|
|
288
295
|
padding === "md" && "p-2",
|
|
289
296
|
padding === "lg" && "p-4",
|
|
290
297
|
)}
|
|
298
|
+
style={{
|
|
299
|
+
minWidth,
|
|
300
|
+
maxWidth,
|
|
301
|
+
}}
|
|
291
302
|
>
|
|
292
303
|
<div
|
|
293
304
|
style={{
|
|
294
|
-
height: virtualizer.getTotalSize(),
|
|
305
|
+
height: virtualizer.getTotalSize() + extraSize,
|
|
295
306
|
}}
|
|
296
307
|
>
|
|
297
308
|
<div style={{ height: virtualItems[0]?.start }} />
|
|
298
309
|
{virtualItems.map(({ key, index, measureElement }) => (
|
|
299
310
|
<div
|
|
300
|
-
className="flex"
|
|
311
|
+
className="flex justify-start"
|
|
301
312
|
key={key}
|
|
302
313
|
data-index={index}
|
|
303
314
|
ref={measureElement}
|
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
|
|
@@ -108,8 +112,11 @@ const createItemList = memoizeOne((items) => items);
|
|
|
108
112
|
|
|
109
113
|
const _VirtualList = <T extends KeyValue>({
|
|
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
|
);
|
|
@@ -199,22 +207,22 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
199
207
|
}, [itemList, calculateActiveScroll]);
|
|
200
208
|
|
|
201
209
|
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) {
|
|
210
|
+
if (onLoadMore) {
|
|
211
|
+
const firstItem = virtualItems[0]?.index;
|
|
212
|
+
const lastItem = virtualItems?.slice(-1).pop()?.index;
|
|
213
|
+
const canLoadPrevPage =
|
|
214
|
+
firstItem !== undefined && items[firstItem] === null;
|
|
215
|
+
const canLoadNextPage =
|
|
216
|
+
lastItem !== undefined && lastItem === count - 1 && count < total;
|
|
213
217
|
const db = debounce(onLoadMore);
|
|
214
|
-
|
|
218
|
+
if (canLoadPrevPage) {
|
|
219
|
+
db(firstItem);
|
|
220
|
+
} else if (!loading && canLoadNextPage) {
|
|
221
|
+
db(lastItem + 1);
|
|
222
|
+
}
|
|
215
223
|
return () => db.cancel();
|
|
216
224
|
}
|
|
217
|
-
}, [onLoadMore,
|
|
225
|
+
}, [onLoadMore, virtualItems]);
|
|
218
226
|
|
|
219
227
|
useLayoutEffect(() => {
|
|
220
228
|
setTimeout(() => {
|
|
@@ -227,14 +235,14 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
227
235
|
listRef,
|
|
228
236
|
() => ({
|
|
229
237
|
scrollToItem(index) {
|
|
230
|
-
virtualizer.scrollToIndex(index, { align: "
|
|
238
|
+
virtualizer.scrollToIndex(index, { align: "auto" });
|
|
231
239
|
},
|
|
232
240
|
hilight(index) {
|
|
233
241
|
scrollerRef.current
|
|
234
242
|
?.querySelector(".hilight")
|
|
235
243
|
?.classList.remove("hilight", "ring-1");
|
|
236
244
|
virtualizer.scrollToIndex(Math.floor(index), {
|
|
237
|
-
align: "
|
|
245
|
+
align: "auto",
|
|
238
246
|
});
|
|
239
247
|
setTimeout(() => {
|
|
240
248
|
scrollerRef.current
|
|
@@ -258,6 +266,7 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
258
266
|
className={classNames(
|
|
259
267
|
"area-content bg-base overflow-auto contain-strict",
|
|
260
268
|
)}
|
|
269
|
+
{...aria}
|
|
261
270
|
>
|
|
262
271
|
<div
|
|
263
272
|
className={classNames(
|
|
@@ -275,12 +284,16 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
275
284
|
padding === "md" && "p-2",
|
|
276
285
|
padding === "lg" && "p-4",
|
|
277
286
|
)}
|
|
287
|
+
style={{
|
|
288
|
+
minWidth,
|
|
289
|
+
maxWidth,
|
|
290
|
+
}}
|
|
278
291
|
>
|
|
279
292
|
<div
|
|
280
293
|
style={
|
|
281
294
|
orientation === "vertical"
|
|
282
|
-
? { height: virtualizer.getTotalSize() }
|
|
283
|
-
: { width: virtualizer.getTotalSize() }
|
|
295
|
+
? { height: virtualizer.getTotalSize() + extraSize }
|
|
296
|
+
: { width: virtualizer.getTotalSize() + extraSize }
|
|
284
297
|
}
|
|
285
298
|
>
|
|
286
299
|
<div
|
|
@@ -304,9 +317,9 @@ const _VirtualList = <T extends KeyValue>({
|
|
|
304
317
|
</div>
|
|
305
318
|
))}
|
|
306
319
|
</div>
|
|
307
|
-
{
|
|
308
|
-
<div className={orientation === "horizontal" ? "min-w-16" : "
|
|
309
|
-
|
|
320
|
+
{loading && (
|
|
321
|
+
<div className={orientation === "horizontal" ? "min-w-16" : ""}>
|
|
322
|
+
<Skeleton />
|
|
310
323
|
</div>
|
|
311
324
|
)}
|
|
312
325
|
</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%)}}}
|
|
@@ -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%)}}}
|