@react-fabric/data 1.0.0-beta.4 → 1.0.0-beta.41

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.
Files changed (81) hide show
  1. package/{src/histogram/Histogram.module.css → css/_histogram.css} +16 -13
  2. package/css/styles.css +24 -0
  3. package/dist/cjs/checklist/CheckList.js +25 -16
  4. package/dist/cjs/histogram/Histogram.js +20 -10
  5. package/dist/cjs/hooks/usePagination.js +7 -4
  6. package/dist/cjs/json/JsonViewer.js +40 -27
  7. package/dist/cjs/table/BodyCell.js +38 -5
  8. package/dist/cjs/table/BodyRow.js +107 -0
  9. package/dist/cjs/table/CheckboxCell.js +1 -1
  10. package/dist/cjs/table/HeaderCell.js +29 -36
  11. package/dist/cjs/table/Table.js +105 -67
  12. package/dist/cjs/table/useResizer.js +4 -4
  13. package/dist/cjs/table/useVirtualTable.js +118 -17
  14. package/dist/cjs/tree/TreeNode.js +44 -24
  15. package/dist/cjs/tree/TreePanel.js +28 -10
  16. package/dist/cjs/tree/useTree.js +96 -14
  17. package/dist/cjs/tree/utils.js +20 -5
  18. package/dist/cjs/virtual/Gallery.js +43 -29
  19. package/dist/cjs/virtual/List.js +43 -31
  20. package/dist/esm/checklist/CheckList.js +27 -18
  21. package/dist/esm/histogram/Histogram.js +21 -11
  22. package/dist/esm/hooks/usePagination.js +7 -4
  23. package/dist/esm/json/JsonViewer.js +41 -28
  24. package/dist/esm/table/BodyCell.js +38 -5
  25. package/dist/esm/table/BodyRow.js +107 -0
  26. package/dist/esm/table/CheckboxCell.js +1 -1
  27. package/dist/esm/table/HeaderCell.js +30 -37
  28. package/dist/esm/table/Table.js +107 -69
  29. package/dist/esm/table/useResizer.js +4 -4
  30. package/dist/esm/table/useVirtualTable.js +120 -19
  31. package/dist/esm/tree/TreeNode.js +46 -26
  32. package/dist/esm/tree/TreePanel.js +28 -10
  33. package/dist/esm/tree/useTree.js +98 -16
  34. package/dist/esm/tree/utils.js +20 -5
  35. package/dist/esm/virtual/Gallery.js +43 -29
  36. package/dist/esm/virtual/List.js +43 -31
  37. package/dist/types/checklist/CheckList.d.ts +2 -2
  38. package/dist/types/histogram/Histogram.d.ts +3 -3
  39. package/dist/types/index.d.ts +1 -1
  40. package/dist/types/table/AddColumn.d.ts +2 -2
  41. package/dist/types/table/BodyCell.d.ts +4 -3
  42. package/dist/types/table/BodyRow.d.ts +20 -0
  43. package/dist/types/table/EmptyCell.d.ts +2 -2
  44. package/dist/types/table/HeaderCell.d.ts +2 -2
  45. package/dist/types/table/Table.d.ts +1 -1
  46. package/dist/types/table/types.d.ts +17 -6
  47. package/dist/types/table/useTableColumns.d.ts +6 -6
  48. package/dist/types/table/useVirtualTable.d.ts +20 -5
  49. package/dist/types/tree/TreeNode.d.ts +1 -1
  50. package/dist/types/tree/TreePanel.d.ts +1 -1
  51. package/dist/types/tree/types.d.ts +25 -5
  52. package/dist/types/tree/useTree.d.ts +2 -9
  53. package/dist/types/tree/utils.d.ts +4 -1
  54. package/dist/types/virtual/Gallery.d.ts +12 -4
  55. package/dist/types/virtual/List.d.ts +7 -3
  56. package/package.json +10 -7
  57. package/src/checklist/CheckList.tsx +81 -77
  58. package/src/histogram/Histogram.tsx +17 -11
  59. package/src/hooks/usePagination.tsx +13 -7
  60. package/src/index.ts +1 -1
  61. package/src/json/JsonViewer.tsx +45 -35
  62. package/src/table/AddColumn.tsx +2 -2
  63. package/src/table/BodyCell.tsx +34 -5
  64. package/src/table/BodyRow.tsx +143 -0
  65. package/src/table/CheckboxCell.tsx +1 -1
  66. package/src/table/EmptyCell.tsx +2 -2
  67. package/src/table/HeaderCell.tsx +27 -13
  68. package/src/table/Table.tsx +141 -73
  69. package/src/table/types.ts +20 -5
  70. package/src/table/useResizer.ts +5 -3
  71. package/src/table/useTableColumns.ts +7 -7
  72. package/src/table/useVirtualTable.ts +143 -28
  73. package/src/tree/TreeNode.tsx +90 -56
  74. package/src/tree/TreePanel.tsx +23 -3
  75. package/src/tree/types.ts +32 -4
  76. package/src/tree/useTree.ts +102 -23
  77. package/src/tree/utils.ts +23 -8
  78. package/src/virtual/Gallery.tsx +46 -35
  79. package/src/virtual/List.tsx +40 -27
  80. package/dist/cjs/histogram/Histogram.module.css +0 -1
  81. package/dist/esm/histogram/Histogram.module.css +0 -1
@@ -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 { type InternalNode, type TreeNodeType } from "./types";
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
- items?: Array<TreeNodeType<T>>;
95
- selected?: string;
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
- node && (node.open = !node.open);
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 && (node.open = true);
123
- state.tree = flattenTree(state.items);
124
- return { ...state };
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
- filterTree(state.itemMap, action.query, matcher);
143
- state.tree = flattenTree(state.items);
144
- return { ...state };
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?.(action.id);
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 sorter = (a: TreeNodeType, b: TreeNodeType) => {
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.sort(sorter).forEach((node) => {
58
+ if (sorter !== false) nodes = nodes.sort(sorter);
59
+ nodes.forEach((node) => {
56
60
  list.push({
57
- open: false,
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(node.children, {
70
- level: options.level + 1,
71
- parent: node.id,
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
  };
@@ -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
- data: T;
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 ? 64 : 0), [onLoadMore]);
126
+ const extraSize = useMemo(() => (onLoadMore ? 8 : 0), [onLoadMore]);
114
127
  const itemList: Array<{
115
128
  index: number;
116
- data: T;
129
+ item: T;
117
130
  isLast: boolean;
118
131
  isSticky: boolean;
119
132
  }> = createItemList(
120
- items.map((item: AnyObject, idx) => {
133
+ items.map((item: AnyObject, index) => {
121
134
  return {
122
- index: idx,
123
- data: item,
124
- isLast: idx + 1 === items.length,
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
- const db = debounce(() => {
207
- const lastItem = virtualItems?.slice(-1).pop();
208
- if (
209
- lastItem &&
210
- !loading &&
211
- lastItem.index * columnCount + columnCount >= count - 1
212
- )
213
- onLoadMore?.();
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
- db(firstItem * columnCount);
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, items, columnCount, virtualItems, count]);
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: "start",
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: "start",
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}
@@ -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
- data: T;
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 ? 64 : 0), [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, idx) => {
140
+ items.map((item: AnyObject, index) => {
133
141
  return {
134
- index: idx,
135
- data: item,
136
- isLast: idx + 1 === items.length,
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
- const db = debounce(() => {
203
- const lastItem = virtualItems?.slice(-1).pop();
204
- if (lastItem && !loading && lastItem.index === count - 1) onLoadMore?.();
205
- });
206
- db();
207
- return () => db.cancel();
208
- }, [onLoadMore, loading, itemList, virtualItems, count]);
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
- db(firstItem);
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, items, virtualItems, count]);
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: "start" });
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: "start",
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
- {onLoadMore && (
308
- <div className={orientation === "horizontal" ? "min-w-16" : "h-16"}>
309
- {loading && <Skeleton />}
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%)}}}