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.
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import { PluginCellEditor } from './PluginCellEditor';
3
3
  import { CellEditorIcon } from './CellEditorIcon';
4
4
  import { CheckboxCellEditor } from './CheckboxCellEditor';
5
+ import { TreeCell } from './TreeCell';
5
6
  function Cell({ index, hostIndex = index, columnIndex, column, item, valueByRowKey, handleSave, handleCancel, handleMove, editable, cellEditable, interactionEditing = false, editSession, }) {
6
7
  const isPluginEditorActive = Boolean(editable && editSession && column.editor?.type === 'plugin');
7
8
  let content;
@@ -36,7 +37,7 @@ function Cell({ index, hostIndex = index, columnIndex, column, item, valueByRowK
36
37
  content = renderSafeCellValue(valueByRowKey);
37
38
  }
38
39
  return (React.createElement("div", { className: isPluginEditorActive ? 'bgrid-cell-content bgrid-cell-content-plugin-editor' : 'bgrid-cell-content' },
39
- React.createElement("div", { className: 'bgrid-cell-value' }, content),
40
+ React.createElement("div", { className: 'bgrid-cell-value' }, column.treeCell ? (React.createElement(TreeCell, { column: column, item: item }, content)) : (content)),
40
41
  column.editorIcon && column.editor?.type !== 'checkbox' && cellEditable && (React.createElement(CellEditorIcon, { hostCell: { rowIndex: hostIndex, columnIndex }, index: index, columnIndex: columnIndex, column: column, item: item, value: valueByRowKey, editing: interactionEditing }))));
41
42
  }
42
43
  function renderSafeCellValue(value, preserveRenderableArrays = false) {
@@ -4,6 +4,7 @@ import { getCellValueByRowKey, isCellEdited, isCellValueChanged, resolveLogicalC
4
4
  import { TableBodyCell } from './TableBodyCell';
5
5
  import RowSelector from './RowSelector';
6
6
  import { GripVertical } from './GripVertical';
7
+ import { useTreeContext } from './TreeContext';
7
8
  function TableBodyCellItemInner({ ri, columnIndex, column, canonicalIndex, canonicalItem, valueByRowKey, editable, disabled, editTrigger, rowSpan, isMerged, tdEditable, cellEditable, isLogicalEditing, isCheckboxEditor, isSearchMatch, isCurrentSearchMatch, cellInteractionSession, handleClick, setEditItem, setItemValue, handleMoveEditFocus, }) {
8
9
  const resolvedEditTrigger = column.editTrigger ?? editTrigger ?? 'dblclick';
9
10
  const edited = isCellEdited(canonicalItem, column);
@@ -55,9 +56,12 @@ function TableBodyCellItemInner({ ri, columnIndex, column, canonicalIndex, canon
55
56
  }
56
57
  const TableBodyCellItem = React.memo(TableBodyCellItemInner);
57
58
  function TableBodyRowInner({ item, data, ri, sourceIndex, columns, startCIdx, endCIdx, frozenColumnIndex, isLeftRegion, showLineNumber, hasRowChecked, isRadio, checked, rowCheckedDisabled, rowStatus, active, className, resolvedRowHeight, itemHeight, itemPadding, editable, disabled, editTrigger, hasOnClick, cellMergeOptions, mergeColumns, odd, variant, rowReorderEnabled, rowReorderRole, rowReorderPhase, rowReorderDirection, rowReorderOffset, reorder, reorderingInfo, searchMatchTokens, currentSearchToken, isRowEditing, cellInteractionSession, handleClick, setEditItem, setItemValue, handleMoveEditFocus, handleChangeChecked, handleChangeCheckedRadio, getRowSpan, onRowReorderPointerDown, onRowReorderKeyDown, }) {
59
+ const tree = useTreeContext();
60
+ const treeRowKey = tree?.getRowKey(item.values);
61
+ const treeMeta = treeRowKey === undefined ? undefined : tree?.metaByRowKey.get(treeRowKey);
58
62
  const clickable = !editable && hasOnClick;
59
63
  const rowClassName = ['bgrid-body-row', active ? 'bgrid-row-active' : '', className].filter(Boolean).join(' ');
60
- return (React.createElement("tr", { className: rowClassName, "data-ri": ri, "data-odd": !mergeColumns && odd ? 'true' : undefined, "data-clickable": clickable ? 'true' : undefined, "data-bgrid-row-reorder-role": rowReorderRole, "data-bgrid-row-reorder-phase": rowReorderRole ? rowReorderPhase : undefined, "data-bgrid-row-reorder-direction": rowReorderRole ? rowReorderDirection : undefined, style: {
64
+ return (React.createElement("tr", { className: rowClassName, "data-ri": ri, "data-bgrid-tree-row": treeMeta ? 'true' : undefined, "aria-level": treeMeta ? treeMeta.depth + 1 : undefined, "aria-posinset": treeMeta ? treeMeta.siblingIndex + 1 : undefined, "aria-setsize": treeMeta?.siblingCount, "aria-expanded": treeMeta?.hasChildren ? treeMeta.expanded : undefined, "data-odd": !mergeColumns && odd ? 'true' : undefined, "data-clickable": clickable ? 'true' : undefined, "data-bgrid-row-reorder-role": rowReorderRole, "data-bgrid-row-reorder-phase": rowReorderRole ? rowReorderPhase : undefined, "data-bgrid-row-reorder-direction": rowReorderRole ? rowReorderDirection : undefined, style: {
61
65
  ['--bgrid-item-line-height']: `${itemHeight}px`,
62
66
  ['--bgrid-item-cell-height']: `${resolvedRowHeight}px`,
63
67
  ...(rowReorderRole && rowReorderRole !== 'source'
@@ -0,0 +1,49 @@
1
+ import * as React from 'react';
2
+ import { useAppStoreApi } from '../store';
3
+ import { getColumnId } from '../utils';
4
+ import { useTreeContext } from './TreeContext';
5
+ export function TreeCell({ column, item, children }) {
6
+ const tree = useTreeContext();
7
+ const store = useAppStoreApi();
8
+ if (!tree || getColumnId(column) !== tree.treeColumnId)
9
+ return React.createElement(React.Fragment, null, children);
10
+ const key = tree.getRowKey(item.values);
11
+ const meta = key === undefined ? undefined : tree.metaByRowKey.get(key);
12
+ if (!meta)
13
+ return React.createElement(React.Fragment, null, children);
14
+ return (React.createElement("span", { className: 'bgrid-tree-cell', style: {
15
+ '--bgrid-tree-depth': meta.depth,
16
+ '--bgrid-tree-indent-size': `${tree.indentSize}px`,
17
+ } },
18
+ meta.hasChildren ? (React.createElement("button", { type: 'button', className: 'bgrid-tree-toggle', "aria-expanded": meta.expanded, "aria-label": meta.expanded ? tree.collapseAriaLabel : tree.expandAriaLabel, disabled: tree.disabled, onClick: event => {
19
+ event.stopPropagation();
20
+ if (meta.expanded) {
21
+ const state = store.getState();
22
+ const activeCell = state.activeCell;
23
+ const activeSourceIndex = activeCell === undefined
24
+ ? undefined
25
+ : state.sourceIndexByVisibleIndex?.[activeCell.rowIndex] ?? activeCell.rowIndex;
26
+ let activeMeta = activeSourceIndex === undefined ? undefined : tree.metaBySourceIndex.get(activeSourceIndex);
27
+ let activeIsDescendant = false;
28
+ while (activeMeta?.parentSourceIndex !== undefined) {
29
+ if (activeMeta.parentSourceIndex === meta.sourceIndex) {
30
+ activeIsDescendant = true;
31
+ break;
32
+ }
33
+ activeMeta = tree.metaBySourceIndex.get(activeMeta.parentSourceIndex);
34
+ }
35
+ state.clearCellSelection();
36
+ if (activeCell && activeIsDescendant) {
37
+ state.endCellEdit();
38
+ state.setActiveCell({
39
+ rowIndex: state.visibleIndexBySourceIndex?.get(meta.sourceIndex) ?? 0,
40
+ columnIndex: activeCell.columnIndex,
41
+ });
42
+ }
43
+ }
44
+ tree.toggle(meta);
45
+ } },
46
+ React.createElement("span", { className: 'bgrid-tree-icon', "aria-hidden": 'true' }, meta.expanded ? tree.icons?.expanded ?? '⌄' : tree.icons?.collapsed ?? '›'))) : (React.createElement("span", { className: 'bgrid-tree-toggle-spacer', "aria-hidden": 'true' },
47
+ React.createElement("span", { className: 'bgrid-tree-icon' }, tree.icons?.leaf))),
48
+ React.createElement("span", { className: 'bgrid-tree-cell-content' }, children)));
49
+ }
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+ export const TreeContext = React.createContext(undefined);
3
+ export function useTreeContext() {
4
+ return React.useContext(TreeContext);
5
+ }
@@ -0,0 +1,97 @@
1
+ export function formatCsvCellValue(value, preventFormulaInjection = true) {
2
+ if (value === null || value === undefined) {
3
+ return '';
4
+ }
5
+ let formatted;
6
+ if (typeof value === 'string') {
7
+ formatted = value;
8
+ const startsWithFormula = /^[=+\-@]/.test(formatted);
9
+ const startsWithDangerousControl = /^[\t\r\n]/.test(formatted);
10
+ const hasFormulaAfterLeadingWhitespace = /^\s+[=+\-@]/.test(formatted);
11
+ if (preventFormulaInjection &&
12
+ (startsWithFormula || startsWithDangerousControl || hasFormulaAfterLeadingWhitespace)) {
13
+ formatted = `'${formatted}`;
14
+ }
15
+ return formatted;
16
+ }
17
+ if (typeof value === 'number') {
18
+ return String(value);
19
+ }
20
+ if (typeof value === 'boolean') {
21
+ return value ? 'true' : 'false';
22
+ }
23
+ if (typeof value === 'bigint') {
24
+ return value.toString();
25
+ }
26
+ if (value instanceof Date) {
27
+ return isNaN(value.getTime()) ? 'Invalid Date' : value.toISOString();
28
+ }
29
+ if (typeof value === 'object') {
30
+ try {
31
+ formatted = JSON.stringify(value);
32
+ }
33
+ catch {
34
+ formatted = String(value);
35
+ }
36
+ return formatted;
37
+ }
38
+ return String(value);
39
+ }
40
+ export function escapeCsvCell(cell, delimiter = ',') {
41
+ const needsQuotes = cell.includes(delimiter) ||
42
+ cell.includes('"') ||
43
+ cell.includes('\r') ||
44
+ cell.includes('\n');
45
+ if (needsQuotes) {
46
+ return `"${cell.replace(/"/g, '""')}"`;
47
+ }
48
+ return cell;
49
+ }
50
+ export function serializeGridExportDataToCsv(data, options) {
51
+ const delimiter = options?.delimiter ?? ',';
52
+ const newline = options?.newline ?? '\r\n';
53
+ const includeHeader = options?.includeHeader ?? true;
54
+ const bom = options?.bom ?? true;
55
+ const preventFormulaInjection = options?.preventFormulaInjection ?? true;
56
+ const lines = [];
57
+ if (includeHeader) {
58
+ const headerLine = data.columns
59
+ .map(col => escapeCsvCell(formatCsvCellValue(col.header, preventFormulaInjection), delimiter))
60
+ .join(delimiter);
61
+ lines.push(headerLine);
62
+ }
63
+ for (const row of data.rows) {
64
+ const rowLine = row.cells
65
+ .map(cell => escapeCsvCell(formatCsvCellValue(cell, preventFormulaInjection), delimiter))
66
+ .join(delimiter);
67
+ lines.push(rowLine);
68
+ }
69
+ const csvText = lines.join(newline);
70
+ return (bom ? '\uFEFF' : '') + csvText;
71
+ }
72
+ export function downloadCsv(csv, fileName = 'export.csv') {
73
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
74
+ return;
75
+ }
76
+ let resolvedName = (fileName || '').trim();
77
+ if (!resolvedName) {
78
+ resolvedName = 'export.csv';
79
+ }
80
+ else if (!resolvedName.toLowerCase().endsWith('.csv')) {
81
+ resolvedName += '.csv';
82
+ }
83
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
84
+ if (typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function') {
85
+ const url = URL.createObjectURL(blob);
86
+ const link = document.createElement('a');
87
+ link.href = url;
88
+ link.setAttribute('download', resolvedName);
89
+ link.style.display = 'none';
90
+ document.body.appendChild(link);
91
+ link.click();
92
+ document.body.removeChild(link);
93
+ if (typeof URL.revokeObjectURL === 'function') {
94
+ URL.revokeObjectURL(url);
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,154 @@
1
+ import { getColumnId } from './getColumnId';
2
+ import { getCellValueByRowKey } from './getCellValue';
3
+ export function resolveExportColumns(columns, sourceColumns, scope = 'visible') {
4
+ let selectedColumns;
5
+ if (Array.isArray(scope)) {
6
+ const candidatePool = sourceColumns && sourceColumns.length > 0 ? sourceColumns : columns;
7
+ const columnMap = new Map();
8
+ for (const col of candidatePool) {
9
+ columnMap.set(getColumnId(col), col);
10
+ }
11
+ selectedColumns = [];
12
+ for (const id of scope) {
13
+ const col = columnMap.get(id);
14
+ if (col) {
15
+ selectedColumns.push(col);
16
+ }
17
+ }
18
+ }
19
+ else if (scope === 'all') {
20
+ selectedColumns = sourceColumns && sourceColumns.length > 0 ? [...sourceColumns] : [...columns];
21
+ }
22
+ else {
23
+ // 'visible'
24
+ selectedColumns = [...columns];
25
+ }
26
+ const exportableColumns = selectedColumns.filter(col => col.exportable !== false);
27
+ return exportableColumns.map((column, columnIndex) => {
28
+ const columnId = getColumnId(column);
29
+ let header;
30
+ if (typeof column.exportHeader === 'function') {
31
+ header = column.exportHeader(column);
32
+ }
33
+ else if (typeof column.exportHeader === 'string') {
34
+ header = column.exportHeader;
35
+ }
36
+ else if (typeof column.label === 'string' || typeof column.label === 'number') {
37
+ header = String(column.label);
38
+ }
39
+ else {
40
+ header = columnId;
41
+ }
42
+ return {
43
+ columnId,
44
+ header,
45
+ column,
46
+ columnIndex,
47
+ };
48
+ });
49
+ }
50
+ export function resolveExportRows(exportColumns, params) {
51
+ const rowScope = params.options?.rows ?? 'displayed';
52
+ const rows = [];
53
+ const sourcePool = params.sourceData && params.sourceData.length > 0 ? params.sourceData : params.data;
54
+ const buildCells = (item, values, sourceIndex, visibleIndex) => {
55
+ const cells = [];
56
+ for (const expCol of exportColumns) {
57
+ const rawValue = getCellValueByRowKey(expCol.column.key, values);
58
+ if (expCol.column.getExportValue) {
59
+ cells.push(expCol.column.getExportValue({
60
+ column: expCol.column,
61
+ columnId: expCol.columnId,
62
+ visibleIndex,
63
+ sourceIndex,
64
+ item,
65
+ values,
66
+ value: rawValue,
67
+ }));
68
+ }
69
+ else {
70
+ cells.push(rawValue);
71
+ }
72
+ }
73
+ return cells;
74
+ };
75
+ const resolveRowKey = (item, values) => {
76
+ let rowKey;
77
+ if (params.rowKey !== undefined && values) {
78
+ const rawKey = getCellValueByRowKey(params.rowKey, values);
79
+ if (rawKey !== undefined && rawKey !== null) {
80
+ rowKey = rawKey;
81
+ }
82
+ }
83
+ if (rowKey === undefined && item?.key !== undefined) {
84
+ rowKey = item.key;
85
+ }
86
+ return rowKey;
87
+ };
88
+ if (rowScope === 'displayed') {
89
+ const displayData = params.data;
90
+ for (let visibleIndex = 0; visibleIndex < displayData.length; visibleIndex++) {
91
+ const item = displayData[visibleIndex] ?? { values: {} };
92
+ const sourceIndex = params.sourceIndexByVisibleIndex?.[visibleIndex] ?? visibleIndex;
93
+ const values = item.values ?? {};
94
+ const rowKey = resolveRowKey(item, values);
95
+ const cells = buildCells(item, values, sourceIndex, visibleIndex);
96
+ rows.push({
97
+ visibleIndex,
98
+ sourceIndex,
99
+ rowKey,
100
+ item,
101
+ values,
102
+ cells,
103
+ });
104
+ }
105
+ }
106
+ else if (rowScope === 'checked') {
107
+ const checkedMap = params.checkedIndexesMap;
108
+ for (let sourceIndex = 0; sourceIndex < sourcePool.length; sourceIndex++) {
109
+ if (!checkedMap?.has(sourceIndex))
110
+ continue;
111
+ const item = sourcePool[sourceIndex] ?? { values: {} };
112
+ const visibleIndex = params.visibleIndexBySourceIndex?.get(sourceIndex);
113
+ const values = item.values ?? {};
114
+ const rowKey = resolveRowKey(item, values);
115
+ const cells = buildCells(item, values, sourceIndex, visibleIndex);
116
+ rows.push({
117
+ visibleIndex,
118
+ sourceIndex,
119
+ rowKey,
120
+ item,
121
+ values,
122
+ cells,
123
+ });
124
+ }
125
+ }
126
+ else {
127
+ // 'source'
128
+ for (let sourceIndex = 0; sourceIndex < sourcePool.length; sourceIndex++) {
129
+ const item = sourcePool[sourceIndex] ?? { values: {} };
130
+ const visibleIndex = params.visibleIndexBySourceIndex?.get(sourceIndex);
131
+ const values = item.values ?? {};
132
+ const rowKey = resolveRowKey(item, values);
133
+ const cells = buildCells(item, values, sourceIndex, visibleIndex);
134
+ rows.push({
135
+ visibleIndex,
136
+ sourceIndex,
137
+ rowKey,
138
+ item,
139
+ values,
140
+ cells,
141
+ });
142
+ }
143
+ }
144
+ return rows;
145
+ }
146
+ export function createGridExportData(params) {
147
+ const columnScope = params.options?.columns ?? 'visible';
148
+ const columns = resolveExportColumns(params.columns, params.sourceColumns, columnScope);
149
+ const rows = resolveExportRows(columns, params);
150
+ return {
151
+ columns,
152
+ rows,
153
+ };
154
+ }
@@ -0,0 +1,246 @@
1
+ function makeCrc32Table() {
2
+ const table = new Uint32Array(256);
3
+ for (let i = 0; i < 256; i++) {
4
+ let c = i;
5
+ for (let k = 0; k < 8; k++) {
6
+ c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
7
+ }
8
+ table[i] = c;
9
+ }
10
+ return table;
11
+ }
12
+ const crcTable = makeCrc32Table();
13
+ function calculateCrc32(bytes) {
14
+ let crc = 0xffffffff;
15
+ for (let i = 0; i < bytes.length; i++) {
16
+ crc = (crc >>> 8) ^ crcTable[(crc ^ bytes[i]) & 0xff];
17
+ }
18
+ return (crc ^ 0xffffffff) >>> 0;
19
+ }
20
+ export function escapeXml(value) {
21
+ return value
22
+ .replace(/&/g, '&amp;')
23
+ .replace(/</g, '&lt;')
24
+ .replace(/>/g, '&gt;')
25
+ .replace(/"/g, '&quot;')
26
+ .replace(/'/g, '&apos;')
27
+ .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '');
28
+ }
29
+ export function colIndexToExcelColumn(colIndex) {
30
+ let name = '';
31
+ let n = colIndex;
32
+ while (n >= 0) {
33
+ name = String.fromCharCode((n % 26) + 65) + name;
34
+ n = Math.floor(n / 26) - 1;
35
+ }
36
+ return name;
37
+ }
38
+ function createZipArchive(files) {
39
+ const encoder = new TextEncoder();
40
+ const fileEntries = [];
41
+ let offset = 0;
42
+ for (const file of files) {
43
+ const nameBytes = encoder.encode(file.name);
44
+ const dataBytes = typeof file.data === 'string' ? encoder.encode(file.data) : file.data;
45
+ const crc = calculateCrc32(dataBytes);
46
+ const localHeader = new Uint8Array(30 + nameBytes.length);
47
+ const dv = new DataView(localHeader.buffer);
48
+ dv.setUint32(0, 0x04034b50, true); // Local file header signature
49
+ dv.setUint16(4, 20, true); // Version needed to extract (2.0)
50
+ dv.setUint16(6, 0x0800, true); // Bit flag: UTF-8 filename (bit 11)
51
+ dv.setUint16(8, 0, true); // Compression method: STORE (0)
52
+ dv.setUint16(10, 0, true); // File last mod time
53
+ dv.setUint16(12, 0, true); // File last mod date
54
+ dv.setUint32(14, crc, true); // CRC-32
55
+ dv.setUint32(18, dataBytes.length, true); // Compressed size
56
+ dv.setUint32(22, dataBytes.length, true); // Uncompressed size
57
+ dv.setUint16(26, nameBytes.length, true); // Filename length
58
+ dv.setUint16(28, 0, true); // Extra field length
59
+ localHeader.set(nameBytes, 30);
60
+ fileEntries.push({
61
+ nameBytes,
62
+ dataBytes,
63
+ crc,
64
+ localHeader,
65
+ offset,
66
+ });
67
+ offset += localHeader.length + dataBytes.length;
68
+ }
69
+ const centralDirEntries = [];
70
+ let centralDirSize = 0;
71
+ for (const entry of fileEntries) {
72
+ const cd = new Uint8Array(46 + entry.nameBytes.length);
73
+ const dv = new DataView(cd.buffer);
74
+ dv.setUint32(0, 0x02014b50, true); // Central file header signature
75
+ dv.setUint16(4, 20, true); // Version made by
76
+ dv.setUint16(6, 20, true); // Version needed to extract
77
+ dv.setUint16(8, 0x0800, true); // Bit flag: UTF-8
78
+ dv.setUint16(10, 0, true); // Compression method: STORE
79
+ dv.setUint16(12, 0, true); // File last mod time
80
+ dv.setUint16(14, 0, true); // File last mod date
81
+ dv.setUint32(16, entry.crc, true); // CRC-32
82
+ dv.setUint32(20, entry.dataBytes.length, true); // Compressed size
83
+ dv.setUint32(24, entry.dataBytes.length, true); // Uncompressed size
84
+ dv.setUint16(28, entry.nameBytes.length, true); // Filename length
85
+ dv.setUint16(30, 0, true); // Extra field length
86
+ dv.setUint16(32, 0, true); // File comment length
87
+ dv.setUint16(34, 0, true); // Disk number start
88
+ dv.setUint16(36, 0, true); // Internal file attributes
89
+ dv.setUint32(38, 0, true); // External file attributes
90
+ dv.setUint32(42, entry.offset, true); // Relative offset of local header
91
+ cd.set(entry.nameBytes, 46);
92
+ centralDirEntries.push(cd);
93
+ centralDirSize += cd.length;
94
+ }
95
+ const eocd = new Uint8Array(22);
96
+ const dvEocd = new DataView(eocd.buffer);
97
+ dvEocd.setUint32(0, 0x06054b50, true); // End of central dir signature
98
+ dvEocd.setUint16(4, 0, true); // Number of this disk
99
+ dvEocd.setUint16(6, 0, true); // Number of disk with start of central directory
100
+ dvEocd.setUint16(8, fileEntries.length, true); // Total entries in this disk
101
+ dvEocd.setUint16(10, fileEntries.length, true); // Total entries
102
+ dvEocd.setUint32(12, centralDirSize, true); // Size of the central directory
103
+ dvEocd.setUint32(16, offset, true); // Offset of start of central directory
104
+ dvEocd.setUint16(20, 0, true); // ZIP file comment length
105
+ const totalLength = offset + centralDirSize + 22;
106
+ const result = new Uint8Array(totalLength);
107
+ let pos = 0;
108
+ for (const entry of fileEntries) {
109
+ result.set(entry.localHeader, pos);
110
+ pos += entry.localHeader.length;
111
+ result.set(entry.dataBytes, pos);
112
+ pos += entry.dataBytes.length;
113
+ }
114
+ for (const cd of centralDirEntries) {
115
+ result.set(cd, pos);
116
+ pos += cd.length;
117
+ }
118
+ result.set(eocd, pos);
119
+ return result;
120
+ }
121
+ function formatCellValueAsXml(cellValue, cellRef) {
122
+ if (cellValue === null || cellValue === undefined || cellValue === '') {
123
+ return `<c r="${cellRef}"/>`;
124
+ }
125
+ if (typeof cellValue === 'number') {
126
+ if (Number.isFinite(cellValue)) {
127
+ return `<c r="${cellRef}"><v>${cellValue}</v></c>`;
128
+ }
129
+ return `<c r="${cellRef}" t="inlineStr"><is><t>${escapeXml(String(cellValue))}</t></is></c>`;
130
+ }
131
+ if (typeof cellValue === 'boolean') {
132
+ return `<c r="${cellRef}" t="b"><v>${cellValue ? 1 : 0}</v></c>`;
133
+ }
134
+ if (typeof cellValue === 'bigint') {
135
+ return `<c r="${cellRef}" t="inlineStr"><is><t>${cellValue.toString()}</t></is></c>`;
136
+ }
137
+ if (cellValue instanceof Date) {
138
+ const dateStr = Number.isNaN(cellValue.getTime()) ? 'Invalid Date' : cellValue.toISOString();
139
+ return `<c r="${cellRef}" t="inlineStr"><is><t>${escapeXml(dateStr)}</t></is></c>`;
140
+ }
141
+ let text;
142
+ if (typeof cellValue === 'object') {
143
+ try {
144
+ text = JSON.stringify(cellValue);
145
+ }
146
+ catch {
147
+ text = String(cellValue);
148
+ }
149
+ }
150
+ else {
151
+ text = String(cellValue);
152
+ }
153
+ return `<c r="${cellRef}" t="inlineStr"><is><t xml:space="preserve">${escapeXml(text)}</t></is></c>`;
154
+ }
155
+ export function serializeGridExportDataToExcel(data, options) {
156
+ const includeHeader = options?.includeHeader ?? true;
157
+ let rawSheetName = (options?.sheetName || 'Sheet1').trim().replace(/[:\\/?*\[\]]/g, '');
158
+ if (!rawSheetName) {
159
+ rawSheetName = 'Sheet1';
160
+ }
161
+ else if (rawSheetName.length > 31) {
162
+ rawSheetName = rawSheetName.slice(0, 31);
163
+ }
164
+ const sheetRows = [];
165
+ let rowIndex = 1;
166
+ if (includeHeader) {
167
+ const headerCells = data.columns
168
+ .map((col, colIdx) => {
169
+ const cellRef = `${colIndexToExcelColumn(colIdx)}${rowIndex}`;
170
+ return formatCellValueAsXml(col.header, cellRef);
171
+ })
172
+ .join('');
173
+ sheetRows.push(`<row r="${rowIndex}">${headerCells}</row>`);
174
+ rowIndex++;
175
+ }
176
+ for (const row of data.rows) {
177
+ const rowCells = row.cells
178
+ .map((cell, colIdx) => {
179
+ const cellRef = `${colIndexToExcelColumn(colIdx)}${rowIndex}`;
180
+ return formatCellValueAsXml(cell, cellRef);
181
+ })
182
+ .join('');
183
+ sheetRows.push(`<row r="${rowIndex}">${rowCells}</row>`);
184
+ rowIndex++;
185
+ }
186
+ const contentTypesXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
187
+ '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
188
+ '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
189
+ '<Default Extension="xml" ContentType="application/xml"/>' +
190
+ '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>' +
191
+ '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>' +
192
+ '</Types>';
193
+ const rootRelsXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
194
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
195
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' +
196
+ '</Relationships>';
197
+ const workbookRelsXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
198
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
199
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>' +
200
+ '</Relationships>';
201
+ const workbookXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
202
+ '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' +
203
+ `<sheets><sheet name="${escapeXml(rawSheetName)}" sheetId="1" r:id="rId1"/></sheets>` +
204
+ '</workbook>';
205
+ const worksheetXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
206
+ '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">' +
207
+ `<sheetData>${sheetRows.join('')}</sheetData>` +
208
+ '</worksheet>';
209
+ return createZipArchive([
210
+ { name: '[Content_Types].xml', data: contentTypesXml },
211
+ { name: '_rels/.rels', data: rootRelsXml },
212
+ { name: 'xl/_rels/workbook.xml.rels', data: workbookRelsXml },
213
+ { name: 'xl/workbook.xml', data: workbookXml },
214
+ { name: 'xl/worksheets/sheet1.xml', data: worksheetXml },
215
+ ]);
216
+ }
217
+ export function downloadExcel(blobOrBytes, fileName = 'export.xlsx') {
218
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
219
+ return;
220
+ }
221
+ let resolvedName = (fileName || '').trim();
222
+ if (!resolvedName) {
223
+ resolvedName = 'export.xlsx';
224
+ }
225
+ else if (!resolvedName.toLowerCase().endsWith('.xlsx')) {
226
+ resolvedName += '.xlsx';
227
+ }
228
+ const blob = blobOrBytes instanceof Blob
229
+ ? blobOrBytes
230
+ : new Blob([blobOrBytes], {
231
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
232
+ });
233
+ if (typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function') {
234
+ const url = URL.createObjectURL(blob);
235
+ const link = document.createElement('a');
236
+ link.href = url;
237
+ link.setAttribute('download', resolvedName);
238
+ link.style.display = 'none';
239
+ document.body.appendChild(link);
240
+ link.click();
241
+ document.body.removeChild(link);
242
+ if (typeof URL.revokeObjectURL === 'function') {
243
+ URL.revokeObjectURL(url);
244
+ }
245
+ }
246
+ }
@@ -16,6 +16,7 @@ export * from './number';
16
16
  export * from './buildHeaderMatrix';
17
17
  export * from './getVisibleScrollableRowRange';
18
18
  export * from './virtualScrollWindow';
19
+ export * from './treeData';
19
20
  export * from './rowHeightMetrics';
20
21
  export * from './cellEditState';
21
22
  export * from './getCellSelectionAxisState';
@@ -26,3 +27,6 @@ export * from './rowReorder';
26
27
  export * from './checkboxEditor';
27
28
  export * from './columnVisibility';
28
29
  export * from './checkedIndexes';
30
+ export * from './exportData';
31
+ export * from './exportCsv';
32
+ export * from './exportExcel';