beautiful-grid 1.0.11 → 1.0.13

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.
@@ -32,6 +32,7 @@ __exportStar(require("./number"), exports);
32
32
  __exportStar(require("./buildHeaderMatrix"), exports);
33
33
  __exportStar(require("./getVisibleScrollableRowRange"), exports);
34
34
  __exportStar(require("./virtualScrollWindow"), exports);
35
+ __exportStar(require("./treeData"), exports);
35
36
  __exportStar(require("./rowHeightMetrics"), exports);
36
37
  __exportStar(require("./cellEditState"), exports);
37
38
  __exportStar(require("./getCellSelectionAxisState"), exports);
@@ -42,3 +43,6 @@ __exportStar(require("./rowReorder"), exports);
42
43
  __exportStar(require("./checkboxEditor"), exports);
43
44
  __exportStar(require("./columnVisibility"), exports);
44
45
  __exportStar(require("./checkedIndexes"), exports);
46
+ __exportStar(require("./exportData"), exports);
47
+ __exportStar(require("./exportCsv"), exports);
48
+ __exportStar(require("./exportExcel"), exports);
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ var __values = (this && this.__values) || function(o) {
3
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
4
+ if (m) return m.call(o);
5
+ if (o && typeof o.length === "number") return {
6
+ next: function () {
7
+ if (o && i >= o.length) o = void 0;
8
+ return { value: o && o[i++], done: !o };
9
+ }
10
+ };
11
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.projectTreeData = projectTreeData;
15
+ exports.getTreeRowPath = getTreeRowPath;
16
+ var getCellValue_1 = require("./getCellValue");
17
+ function flatFallback(data, diagnostics) {
18
+ var sourceIndexByVisibleIndex = data.map(function (_, index) { return index; });
19
+ return {
20
+ valid: false,
21
+ data: data,
22
+ sourceIndexByVisibleIndex: sourceIndexByVisibleIndex,
23
+ visibleIndexBySourceIndex: new Map(sourceIndexByVisibleIndex.map(function (index) { return [index, index]; })),
24
+ metaByRowKey: new Map(),
25
+ metaBySourceIndex: new Map(),
26
+ diagnostics: Array.from(new Set(diagnostics)),
27
+ };
28
+ }
29
+ /** Projects flat parent-key data into visible pre-order rows without mutating the input. */
30
+ function projectTreeData(params) {
31
+ var e_1, _a;
32
+ var _b;
33
+ var data = params.data, rowKey = params.rowKey, parentRowKey = params.parentRowKey, expandedRowKeys = params.expandedRowKeys, includedSourceIndexes = params.includedSourceIndexes, orderedSourceIndexes = params.orderedSourceIndexes;
34
+ if (data.length === 0) {
35
+ return {
36
+ valid: true,
37
+ data: [],
38
+ sourceIndexByVisibleIndex: [],
39
+ visibleIndexBySourceIndex: new Map(),
40
+ metaByRowKey: new Map(),
41
+ metaBySourceIndex: new Map(),
42
+ diagnostics: [],
43
+ };
44
+ }
45
+ var diagnostics = [];
46
+ var keys = new Array(data.length);
47
+ var indexByKey = new Map();
48
+ data.forEach(function (item, sourceIndex) {
49
+ var key = (0, getCellValue_1.getCellValueByRowKey)(rowKey, item.values);
50
+ if (key === undefined || key === null) {
51
+ diagnostics.push('missingRowKey');
52
+ return;
53
+ }
54
+ keys[sourceIndex] = key;
55
+ if (indexByKey.has(key))
56
+ diagnostics.push('duplicateRowKey');
57
+ else
58
+ indexByKey.set(key, sourceIndex);
59
+ });
60
+ if (diagnostics.length > 0)
61
+ return flatFallback(data, diagnostics);
62
+ var parentIndexes = new Array(data.length);
63
+ var childrenByParentIndex = new Map();
64
+ var roots = [];
65
+ data.forEach(function (item, sourceIndex) {
66
+ var _a;
67
+ var parentKey = (0, getCellValue_1.getCellValueByRowKey)(parentRowKey, item.values);
68
+ var parentIndex = parentKey === undefined || parentKey === null ? undefined : indexByKey.get(parentKey);
69
+ if (parentIndex === undefined) {
70
+ roots.push(sourceIndex);
71
+ return;
72
+ }
73
+ parentIndexes[sourceIndex] = parentIndex;
74
+ var children = (_a = childrenByParentIndex.get(parentIndex)) !== null && _a !== void 0 ? _a : [];
75
+ children.push(sourceIndex);
76
+ childrenByParentIndex.set(parentIndex, children);
77
+ });
78
+ // Every node has at most one parent, so iterative parent walking detects all cycles in O(N).
79
+ var visitState = new Uint8Array(data.length);
80
+ for (var start = 0; start < data.length; start++) {
81
+ if (visitState[start] !== 0)
82
+ continue;
83
+ var path = [];
84
+ var current = start;
85
+ while (current !== undefined && visitState[current] === 0) {
86
+ visitState[current] = 1;
87
+ path.push(current);
88
+ current = parentIndexes[current];
89
+ }
90
+ if (current !== undefined && visitState[current] === 1)
91
+ diagnostics.push('cycle');
92
+ path.forEach(function (index) {
93
+ visitState[index] = 2;
94
+ });
95
+ }
96
+ if (diagnostics.length > 0)
97
+ return flatFallback(data, diagnostics);
98
+ var orderRank = new Map();
99
+ orderedSourceIndexes === null || orderedSourceIndexes === void 0 ? void 0 : orderedSourceIndexes.forEach(function (sourceIndex, rank) { return orderRank.set(sourceIndex, rank); });
100
+ var compareSourceOrder = function (left, right) {
101
+ var _a, _b;
102
+ return ((_a = orderRank.get(left)) !== null && _a !== void 0 ? _a : Number.MAX_SAFE_INTEGER) - ((_b = orderRank.get(right)) !== null && _b !== void 0 ? _b : Number.MAX_SAFE_INTEGER) ||
103
+ left - right;
104
+ };
105
+ roots.sort(compareSourceOrder);
106
+ childrenByParentIndex.forEach(function (children) { return children.sort(compareSourceOrder); });
107
+ var included = includedSourceIndexes ? new Set(includedSourceIndexes) : undefined;
108
+ var forcedExpanded = new Set();
109
+ if (included) {
110
+ try {
111
+ for (var _c = __values(includedSourceIndexes !== null && includedSourceIndexes !== void 0 ? includedSourceIndexes : []), _d = _c.next(); !_d.done; _d = _c.next()) {
112
+ var matchedIndex = _d.value;
113
+ var current = parentIndexes[matchedIndex];
114
+ while (current !== undefined) {
115
+ included.add(current);
116
+ forcedExpanded.add(current);
117
+ current = parentIndexes[current];
118
+ }
119
+ }
120
+ }
121
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
122
+ finally {
123
+ try {
124
+ if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
125
+ }
126
+ finally { if (e_1) throw e_1.error; }
127
+ }
128
+ }
129
+ var expanded = new Set(expandedRowKeys);
130
+ var visibleData = [];
131
+ var sourceIndexByVisibleIndex = [];
132
+ var visibleIndexBySourceIndex = new Map();
133
+ var metaByRowKey = new Map();
134
+ var metaBySourceIndex = new Map();
135
+ var stack = roots
136
+ .slice()
137
+ .reverse()
138
+ .map(function (sourceIndex, reverseIndex) { return ({
139
+ sourceIndex: sourceIndex,
140
+ depth: 0,
141
+ visible: true,
142
+ siblingIndex: roots.length - reverseIndex - 1,
143
+ siblingCount: roots.length,
144
+ }); });
145
+ while (stack.length > 0) {
146
+ var entry = stack.pop();
147
+ var sourceIndex = entry.sourceIndex, depth = entry.depth;
148
+ var key = keys[sourceIndex];
149
+ var parentSourceIndex = parentIndexes[sourceIndex];
150
+ var children = (_b = childrenByParentIndex.get(sourceIndex)) !== null && _b !== void 0 ? _b : [];
151
+ var isIncluded = !included || included.has(sourceIndex);
152
+ var isExpanded = expanded.has(key);
153
+ var effectiveExpanded = isExpanded || forcedExpanded.has(sourceIndex);
154
+ var meta = {
155
+ rowKey: key,
156
+ parentRowKey: parentSourceIndex === undefined ? undefined : keys[parentSourceIndex],
157
+ depth: depth,
158
+ hasChildren: children.length > 0,
159
+ expanded: isExpanded,
160
+ sourceIndex: sourceIndex,
161
+ parentSourceIndex: parentSourceIndex,
162
+ siblingIndex: entry.siblingIndex,
163
+ siblingCount: entry.siblingCount,
164
+ };
165
+ metaByRowKey.set(key, meta);
166
+ metaBySourceIndex.set(sourceIndex, meta);
167
+ var visible = entry.visible && isIncluded;
168
+ if (visible) {
169
+ visibleIndexBySourceIndex.set(sourceIndex, visibleData.length);
170
+ visibleData.push(data[sourceIndex]);
171
+ sourceIndexByVisibleIndex.push(sourceIndex);
172
+ }
173
+ // Collapsed subtrees do not need row metadata until they become visible. Skipping
174
+ // them keeps folding work proportional to the visible hierarchy, not all input rows.
175
+ if (!visible || !effectiveExpanded)
176
+ continue;
177
+ for (var childIndex = children.length - 1; childIndex >= 0; childIndex--) {
178
+ stack.push({
179
+ sourceIndex: children[childIndex],
180
+ depth: depth + 1,
181
+ visible: true,
182
+ siblingIndex: childIndex,
183
+ siblingCount: children.length,
184
+ });
185
+ }
186
+ }
187
+ return {
188
+ valid: true,
189
+ data: visibleData,
190
+ sourceIndexByVisibleIndex: sourceIndexByVisibleIndex,
191
+ visibleIndexBySourceIndex: visibleIndexBySourceIndex,
192
+ metaByRowKey: metaByRowKey,
193
+ metaBySourceIndex: metaBySourceIndex,
194
+ diagnostics: [],
195
+ };
196
+ }
197
+ function getTreeRowPath(metaBySourceIndex, sourceIndex) {
198
+ var path = [];
199
+ var current = metaBySourceIndex.get(sourceIndex);
200
+ while (current) {
201
+ path.push(current.rowKey);
202
+ current = current.parentSourceIndex === undefined ? undefined : metaBySourceIndex.get(current.parentSourceIndex);
203
+ }
204
+ return path.reverse();
205
+ }
package/esm/BGrid.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import Table from './components/Table';
3
- import { createPivotData, createRowHeightMetrics, buildHeaderMatrix, countVisibleCheckedIndexes, findDuplicateColumnIds, getCellValueByRowKey, getColumnId, getColumnKeyToken, getFrozenColumnsWidth, processDataQuery, projectColumnVisibility, resolveStatusOptions, resolvePaginationViewOptions, resolveScrollbarOptions, shouldRenderBottomBar, } from './utils';
3
+ import { createPivotData, createRowHeightMetrics, buildHeaderMatrix, countVisibleCheckedIndexes, findDuplicateColumnIds, getCellValueByRowKey, getColumnId, getColumnKeyToken, getFrozenColumnsWidth, getTreeRowPath, processDataQuery, projectTreeData, projectColumnVisibility, resolveStatusOptions, resolvePaginationViewOptions, resolveScrollbarOptions, shouldRenderBottomBar, } from './utils';
4
4
  import { AppStoreProvider } from './store';
5
+ import { TreeContext } from './components/TreeContext';
5
6
  function computeModelColumns(columns, frozenColumnIndex, duplicateToolboxColumnIds) {
6
7
  let left = 0;
7
8
  let previousWidth = 0;
@@ -21,7 +22,11 @@ function computeModelColumns(columns, frozenColumnIndex, duplicateToolboxColumnI
21
22
  };
22
23
  });
23
24
  }
24
- export function BGrid({ ref, width, height, headerHeight = 30, footerHeight, summaryHeight = 30, itemHeight = 15, itemPadding = 7, getRowHeight, columns, columnsGroup, columnGroups, onChangeColumns, frozenColumnIndex = 0, frozenRowCount = 0, data = [], page, scrollbar, status, pagination, bottomBarHeight, scrollTop = 0, scrollLeft = 0, className, style, rowChecked, sort, onClick, loading = false, spinning, disabled, rowKey, selectedRowKey, editable, onChangeData, showLineNumber, msg, getRowClassName, editTrigger = 'dblclick', cellMergeOptions, cellSelectionOptions, cellNavigationOptions, variant, summary, columnSortable, reorder, pivot, dataControl, icons, columnVisibility, searchOptions, contextMenuOptions, }) {
25
+ export function BGrid({ ref, width, height, headerHeight = 30, footerHeight, summaryHeight = 30, itemHeight = 15, itemPadding = 7, getRowHeight, columns, columnsGroup, columnGroups, onChangeColumns, frozenColumnIndex = 0, frozenRowCount = 0, data = [], page, scrollbar, status, pagination, bottomBarHeight, scrollTop = 0, scrollLeft = 0, className, style, rowChecked, sort, onClick, loading = false, spinning, disabled, rowKey, selectedRowKey, editable, onChangeData, showLineNumber, msg, getRowClassName, editTrigger = 'dblclick', cellMergeOptions, cellSelectionOptions, cellNavigationOptions, variant, summary, columnSortable, reorder, pivot, dataControl, icons, columnVisibility, searchOptions, contextMenuOptions, tree, }) {
26
+ const [uncontrolledExpandedRowKeys, setUncontrolledExpandedRowKeys] = React.useState(() => [
27
+ ...(tree?.defaultExpandedRowKeys ?? []),
28
+ ]);
29
+ const expandedRowKeys = tree?.expandedRowKeys ?? uncontrolledExpandedRowKeys;
25
30
  const warnedSearchControlledRef = React.useRef({
26
31
  open: false,
27
32
  query: false,
@@ -79,13 +84,15 @@ export function BGrid({ ref, width, height, headerHeight = 30, footerHeight, sum
79
84
  const resolvedDisabled = !!disabled;
80
85
  const resolvedOnClick = pivotEnabled || resolvedDisabled ? undefined : onClick;
81
86
  const baseOnChangeColumns = pivotEnabled ? undefined : onChangeColumns;
82
- const resolvedOnChangeData = pivotEnabled ? undefined : onChangeData;
87
+ const baseOnChangeData = pivotEnabled ? undefined : onChangeData;
83
88
  const resolvedRowKey = pivotEnabled ? undefined : rowKey;
84
89
  const resolvedSelectedRowKey = pivotEnabled ? undefined : selectedRowKey;
85
90
  const resolvedEditable = pivotEnabled || resolvedDisabled ? false : editable;
86
91
  const resolvedShowLineNumber = pivotEnabled ? false : showLineNumber;
87
92
  const resolvedGetRowClassName = pivotEnabled ? undefined : getRowClassName;
88
- const resolvedCellMergeOptions = visibilityProjection.cellMergeOptions;
93
+ const requestedTreeEnabled = !!tree && tree.enabled !== false;
94
+ const treeEnabled = requestedTreeEnabled && !pivotEnabled && resolvedRowKey !== undefined;
95
+ const resolvedCellMergeOptions = treeEnabled ? undefined : visibilityProjection.cellMergeOptions;
89
96
  const resolvedCellSelectionOptions = resolvedDisabled
90
97
  ? { ...cellSelectionOptions, enabled: false }
91
98
  : cellSelectionOptions;
@@ -97,11 +104,11 @@ export function BGrid({ ref, width, height, headerHeight = 30, footerHeight, sum
97
104
  const resolvedContextMenuOptions = pivotEnabled || resolvedDisabled ? undefined : contextMenuOptions;
98
105
  const hasActiveClientQuery = resolvedDataControl?.mode === 'client' &&
99
106
  (resolvedDataControl.query.sortParams.length > 0 || resolvedDataControl.query.filterParams.length > 0);
100
- const resolvedReorder = React.useMemo(() => pivotEnabled || resolvedDisabled
107
+ const resolvedReorder = React.useMemo(() => pivotEnabled || resolvedDisabled || treeEnabled
101
108
  ? undefined
102
109
  : (hasActiveClientQuery || frozenRowCount > 0) && reorder
103
110
  ? { ...reorder, enabled: false }
104
- : reorder, [frozenRowCount, hasActiveClientQuery, pivotEnabled, reorder, resolvedDisabled]);
111
+ : reorder, [frozenRowCount, hasActiveClientQuery, pivotEnabled, reorder, resolvedDisabled, treeEnabled]);
105
112
  // Development warnings
106
113
  React.useEffect(() => {
107
114
  if (process.env.NODE_ENV !== 'production') {
@@ -130,6 +137,18 @@ export function BGrid({ ref, width, height, headerHeight = 30, footerHeight, sum
130
137
  warnedSearchControlledRef.current.pivotContextMenu = true;
131
138
  console.warn('[BGrid] contextMenuOptions is disabled while pivot mode is active.');
132
139
  }
140
+ if (requestedTreeEnabled && rowKey === undefined) {
141
+ console.warn('[BGrid] tree requires rowKey. Falling back to flat rows.');
142
+ }
143
+ if (pivotEnabled && requestedTreeEnabled) {
144
+ console.warn('[BGrid] tree is disabled while pivot mode is active.');
145
+ }
146
+ if (treeEnabled && cellMergeOptions) {
147
+ console.warn('[BGrid] cellMergeOptions is disabled while tree mode is active.');
148
+ }
149
+ if (treeEnabled && reorder?.enabled) {
150
+ console.warn('[BGrid] Row reordering is disabled while tree mode is active.');
151
+ }
133
152
  if (hasHiddenColumns && columnSortable) {
134
153
  console.warn('[BGrid] Column reordering is disabled while columns are hidden.');
135
154
  }
@@ -166,6 +185,10 @@ export function BGrid({ ref, width, height, headerHeight = 30, footerHeight, sum
166
185
  hasHiddenColumns,
167
186
  visibilityOptions?.hiddenColumnIds,
168
187
  visibilityOptions?.onChange,
188
+ cellMergeOptions,
189
+ requestedTreeEnabled,
190
+ rowKey,
191
+ treeEnabled,
169
192
  ]);
170
193
  const duplicateToolboxColumnIds = React.useMemo(() => {
171
194
  const duplicates = findDuplicateColumnIds(resolvedColumns, true);
@@ -218,9 +241,33 @@ export function BGrid({ ref, width, height, headerHeight = 30, footerHeight, sum
218
241
  resolvedColumns,
219
242
  visibilityEnabled,
220
243
  ]);
244
+ const resolvedTreeColumnId = React.useMemo(() => {
245
+ if (!treeEnabled || visibilityProjection.columns.length === 0)
246
+ return undefined;
247
+ const requestedId = tree?.treeColumnId;
248
+ return requestedId &&
249
+ visibilityProjection.columns.some(column => getColumnId(column) === requestedId)
250
+ ? requestedId
251
+ : getColumnId(visibilityProjection.columns[0]);
252
+ }, [tree?.treeColumnId, treeEnabled, visibilityProjection.columns]);
221
253
  const computedColumns = React.useMemo(() => {
222
- return computeModelColumns(visibilityProjection.columns, resolvedFrozenColumnIndex, duplicateToolboxColumnIds);
223
- }, [duplicateToolboxColumnIds, resolvedFrozenColumnIndex, visibilityProjection.columns]);
254
+ const columns = computeModelColumns(visibilityProjection.columns, resolvedFrozenColumnIndex, duplicateToolboxColumnIds);
255
+ if (!resolvedTreeColumnId)
256
+ return columns;
257
+ return columns.map(column => (column.columnId === resolvedTreeColumnId ? { ...column, treeCell: true } : column));
258
+ }, [duplicateToolboxColumnIds, resolvedFrozenColumnIndex, resolvedTreeColumnId, visibilityProjection.columns]);
259
+ const warnedTreeColumnIdRef = React.useRef(undefined);
260
+ React.useEffect(() => {
261
+ if (process.env.NODE_ENV === 'production' ||
262
+ !treeEnabled ||
263
+ !tree?.treeColumnId ||
264
+ tree.treeColumnId === resolvedTreeColumnId ||
265
+ warnedTreeColumnIdRef.current === tree.treeColumnId) {
266
+ return;
267
+ }
268
+ warnedTreeColumnIdRef.current = tree.treeColumnId;
269
+ console.warn(`[BGrid] tree.treeColumnId "${tree.treeColumnId}" is not visible. Falling back to the first visible column.`);
270
+ }, [resolvedTreeColumnId, tree?.treeColumnId, treeEnabled]);
224
271
  const queryColumns = React.useMemo(() => computeModelColumns(resolvedColumns, pivotEnabled ? 0 : frozenColumnIndex, duplicateToolboxColumnIds), [duplicateToolboxColumnIds, frozenColumnIndex, pivotEnabled, resolvedColumns]);
225
272
  const projectedOnChangeColumns = React.useCallback((visibleColumnIndex, info) => {
226
273
  if (!baseOnChangeColumns)
@@ -305,8 +352,9 @@ export function BGrid({ ref, width, height, headerHeight = 30, footerHeight, sum
305
352
  filterParams: [],
306
353
  };
307
354
  }, [resolvedDataControl, resolvedSort]);
308
- // Process data in client mode
309
- const processedResult = React.useMemo(() => {
355
+ // Apply the existing client query first. Tree mode uses its result as sibling order
356
+ // and as the direct-match set, then restores the hierarchy during projection.
357
+ const queryProcessedResult = React.useMemo(() => {
310
358
  if (resolvedDataControl?.mode === 'client') {
311
359
  return processDataQuery({
312
360
  data: resolvedData,
@@ -323,7 +371,111 @@ export function BGrid({ ref, width, height, headerHeight = 30, footerHeight, sum
323
371
  visibleIndexBySourceIndex: undefined,
324
372
  };
325
373
  }, [queryColumns, resolvedData, resolvedDataControl?.mode, resolvedDataQuery, resolvedRowKey]);
374
+ const treeProjection = React.useMemo(() => {
375
+ if (!treeEnabled || !tree || resolvedRowKey === undefined)
376
+ return undefined;
377
+ const clientQuery = resolvedDataControl?.mode === 'client';
378
+ return projectTreeData({
379
+ data: resolvedData,
380
+ rowKey: resolvedRowKey,
381
+ parentRowKey: tree.parentRowKey,
382
+ expandedRowKeys,
383
+ includedSourceIndexes: clientQuery && resolvedDataQuery.filterParams.length > 0
384
+ ? queryProcessedResult.sourceIndexByVisibleIndex
385
+ : undefined,
386
+ orderedSourceIndexes: clientQuery && resolvedDataQuery.sortParams.length > 0
387
+ ? queryProcessedResult.sourceIndexByVisibleIndex
388
+ : undefined,
389
+ });
390
+ }, [
391
+ expandedRowKeys,
392
+ queryProcessedResult.sourceIndexByVisibleIndex,
393
+ resolvedData,
394
+ resolvedDataControl?.mode,
395
+ resolvedDataQuery.filterParams.length,
396
+ resolvedDataQuery.sortParams.length,
397
+ resolvedRowKey,
398
+ tree,
399
+ treeEnabled,
400
+ ]);
401
+ React.useEffect(() => {
402
+ if (process.env.NODE_ENV === 'production' || !treeProjection || treeProjection.valid)
403
+ return;
404
+ console.warn(`[BGrid] Invalid tree data (${treeProjection.diagnostics.join(', ')}). Falling back to flat rows.`);
405
+ }, [treeProjection]);
406
+ const resolvedTreeProjection = treeProjection?.valid ? treeProjection : undefined;
407
+ const processedResult = resolvedTreeProjection
408
+ ? {
409
+ rows: [],
410
+ data: resolvedTreeProjection.data,
411
+ sourceIndexByVisibleIndex: resolvedTreeProjection.sourceIndexByVisibleIndex,
412
+ visibleIndexBySourceIndex: resolvedTreeProjection.visibleIndexBySourceIndex,
413
+ }
414
+ : queryProcessedResult;
326
415
  const displayData = processedResult.data;
416
+ const resolvedOnChangeData = React.useMemo(() => {
417
+ if (!baseOnChangeData)
418
+ return undefined;
419
+ if (!resolvedTreeProjection)
420
+ return baseOnChangeData;
421
+ return (index, columnIndex, item, column, meta) => {
422
+ const treeMeta = resolvedTreeProjection.metaBySourceIndex.get(index);
423
+ if (!meta || !treeMeta) {
424
+ baseOnChangeData(index, columnIndex, item, column, meta);
425
+ return;
426
+ }
427
+ baseOnChangeData(index, columnIndex, item, column, {
428
+ ...meta,
429
+ tree: {
430
+ rowKey: treeMeta.rowKey,
431
+ parentRowKey: treeMeta.parentRowKey,
432
+ sourceIndex: treeMeta.sourceIndex,
433
+ depth: treeMeta.depth,
434
+ path: getTreeRowPath(resolvedTreeProjection.metaBySourceIndex, index),
435
+ },
436
+ });
437
+ };
438
+ }, [baseOnChangeData, resolvedTreeProjection]);
439
+ const toggleTreeRow = React.useCallback((meta) => {
440
+ if (resolvedDisabled || !tree || !meta.hasChildren)
441
+ return;
442
+ const nextExpanded = meta.expanded
443
+ ? expandedRowKeys.filter(key => !Object.is(key, meta.rowKey))
444
+ : expandedRowKeys.some(key => Object.is(key, meta.rowKey))
445
+ ? [...expandedRowKeys]
446
+ : [...expandedRowKeys, meta.rowKey];
447
+ if (tree.expandedRowKeys === undefined)
448
+ setUncontrolledExpandedRowKeys(nextExpanded);
449
+ const item = resolvedData[meta.sourceIndex];
450
+ if (item) {
451
+ tree.onExpandedRowKeysChange?.(nextExpanded, {
452
+ rowKey: meta.rowKey,
453
+ expanded: !meta.expanded,
454
+ item,
455
+ sourceIndex: meta.sourceIndex,
456
+ });
457
+ }
458
+ }, [expandedRowKeys, resolvedData, resolvedDisabled, tree]);
459
+ const treeContextValue = React.useMemo(() => {
460
+ if (!resolvedTreeProjection || !resolvedTreeColumnId || resolvedRowKey === undefined || !tree)
461
+ return undefined;
462
+ const indentSize = Number.isFinite(tree.indentSize) && (tree.indentSize ?? -1) >= 0 ? tree.indentSize : 16;
463
+ return {
464
+ treeColumnId: resolvedTreeColumnId,
465
+ indentSize,
466
+ icons: tree.icons,
467
+ expandAriaLabel: tree.expandAriaLabel ?? 'Expand row',
468
+ collapseAriaLabel: tree.collapseAriaLabel ?? 'Collapse row',
469
+ disabled: resolvedDisabled,
470
+ metaByRowKey: resolvedTreeProjection.metaByRowKey,
471
+ metaBySourceIndex: resolvedTreeProjection.metaBySourceIndex,
472
+ getRowKey: values => {
473
+ const key = getCellValueByRowKey(resolvedRowKey, values);
474
+ return key === undefined || key === null ? undefined : key;
475
+ },
476
+ toggle: toggleTreeRow,
477
+ };
478
+ }, [resolvedDisabled, resolvedRowKey, resolvedTreeColumnId, resolvedTreeProjection, toggleTreeRow, tree]);
327
479
  const rowHeightMetrics = React.useMemo(() => createRowHeightMetrics(displayData, itemHeight + itemPadding * 2, getRowHeight), [displayData, getRowHeight, itemHeight, itemPadding]);
328
480
  const resolvedFrozenRowCount = pivotEnabled
329
481
  ? 0
@@ -532,60 +684,63 @@ export function BGrid({ ref, width, height, headerHeight = 30, footerHeight, sum
532
684
  initialShowBottomBar,
533
685
  ]);
534
686
  return (React.createElement(AppStoreProvider, { initialState: initialStoreState },
535
- React.createElement(Table, { ref: ref, columns: computedColumns,
536
- columnsGroup: visibilityProjection.columnsGroup,
537
- columnGroups: visibilityProjection.columnGroups,
538
- onChangeColumns: resolvedOnChangeColumns,
539
- width,
540
- height,
541
- className,
542
- style,
543
- loading,
544
- disabled: resolvedDisabled,
545
- spinning,
546
- scrollLeft,
547
- scrollTop,
548
- headerHeight,
549
- footerHeight,
550
- bottomBarHeight: resolvedBottomBarHeight,
551
- scrollbar: resolvedScrollbar,
552
- status: resolvedStatus,
553
- pagination: resolvedPagination,
554
- summaryHeight,
555
- itemHeight,
556
- itemPadding,
557
- rowHeightMetrics,
558
- frozenColumnIndex: resolvedFrozenColumnIndex,
559
- frozenRowCount: resolvedFrozenRowCount,
560
- rowChecked: resolvedRowChecked,
561
- checkedIndexesMap,
562
- sort: resolvedSort,
563
- sortParams,
564
- dataQuery: resolvedDataQuery,
565
- dataControl: resolvedDataControl,
566
- icons,
567
- columnVisibilityState,
568
- searchOptions: resolvedSearchOptions,
569
- contextMenuOptions: resolvedContextMenuOptions,
570
- page: resolvedPage,
571
- data: displayData,
572
- sourceData: resolvedData,
573
- sourceIndexByVisibleIndex: processedResult.sourceIndexByVisibleIndex,
574
- visibleIndexBySourceIndex: processedResult.visibleIndexBySourceIndex,
575
- onClick: resolvedOnClick,
576
- rowKey: resolvedRowKey,
577
- selectedRowKey: resolvedSelectedRowKey,
578
- editable: resolvedEditable,
579
- editTrigger,
580
- onChangeData: resolvedOnChangeData,
581
- showLineNumber: resolvedShowLineNumber,
582
- msg,
583
- getRowClassName: resolvedGetRowClassName,
584
- cellMergeOptions: resolvedCellMergeOptions,
585
- cellSelectionOptions: resolvedCellSelectionOptions,
586
- cellNavigationOptions,
587
- variant,
588
- summary: resolvedSummary,
589
- columnSortable: resolvedColumnSortable,
590
- reorder: resolvedReorder })));
687
+ React.createElement(TreeContext.Provider, { value: treeContextValue },
688
+ React.createElement(Table, { ref: ref, sourceColumns: resolvedColumns,
689
+ columns: computedColumns,
690
+ columnsGroup: visibilityProjection.columnsGroup,
691
+ columnGroups: visibilityProjection.columnGroups,
692
+ onChangeColumns: resolvedOnChangeColumns,
693
+ width,
694
+ height,
695
+ className,
696
+ style,
697
+ loading,
698
+ disabled: resolvedDisabled,
699
+ spinning,
700
+ scrollLeft,
701
+ scrollTop,
702
+ headerHeight,
703
+ footerHeight,
704
+ bottomBarHeight: resolvedBottomBarHeight,
705
+ scrollbar: resolvedScrollbar,
706
+ status: resolvedStatus,
707
+ pagination: resolvedPagination,
708
+ summaryHeight,
709
+ itemHeight,
710
+ itemPadding,
711
+ rowHeightMetrics,
712
+ frozenColumnIndex: resolvedFrozenColumnIndex,
713
+ frozenRowCount: resolvedFrozenRowCount,
714
+ rowChecked: resolvedRowChecked,
715
+ checkedIndexesMap,
716
+ sort: resolvedSort,
717
+ sortParams,
718
+ dataQuery: resolvedDataQuery,
719
+ dataControl: resolvedDataControl,
720
+ icons,
721
+ columnVisibilityState,
722
+ searchOptions: resolvedSearchOptions,
723
+ contextMenuOptions: resolvedContextMenuOptions,
724
+ page: resolvedPage,
725
+ data: displayData,
726
+ sourceData: resolvedData,
727
+ sourceIndexByVisibleIndex: processedResult.sourceIndexByVisibleIndex,
728
+ visibleIndexBySourceIndex: processedResult.visibleIndexBySourceIndex,
729
+ onClick: resolvedOnClick,
730
+ rowKey: resolvedRowKey,
731
+ selectedRowKey: resolvedSelectedRowKey,
732
+ editable: resolvedEditable,
733
+ editTrigger,
734
+ onChangeData: resolvedOnChangeData,
735
+ showLineNumber: resolvedShowLineNumber,
736
+ msg,
737
+ getRowClassName: resolvedGetRowClassName,
738
+ cellMergeOptions: resolvedCellMergeOptions,
739
+ cellSelectionOptions: resolvedCellSelectionOptions,
740
+ cellNavigationOptions,
741
+ variant,
742
+ summary: resolvedSummary,
743
+ columnSortable: resolvedColumnSortable,
744
+ reorder: resolvedReorder,
745
+ treeEnabled: !!treeContextValue }))));
591
746
  }
@@ -1,9 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import { useCallback, useEffect, useRef } from 'react';
3
3
  import { useShallow } from 'zustand/react/shallow';
4
- import { useAppStore } from '../store';
4
+ import { useAppStore, useAppStoreApi } from '../store';
5
5
  import { BGridDataItemStatus, } from '../types';
6
- import { getCellValueByRowKey, getFrozenColumnsWidth, getRowIndexAtOffset, getVisibleScrollableRowRange, getVirtualScrollWindowMetrics, getVirtualScrollWindowPosition, isCheckboxValueChecked, markCellEdited, markCellValueChanged, rebaseVirtualScrollWindow, resolveLogicalCell, shouldRenderBottomBar, } from '../utils';
6
+ import { getCellValueByRowKey, getFrozenColumnsWidth, getRowIndexAtOffset, getVisibleScrollableRowRange, getVirtualScrollWindowMetrics, getVirtualScrollWindowPosition, isCheckboxValueChecked, markCellEdited, markCellValueChanged, rebaseVirtualScrollWindow, resolveLogicalCell, shouldRenderBottomBar, createGridExportData, serializeGridExportDataToCsv, downloadCsv, } from '../utils';
7
7
  import { clamp, ensureCellVisible } from '../utils/coordinate';
8
8
  import Loading from './Loading';
9
9
  import TableBody, { createRowKeyRegistry } from './TableBody';
@@ -29,6 +29,7 @@ const LazyGridOptionalSurfaces = React.lazy(() => import('./GridOptionalSurfaces
29
29
  default: module.GridOptionalSurfaces,
30
30
  })));
31
31
  function Table(props) {
32
+ const store = useAppStoreApi();
32
33
  const { cellSelectionOptions, onChangeData, sourceIndexByVisibleIndex } = props;
33
34
  const cellSelectionEnabled = cellSelectionOptions?.enabled ?? true;
34
35
  const rowKeyRegistryState = useRef({
@@ -507,11 +508,42 @@ function Table(props) {
507
508
  const scrollPropsRef = useRef({ top: props.scrollTop, left: props.scrollLeft });
508
509
  const syncedDataPropRef = useRef(props.data);
509
510
  const [rowScrollRequest, setRowScrollRequest] = React.useState(null);
511
+ const sourceColumnsRef = useRef(props.sourceColumns);
512
+ sourceColumnsRef.current = props.sourceColumns;
513
+ const getCurrentExportData = useCallback((options) => {
514
+ const state = store.getState();
515
+ return createGridExportData({
516
+ options,
517
+ columns: state.columns,
518
+ sourceColumns: sourceColumnsRef.current,
519
+ data: state.data,
520
+ sourceData: state.sourceData,
521
+ sourceIndexByVisibleIndex: state.sourceIndexByVisibleIndex,
522
+ visibleIndexBySourceIndex: state.visibleIndexBySourceIndex,
523
+ checkedIndexesMap: state.checkedIndexesMap,
524
+ rowKey: state.rowKey,
525
+ });
526
+ }, [store]);
510
527
  React.useImperativeHandle(props.ref, () => ({
511
528
  scrollToRow(rowIndex, options) {
512
529
  setRowScrollRequest({ rowIndex, align: options?.align ?? 'nearest' });
513
530
  },
514
- }), []);
531
+ getExportData(options) {
532
+ return getCurrentExportData(options);
533
+ },
534
+ exportCsv(options) {
535
+ const exportData = getCurrentExportData(options);
536
+ const csv = serializeGridExportDataToCsv(exportData, options);
537
+ downloadCsv(csv, options?.fileName ?? 'export.csv');
538
+ },
539
+ exportExcel(options) {
540
+ const exportData = getCurrentExportData(options);
541
+ void import('../utils/exportExcel').then(({ serializeGridExportDataToExcel, downloadExcel }) => {
542
+ const excelBytes = serializeGridExportDataToExcel(exportData, options);
543
+ downloadExcel(excelBytes, options?.fileName ?? 'export.xlsx');
544
+ });
545
+ },
546
+ }), [getCurrentExportData]);
515
547
  const scrollRafRef = useRef(null);
516
548
  const scrollIdleTimerRef = useRef(null);
517
549
  const selectionAutoScrollRafRef = useRef(null);
@@ -2151,7 +2183,7 @@ function Table(props) {
2151
2183
  };
2152
2184
  }, []);
2153
2185
  return (React.createElement(EditorPortalContext.Provider, { value: editorPortalContext },
2154
- React.createElement(Container, { ref: containerRef, role: 'grid', style: {
2186
+ React.createElement(Container, { ref: containerRef, role: props.treeEnabled ? 'treegrid' : 'grid', style: {
2155
2187
  ...style,
2156
2188
  width,
2157
2189
  height,