@alaarab/ogrid-core 2.1.3 → 2.1.4

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 (35) hide show
  1. package/dist/esm/index.js +1426 -54
  2. package/package.json +3 -3
  3. package/dist/esm/constants/index.js +0 -3
  4. package/dist/esm/constants/layout.js +0 -13
  5. package/dist/esm/constants/timing.js +0 -10
  6. package/dist/esm/constants/zIndex.js +0 -16
  7. package/dist/esm/types/columnTypes.js +0 -1
  8. package/dist/esm/types/dataGridTypes.js +0 -27
  9. package/dist/esm/types/index.js +0 -2
  10. package/dist/esm/utils/aggregationUtils.js +0 -48
  11. package/dist/esm/utils/cellValue.js +0 -14
  12. package/dist/esm/utils/clientSideData.js +0 -155
  13. package/dist/esm/utils/clipboardHelpers.js +0 -142
  14. package/dist/esm/utils/columnAutosize.js +0 -38
  15. package/dist/esm/utils/columnReorder.js +0 -99
  16. package/dist/esm/utils/columnUtils.js +0 -122
  17. package/dist/esm/utils/dataGridStatusBar.js +0 -15
  18. package/dist/esm/utils/dataGridViewModel.js +0 -206
  19. package/dist/esm/utils/debounce.js +0 -40
  20. package/dist/esm/utils/dom.js +0 -53
  21. package/dist/esm/utils/exportToCsv.js +0 -50
  22. package/dist/esm/utils/fillHelpers.js +0 -47
  23. package/dist/esm/utils/gridContextMenuHelpers.js +0 -80
  24. package/dist/esm/utils/gridRowComparator.js +0 -78
  25. package/dist/esm/utils/index.js +0 -25
  26. package/dist/esm/utils/keyboardNavigation.js +0 -181
  27. package/dist/esm/utils/ogridHelpers.js +0 -67
  28. package/dist/esm/utils/paginationHelpers.js +0 -58
  29. package/dist/esm/utils/selectionHelpers.js +0 -94
  30. package/dist/esm/utils/sortHelpers.js +0 -28
  31. package/dist/esm/utils/statusBarHelpers.js +0 -27
  32. package/dist/esm/utils/undoRedoStack.js +0 -130
  33. package/dist/esm/utils/validation.js +0 -43
  34. package/dist/esm/utils/valueParsers.js +0 -121
  35. package/dist/esm/utils/virtualScroll.js +0 -46
package/dist/esm/index.js CHANGED
@@ -1,54 +1,1426 @@
1
- export { toUserLike, isInSelectionRange, normalizeSelectionRange, } from './types';
2
- // Utils — exportToCsv
3
- export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, } from './utils';
4
- // Utils — cellValue, columnUtils
5
- export { getCellValue } from './utils';
6
- export { flattenColumns, buildHeaderRows } from './utils';
7
- // Utils ogridHelpers
8
- export { isFilterConfig, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, } from './utils';
9
- // Utils — statusBarHelpers, dataGridStatusBar
10
- export { getStatusBarParts } from './utils';
11
- export { getDataGridStatusBarConfig } from './utils';
12
- // Utils paginationHelpers
13
- export { getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, } from './utils';
14
- // Utils gridContextMenuHelpers
15
- export { GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut, } from './utils';
16
- // Utils valueParsers
17
- export { parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, } from './utils';
18
- // Utils — aggregationUtils
19
- export { computeAggregations } from './utils';
20
- // Utils — clientSideData
21
- export { processClientSideData } from './utils';
22
- // Utils — gridRowComparator
23
- export { areGridRowPropsEqual, isRowInRange } from './utils';
24
- // Utils — columnReorder
25
- export { getPinStateForColumn, reorderColumnArray, calculateDropTarget, } from './utils';
26
- // Utils — virtualScroll
27
- export { computeVisibleRange, computeTotalHeight, getScrollTopForRow, } from './utils';
28
- // Utils — dataGridViewModel
29
- export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from './utils';
30
- // Utils — debounce, dom
31
- export { debounce } from './utils';
32
- export { measureRange, injectGlobalStyles } from './utils';
33
- // Utils sortHelpers
34
- export { computeNextSortState } from './utils';
35
- // Utils — columnAutosize
36
- export { measureColumnContentWidth, AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX } from './utils';
37
- // Utils — keyboardNavigation
38
- export { findCtrlArrowTarget, computeTabNavigation, computeArrowNavigation, applyCellDeletion } from './utils';
39
- // Utils selectionHelpers
40
- export { rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, applyRangeRowSelection, computeRowSelectionState } from './utils';
41
- // Utils clipboardHelpers
42
- export { formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, } from './utils';
43
- // Utils fillHelpers
44
- export { applyFillValues } from './utils';
45
- // Utils — undoRedoStack
46
- export { UndoRedoStack } from './utils';
47
- // Utils validation
48
- export { validateColumns, validateRowIds } from './utils';
49
- // Constants layout
50
- export { CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, GRID_BORDER_RADIUS, } from './constants';
51
- // Constants — timing
52
- export { DEFAULT_DEBOUNCE_MS, PEOPLE_SEARCH_DEBOUNCE_MS, SIDEBAR_TRANSITION_MS, } from './constants';
53
- // Constants zIndex
54
- export { Z_INDEX } from './constants';
1
+ // src/types/dataGridTypes.ts
2
+ function toUserLike(u) {
3
+ if (!u) return void 0;
4
+ return {
5
+ id: u.id,
6
+ displayName: u.displayName,
7
+ email: "email" in u && u.email ? u.email : u.mail || u.userPrincipalName || "",
8
+ photo: u.photo
9
+ };
10
+ }
11
+ function isInSelectionRange(range, row, col) {
12
+ const minR = Math.min(range.startRow, range.endRow);
13
+ const maxR = Math.max(range.startRow, range.endRow);
14
+ const minC = Math.min(range.startCol, range.endCol);
15
+ const maxC = Math.max(range.startCol, range.endCol);
16
+ return row >= minR && row <= maxR && col >= minC && col <= maxC;
17
+ }
18
+ function normalizeSelectionRange(range) {
19
+ return {
20
+ startRow: Math.min(range.startRow, range.endRow),
21
+ endRow: Math.max(range.startRow, range.endRow),
22
+ startCol: Math.min(range.startCol, range.endCol),
23
+ endCol: Math.max(range.startCol, range.endCol)
24
+ };
25
+ }
26
+
27
+ // src/utils/exportToCsv.ts
28
+ function escapeCsvValue(value) {
29
+ if (value === null || value === void 0) {
30
+ return "";
31
+ }
32
+ const s = String(value);
33
+ if (s.includes(",") || s.includes('"') || s.includes("\n")) {
34
+ return `"${s.replace(/"/g, '""')}"`;
35
+ }
36
+ return s;
37
+ }
38
+ function buildCsvHeader(columns) {
39
+ return columns.map((c) => escapeCsvValue(c.name)).join(",");
40
+ }
41
+ function buildCsvRows(items, columns, getValue) {
42
+ return items.map(
43
+ (item) => columns.map((c) => escapeCsvValue(getValue(item, c.columnId))).join(",")
44
+ );
45
+ }
46
+ function exportToCsv(items, columns, getValue, filename) {
47
+ const header = buildCsvHeader(columns);
48
+ const rows = buildCsvRows(items, columns, getValue);
49
+ const csv = [header, ...rows].join("\n");
50
+ triggerCsvDownload(csv, filename ?? `export_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`);
51
+ }
52
+ function triggerCsvDownload(csvContent, filename) {
53
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
54
+ const url = URL.createObjectURL(blob);
55
+ const link = document.createElement("a");
56
+ try {
57
+ link.setAttribute("href", url);
58
+ link.setAttribute("download", filename);
59
+ link.style.visibility = "hidden";
60
+ document.body.appendChild(link);
61
+ link.click();
62
+ } finally {
63
+ try {
64
+ document.body.removeChild(link);
65
+ } catch {
66
+ }
67
+ URL.revokeObjectURL(url);
68
+ }
69
+ }
70
+
71
+ // src/utils/cellValue.ts
72
+ function getCellValue(item, col) {
73
+ if (col.valueGetter) return col.valueGetter(item);
74
+ return item[col.columnId];
75
+ }
76
+
77
+ // src/utils/columnUtils.ts
78
+ function isColumnGroupDef(c) {
79
+ return "children" in c && Array.isArray(c.children);
80
+ }
81
+ function flattenColumns(columns) {
82
+ const result = [];
83
+ for (const c of columns) {
84
+ if (isColumnGroupDef(c)) {
85
+ result.push(...flattenColumns(c.children));
86
+ } else {
87
+ result.push(c);
88
+ }
89
+ }
90
+ return result;
91
+ }
92
+ function buildHeaderRows(columns, visibleColumns) {
93
+ function getMaxDepth(cols, depth) {
94
+ let max = depth;
95
+ for (const c of cols) {
96
+ if (isColumnGroupDef(c)) {
97
+ max = Math.max(max, getMaxDepth(c.children, depth + 1));
98
+ }
99
+ }
100
+ return max;
101
+ }
102
+ const maxDepth = getMaxDepth(columns, 0);
103
+ if (maxDepth === 0) {
104
+ const row = [];
105
+ for (const c of columns) {
106
+ if (!isColumnGroupDef(c)) {
107
+ if (visibleColumns && !visibleColumns.has(c.columnId)) continue;
108
+ row.push({
109
+ label: c.name,
110
+ colSpan: 1,
111
+ isGroup: false,
112
+ columnDef: c,
113
+ depth: 0
114
+ });
115
+ }
116
+ }
117
+ return [row];
118
+ }
119
+ const totalRows = maxDepth + 1;
120
+ const rows = Array.from({ length: totalRows }, () => []);
121
+ const leafCountCache = /* @__PURE__ */ new Map();
122
+ function countVisibleLeaves(cols) {
123
+ const cached = leafCountCache.get(cols);
124
+ if (cached !== void 0) return cached;
125
+ let count = 0;
126
+ for (const c of cols) {
127
+ if (isColumnGroupDef(c)) {
128
+ count += countVisibleLeaves(c.children);
129
+ } else {
130
+ if (!visibleColumns || visibleColumns.has(c.columnId)) {
131
+ count++;
132
+ }
133
+ }
134
+ }
135
+ leafCountCache.set(cols, count);
136
+ return count;
137
+ }
138
+ function walk(cols, depth) {
139
+ for (const c of cols) {
140
+ if (isColumnGroupDef(c)) {
141
+ const leafCount = countVisibleLeaves(c.children);
142
+ if (leafCount === 0) continue;
143
+ rows[depth].push({
144
+ label: c.headerName,
145
+ colSpan: leafCount,
146
+ isGroup: true,
147
+ depth
148
+ });
149
+ walk(c.children, depth + 1);
150
+ } else {
151
+ if (visibleColumns && !visibleColumns.has(c.columnId)) continue;
152
+ rows[depth].push({
153
+ label: c.name,
154
+ colSpan: 1,
155
+ isGroup: false,
156
+ columnDef: c,
157
+ depth
158
+ });
159
+ }
160
+ }
161
+ }
162
+ walk(columns, 0);
163
+ return rows.filter((row) => row.length > 0);
164
+ }
165
+
166
+ // src/utils/ogridHelpers.ts
167
+ function isFilterConfig(val) {
168
+ return val != null && typeof val === "object" && "type" in val;
169
+ }
170
+ function getFilterField(col) {
171
+ const f = isFilterConfig(col.filterable) ? col.filterable : null;
172
+ return f?.filterField ?? col.columnId;
173
+ }
174
+ function mergeFilter(prev, key, value) {
175
+ const isEmpty = value === void 0 || value.type === "text" && value.value.trim() === "" || value.type === "multiSelect" && value.value.length === 0 || value.type === "date" && !value.value.from && !value.value.to || value.type === "people" && !value.value;
176
+ if (isEmpty) {
177
+ const { [key]: _, ...rest } = prev;
178
+ return rest;
179
+ }
180
+ return { ...prev, [key]: value };
181
+ }
182
+ function deriveFilterOptionsFromData(items, columns) {
183
+ const filterCols = [];
184
+ for (let i = 0; i < columns.length; i++) {
185
+ const col = columns[i];
186
+ const f = isFilterConfig(col.filterable) ? col.filterable : null;
187
+ if (f?.type === "multiSelect") {
188
+ filterCols.push({ col, field: getFilterField(col) });
189
+ }
190
+ }
191
+ if (filterCols.length === 0) return {};
192
+ const valueSets = /* @__PURE__ */ new Map();
193
+ for (let i = 0; i < filterCols.length; i++) {
194
+ valueSets.set(filterCols[i].field, /* @__PURE__ */ new Set());
195
+ }
196
+ for (let i = 0; i < items.length; i++) {
197
+ const item = items[i];
198
+ for (let j = 0; j < filterCols.length; j++) {
199
+ const v = getCellValue(item, filterCols[j].col);
200
+ const set = valueSets.get(filterCols[j].field);
201
+ if (v != null && v !== "" && set) set.add(String(v));
202
+ }
203
+ }
204
+ const out = {};
205
+ for (let i = 0; i < filterCols.length; i++) {
206
+ const set = valueSets.get(filterCols[i].field);
207
+ out[filterCols[i].field] = set ? Array.from(set).sort() : [];
208
+ }
209
+ return out;
210
+ }
211
+ function getMultiSelectFilterFields(columns) {
212
+ const fields = [];
213
+ for (const col of columns) {
214
+ const f = isFilterConfig(col.filterable) ? col.filterable : null;
215
+ if (f?.type === "multiSelect") fields.push(getFilterField(col));
216
+ }
217
+ return fields;
218
+ }
219
+
220
+ // src/utils/statusBarHelpers.ts
221
+ function getStatusBarParts(input) {
222
+ const { totalCount, filteredCount, selectedCount, selectedCellCount, aggregation, suppressRowCount } = input;
223
+ const parts = [];
224
+ if (!suppressRowCount) {
225
+ parts.push({ key: "total", label: "Rows:", value: totalCount });
226
+ }
227
+ if (filteredCount !== void 0 && filteredCount !== totalCount) {
228
+ parts.push({ key: "filtered", label: "Filtered:", value: filteredCount });
229
+ }
230
+ if (selectedCount !== void 0 && selectedCount > 0) {
231
+ parts.push({ key: "selected", label: "Selected:", value: selectedCount });
232
+ }
233
+ if (selectedCellCount !== void 0 && selectedCellCount > 1) {
234
+ parts.push({ key: "cells", label: "Cells:", value: selectedCellCount });
235
+ }
236
+ if (aggregation) {
237
+ parts.push({ key: "sum", label: "Sum:", value: aggregation.sum });
238
+ parts.push({ key: "avg", label: "Avg:", value: Math.round(aggregation.avg * 100) / 100 });
239
+ parts.push({ key: "min", label: "Min:", value: aggregation.min });
240
+ parts.push({ key: "max", label: "Max:", value: aggregation.max });
241
+ parts.push({ key: "count", label: "Count:", value: aggregation.count });
242
+ }
243
+ return parts;
244
+ }
245
+
246
+ // src/utils/dataGridStatusBar.ts
247
+ function getDataGridStatusBarConfig(statusBar, itemsLength, selectedCount, filteredCount) {
248
+ if (!statusBar) return null;
249
+ if (typeof statusBar === "object") return statusBar;
250
+ return {
251
+ totalCount: itemsLength,
252
+ selectedCount: selectedCount > 0 ? selectedCount : void 0,
253
+ filteredCount: filteredCount !== void 0 && filteredCount !== itemsLength ? filteredCount : void 0
254
+ };
255
+ }
256
+
257
+ // src/utils/paginationHelpers.ts
258
+ var PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
259
+ function ensurePageSizeInOptions(pageSize, options) {
260
+ if (options.includes(pageSize)) return options;
261
+ return [...options, pageSize].sort((a, b) => a - b);
262
+ }
263
+ var MAX_PAGE_BUTTONS = 5;
264
+ function getPaginationViewModel(currentPage, pageSize, totalCount, options) {
265
+ if (totalCount <= 0) return null;
266
+ const maxPageButtons = options?.maxPageButtons ?? MAX_PAGE_BUTTONS;
267
+ const totalPages = Math.ceil(totalCount / pageSize);
268
+ let pageNumbers;
269
+ let showStartEllipsis;
270
+ let showEndEllipsis;
271
+ if (totalPages <= maxPageButtons) {
272
+ pageNumbers = [];
273
+ for (let i = 1; i <= totalPages; i++) pageNumbers.push(i);
274
+ showStartEllipsis = false;
275
+ showEndEllipsis = false;
276
+ } else {
277
+ let start = Math.max(1, currentPage - 2);
278
+ let end = Math.min(totalPages, currentPage + 2);
279
+ if (end - start + 1 < maxPageButtons) {
280
+ if (start === 1) end = Math.min(totalPages, start + maxPageButtons - 1);
281
+ else if (end === totalPages) start = Math.max(1, end - maxPageButtons + 1);
282
+ }
283
+ pageNumbers = [];
284
+ for (let i = start; i <= end; i++) pageNumbers.push(i);
285
+ showStartEllipsis = start > 1;
286
+ showEndEllipsis = end < totalPages;
287
+ }
288
+ const startItem = Math.max(1, (currentPage - 1) * pageSize + 1);
289
+ const endItem = Math.min(currentPage * pageSize, totalCount);
290
+ return {
291
+ totalPages,
292
+ pageNumbers,
293
+ showStartEllipsis,
294
+ showEndEllipsis,
295
+ startItem,
296
+ endItem,
297
+ pageSizeOptions: ensurePageSizeInOptions(pageSize, options?.pageSizeOptions ?? PAGE_SIZE_OPTIONS)
298
+ };
299
+ }
300
+
301
+ // src/utils/gridContextMenuHelpers.ts
302
+ var GRID_CONTEXT_MENU_ITEMS = [
303
+ { id: "undo", label: "Undo", shortcut: "Ctrl+Z" },
304
+ { id: "redo", label: "Redo", shortcut: "Ctrl+Y" },
305
+ { id: "copy", label: "Copy", shortcut: "Ctrl+C", disabledWhenNoSelection: true, dividerBefore: true },
306
+ { id: "cut", label: "Cut", shortcut: "Ctrl+X", disabledWhenNoSelection: true },
307
+ { id: "paste", label: "Paste", shortcut: "Ctrl+V" },
308
+ { id: "selectAll", label: "Select all", shortcut: "Ctrl+A", dividerBefore: true }
309
+ ];
310
+ function formatShortcut(shortcut) {
311
+ const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
312
+ return isMac ? shortcut.replace("Ctrl", "\u2318") : shortcut;
313
+ }
314
+ function getContextMenuHandlers(props) {
315
+ const { onCopy, onCut, onPaste, onSelectAll, onUndo, onRedo, onClose } = props;
316
+ return {
317
+ undo: () => {
318
+ onUndo();
319
+ onClose();
320
+ },
321
+ redo: () => {
322
+ onRedo();
323
+ onClose();
324
+ },
325
+ copy: () => {
326
+ onCopy();
327
+ onClose();
328
+ },
329
+ cut: () => {
330
+ onCut();
331
+ onClose();
332
+ },
333
+ paste: () => {
334
+ onPaste();
335
+ onClose();
336
+ },
337
+ selectAll: () => {
338
+ onSelectAll();
339
+ onClose();
340
+ }
341
+ };
342
+ }
343
+ var COLUMN_HEADER_MENU_ITEMS = [
344
+ { id: "pinLeft", label: "Pin left" },
345
+ { id: "pinRight", label: "Pin right" },
346
+ { id: "unpin", label: "Unpin" }
347
+ ];
348
+ function getColumnHeaderMenuItems(input) {
349
+ const { canPinLeft, canPinRight, canUnpin, currentSort, isSortable = true, isResizable = true } = input;
350
+ const items = [];
351
+ items.push(
352
+ { id: "pinLeft", label: "Pin left", disabled: !canPinLeft },
353
+ { id: "pinRight", label: "Pin right", disabled: !canPinRight },
354
+ { id: "unpin", label: "Unpin", disabled: !canUnpin, divider: isSortable || isResizable }
355
+ );
356
+ if (isSortable) {
357
+ if (!currentSort) {
358
+ items.push(
359
+ { id: "sortAsc", label: "Sort ascending" },
360
+ { id: "sortDesc", label: "Sort descending", divider: isResizable }
361
+ );
362
+ } else {
363
+ const oppositeSort = currentSort === "asc" ? "desc" : "asc";
364
+ const oppositeLabel = currentSort === "asc" ? "Sort descending" : "Sort ascending";
365
+ items.push(
366
+ { id: `sort${oppositeSort === "asc" ? "Asc" : "Desc"}`, label: oppositeLabel },
367
+ { id: "clearSort", label: "Clear sort", divider: isResizable }
368
+ );
369
+ }
370
+ }
371
+ if (isResizable) {
372
+ items.push(
373
+ { id: "autosizeThis", label: "Autosize this column" },
374
+ { id: "autosizeAll", label: "Autosize all columns" }
375
+ );
376
+ }
377
+ return items;
378
+ }
379
+
380
+ // src/utils/valueParsers.ts
381
+ function parseValue(newValue, oldValue, item, col) {
382
+ if (col.valueParser) {
383
+ const params = {
384
+ newValue,
385
+ oldValue,
386
+ data: item,
387
+ column: col
388
+ };
389
+ const parsed = col.valueParser(params);
390
+ if (parsed === void 0) {
391
+ return { valid: false, value: void 0 };
392
+ }
393
+ return { valid: true, value: parsed };
394
+ }
395
+ if (col.cellEditor === "select" && col.cellEditorParams?.values != null && Array.isArray(col.cellEditorParams.values)) {
396
+ const allowedValues = col.cellEditorParams.values;
397
+ const strValue = typeof newValue === "string" ? newValue : String(newValue ?? "");
398
+ if (strValue === "") {
399
+ return { valid: true, value: "" };
400
+ }
401
+ const match = allowedValues.find(
402
+ (v) => String(v).toLowerCase() === strValue.toLowerCase()
403
+ );
404
+ if (match !== void 0) {
405
+ return { valid: true, value: match };
406
+ }
407
+ return { valid: false, value: void 0 };
408
+ }
409
+ const colType = col.type;
410
+ if (colType === "date") {
411
+ const parsed = dateParser({ newValue});
412
+ return parsed === void 0 ? { valid: false, value: void 0 } : { valid: true, value: parsed };
413
+ }
414
+ if (colType === "boolean") {
415
+ const parsed = booleanParser({ newValue});
416
+ return parsed === void 0 ? { valid: false, value: void 0 } : { valid: true, value: parsed };
417
+ }
418
+ if (colType === "numeric") {
419
+ const parsed = numberParser({ newValue});
420
+ return parsed === void 0 ? { valid: false, value: void 0 } : { valid: true, value: parsed };
421
+ }
422
+ return { valid: true, value: newValue };
423
+ }
424
+ function numberParser(params) {
425
+ const { newValue } = params;
426
+ if (newValue === "" || newValue == null) return null;
427
+ const str = String(newValue).replace(/[\s,]/g, "");
428
+ const num = Number(str);
429
+ return Number.isNaN(num) ? void 0 : num;
430
+ }
431
+ function currencyParser(params) {
432
+ const { newValue } = params;
433
+ if (newValue === "" || newValue == null) return null;
434
+ const str = String(newValue).replace(/[$\u20AC\u00A3\u00A5]/g, "").replace(/[\s,]/g, "");
435
+ const num = Number(str);
436
+ return Number.isNaN(num) ? void 0 : num;
437
+ }
438
+ function dateParser(params) {
439
+ const { newValue } = params;
440
+ if (newValue === "" || newValue == null) return null;
441
+ const str = String(newValue).trim();
442
+ const date = new Date(str);
443
+ if (Number.isNaN(date.getTime())) return void 0;
444
+ return date.toISOString();
445
+ }
446
+ function emailParser(params) {
447
+ const { newValue } = params;
448
+ if (newValue === "" || newValue == null) return null;
449
+ const str = String(newValue).trim();
450
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str) ? str : void 0;
451
+ }
452
+ function booleanParser(params) {
453
+ const { newValue } = params;
454
+ if (newValue === "" || newValue == null) return null;
455
+ const str = String(newValue).trim().toLowerCase();
456
+ if (["true", "yes", "1"].includes(str)) return true;
457
+ if (["false", "no", "0"].includes(str)) return false;
458
+ return void 0;
459
+ }
460
+
461
+ // src/utils/aggregationUtils.ts
462
+ function computeAggregations(items, visibleCols, selectionRange) {
463
+ if (!selectionRange) return null;
464
+ const norm = normalizeSelectionRange(selectionRange);
465
+ let totalCells = 0;
466
+ let sum = 0;
467
+ let min = Infinity;
468
+ let max = -Infinity;
469
+ let count = 0;
470
+ for (let r = norm.startRow; r <= norm.endRow; r++) {
471
+ for (let c = norm.startCol; c <= norm.endCol; c++) {
472
+ if (r >= items.length || c >= visibleCols.length) continue;
473
+ totalCells++;
474
+ const item = items[r];
475
+ const col = visibleCols[c];
476
+ const raw = getCellValue(item, col);
477
+ const num = typeof raw === "number" ? raw : Number(raw);
478
+ if (!isNaN(num) && isFinite(num)) {
479
+ sum += num;
480
+ if (num < min) min = num;
481
+ if (num > max) max = num;
482
+ count++;
483
+ }
484
+ }
485
+ }
486
+ if (totalCells < 2 || count === 0) return null;
487
+ return {
488
+ sum,
489
+ avg: sum / count,
490
+ min,
491
+ max,
492
+ count
493
+ };
494
+ }
495
+
496
+ // src/utils/clientSideData.ts
497
+ var columnMapCache = /* @__PURE__ */ new WeakMap();
498
+ function processClientSideData(data, columns, filters, sortBy, sortDirection) {
499
+ let columnMap = columnMapCache.get(columns);
500
+ if (!columnMap) {
501
+ columnMap = /* @__PURE__ */ new Map();
502
+ for (let i = 0; i < columns.length; i++) {
503
+ columnMap.set(columns[i].columnId, columns[i]);
504
+ }
505
+ columnMapCache.set(columns, columnMap);
506
+ }
507
+ const predicates = [];
508
+ for (let i = 0; i < columns.length; i++) {
509
+ const col = columns[i];
510
+ const filterKey = getFilterField(col);
511
+ const val = filters[filterKey];
512
+ if (!val) continue;
513
+ switch (val.type) {
514
+ case "multiSelect":
515
+ if (val.value.length > 0) {
516
+ const allowedSet = new Set(val.value);
517
+ predicates.push((r) => allowedSet.has(String(getCellValue(r, col))));
518
+ }
519
+ break;
520
+ case "text": {
521
+ const trimmed = val.value.trim();
522
+ if (trimmed) {
523
+ const lower = trimmed.toLowerCase();
524
+ predicates.push((r) => String(getCellValue(r, col) ?? "").toLowerCase().includes(lower));
525
+ }
526
+ break;
527
+ }
528
+ case "people": {
529
+ const email = val.value.email.toLowerCase();
530
+ predicates.push((r) => String(getCellValue(r, col) ?? "").toLowerCase() === email);
531
+ break;
532
+ }
533
+ case "date": {
534
+ const dv = val.value;
535
+ const fromTs = dv.from ? (/* @__PURE__ */ new Date(dv.from + "T00:00:00")).getTime() : NaN;
536
+ const toTs = dv.to ? (/* @__PURE__ */ new Date(dv.to + "T23:59:59.999")).getTime() : NaN;
537
+ predicates.push((r) => {
538
+ const cellVal = getCellValue(r, col);
539
+ if (cellVal == null) return false;
540
+ const cellTs = new Date(String(cellVal)).getTime();
541
+ if (Number.isNaN(cellTs)) return false;
542
+ if (!Number.isNaN(fromTs) && cellTs < fromTs) return false;
543
+ if (!Number.isNaN(toTs) && cellTs > toTs) return false;
544
+ return true;
545
+ });
546
+ break;
547
+ }
548
+ }
549
+ }
550
+ const filtered = predicates.length > 0;
551
+ const rows = filtered ? data.filter((row) => {
552
+ for (let i = 0; i < predicates.length; i++) {
553
+ if (!predicates[i](row)) return false;
554
+ }
555
+ return true;
556
+ }) : data;
557
+ if (sortBy) {
558
+ const sortable = filtered ? rows : rows.slice();
559
+ const sortCol = columnMap.get(sortBy);
560
+ const compare = sortCol?.compare;
561
+ const dir = sortDirection === "asc" ? 1 : -1;
562
+ const isDateSort = sortCol?.type === "date";
563
+ if (isDateSort && !compare) {
564
+ const timestampCache = /* @__PURE__ */ new Map();
565
+ for (let i = 0; i < sortable.length; i++) {
566
+ const row = sortable[i];
567
+ const val = sortCol ? getCellValue(row, sortCol) : row[sortBy];
568
+ if (val == null) {
569
+ timestampCache.set(row, NaN);
570
+ } else {
571
+ const t = new Date(String(val)).getTime();
572
+ timestampCache.set(row, Number.isNaN(t) ? 0 : t);
573
+ }
574
+ }
575
+ sortable.sort((a, b) => {
576
+ const at = timestampCache.get(a) ?? NaN;
577
+ const bt = timestampCache.get(b) ?? NaN;
578
+ if (Number.isNaN(at) && Number.isNaN(bt)) return 0;
579
+ if (Number.isNaN(at)) return -1 * dir;
580
+ if (Number.isNaN(bt)) return 1 * dir;
581
+ return at === bt ? 0 : at > bt ? dir : -dir;
582
+ });
583
+ } else {
584
+ sortable.sort((a, b) => {
585
+ if (compare) return compare(a, b) * dir;
586
+ const av = sortCol ? getCellValue(a, sortCol) : a[sortBy];
587
+ const bv = sortCol ? getCellValue(b, sortCol) : b[sortBy];
588
+ if (av == null && bv == null) return 0;
589
+ if (av == null) return -1 * dir;
590
+ if (bv == null) return 1 * dir;
591
+ if (typeof av === "number" && typeof bv === "number")
592
+ return av === bv ? 0 : av > bv ? dir : -dir;
593
+ const as = String(av).toLowerCase();
594
+ const bs = String(bv).toLowerCase();
595
+ return as === bs ? 0 : as > bs ? dir : -dir;
596
+ });
597
+ }
598
+ return sortable;
599
+ }
600
+ return rows;
601
+ }
602
+
603
+ // src/utils/gridRowComparator.ts
604
+ function isRowInRange(range, rowIndex) {
605
+ if (!range) return false;
606
+ const minR = Math.min(range.startRow, range.endRow);
607
+ const maxR = Math.max(range.startRow, range.endRow);
608
+ return rowIndex >= minR && rowIndex <= maxR;
609
+ }
610
+ function areGridRowPropsEqual(prev, next) {
611
+ if (prev.item !== next.item) return false;
612
+ if (prev.isSelected !== next.isSelected) return false;
613
+ if (prev.hasCheckboxCol !== next.hasCheckboxCol) return false;
614
+ if (prev.visibleCols !== next.visibleCols) return false;
615
+ if (prev.columnMeta !== next.columnMeta) return false;
616
+ if (prev.cellClassMap !== next.cellClassMap) return false;
617
+ if (prev.columnLayouts !== next.columnLayouts) return false;
618
+ const ri = prev.rowIndex;
619
+ if (prev.editingRowId !== next.editingRowId) {
620
+ if (prev.editingRowId === prev.rowId || next.editingRowId === next.rowId) return false;
621
+ }
622
+ const prevActive = prev.activeCell?.rowIndex === ri;
623
+ const nextActive = next.activeCell?.rowIndex === ri;
624
+ if (prevActive !== nextActive) return false;
625
+ if (prevActive && nextActive && prev.activeCell?.columnIndex !== next.activeCell?.columnIndex) return false;
626
+ const prevInSel = isRowInRange(prev.selectionRange, ri);
627
+ const nextInSel = isRowInRange(next.selectionRange, ri);
628
+ if (prevInSel !== nextInSel) return false;
629
+ if (prevInSel && nextInSel) {
630
+ if (prev.selectionRange?.startCol !== next.selectionRange?.startCol || prev.selectionRange?.endCol !== next.selectionRange?.endCol) return false;
631
+ }
632
+ const prevIsEnd = prev.selectionRange?.endRow === ri;
633
+ const nextIsEnd = next.selectionRange?.endRow === ri;
634
+ if (prevIsEnd !== nextIsEnd) return false;
635
+ if ((prevIsEnd || nextIsEnd) && prev.isDragging !== next.isDragging) return false;
636
+ if (prev.cutRange !== next.cutRange) {
637
+ if (isRowInRange(prev.cutRange, ri) || isRowInRange(next.cutRange, ri)) return false;
638
+ }
639
+ if (prev.copyRange !== next.copyRange) {
640
+ if (isRowInRange(prev.copyRange, ri) || isRowInRange(next.copyRange, ri)) return false;
641
+ }
642
+ return true;
643
+ }
644
+
645
+ // src/utils/columnReorder.ts
646
+ function getPinStateForColumn(columnId, pinnedColumns) {
647
+ if (!pinnedColumns) return "unpinned";
648
+ if (pinnedColumns.left?.includes(columnId)) return "left";
649
+ if (pinnedColumns.right?.includes(columnId)) return "right";
650
+ return "unpinned";
651
+ }
652
+ function reorderColumnArray(order, columnId, targetIndex) {
653
+ const filtered = order.filter((id) => id !== columnId);
654
+ const clampedIndex = Math.max(0, Math.min(targetIndex, filtered.length));
655
+ const result = [...filtered];
656
+ result.splice(clampedIndex, 0, columnId);
657
+ return result;
658
+ }
659
+ function calculateDropTarget(params) {
660
+ const { mouseX, columnOrder, draggedColumnId, draggedPinState, tableElement, pinnedColumns } = params;
661
+ const headerCells = tableElement.querySelectorAll("[data-column-id]");
662
+ if (headerCells.length === 0) return null;
663
+ const targets = [];
664
+ headerCells.forEach((cell) => {
665
+ const colId = cell.getAttribute("data-column-id");
666
+ if (!colId) return;
667
+ const pinState = getPinStateForColumn(colId, pinnedColumns);
668
+ if (pinState !== draggedPinState) return;
669
+ const rect = cell.getBoundingClientRect();
670
+ const orderIndex = columnOrder.indexOf(colId);
671
+ if (orderIndex === -1) return;
672
+ targets.push({
673
+ columnId: colId,
674
+ left: rect.left,
675
+ right: rect.right,
676
+ midX: rect.left + rect.width / 2,
677
+ orderIndex
678
+ });
679
+ });
680
+ if (targets.length === 0) return null;
681
+ targets.sort((a, b) => a.left - b.left);
682
+ let targetIndex;
683
+ let indicatorX;
684
+ if (mouseX <= targets[0].midX) {
685
+ targetIndex = targets[0].orderIndex;
686
+ indicatorX = targets[0].left;
687
+ } else if (mouseX >= targets[targets.length - 1].midX) {
688
+ const last = targets[targets.length - 1];
689
+ targetIndex = last.orderIndex + 1;
690
+ indicatorX = last.right;
691
+ } else {
692
+ let matchIndex = -1;
693
+ for (let i = 0; i < targets.length - 1; i++) {
694
+ if (mouseX >= targets[i].midX && mouseX < targets[i + 1].midX) {
695
+ matchIndex = i;
696
+ break;
697
+ }
698
+ }
699
+ if (matchIndex === -1) return null;
700
+ targetIndex = targets[matchIndex].orderIndex + 1;
701
+ indicatorX = targets[matchIndex].right;
702
+ }
703
+ const currentIndex = columnOrder.indexOf(draggedColumnId);
704
+ if (currentIndex === targetIndex || currentIndex + 1 === targetIndex) {
705
+ return { targetIndex, indicatorX: null };
706
+ }
707
+ return { targetIndex, indicatorX };
708
+ }
709
+
710
+ // src/utils/virtualScroll.ts
711
+ function computeVisibleRange(scrollTop, rowHeight, containerHeight, totalRows, overscan = 5) {
712
+ if (totalRows <= 0 || rowHeight <= 0 || containerHeight <= 0) {
713
+ return { startIndex: 0, endIndex: 0, offsetTop: 0, offsetBottom: 0 };
714
+ }
715
+ const startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
716
+ const endIndex = Math.min(
717
+ totalRows - 1,
718
+ Math.ceil((scrollTop + containerHeight) / rowHeight) + overscan
719
+ );
720
+ const offsetTop = startIndex * rowHeight;
721
+ const offsetBottom = Math.max(0, (totalRows - endIndex - 1) * rowHeight);
722
+ return { startIndex, endIndex, offsetTop, offsetBottom };
723
+ }
724
+ function computeTotalHeight(totalRows, rowHeight) {
725
+ return totalRows * rowHeight;
726
+ }
727
+ function getScrollTopForRow(rowIndex, rowHeight, containerHeight, align = "start") {
728
+ const rowTop = rowIndex * rowHeight;
729
+ switch (align) {
730
+ case "start":
731
+ return rowTop;
732
+ case "center":
733
+ return Math.max(0, rowTop - (containerHeight - rowHeight) / 2);
734
+ case "end":
735
+ return Math.max(0, rowTop - containerHeight + rowHeight);
736
+ }
737
+ }
738
+
739
+ // src/utils/dataGridViewModel.ts
740
+ function getHeaderFilterConfig(col, input) {
741
+ const filterable = isFilterConfig(col.filterable) ? col.filterable : null;
742
+ const filterType = filterable?.type ?? "none";
743
+ const filterField = filterable?.filterField ?? col.columnId;
744
+ const sortable = col.sortable !== false;
745
+ const filterValue = input.filters[filterField];
746
+ const base = {
747
+ columnKey: col.columnId,
748
+ columnName: col.name,
749
+ filterType,
750
+ isSorted: input.sortBy === col.columnId,
751
+ isSortedDescending: input.sortBy === col.columnId && input.sortDirection === "desc",
752
+ onSort: sortable ? () => input.onColumnSort(col.columnId) : void 0
753
+ };
754
+ if (filterType === "text") {
755
+ return {
756
+ ...base,
757
+ textValue: filterValue?.type === "text" ? filterValue.value : "",
758
+ onTextChange: (v) => input.onFilterChange(filterField, v.trim() ? { type: "text", value: v } : void 0)
759
+ };
760
+ }
761
+ if (filterType === "people") {
762
+ return {
763
+ ...base,
764
+ selectedUser: filterValue?.type === "people" ? filterValue.value : void 0,
765
+ onUserChange: (u) => input.onFilterChange(filterField, u ? { type: "people", value: u } : void 0),
766
+ peopleSearch: input.peopleSearch
767
+ };
768
+ }
769
+ if (filterType === "multiSelect") {
770
+ return {
771
+ ...base,
772
+ options: input.filterOptions[filterField] ?? [],
773
+ isLoadingOptions: input.loadingFilterOptions[filterField] ?? false,
774
+ selectedValues: filterValue?.type === "multiSelect" ? filterValue.value : [],
775
+ onFilterChange: (values) => input.onFilterChange(filterField, values.length ? { type: "multiSelect", value: values } : void 0)
776
+ };
777
+ }
778
+ if (filterType === "date") {
779
+ return {
780
+ ...base,
781
+ dateValue: filterValue?.type === "date" ? filterValue.value : void 0,
782
+ onDateChange: (v) => input.onFilterChange(filterField, v ? { type: "date", value: v } : void 0)
783
+ };
784
+ }
785
+ return base;
786
+ }
787
+ function getCellRenderDescriptor(item, col, rowIndex, colIdx, input) {
788
+ const rowId = input.getRowId(item);
789
+ const globalColIndex = colIdx + input.colOffset;
790
+ const colEditable = col.editable === true || typeof col.editable === "function" && col.editable(item);
791
+ const canEditInline = input.editable !== false && !!colEditable && !!input.onCellValueChanged && typeof col.cellEditor !== "function";
792
+ const canEditPopup = input.editable !== false && !!colEditable && !!input.onCellValueChanged && typeof col.cellEditor === "function" && col.cellEditorPopup !== false;
793
+ const canEditAny = canEditInline || canEditPopup;
794
+ const isEditing = input.editingCell?.rowId === rowId && input.editingCell?.columnId === col.columnId;
795
+ const isActive = !input.isDragging && input.activeCell?.rowIndex === rowIndex && input.activeCell?.columnIndex === globalColIndex;
796
+ const isInRange = input.selectionRange != null && isInSelectionRange(input.selectionRange, rowIndex, colIdx);
797
+ const isInCutRange = input.cutRange != null && isInSelectionRange(input.cutRange, rowIndex, colIdx);
798
+ const isInCopyRange = input.copyRange != null && isInSelectionRange(input.copyRange, rowIndex, colIdx);
799
+ const isSelectionEndCell = !input.isDragging && input.copyRange == null && input.cutRange == null && input.selectionRange != null && rowIndex === input.selectionRange.endRow && colIdx === input.selectionRange.endCol;
800
+ const isPinned = col.pinned != null;
801
+ const pinnedSide = col.pinned ?? void 0;
802
+ const cellValue = getCellValue(item, col);
803
+ let mode = "display";
804
+ let editorType;
805
+ if (isEditing && canEditInline) {
806
+ mode = "editing-inline";
807
+ if (col.cellEditor === "text" || col.cellEditor === "select" || col.cellEditor === "checkbox" || col.cellEditor === "richSelect" || col.cellEditor === "date") {
808
+ editorType = col.cellEditor;
809
+ } else if (col.type === "date") {
810
+ editorType = "date";
811
+ } else if (col.type === "boolean") {
812
+ editorType = "checkbox";
813
+ } else {
814
+ editorType = "text";
815
+ }
816
+ } else if (isEditing && canEditPopup && typeof col.cellEditor === "function") {
817
+ mode = "editing-popover";
818
+ }
819
+ return {
820
+ mode,
821
+ editorType,
822
+ value: cellValue,
823
+ isActive,
824
+ isInRange,
825
+ isInCutRange,
826
+ isInCopyRange,
827
+ isSelectionEndCell,
828
+ canEditAny,
829
+ isPinned,
830
+ pinnedSide,
831
+ globalColIndex,
832
+ rowId,
833
+ rowIndex,
834
+ displayValue: cellValue
835
+ };
836
+ }
837
+ function resolveCellDisplayContent(col, item, displayValue) {
838
+ const c = col;
839
+ if (c.renderCell && typeof c.renderCell === "function") {
840
+ return c.renderCell(item);
841
+ }
842
+ if (col.valueFormatter) return col.valueFormatter(displayValue, item);
843
+ if (displayValue == null) return null;
844
+ if (col.type === "date") {
845
+ const d = new Date(String(displayValue));
846
+ if (!Number.isNaN(d.getTime())) return d.toLocaleDateString();
847
+ }
848
+ if (col.type === "boolean") {
849
+ return displayValue ? "True" : "False";
850
+ }
851
+ return String(displayValue);
852
+ }
853
+ function resolveCellStyle(col, item) {
854
+ const c = col;
855
+ if (!c.cellStyle) return void 0;
856
+ return typeof c.cellStyle === "function" ? c.cellStyle(item) : c.cellStyle;
857
+ }
858
+ function buildInlineEditorProps(item, col, descriptor, callbacks) {
859
+ return {
860
+ value: descriptor.value,
861
+ item,
862
+ column: col,
863
+ rowIndex: descriptor.rowIndex,
864
+ editorType: descriptor.editorType ?? "text",
865
+ onCommit: (newValue) => callbacks.commitCellEdit(item, col.columnId, descriptor.value, newValue, descriptor.rowIndex, descriptor.globalColIndex),
866
+ onCancel: () => callbacks.setEditingCell(null)
867
+ };
868
+ }
869
+ function buildPopoverEditorProps(item, col, descriptor, pendingEditorValue, callbacks) {
870
+ const oldValue = descriptor.value;
871
+ const displayValue = pendingEditorValue !== void 0 ? pendingEditorValue : oldValue;
872
+ return {
873
+ value: displayValue,
874
+ onValueChange: callbacks.setPendingEditorValue,
875
+ onCommit: () => {
876
+ const newValue = pendingEditorValue !== void 0 ? pendingEditorValue : oldValue;
877
+ callbacks.commitCellEdit(item, col.columnId, oldValue, newValue, descriptor.rowIndex, descriptor.globalColIndex);
878
+ },
879
+ onCancel: callbacks.cancelPopoverEdit,
880
+ item,
881
+ column: col,
882
+ cellEditorParams: col.cellEditorParams
883
+ };
884
+ }
885
+
886
+ // src/utils/debounce.ts
887
+ function debounce(fn, delayMs) {
888
+ let timeoutId = null;
889
+ const debounced = ((...args) => {
890
+ if (timeoutId !== null) {
891
+ clearTimeout(timeoutId);
892
+ }
893
+ timeoutId = setTimeout(() => {
894
+ fn(...args);
895
+ timeoutId = null;
896
+ }, delayMs);
897
+ });
898
+ debounced.cancel = () => {
899
+ if (timeoutId !== null) {
900
+ clearTimeout(timeoutId);
901
+ timeoutId = null;
902
+ }
903
+ };
904
+ return debounced;
905
+ }
906
+
907
+ // src/utils/dom.ts
908
+ function measureRange(container, range, colOffset) {
909
+ const startGlobalCol = range.startCol + colOffset;
910
+ const endGlobalCol = range.endCol + colOffset;
911
+ const topLeft = container.querySelector(
912
+ `[data-row-index="${range.startRow}"][data-col-index="${startGlobalCol}"]`
913
+ );
914
+ const bottomRight = container.querySelector(
915
+ `[data-row-index="${range.endRow}"][data-col-index="${endGlobalCol}"]`
916
+ );
917
+ if (!topLeft || !bottomRight) return null;
918
+ const cRect = container.getBoundingClientRect();
919
+ const tlRect = topLeft.getBoundingClientRect();
920
+ const brRect = bottomRight.getBoundingClientRect();
921
+ return {
922
+ top: tlRect.top - cRect.top,
923
+ left: tlRect.left - cRect.left,
924
+ width: brRect.right - tlRect.left,
925
+ height: brRect.bottom - tlRect.top
926
+ };
927
+ }
928
+ function injectGlobalStyles(id, css) {
929
+ if (typeof document === "undefined") return;
930
+ if (document.getElementById(id)) return;
931
+ const style = document.createElement("style");
932
+ style.id = id;
933
+ style.textContent = css;
934
+ document.head.appendChild(style);
935
+ }
936
+
937
+ // src/utils/sortHelpers.ts
938
+ function computeNextSortState(current, columnKey, direction) {
939
+ if (direction === null) {
940
+ return { field: "", direction: "asc" };
941
+ } else if (direction) {
942
+ return { field: columnKey, direction };
943
+ } else {
944
+ return {
945
+ field: columnKey,
946
+ direction: current.field === columnKey && current.direction === "asc" ? "desc" : "asc"
947
+ };
948
+ }
949
+ }
950
+
951
+ // src/constants/layout.ts
952
+ var CHECKBOX_COLUMN_WIDTH = 48;
953
+ var ROW_NUMBER_COLUMN_WIDTH = 50;
954
+ var DEFAULT_MIN_COLUMN_WIDTH = 80;
955
+ var CELL_PADDING = 16;
956
+ var GRID_BORDER_RADIUS = 6;
957
+
958
+ // src/utils/columnAutosize.ts
959
+ var AUTOSIZE_EXTRA_PX = 28;
960
+ var AUTOSIZE_MAX_PX = 520;
961
+ function measureColumnContentWidth(columnId, minWidth, container) {
962
+ const minW = minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
963
+ const root = container ?? document;
964
+ const cells = root.querySelectorAll(`[data-column-id="${columnId}"]`);
965
+ if (cells.length === 0) return minW;
966
+ let maxWidth = minW;
967
+ cells.forEach((cell) => {
968
+ const el = cell;
969
+ const label = el.querySelector?.("[data-header-label]");
970
+ if (label) {
971
+ maxWidth = Math.max(maxWidth, label.scrollWidth + AUTOSIZE_EXTRA_PX);
972
+ } else {
973
+ maxWidth = Math.max(maxWidth, el.scrollWidth);
974
+ }
975
+ });
976
+ return Math.min(AUTOSIZE_MAX_PX, Math.max(minW, Math.ceil(maxWidth)));
977
+ }
978
+
979
+ // src/utils/keyboardNavigation.ts
980
+ function findCtrlArrowTarget(pos, edge, step, isEmpty) {
981
+ if (pos === edge) return pos;
982
+ const next = pos + step;
983
+ if (!isEmpty(pos) && !isEmpty(next)) {
984
+ let p2 = next;
985
+ while (p2 !== edge) {
986
+ if (isEmpty(p2 + step)) return p2;
987
+ p2 += step;
988
+ }
989
+ return edge;
990
+ }
991
+ let p = next;
992
+ while (p !== edge) {
993
+ if (!isEmpty(p)) return p;
994
+ p += step;
995
+ }
996
+ return edge;
997
+ }
998
+ function computeTabNavigation(rowIndex, columnIndex, maxRowIndex, maxColIndex, colOffset, shiftKey) {
999
+ let newRow = rowIndex;
1000
+ let newCol = columnIndex;
1001
+ if (shiftKey) {
1002
+ if (columnIndex > colOffset) {
1003
+ newCol = columnIndex - 1;
1004
+ } else if (rowIndex > 0) {
1005
+ newRow = rowIndex - 1;
1006
+ newCol = maxColIndex;
1007
+ }
1008
+ } else {
1009
+ if (columnIndex < maxColIndex) {
1010
+ newCol = columnIndex + 1;
1011
+ } else if (rowIndex < maxRowIndex) {
1012
+ newRow = rowIndex + 1;
1013
+ newCol = colOffset;
1014
+ }
1015
+ }
1016
+ return { rowIndex: newRow, columnIndex: newCol };
1017
+ }
1018
+ function computeArrowNavigation(ctx) {
1019
+ const {
1020
+ direction,
1021
+ rowIndex,
1022
+ columnIndex,
1023
+ dataColIndex,
1024
+ colOffset,
1025
+ maxRowIndex,
1026
+ maxColIndex,
1027
+ visibleColCount,
1028
+ isCtrl,
1029
+ isShift,
1030
+ selectionRange,
1031
+ isEmptyAt
1032
+ } = ctx;
1033
+ let newRowIndex = rowIndex;
1034
+ let newColumnIndex = columnIndex;
1035
+ if (direction === "ArrowDown") {
1036
+ newRowIndex = isCtrl ? findCtrlArrowTarget(rowIndex, maxRowIndex, 1, (r) => isEmptyAt(r, Math.max(0, dataColIndex))) : Math.min(rowIndex + 1, maxRowIndex);
1037
+ } else if (direction === "ArrowUp") {
1038
+ newRowIndex = isCtrl ? findCtrlArrowTarget(rowIndex, 0, -1, (r) => isEmptyAt(r, Math.max(0, dataColIndex))) : Math.max(rowIndex - 1, 0);
1039
+ } else if (direction === "ArrowRight") {
1040
+ if (isCtrl && dataColIndex >= 0) {
1041
+ newColumnIndex = findCtrlArrowTarget(dataColIndex, visibleColCount - 1, 1, (c) => isEmptyAt(rowIndex, c)) + colOffset;
1042
+ } else {
1043
+ newColumnIndex = Math.min(columnIndex + 1, maxColIndex);
1044
+ }
1045
+ } else {
1046
+ if (isCtrl && dataColIndex >= 0) {
1047
+ newColumnIndex = findCtrlArrowTarget(dataColIndex, 0, -1, (c) => isEmptyAt(rowIndex, c)) + colOffset;
1048
+ } else {
1049
+ newColumnIndex = Math.max(columnIndex - 1, colOffset);
1050
+ }
1051
+ }
1052
+ const newDataColIndex = newColumnIndex - colOffset;
1053
+ const isVertical = direction === "ArrowDown" || direction === "ArrowUp";
1054
+ let newRange;
1055
+ if (isShift) {
1056
+ if (isVertical) {
1057
+ newRange = normalizeSelectionRange({
1058
+ startRow: selectionRange?.startRow ?? rowIndex,
1059
+ startCol: selectionRange?.startCol ?? dataColIndex,
1060
+ endRow: newRowIndex,
1061
+ endCol: selectionRange?.endCol ?? dataColIndex
1062
+ });
1063
+ } else {
1064
+ newRange = normalizeSelectionRange({
1065
+ startRow: selectionRange?.startRow ?? rowIndex,
1066
+ startCol: selectionRange?.startCol ?? dataColIndex,
1067
+ endRow: selectionRange?.endRow ?? rowIndex,
1068
+ endCol: newDataColIndex
1069
+ });
1070
+ }
1071
+ } else {
1072
+ newRange = {
1073
+ startRow: newRowIndex,
1074
+ startCol: newDataColIndex,
1075
+ endRow: newRowIndex,
1076
+ endCol: newDataColIndex
1077
+ };
1078
+ }
1079
+ return { newRowIndex, newColumnIndex, newDataColIndex, newRange };
1080
+ }
1081
+ function applyCellDeletion(range, items, visibleCols) {
1082
+ const norm = normalizeSelectionRange(range);
1083
+ const events = [];
1084
+ for (let r = norm.startRow; r <= norm.endRow; r++) {
1085
+ for (let c = norm.startCol; c <= norm.endCol; c++) {
1086
+ if (r >= items.length || c >= visibleCols.length) continue;
1087
+ const item = items[r];
1088
+ const col = visibleCols[c];
1089
+ const colEditable = col.editable === true || typeof col.editable === "function" && col.editable(item);
1090
+ if (!colEditable) continue;
1091
+ const oldValue = getCellValue(item, col);
1092
+ const result = parseValue("", oldValue, item, col);
1093
+ if (!result.valid) continue;
1094
+ events.push({
1095
+ item,
1096
+ columnId: col.columnId,
1097
+ oldValue,
1098
+ newValue: result.value,
1099
+ rowIndex: r
1100
+ });
1101
+ }
1102
+ }
1103
+ return events;
1104
+ }
1105
+
1106
+ // src/utils/selectionHelpers.ts
1107
+ function rangesEqual(a, b) {
1108
+ if (a === b) return true;
1109
+ if (!a || !b) return false;
1110
+ return a.startRow === b.startRow && a.endRow === b.endRow && a.startCol === b.startCol && a.endCol === b.endCol;
1111
+ }
1112
+ function clampSelectionToBounds(range, maxRow, maxCol) {
1113
+ if (maxRow < 0 || maxCol < 0) return null;
1114
+ return {
1115
+ startRow: Math.max(0, Math.min(range.startRow, maxRow)),
1116
+ endRow: Math.max(0, Math.min(range.endRow, maxRow)),
1117
+ startCol: Math.max(0, Math.min(range.startCol, maxCol)),
1118
+ endCol: Math.max(0, Math.min(range.endCol, maxCol))
1119
+ };
1120
+ }
1121
+ function computeAutoScrollSpeed(distance, edgePx = 40, minSpeed = 2, maxSpeed = 20) {
1122
+ const t = Math.min(distance / edgePx, 1);
1123
+ return minSpeed + t * (maxSpeed - minSpeed);
1124
+ }
1125
+ function applyRangeRowSelection(start, end, checked, items, getRowId, currentSelection) {
1126
+ const next = new Set(currentSelection);
1127
+ const lo = Math.min(start, end);
1128
+ const hi = Math.max(start, end);
1129
+ for (let i = lo; i <= hi; i++) {
1130
+ if (i < items.length) {
1131
+ const id = getRowId(items[i]);
1132
+ if (checked) next.add(id);
1133
+ else next.delete(id);
1134
+ }
1135
+ }
1136
+ return next;
1137
+ }
1138
+ function computeRowSelectionState(selectedIds, items, getRowId) {
1139
+ if (selectedIds.size === 0 || items.length === 0) {
1140
+ return { allSelected: false, someSelected: false };
1141
+ }
1142
+ const allSelected = items.every((item) => selectedIds.has(getRowId(item)));
1143
+ const someSelected = !allSelected && selectedIds.size > 0;
1144
+ return { allSelected, someSelected };
1145
+ }
1146
+
1147
+ // src/utils/clipboardHelpers.ts
1148
+ function formatCellValueForTsv(raw, formatted) {
1149
+ const val = formatted != null && formatted !== "" ? formatted : raw;
1150
+ if (val == null || val === "") return "";
1151
+ try {
1152
+ return String(val).replace(/[\t\n]/g, " ");
1153
+ } catch {
1154
+ return "[Object]";
1155
+ }
1156
+ }
1157
+ function formatSelectionAsTsv(items, visibleCols, range) {
1158
+ const norm = normalizeSelectionRange(range);
1159
+ const rows = [];
1160
+ for (let r = norm.startRow; r <= norm.endRow; r++) {
1161
+ const cells = [];
1162
+ for (let c = norm.startCol; c <= norm.endCol; c++) {
1163
+ if (r >= items.length || c >= visibleCols.length) break;
1164
+ const item = items[r];
1165
+ const col = visibleCols[c];
1166
+ const raw = getCellValue(item, col);
1167
+ const clipboard = col.clipboardFormatter ? col.clipboardFormatter(raw, item) : null;
1168
+ const formatted = clipboard ?? (col.valueFormatter ? col.valueFormatter(raw, item) : raw);
1169
+ cells.push(formatCellValueForTsv(raw, formatted));
1170
+ }
1171
+ rows.push(cells.join(" "));
1172
+ }
1173
+ return rows.join("\r\n");
1174
+ }
1175
+ function parseTsvClipboard(text) {
1176
+ if (!text.trim()) return [];
1177
+ const lines = text.split(/\r?\n/).filter((l) => l.length > 0);
1178
+ return lines.map((line) => line.split(" "));
1179
+ }
1180
+ function applyPastedValues(parsedRows, anchorRow, anchorCol, items, visibleCols) {
1181
+ const events = [];
1182
+ for (let r = 0; r < parsedRows.length; r++) {
1183
+ const cells = parsedRows[r];
1184
+ for (let c = 0; c < cells.length; c++) {
1185
+ const targetRow = anchorRow + r;
1186
+ const targetCol = anchorCol + c;
1187
+ if (targetRow >= items.length || targetCol >= visibleCols.length) continue;
1188
+ const item = items[targetRow];
1189
+ const col = visibleCols[targetCol];
1190
+ const colEditable = col.editable === true || typeof col.editable === "function" && col.editable(item);
1191
+ if (!colEditable) continue;
1192
+ const rawValue = cells[c] ?? "";
1193
+ const oldValue = getCellValue(item, col);
1194
+ const result = parseValue(rawValue, oldValue, item, col);
1195
+ if (!result.valid) continue;
1196
+ events.push({
1197
+ item,
1198
+ columnId: col.columnId,
1199
+ oldValue,
1200
+ newValue: result.value,
1201
+ rowIndex: targetRow
1202
+ });
1203
+ }
1204
+ }
1205
+ return events;
1206
+ }
1207
+ function applyCutClear(cutRange, items, visibleCols) {
1208
+ const events = [];
1209
+ for (let r = cutRange.startRow; r <= cutRange.endRow; r++) {
1210
+ for (let c = cutRange.startCol; c <= cutRange.endCol; c++) {
1211
+ if (r >= items.length || c >= visibleCols.length) continue;
1212
+ const item = items[r];
1213
+ const col = visibleCols[c];
1214
+ const colEditable = col.editable === true || typeof col.editable === "function" && col.editable(item);
1215
+ if (!colEditable) continue;
1216
+ const oldValue = getCellValue(item, col);
1217
+ const result = parseValue("", oldValue, item, col);
1218
+ if (!result.valid) continue;
1219
+ events.push({
1220
+ item,
1221
+ columnId: col.columnId,
1222
+ oldValue,
1223
+ newValue: result.value,
1224
+ rowIndex: r
1225
+ });
1226
+ }
1227
+ }
1228
+ return events;
1229
+ }
1230
+
1231
+ // src/utils/fillHelpers.ts
1232
+ function applyFillValues(range, sourceRow, sourceCol, items, visibleCols) {
1233
+ const events = [];
1234
+ const startItem = items[range.startRow];
1235
+ const startColDef = visibleCols[range.startCol];
1236
+ if (!startItem || !startColDef) return events;
1237
+ const startValue = getCellValue(startItem, startColDef);
1238
+ for (let row = range.startRow; row <= range.endRow; row++) {
1239
+ for (let col = range.startCol; col <= range.endCol; col++) {
1240
+ if (row === sourceRow && col === sourceCol) continue;
1241
+ if (row >= items.length || col >= visibleCols.length) continue;
1242
+ const item = items[row];
1243
+ const colDef = visibleCols[col];
1244
+ const colEditable = colDef.editable === true || typeof colDef.editable === "function" && colDef.editable(item);
1245
+ if (!colEditable) continue;
1246
+ const oldValue = getCellValue(item, colDef);
1247
+ const result = parseValue(startValue, oldValue, item, colDef);
1248
+ if (!result.valid) continue;
1249
+ events.push({
1250
+ item,
1251
+ columnId: colDef.columnId,
1252
+ oldValue,
1253
+ newValue: result.value,
1254
+ rowIndex: row
1255
+ });
1256
+ }
1257
+ }
1258
+ return events;
1259
+ }
1260
+
1261
+ // src/utils/undoRedoStack.ts
1262
+ var UndoRedoStack = class {
1263
+ constructor(maxDepth = 100) {
1264
+ this.history = [];
1265
+ this.redoStack = [];
1266
+ this.batch = null;
1267
+ this.maxDepth = maxDepth;
1268
+ }
1269
+ /** Whether there are undo steps available. */
1270
+ get canUndo() {
1271
+ return this.history.length > 0;
1272
+ }
1273
+ /** Whether there are redo steps available. */
1274
+ get canRedo() {
1275
+ return this.redoStack.length > 0;
1276
+ }
1277
+ /** Number of history entries. */
1278
+ get historyLength() {
1279
+ return this.history.length;
1280
+ }
1281
+ /** Number of redo entries. */
1282
+ get redoLength() {
1283
+ return this.redoStack.length;
1284
+ }
1285
+ /** Whether a batch is currently open. */
1286
+ get isBatching() {
1287
+ return this.batch !== null;
1288
+ }
1289
+ /**
1290
+ * Record a group of events as a single undoable step.
1291
+ * If a batch is open, accumulates into the batch instead.
1292
+ * Clears the redo stack on any new entry.
1293
+ */
1294
+ push(events) {
1295
+ if (events.length === 0) return;
1296
+ if (this.batch !== null) {
1297
+ this.batch.push(...events);
1298
+ } else {
1299
+ this.history.push(events);
1300
+ if (this.history.length > this.maxDepth) {
1301
+ this.history.splice(0, this.history.length - this.maxDepth);
1302
+ }
1303
+ this.redoStack.length = 0;
1304
+ }
1305
+ }
1306
+ /**
1307
+ * Record a single event as a step (shorthand for push([event])).
1308
+ * If a batch is open, accumulates into the batch instead.
1309
+ */
1310
+ record(event) {
1311
+ this.push([event]);
1312
+ }
1313
+ /**
1314
+ * Start a batch — subsequent record/push calls accumulate into one undo step.
1315
+ * Has no effect if a batch is already open.
1316
+ */
1317
+ beginBatch() {
1318
+ if (this.batch === null) {
1319
+ this.batch = [];
1320
+ }
1321
+ }
1322
+ /**
1323
+ * End a batch — commits all accumulated events as one undo step.
1324
+ * Has no effect if no batch is open or if the batch is empty.
1325
+ */
1326
+ endBatch() {
1327
+ const b = this.batch;
1328
+ this.batch = null;
1329
+ if (!b || b.length === 0) return;
1330
+ this.history.push(b);
1331
+ if (this.history.length > this.maxDepth) {
1332
+ this.history.splice(0, this.history.length - this.maxDepth);
1333
+ }
1334
+ this.redoStack.length = 0;
1335
+ }
1336
+ /**
1337
+ * Pop the most recent history entry for undo.
1338
+ * Returns the batch of events (in original order) to be reversed by the caller,
1339
+ * or null if there is nothing to undo.
1340
+ *
1341
+ * The caller is responsible for applying the events in reverse order.
1342
+ */
1343
+ undo() {
1344
+ const lastBatch = this.history.pop();
1345
+ if (!lastBatch) return null;
1346
+ this.redoStack.push(lastBatch);
1347
+ return lastBatch;
1348
+ }
1349
+ /**
1350
+ * Pop the most recent redo entry.
1351
+ * Returns the batch of events (in original order) to be re-applied by the caller,
1352
+ * or null if there is nothing to redo.
1353
+ */
1354
+ redo() {
1355
+ const nextBatch = this.redoStack.pop();
1356
+ if (!nextBatch) return null;
1357
+ this.history.push(nextBatch);
1358
+ return nextBatch;
1359
+ }
1360
+ /**
1361
+ * Clear all history and redo state.
1362
+ * Does not affect any open batch — call endBatch() first if needed.
1363
+ */
1364
+ clear() {
1365
+ this.history = [];
1366
+ this.redoStack = [];
1367
+ }
1368
+ };
1369
+
1370
+ // src/utils/validation.ts
1371
+ function validateColumns(columns) {
1372
+ if (!Array.isArray(columns) || columns.length === 0) {
1373
+ console.warn("[OGrid] columns prop is empty or not an array");
1374
+ return;
1375
+ }
1376
+ const ids = /* @__PURE__ */ new Set();
1377
+ for (const col of columns) {
1378
+ if (!col.columnId) {
1379
+ console.warn("[OGrid] Column missing columnId:", col);
1380
+ }
1381
+ if (ids.has(col.columnId)) {
1382
+ console.warn(`[OGrid] Duplicate columnId: "${col.columnId}"`);
1383
+ }
1384
+ ids.add(col.columnId);
1385
+ }
1386
+ }
1387
+ function validateRowIds(items, getRowId) {
1388
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
1389
+ const ids = /* @__PURE__ */ new Set();
1390
+ const limit = Math.min(items.length, 100);
1391
+ for (let i = 0; i < limit; i++) {
1392
+ const id = getRowId(items[i]);
1393
+ if (id == null) {
1394
+ console.warn(`[OGrid] getRowId returned null/undefined for row ${i}`);
1395
+ return;
1396
+ }
1397
+ if (ids.has(id)) {
1398
+ console.warn(
1399
+ `[OGrid] Duplicate row ID "${id}" at index ${i}. getRowId must return unique values.`
1400
+ );
1401
+ return;
1402
+ }
1403
+ ids.add(id);
1404
+ }
1405
+ }
1406
+
1407
+ // src/constants/timing.ts
1408
+ var DEFAULT_DEBOUNCE_MS = 300;
1409
+ var PEOPLE_SEARCH_DEBOUNCE_MS = DEFAULT_DEBOUNCE_MS;
1410
+ var SIDEBAR_TRANSITION_MS = 300;
1411
+
1412
+ // src/constants/zIndex.ts
1413
+ var Z_INDEX = {
1414
+ /** Selection range overlay (marching ants) */
1415
+ SELECTION_OVERLAY: 4,
1416
+ /** Clipboard overlay (copy/cut animation) */
1417
+ CLIPBOARD_OVERLAY: 5,
1418
+ /** Dropdown menus (column chooser, pagination size select) */
1419
+ DROPDOWN: 1e3,
1420
+ /** Modal dialogs */
1421
+ MODAL: 2e3,
1422
+ /** Context menus (right-click grid menu) */
1423
+ CONTEXT_MENU: 9999
1424
+ };
1425
+
1426
+ export { AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, DEFAULT_DEBOUNCE_MS, DEFAULT_MIN_COLUMN_WIDTH, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, MAX_PAGE_BUTTONS, PAGE_SIZE_OPTIONS, PEOPLE_SEARCH_DEBOUNCE_MS, ROW_NUMBER_COLUMN_WIDTH, SIDEBAR_TRANSITION_MS, UndoRedoStack, Z_INDEX, applyCellDeletion, applyCutClear, applyFillValues, applyPastedValues, applyRangeRowSelection, areGridRowPropsEqual, booleanParser, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps, buildPopoverEditorProps, calculateDropTarget, clampSelectionToBounds, computeAggregations, computeArrowNavigation, computeAutoScrollSpeed, computeNextSortState, computeRowSelectionState, computeTabNavigation, computeTotalHeight, computeVisibleRange, currencyParser, dateParser, debounce, deriveFilterOptionsFromData, emailParser, escapeCsvValue, exportToCsv, findCtrlArrowTarget, flattenColumns, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, getCellRenderDescriptor, getCellValue, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getPinStateForColumn, getScrollTopForRow, getStatusBarParts, injectGlobalStyles, isFilterConfig, isInSelectionRange, isRowInRange, measureColumnContentWidth, measureRange, mergeFilter, normalizeSelectionRange, numberParser, parseTsvClipboard, parseValue, processClientSideData, rangesEqual, reorderColumnArray, resolveCellDisplayContent, resolveCellStyle, toUserLike, triggerCsvDownload, validateColumns, validateRowIds };