@react-fabric/data 1.0.0-beta.5 → 1.0.0-beta.52

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 (109) 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 +48 -16
  4. package/dist/cjs/histogram/Histogram.js +48 -10
  5. package/dist/cjs/hooks/useFilteredList.js +20 -0
  6. package/dist/cjs/hooks/usePagination.js +27 -4
  7. package/dist/cjs/hooks/useSelectableList.js +29 -0
  8. package/dist/cjs/i18n/ar.json.js +8 -1
  9. package/dist/cjs/i18n/en.json.js +8 -1
  10. package/dist/cjs/json/JsonViewer.js +67 -27
  11. package/dist/cjs/pagination/Pagination.js +10 -0
  12. package/dist/cjs/table/AddColumn.js +1 -1
  13. package/dist/cjs/table/BodyCell.js +38 -5
  14. package/dist/cjs/table/BodyRow.js +107 -0
  15. package/dist/cjs/table/CheckboxCell.js +1 -1
  16. package/dist/cjs/table/ColumnFilters.js +97 -0
  17. package/dist/cjs/table/HeaderCell.js +70 -37
  18. package/dist/cjs/table/Table.js +113 -71
  19. package/dist/cjs/table/types.js +29 -0
  20. package/dist/cjs/table/useResizer.js +4 -4
  21. package/dist/cjs/table/useVirtualTable.js +122 -18
  22. package/dist/cjs/tree/TreeNode.js +45 -25
  23. package/dist/cjs/tree/TreePanel.js +28 -10
  24. package/dist/cjs/tree/useTree.js +97 -15
  25. package/dist/cjs/tree/utils.js +20 -5
  26. package/dist/cjs/virtual/Gallery.js +84 -35
  27. package/dist/cjs/virtual/List.js +82 -32
  28. package/dist/esm/checklist/CheckList.js +50 -18
  29. package/dist/esm/histogram/Histogram.js +49 -11
  30. package/dist/esm/hooks/useFilteredList.js +20 -0
  31. package/dist/esm/hooks/usePagination.js +27 -4
  32. package/dist/esm/hooks/useSelectableList.js +29 -0
  33. package/dist/esm/i18n/ar.json.js +8 -2
  34. package/dist/esm/i18n/en.json.js +8 -2
  35. package/dist/esm/json/JsonViewer.js +68 -28
  36. package/dist/esm/pagination/Pagination.js +10 -0
  37. package/dist/esm/table/AddColumn.js +1 -1
  38. package/dist/esm/table/BodyCell.js +38 -5
  39. package/dist/esm/table/BodyRow.js +107 -0
  40. package/dist/esm/table/CheckboxCell.js +1 -1
  41. package/dist/esm/table/ColumnFilters.js +97 -0
  42. package/dist/esm/table/HeaderCell.js +71 -38
  43. package/dist/esm/table/Table.js +115 -73
  44. package/dist/esm/table/types.js +29 -0
  45. package/dist/esm/table/useResizer.js +4 -4
  46. package/dist/esm/table/useVirtualTable.js +124 -20
  47. package/dist/esm/tree/TreeNode.js +47 -27
  48. package/dist/esm/tree/TreePanel.js +28 -10
  49. package/dist/esm/tree/useTree.js +99 -17
  50. package/dist/esm/tree/utils.js +20 -5
  51. package/dist/esm/virtual/Gallery.js +84 -35
  52. package/dist/esm/virtual/List.js +82 -32
  53. package/dist/types/checklist/CheckList.d.ts +24 -2
  54. package/dist/types/histogram/Histogram.d.ts +31 -4
  55. package/dist/types/hooks/useFilteredList.d.ts +20 -2
  56. package/dist/types/hooks/usePagination.d.ts +19 -0
  57. package/dist/types/hooks/useSelectableList.d.ts +29 -0
  58. package/dist/types/index.d.ts +1 -1
  59. package/dist/types/json/JsonViewer.d.ts +27 -2
  60. package/dist/types/pagination/Pagination.d.ts +10 -1
  61. package/dist/types/table/AddColumn.d.ts +2 -2
  62. package/dist/types/table/BodyCell.d.ts +4 -3
  63. package/dist/types/table/BodyRow.d.ts +20 -0
  64. package/dist/types/table/ColumnFilters.d.ts +9 -0
  65. package/dist/types/table/EmptyCell.d.ts +2 -2
  66. package/dist/types/table/HeaderCell.d.ts +2 -2
  67. package/dist/types/table/Table.d.ts +14 -1
  68. package/dist/types/table/types.d.ts +48 -7
  69. package/dist/types/table/useTableColumns.d.ts +6 -6
  70. package/dist/types/table/useVirtualTable.d.ts +23 -8
  71. package/dist/types/tree/TreeNode.d.ts +1 -1
  72. package/dist/types/tree/TreePanel.d.ts +42 -1
  73. package/dist/types/tree/types.d.ts +25 -5
  74. package/dist/types/tree/useTree.d.ts +3 -10
  75. package/dist/types/tree/utils.d.ts +4 -1
  76. package/dist/types/virtual/Gallery.d.ts +44 -4
  77. package/dist/types/virtual/List.d.ts +44 -4
  78. package/package.json +13 -10
  79. package/src/checklist/CheckList.tsx +103 -77
  80. package/src/histogram/Histogram.tsx +45 -11
  81. package/src/hooks/useFilteredList.tsx +19 -0
  82. package/src/hooks/usePagination.tsx +32 -7
  83. package/src/hooks/useSelectableList.ts +29 -0
  84. package/src/i18n/ar.json +5 -0
  85. package/src/i18n/en.json +5 -0
  86. package/src/index.ts +1 -1
  87. package/src/json/JsonViewer.tsx +71 -35
  88. package/src/pagination/Pagination.tsx +10 -0
  89. package/src/table/AddColumn.tsx +3 -3
  90. package/src/table/BodyCell.tsx +34 -5
  91. package/src/table/BodyRow.tsx +143 -0
  92. package/src/table/CheckboxCell.tsx +1 -1
  93. package/src/table/ColumnFilters.tsx +105 -0
  94. package/src/table/EmptyCell.tsx +2 -2
  95. package/src/table/HeaderCell.tsx +72 -14
  96. package/src/table/Table.tsx +177 -77
  97. package/src/table/types.ts +52 -6
  98. package/src/table/useResizer.ts +5 -3
  99. package/src/table/useTableColumns.ts +7 -7
  100. package/src/table/useVirtualTable.ts +146 -24
  101. package/src/tree/TreeNode.tsx +98 -56
  102. package/src/tree/TreePanel.tsx +64 -3
  103. package/src/tree/types.ts +32 -4
  104. package/src/tree/useTree.ts +109 -26
  105. package/src/tree/utils.ts +23 -8
  106. package/src/virtual/Gallery.tsx +96 -42
  107. package/src/virtual/List.tsx +82 -33
  108. package/dist/cjs/histogram/Histogram.module.css +0 -1
  109. 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(() => {
@@ -215,9 +294,13 @@ export const useTree = <T extends KeyValue>({
215
294
  dispatch({ type: "collapseAll" });
216
295
  }, []);
217
296
 
218
- const onFilter = useDebounce((query: string) => {
219
- dispatch({ type: "filter", query });
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 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?: (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 KeyValue>({
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 ? 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
  );
@@ -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(1, Math.floor(((el?.offsetWidth ?? 800) - 32) / width)),
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
- 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
- ) {
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
- db(firstItem * columnCount);
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, items, columnCount, virtualItems, count]);
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: "start",
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: "start",
271
+ align: "auto",
255
272
  });
256
273
  setTimeout(() => {
257
274
  scrollerRef.current
@@ -272,35 +289,39 @@ const _VirtualGallery = <T extends KeyValue>({
272
289
  <div
273
290
  ref={scrollerRef}
274
291
  onScroll={handleScroll}
275
- className={classNames(
276
- "area-content bg-base overflow-auto contain-strict",
277
- )}
292
+ className={classNames("area-content bg-base overflow-auto")}
293
+ {...aria}
278
294
  >
279
295
  <div
280
296
  className={classNames(
281
- "inline-flex flex-nowrap gap-2 justify-center min-h-full min-w-full",
297
+ "flex flex-nowrap justify-center gap-2 min-h-full",
282
298
  )}
283
299
  >
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, measureElement }) => (
319
+ {virtualItems.map(({ key, index }) => (
299
320
  <div
300
321
  className="flex"
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
@@ -346,4 +367,37 @@ const _VirtualGallery = <T extends KeyValue>({
346
367
  _VirtualGallery.displayName = "VirtualGallery";
347
368
 
348
369
  const GenericMemo: <T>(c: T) => T = memo;
370
+
371
+ /**
372
+ * VirtualGallery component provides a virtualized gallery view for displaying a large list of items efficiently.
373
+ * It supports features like fixed columns, loading states, and customizable item rendering.
374
+ * The component uses virtualization to render only the visible items, improving performance for large datasets.
375
+ * It also provides a scroller for easy navigation through the items.
376
+ *
377
+ * @param {VirtualGalleryProps<T>} props - The properties for the VirtualGallery component.
378
+ * @returns {JSX.Element} The rendered VirtualGallery component.
379
+ *
380
+ * @example
381
+ * ```jsx
382
+ * <VirtualGallery
383
+ * items={items}
384
+ * columns={3}
385
+ * defaultHeight={100}
386
+ * defaultWidth={100}
387
+ * fullWidth={true}
388
+ * onLoadMore={(lastIndex) => console.log("Load more items from index:", lastIndex)}
389
+ * onScroll={(top) => console.log("Scrolled to top:", top)}
390
+ * >
391
+ * {({ item, index, isLast }) => (
392
+ * <div className="gallery-item" key={item.id}>
393
+ * <img src={item.image} alt={item.label} />
394
+ * <p>{item.label}</p>
395
+ * </div>
396
+ * )}
397
+ * </VirtualGallery>
398
+ * ```
399
+ *
400
+ * @see {@link https://adarshpastakia.github.io/react-fabric/?path=/docs/data-virtualgallery--docs}
401
+ * @see {@link https://tanstack.com/docs/react-virtual/latest/overview}
402
+ */
349
403
  export const VirtualGallery = GenericMemo(_VirtualGallery);