@stonecrop/atable 0.31.0 → 0.33.0

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 (79) hide show
  1. package/dist/assets/index.css +804 -1
  2. package/dist/atable.js +2809 -1944
  3. package/dist/atable.js.map +1 -1
  4. package/dist/src/components/ACell.vue.d.ts +17 -0
  5. package/dist/src/components/ACell.vue.d.ts.map +1 -0
  6. package/dist/src/components/AGanttCell.vue.d.ts +34 -0
  7. package/dist/src/components/AGanttCell.vue.d.ts.map +1 -0
  8. package/dist/src/components/AGanttConnection.vue.d.ts +13 -0
  9. package/dist/src/components/AGanttConnection.vue.d.ts.map +1 -0
  10. package/dist/src/components/ARow.vue.d.ts +2693 -0
  11. package/dist/src/components/ARow.vue.d.ts.map +1 -0
  12. package/dist/src/components/ARowActions.vue.d.ts +16 -0
  13. package/dist/src/components/ARowActions.vue.d.ts.map +1 -0
  14. package/dist/src/components/ATable.vue.d.ts +13358 -0
  15. package/dist/src/components/ATable.vue.d.ts.map +1 -0
  16. package/dist/src/components/ATableColumnFilter.vue.d.ts +11 -0
  17. package/dist/src/components/ATableColumnFilter.vue.d.ts.map +1 -0
  18. package/dist/src/components/ATableHeader.vue.d.ts +20 -0
  19. package/dist/src/components/ATableHeader.vue.d.ts.map +1 -0
  20. package/dist/src/components/ATableLoading.vue.d.ts +14 -0
  21. package/dist/src/components/ATableLoading.vue.d.ts.map +1 -0
  22. package/dist/src/components/ATableLoadingBar.vue.d.ts +14 -0
  23. package/dist/src/components/ATableLoadingBar.vue.d.ts.map +1 -0
  24. package/dist/src/components/ATableModal.vue.d.ts +18 -0
  25. package/dist/src/components/ATableModal.vue.d.ts.map +1 -0
  26. package/dist/src/components/ATablePaginationFooter.vue.d.ts +17 -0
  27. package/dist/src/components/ATablePaginationFooter.vue.d.ts.map +1 -0
  28. package/dist/src/icons/index.d.ts +14 -7
  29. package/dist/src/icons/index.d.ts.map +1 -1
  30. package/dist/{tsdoc-metadata.json → src/tsdoc-metadata.json} +1 -1
  31. package/dist/src/types/index.d.ts.map +1 -1
  32. package/package.json +35 -29
  33. package/dist/atable.d.ts +0 -3560
  34. package/dist/atable.tsbuildinfo +0 -1
  35. package/dist/icons/stonecrop-ui-icon-add.svg +0 -5
  36. package/dist/icons/stonecrop-ui-icon-delete.svg +0 -5
  37. package/dist/icons/stonecrop-ui-icon-duplicate.svg +0 -5
  38. package/dist/icons/stonecrop-ui-icon-insert-above.svg +0 -15
  39. package/dist/icons/stonecrop-ui-icon-insert-below.svg +0 -15
  40. package/dist/icons/stonecrop-ui-icon-move.svg +0 -4
  41. package/dist/icons/stonecrop-ui-icon-open.svg +0 -5
  42. package/dist/src/composables/table-pagination.js +0 -108
  43. package/dist/src/icons/index.js +0 -32
  44. package/dist/src/index.js +0 -33
  45. package/dist/src/linkSearchableText.js +0 -44
  46. package/dist/src/resolveFilterType.js +0 -27
  47. package/dist/src/schemaToColumns.js +0 -59
  48. package/dist/src/stores/table.js +0 -833
  49. package/dist/src/types/index.js +0 -0
  50. package/dist/src/utils.js +0 -39
  51. package/src/components/ACell.vue +0 -410
  52. package/src/components/AGanttCell.vue +0 -638
  53. package/src/components/AGanttConnection.vue +0 -164
  54. package/src/components/ARow.vue +0 -251
  55. package/src/components/ARowActions.vue +0 -407
  56. package/src/components/ATable.vue +0 -538
  57. package/src/components/ATableColumnFilter.vue +0 -216
  58. package/src/components/ATableHeader.vue +0 -141
  59. package/src/components/ATableLoading.vue +0 -86
  60. package/src/components/ATableLoadingBar.vue +0 -81
  61. package/src/components/ATableModal.vue +0 -52
  62. package/src/components/ATablePaginationFooter.vue +0 -100
  63. package/src/composables/table-pagination.ts +0 -172
  64. package/src/icons/index.ts +0 -38
  65. package/src/icons/stonecrop-ui-icon-add.svg +0 -5
  66. package/src/icons/stonecrop-ui-icon-delete.svg +0 -5
  67. package/src/icons/stonecrop-ui-icon-duplicate.svg +0 -5
  68. package/src/icons/stonecrop-ui-icon-insert-above.svg +0 -15
  69. package/src/icons/stonecrop-ui-icon-insert-below.svg +0 -15
  70. package/src/icons/stonecrop-ui-icon-move.svg +0 -4
  71. package/src/icons/stonecrop-ui-icon-open.svg +0 -5
  72. package/src/index.ts +0 -62
  73. package/src/linkSearchableText.ts +0 -42
  74. package/src/resolveFilterType.ts +0 -32
  75. package/src/schemaToColumns.ts +0 -67
  76. package/src/shims-vue.d.ts +0 -10
  77. package/src/stores/table.ts +0 -978
  78. package/src/types/index.ts +0 -794
  79. package/src/utils.ts +0 -40
@@ -1,833 +0,0 @@
1
- import { defineStore } from 'pinia';
2
- import { componentCategory } from '@stonecrop/schema';
3
- import { computed, ref } from 'vue';
4
- import { linkSearchableText } from '../linkSearchableText';
5
- import { resolveFilterType } from '../resolveFilterType';
6
- import { formatCurrency, formatQuantity, generateHash } from '../utils';
7
- // Quantity columns hold a composite `{ qty, uom, ... }` value (see `QuantityValue` in
8
- // `@stonecrop/aform`); numeric comparisons (filtering, sorting) operate on `qty`.
9
- //
10
- // Currency columns hold a composite `{ amount, currency, baseAmount, ... }` value (see
11
- // `CurrencyValue` in `@stonecrop/aform`), and a single column routinely mixes currencies —
12
- // comparing raw `amount` would rank 5 EUR equal to 5 USD and match 100 JPY against a `> 100`
13
- // filter. `baseAmount` is the whole reason the value carries one: it restates every row in the
14
- // record's base currency, which is the only axis on which they are comparable. Fall back to
15
- // `amount` for a value that predates the base conversion (or was authored without one).
16
- function toComparableNumber(cellValue) {
17
- if (cellValue !== null && typeof cellValue === 'object') {
18
- if ('qty' in cellValue)
19
- return Number(cellValue.qty);
20
- if ('baseAmount' in cellValue && cellValue.baseAmount != null) {
21
- return Number(cellValue.baseAmount);
22
- }
23
- if ('amount' in cellValue)
24
- return Number(cellValue.amount);
25
- }
26
- return Number(cellValue);
27
- }
28
- function isNodeOpen(rowIndex, treeDisplay) {
29
- const row = treeDisplay[rowIndex];
30
- if (row.isRoot) {
31
- return true; // Root nodes are always open
32
- }
33
- if (row.parent === null || row.parent === undefined) {
34
- return true;
35
- }
36
- const parentIndex = row.parent;
37
- if (parentIndex < 0 || parentIndex >= treeDisplay.length) {
38
- return false;
39
- }
40
- const parent = treeDisplay[parentIndex];
41
- // Node is open if parent's children are open AND parent itself is open
42
- return (parent.childrenOpen || false) && isNodeOpen(parentIndex, treeDisplay);
43
- }
44
- function applyFilter(cellValue, filter, column) {
45
- const filterType = resolveFilterType(column);
46
- const value = filter.value;
47
- if (!value && filterType !== 'dateRange' && filterType !== 'checkbox')
48
- return true;
49
- switch (filterType) {
50
- case 'text': {
51
- const searchableText = column.linkDoctype
52
- ? linkSearchableText(cellValue)
53
- : typeof cellValue === 'object' && cellValue !== null
54
- ? Object.values(cellValue).join(' ')
55
- : String(cellValue || '');
56
- return searchableText.toLowerCase().includes(String(value).toLowerCase());
57
- }
58
- case 'number': {
59
- const numValue = toComparableNumber(cellValue);
60
- const filterNum = Number(value);
61
- return !isNaN(numValue) && !isNaN(filterNum) && numValue === filterNum;
62
- }
63
- case 'select':
64
- return cellValue === value;
65
- case 'checkbox':
66
- // For checkbox filter, if checked (true), show only truthy values
67
- // If unchecked (false/undefined), show all values
68
- if (value === true) {
69
- return !!cellValue;
70
- }
71
- return true;
72
- case 'date': {
73
- // Handle both timestamp numbers and date strings
74
- let cellDate;
75
- if (typeof cellValue === 'number') {
76
- // Apply the same year transformation as in the format function
77
- const originalDate = new Date(cellValue);
78
- const currentYear = new Date().getFullYear();
79
- cellDate = new Date(currentYear, originalDate.getMonth(), originalDate.getDate());
80
- }
81
- else {
82
- cellDate = new Date(String(cellValue));
83
- }
84
- const filterDate = new Date(String(value));
85
- return cellDate.toDateString() === filterDate.toDateString();
86
- }
87
- case 'dateRange': {
88
- const startValue = filter.startValue;
89
- const endValue = filter.endValue;
90
- if (!startValue && !endValue)
91
- return true;
92
- // Handle both timestamp numbers and date strings
93
- let cellDateRange;
94
- if (typeof cellValue === 'number') {
95
- // Apply the same year transformation as in the format function
96
- const originalDate = new Date(cellValue);
97
- const currentYear = new Date().getFullYear();
98
- cellDateRange = new Date(currentYear, originalDate.getMonth(), originalDate.getDate());
99
- }
100
- else {
101
- cellDateRange = new Date(String(cellValue));
102
- }
103
- if (startValue && cellDateRange < new Date(String(startValue)))
104
- return false;
105
- if (endValue && cellDateRange > new Date(String(endValue)))
106
- return false;
107
- return true;
108
- }
109
- default:
110
- return true;
111
- }
112
- }
113
- /**
114
- * Compute the CSS indentation for a tree-table cell. Pure helper (captures no store state);
115
- * imported directly by cell components rather than exposed on the store's public return.
116
- * @internal
117
- */
118
- export function getIndent(colIndex, indentLevel) {
119
- if (indentLevel && colIndex === 0 && indentLevel > 0) {
120
- return `${indentLevel}ch`;
121
- }
122
- else {
123
- return 'inherit';
124
- }
125
- }
126
- /**
127
- * Create a table store
128
- * @param initData - Initial data for the table store
129
- * @returns table store instance
130
- * @public
131
- */
132
- export const createTableStore = (initData) => {
133
- const id = initData.id || generateHash();
134
- const linkResolver = initData.linkResolver ?? null;
135
- const createStore = defineStore(`table-${id}`, () => {
136
- const createDisplayObject = () => {
137
- const defaultDisplay = [Object.assign({}, { rowModified: false })];
138
- // TODO: (typing) is this type correct for the parent set?
139
- const parents = new Set();
140
- for (let rowIndex = 0; rowIndex < rows.value.length; rowIndex++) {
141
- const row = rows.value[rowIndex];
142
- if (row.parent !== null && row.parent !== undefined) {
143
- parents.add(row.parent);
144
- }
145
- }
146
- // Helper function to check if a row has gantt data
147
- const hasGanttData = (rowIndex) => {
148
- return rows.value[rowIndex]?.gantt !== undefined;
149
- };
150
- // Helper function to check if any descendant has gantt data
151
- const hasGanttDescendant = (rowIndex) => {
152
- for (let i = 0; i < rows.value.length; i++) {
153
- if (rows.value[i].parent === rowIndex) {
154
- if (hasGanttData(i) || hasGanttDescendant(i)) {
155
- return true;
156
- }
157
- }
158
- }
159
- return false;
160
- };
161
- // Helper function to determine if children should be open based on expansion mode
162
- const shouldChildrenBeOpen = (rowIndex) => {
163
- const currentConfig = config.value;
164
- const expansionMode = currentConfig.view === 'tree' || currentConfig.view === 'tree-gantt'
165
- ? currentConfig.defaultTreeExpansion
166
- : undefined;
167
- if (!expansionMode)
168
- return true; // Default behavior - start expanded (leaf mode)
169
- switch (expansionMode) {
170
- case 'root':
171
- return false; // Only root nodes are visible, all children start collapsed
172
- case 'branch':
173
- // Only expand if this node leads to gantt nodes OR if this node itself has gantt data AND has gantt children
174
- return hasGanttDescendant(rowIndex);
175
- case 'leaf':
176
- return true; // All nodes should be expanded
177
- default:
178
- return true; // Default to expanded if unknown mode
179
- }
180
- };
181
- for (let rowIndex = 0; rowIndex < rows.value.length; rowIndex++) {
182
- const row = rows.value[rowIndex];
183
- const isRootNode = row.parent === null || row.parent === undefined;
184
- const isParentNode = parents.has(rowIndex);
185
- defaultDisplay[rowIndex] = {
186
- childrenOpen: shouldChildrenBeOpen(rowIndex),
187
- expanded: false,
188
- indent: row.indent || 0,
189
- isParent: isParentNode,
190
- isRoot: isRootNode,
191
- rowModified: false,
192
- open: isRootNode, // This will be recalculated later for non-root nodes
193
- parent: row.parent,
194
- };
195
- }
196
- return defaultDisplay;
197
- };
198
- // state
199
- const columns = ref(initData.columns);
200
- const rows = ref(initData.rows);
201
- const config = ref(initData.config || {});
202
- // Track row modifications and expand states separately from the computed display
203
- const rowModifications = ref({});
204
- const rowExpandStates = ref({});
205
- const table = computed(() => {
206
- const tableData = {};
207
- for (const [colIndex, column] of columns.value.entries()) {
208
- for (const [rowIndex, row] of rows.value.entries()) {
209
- tableData[`${colIndex}:${rowIndex}`] = row[column.name];
210
- }
211
- }
212
- return tableData;
213
- });
214
- const display = computed({
215
- get: () => {
216
- const baseDisplay = createDisplayObject();
217
- // Apply persistent modifications and expand states
218
- for (let i = 0; i < baseDisplay.length; i++) {
219
- if (rowModifications.value[i]) {
220
- baseDisplay[i].rowModified = rowModifications.value[i];
221
- }
222
- if (rowExpandStates.value[i]) {
223
- if (rowExpandStates.value[i].childrenOpen !== undefined) {
224
- baseDisplay[i].childrenOpen = rowExpandStates.value[i].childrenOpen;
225
- }
226
- if (rowExpandStates.value[i].expanded !== undefined) {
227
- baseDisplay[i].expanded = rowExpandStates.value[i].expanded;
228
- }
229
- }
230
- }
231
- // Calculate 'open' property for tree view based on parent's childrenOpen state
232
- if (isTreeView.value) {
233
- // Helper function to check if all ancestors are open
234
- for (let i = 0; i < baseDisplay.length; i++) {
235
- const row = baseDisplay[i];
236
- if (!row.isRoot) {
237
- baseDisplay[i].open = isNodeOpen(i, baseDisplay);
238
- }
239
- }
240
- }
241
- return baseDisplay;
242
- },
243
- set: (newDisplay) => {
244
- // Only update if the new display is different from the current one; also avoids recursive updates
245
- if (JSON.stringify(newDisplay) !== JSON.stringify(display.value)) {
246
- display.value = newDisplay;
247
- }
248
- },
249
- });
250
- const modal = ref(initData.modal || { visible: false });
251
- const updates = ref({});
252
- const ganttBars = ref([]);
253
- const connectionHandles = ref([]);
254
- const connectionPaths = ref([]);
255
- const sortState = ref({
256
- column: null,
257
- direction: null,
258
- });
259
- const filterState = ref({});
260
- // getters
261
- const hasPinnedColumns = computed(() => columns.value.some(col => col.pinned));
262
- const isGanttView = computed(() => config.value.view === 'gantt' || config.value.view === 'tree-gantt');
263
- const isTreeView = computed(() => config.value.view === 'tree' || config.value.view === 'tree-gantt');
264
- const isDependencyGraphEnabled = computed(() => {
265
- const currentConfig = config.value;
266
- return currentConfig.view === 'gantt' || currentConfig.view === 'tree-gantt'
267
- ? currentConfig.dependencyGraph !== false
268
- : true;
269
- });
270
- const numberedRowWidth = computed(() => {
271
- const indent = Math.ceil(rows.value.length / 100 + 1);
272
- return `${indent}ch`;
273
- });
274
- const zeroColumn = computed(() => config.value.view ? ['list', 'tree', 'tree-gantt', 'list-expansion'].includes(config.value.view) : false);
275
- const filteredRows = computed(() => {
276
- let filtered = rows.value.map((row, originalIndex) => ({
277
- ...row,
278
- originalIndex,
279
- }));
280
- // Apply filters
281
- Object.entries(filterState.value).forEach(([colIndexStr, filter]) => {
282
- const colIndex = parseInt(colIndexStr);
283
- const column = columns.value[colIndex];
284
- if (!column)
285
- return;
286
- // Skip if filter has no value (except for dateRange and checkbox which can have different value structures)
287
- const hasFilterValue = filter.value ||
288
- filter.startValue ||
289
- filter.endValue ||
290
- (resolveFilterType(column) === 'checkbox' && filter.value !== undefined);
291
- if (!hasFilterValue)
292
- return;
293
- filtered = filtered.filter(row => {
294
- const cellValue = row[column.name];
295
- return applyFilter(cellValue, filter, column);
296
- });
297
- });
298
- // Apply sorting if active
299
- if (sortState.value.column !== null && sortState.value.direction) {
300
- const column = columns.value[sortState.value.column];
301
- const direction = sortState.value.direction;
302
- filtered.sort((a, b) => {
303
- let aVal = a[column.name];
304
- let bVal = b[column.name];
305
- if (aVal === null || aVal === undefined)
306
- aVal = '';
307
- if (bVal === null || bVal === undefined)
308
- bVal = '';
309
- const aNum = toComparableNumber(aVal);
310
- const bNum = toComparableNumber(bVal);
311
- const isNumeric = !isNaN(aNum) && !isNaN(bNum) && aVal !== '' && bVal !== '';
312
- if (isNumeric) {
313
- return direction === 'asc' ? aNum - bNum : bNum - aNum;
314
- }
315
- else {
316
- const aStr = String(aVal).toLowerCase();
317
- const bStr = String(bVal).toLowerCase();
318
- return direction === 'asc' ? aStr.localeCompare(bStr) : bStr.localeCompare(aStr);
319
- }
320
- });
321
- }
322
- return filtered;
323
- });
324
- // actions
325
- const getCellData = (colIndex, rowIndex) => table.value[`${colIndex}:${rowIndex}`];
326
- const setCellData = (colIndex, rowIndex, value) => {
327
- const index = `${colIndex}:${rowIndex}`;
328
- const col = columns.value[colIndex];
329
- if (table.value[index] !== value) {
330
- rowModifications.value[rowIndex] = true;
331
- }
332
- table.value[index] = value;
333
- // Create a new row object to ensure reactivity
334
- rows.value[rowIndex] = {
335
- ...rows.value[rowIndex],
336
- [col.name]: value,
337
- };
338
- };
339
- const updateRows = (newRows) => {
340
- rows.value = newRows;
341
- };
342
- const setCellText = (colIndex, rowIndex, value) => {
343
- const index = `${colIndex}:${rowIndex}`;
344
- if (table.value[index] !== value) {
345
- rowModifications.value[rowIndex] = true;
346
- updates.value[index] = value;
347
- }
348
- };
349
- const getHeaderCellStyle = (column) => {
350
- const isLastCol = columns.value.indexOf(column) === columns.value.length - 1;
351
- // if the table is full width, the last column should not be resizable;
352
- // ref: https://github.com/agritheory/stonecrop/pull/196#issuecomment-2503762641
353
- const isResizable = config.value.fullWidth ? column.resizable && !isLastCol : column.resizable;
354
- return {
355
- width: column.width || '40ch',
356
- textAlign: column.align || 'center',
357
- ...(isResizable && {
358
- resize: 'horizontal',
359
- overflow: 'hidden',
360
- whiteSpace: 'nowrap',
361
- }),
362
- };
363
- };
364
- const resizeColumn = (colIndex, newWidth) => {
365
- if (colIndex < 0 || colIndex >= columns.value.length)
366
- return;
367
- const minWidth = 40;
368
- const finalWidth = Math.max(newWidth, minWidth);
369
- columns.value[colIndex] = {
370
- ...columns.value[colIndex],
371
- width: `${finalWidth}px`,
372
- };
373
- };
374
- const isRowGantt = (rowIndex) => {
375
- const row = rows.value[rowIndex];
376
- return isGanttView.value && row.gantt !== undefined;
377
- };
378
- const isRowVisible = (rowIndex) => {
379
- return !isTreeView.value || display.value[rowIndex].isRoot || display.value[rowIndex].open;
380
- };
381
- const getRowExpandSymbol = (rowIndex) => {
382
- if (!isTreeView.value && config.value.view !== 'list-expansion') {
383
- return '';
384
- }
385
- if (isTreeView.value && (display.value[rowIndex].isRoot || display.value[rowIndex].isParent)) {
386
- return display.value[rowIndex].childrenOpen ? '▼' : '►';
387
- }
388
- if (config.value.view === 'list-expansion') {
389
- return display.value[rowIndex].expanded ? '▼' : '►';
390
- }
391
- return '';
392
- };
393
- const toggleRowExpand = (rowIndex) => {
394
- if (isTreeView.value) {
395
- const currentState = rowExpandStates.value[rowIndex] || {};
396
- const currentChildrenOpen = currentState.childrenOpen ?? display.value[rowIndex].childrenOpen;
397
- const newChildrenOpen = !currentChildrenOpen;
398
- rowExpandStates.value[rowIndex] = {
399
- ...currentState,
400
- childrenOpen: newChildrenOpen,
401
- };
402
- // If we're closing, recursively close all descendant nodes
403
- if (!newChildrenOpen) {
404
- closeDescendants(rowIndex);
405
- }
406
- }
407
- else if (config.value.view === 'list-expansion') {
408
- const currentState = rowExpandStates.value[rowIndex] || {};
409
- const currentExpanded = currentState.expanded ?? display.value[rowIndex].expanded;
410
- rowExpandStates.value[rowIndex] = {
411
- ...currentState,
412
- expanded: !currentExpanded,
413
- };
414
- }
415
- };
416
- const closeDescendants = (parentRowIndex) => {
417
- for (let index = 0; index < rows.value.length; index++) {
418
- if (display.value[index].parent === parentRowIndex) {
419
- const childState = rowExpandStates.value[index] || {};
420
- rowExpandStates.value[index] = {
421
- ...childState,
422
- childrenOpen: false,
423
- };
424
- // Recursively close this child's descendants
425
- closeDescendants(index);
426
- }
427
- }
428
- };
429
- const getCellDisplayValue = (colIndex, rowIndex) => {
430
- const cellData = getCellData(colIndex, rowIndex);
431
- return getFormattedValue(colIndex, rowIndex, cellData);
432
- };
433
- const getFormattedValue = (colIndex, rowIndex, value) => {
434
- const column = columns.value[colIndex];
435
- const row = rows.value[rowIndex];
436
- const format = column.format;
437
- if (!format) {
438
- // Default cell formatting comes from the component's category; anything without an
439
- // opinion (including an unknown component) renders the raw value.
440
- const category = componentCategory(column.component);
441
- if (category === 'boolean')
442
- return value ? '✓' : '✗';
443
- if (category === 'date')
444
- return value != null ? new Date(String(value)).toLocaleDateString() : value;
445
- if (category === 'datetime')
446
- return value != null ? new Date(String(value)).toLocaleString() : value;
447
- if (category === 'quantity')
448
- return formatQuantity(value);
449
- if (category === 'currency')
450
- return formatCurrency(value);
451
- return value;
452
- }
453
- if (typeof format === 'function') {
454
- return format(value, { table: table.value, row, column });
455
- }
456
- else if (typeof format === 'string') {
457
- // oxlint-disable-next-line @typescript-eslint/no-implied-eval
458
- const formatFn = Function(`"use strict";return (${format})`)();
459
- return formatFn(value, { table: table.value, row, column });
460
- }
461
- return value;
462
- };
463
- const closeModal = (event) => {
464
- if (!(event.target instanceof Node)) {
465
- // if the target is not a node, it's probably a custom click event to Document or Window
466
- // err on the side of closing the modal in that case
467
- if (modal.value.visible)
468
- modal.value.visible = false;
469
- }
470
- else if (!modal.value.parent?.contains(event.target)) {
471
- if (modal.value.visible)
472
- modal.value.visible = false;
473
- }
474
- };
475
- const updateGanttBar = (event) => {
476
- // update the local gantt bar cache
477
- const ganttBar = rows.value[event.rowIndex]?.gantt;
478
- if (ganttBar) {
479
- if (event.type === 'resize') {
480
- if (event.edge === 'start') {
481
- ganttBar.startIndex = event.newStart;
482
- ganttBar.endIndex = event.end;
483
- ganttBar.colspan = ganttBar.endIndex - ganttBar.startIndex;
484
- }
485
- else if (event.edge === 'end') {
486
- ganttBar.startIndex = event.start;
487
- ganttBar.endIndex = event.newEnd;
488
- ganttBar.colspan = ganttBar.endIndex - ganttBar.startIndex;
489
- }
490
- }
491
- else if (event.type === 'bar') {
492
- ganttBar.startIndex = event.newStart;
493
- ganttBar.endIndex = event.newEnd;
494
- ganttBar.colspan = ganttBar.endIndex - ganttBar.startIndex;
495
- }
496
- }
497
- };
498
- const registerGanttBar = (barInfo) => {
499
- const existingIndex = ganttBars.value.findIndex(bar => bar.id === barInfo.id);
500
- if (existingIndex >= 0) {
501
- // @ts-expect-error TODO: for some reason, the IDE is expecting an unref'd value
502
- ganttBars.value[existingIndex] = barInfo;
503
- }
504
- else {
505
- // @ts-expect-error TODO: for some reason, the IDE is expecting an unref'd value
506
- ganttBars.value.push(barInfo);
507
- }
508
- };
509
- const unregisterGanttBar = (barId) => {
510
- const index = ganttBars.value.findIndex(bar => bar.id === barId);
511
- if (index >= 0) {
512
- ganttBars.value.splice(index, 1);
513
- }
514
- };
515
- const registerConnectionHandle = (handleInfo) => {
516
- const existingIndex = connectionHandles.value.findIndex(handle => handle.id === handleInfo.id);
517
- if (existingIndex >= 0) {
518
- // @ts-expect-error TODO: for some reason, the IDE is expecting an unref'd value
519
- connectionHandles.value[existingIndex] = handleInfo;
520
- }
521
- else {
522
- // @ts-expect-error TODO: for some reason, the IDE is expecting an unref'd value
523
- connectionHandles.value.push(handleInfo);
524
- }
525
- };
526
- const unregisterConnectionHandle = (handleId) => {
527
- const index = connectionHandles.value.findIndex(handle => handle.id === handleId);
528
- if (index >= 0) {
529
- connectionHandles.value.splice(index, 1);
530
- }
531
- };
532
- const createConnection = (fromHandleId, toHandleId, options) => {
533
- const fromHandle = connectionHandles.value.find(h => h.id === fromHandleId);
534
- const toHandle = connectionHandles.value.find(h => h.id === toHandleId);
535
- if (!fromHandle || !toHandle) {
536
- console.warn('Cannot create connection: handle not found');
537
- return null;
538
- }
539
- const connection = {
540
- id: `connection-${fromHandleId}-${toHandleId}`,
541
- from: {
542
- barId: fromHandle.barId,
543
- side: fromHandle.side,
544
- },
545
- to: {
546
- barId: toHandle.barId,
547
- side: toHandle.side,
548
- },
549
- style: options?.style,
550
- label: options?.label,
551
- };
552
- connectionPaths.value.push(connection);
553
- return connection;
554
- };
555
- const deleteConnection = (connectionId) => {
556
- const index = connectionPaths.value.findIndex(conn => conn.id === connectionId);
557
- if (index >= 0) {
558
- connectionPaths.value.splice(index, 1);
559
- return true;
560
- }
561
- return false;
562
- };
563
- const getConnectionsForBar = (barId) => {
564
- return connectionPaths.value.filter(conn => conn.from.barId === barId || conn.to.barId === barId);
565
- };
566
- const getHandlesForBar = (barId) => {
567
- return connectionHandles.value.filter(handle => handle.barId === barId);
568
- };
569
- const sortByColumn = (colIndex) => {
570
- const column = columns.value[colIndex];
571
- if (column.sortable === false)
572
- return;
573
- let newDirection;
574
- if (sortState.value.column === colIndex) {
575
- if (sortState.value.direction === 'asc') {
576
- newDirection = 'desc';
577
- }
578
- else {
579
- newDirection = 'asc';
580
- }
581
- }
582
- else {
583
- newDirection = 'asc';
584
- }
585
- sortState.value.column = colIndex;
586
- sortState.value.direction = newDirection;
587
- // Note: The actual sorting is now handled in the filteredRows computed property
588
- // This ensures that sorting works on filtered data without modifying the original rows
589
- };
590
- const setFilter = (colIndex, filter) => {
591
- if (!filter.value && !filter.startValue && !filter.endValue) {
592
- // Remove filter if empty
593
- delete filterState.value[colIndex];
594
- }
595
- else {
596
- filterState.value[colIndex] = filter;
597
- }
598
- };
599
- const clearFilter = (colIndex) => {
600
- delete filterState.value[colIndex];
601
- };
602
- // Row action methods
603
- /**
604
- * Add a new row to the table.
605
- * @param rowData - Optional partial row data to initialize the new row with
606
- * @param position - Where to insert the row: 'start', 'end', or a specific index
607
- * @returns The index of the newly added row
608
- */
609
- const addRow = (rowData, position = 'end') => {
610
- // Create a new row with default empty values for each column
611
- const newRow = {};
612
- for (const column of columns.value) {
613
- newRow[column.name] = '';
614
- }
615
- // Merge in any provided row data
616
- if (rowData) {
617
- Object.assign(newRow, rowData);
618
- }
619
- let insertIndex;
620
- if (position === 'start') {
621
- insertIndex = 0;
622
- rows.value.unshift(newRow);
623
- }
624
- else if (position === 'end') {
625
- insertIndex = rows.value.length;
626
- rows.value.push(newRow);
627
- }
628
- else {
629
- insertIndex = Math.max(0, Math.min(position, rows.value.length));
630
- rows.value.splice(insertIndex, 0, newRow);
631
- }
632
- return insertIndex;
633
- };
634
- /**
635
- * Delete a row from the table.
636
- * @param rowIndex - The index of the row to delete
637
- * @returns The deleted row, or null if the index was invalid
638
- */
639
- const deleteRow = (rowIndex) => {
640
- if (rowIndex < 0 || rowIndex >= rows.value.length) {
641
- return null;
642
- }
643
- const [deletedRow] = rows.value.splice(rowIndex, 1);
644
- // Clean up row modifications tracking for the deleted row
645
- delete rowModifications.value[rowIndex];
646
- delete rowExpandStates.value[rowIndex];
647
- // Shift modification/expand state indices for rows after the deleted one
648
- const newModifications = {};
649
- const newExpandStates = {};
650
- for (const [key, value] of Object.entries(rowModifications.value)) {
651
- const idx = parseInt(key);
652
- if (idx > rowIndex) {
653
- newModifications[idx - 1] = value;
654
- }
655
- else {
656
- newModifications[idx] = value;
657
- }
658
- }
659
- for (const [key, value] of Object.entries(rowExpandStates.value)) {
660
- const idx = parseInt(key);
661
- if (idx > rowIndex) {
662
- newExpandStates[idx - 1] = value;
663
- }
664
- else {
665
- newExpandStates[idx] = value;
666
- }
667
- }
668
- rowModifications.value = newModifications;
669
- rowExpandStates.value = newExpandStates;
670
- return deletedRow;
671
- };
672
- /**
673
- * Duplicate a row in the table.
674
- * @param rowIndex - The index of the row to duplicate
675
- * @returns The index of the new duplicated row, or -1 if the index was invalid
676
- */
677
- const duplicateRow = (rowIndex) => {
678
- if (rowIndex < 0 || rowIndex >= rows.value.length) {
679
- return -1;
680
- }
681
- // Deep clone the row data
682
- const originalRow = rows.value[rowIndex];
683
- const duplicatedRow = JSON.parse(JSON.stringify(originalRow));
684
- // Insert the duplicated row after the original
685
- const newIndex = rowIndex + 1;
686
- rows.value.splice(newIndex, 0, duplicatedRow);
687
- return newIndex;
688
- };
689
- /**
690
- * Insert a new row above the specified row.
691
- * @param rowIndex - The index of the row to insert above
692
- * @param rowData - Optional partial row data to initialize the new row with
693
- * @returns The index of the newly inserted row
694
- */
695
- const insertRowAbove = (rowIndex, rowData) => {
696
- const insertIndex = Math.max(0, rowIndex);
697
- return addRow(rowData, insertIndex);
698
- };
699
- /**
700
- * Insert a new row below the specified row.
701
- * @param rowIndex - The index of the row to insert below
702
- * @param rowData - Optional partial row data to initialize the new row with
703
- * @returns The index of the newly inserted row
704
- */
705
- const insertRowBelow = (rowIndex, rowData) => {
706
- const insertIndex = Math.min(rowIndex + 1, rows.value.length);
707
- return addRow(rowData, insertIndex);
708
- };
709
- /**
710
- * Move a row from one position to another.
711
- * @param fromIndex - The current index of the row to move
712
- * @param toIndex - The target index to move the row to
713
- * @returns true if the move was successful, false otherwise
714
- */
715
- const moveRow = (fromIndex, toIndex) => {
716
- // Validate indices
717
- if (fromIndex < 0 ||
718
- fromIndex >= rows.value.length ||
719
- toIndex < 0 ||
720
- toIndex >= rows.value.length ||
721
- fromIndex === toIndex) {
722
- return false;
723
- }
724
- // Remove the row from its current position
725
- const [movedRow] = rows.value.splice(fromIndex, 1);
726
- // Insert at the new position
727
- rows.value.splice(toIndex, 0, movedRow);
728
- // Update row modification and expand state indices
729
- const newModifications = {};
730
- const newExpandStates = {};
731
- for (const [key, value] of Object.entries(rowModifications.value)) {
732
- const idx = parseInt(key);
733
- let newIdx = idx;
734
- if (idx === fromIndex) {
735
- // The moved row
736
- newIdx = toIndex;
737
- }
738
- else if (fromIndex < toIndex) {
739
- // Moving down: rows between fromIndex and toIndex shift up
740
- if (idx > fromIndex && idx <= toIndex) {
741
- newIdx = idx - 1;
742
- }
743
- }
744
- else {
745
- // Moving up: rows between toIndex and fromIndex shift down
746
- if (idx >= toIndex && idx < fromIndex) {
747
- newIdx = idx + 1;
748
- }
749
- }
750
- newModifications[newIdx] = value;
751
- }
752
- for (const [key, value] of Object.entries(rowExpandStates.value)) {
753
- const idx = parseInt(key);
754
- let newIdx = idx;
755
- if (idx === fromIndex) {
756
- newIdx = toIndex;
757
- }
758
- else if (fromIndex < toIndex) {
759
- if (idx > fromIndex && idx <= toIndex) {
760
- newIdx = idx - 1;
761
- }
762
- }
763
- else {
764
- if (idx >= toIndex && idx < fromIndex) {
765
- newIdx = idx + 1;
766
- }
767
- }
768
- newExpandStates[newIdx] = value;
769
- }
770
- rowModifications.value = newModifications;
771
- rowExpandStates.value = newExpandStates;
772
- return true;
773
- };
774
- return {
775
- // state
776
- columns,
777
- config,
778
- connectionHandles,
779
- connectionPaths,
780
- display,
781
- filterState,
782
- ganttBars,
783
- modal,
784
- rows,
785
- sortState,
786
- table,
787
- updates,
788
- // getters
789
- filteredRows,
790
- hasPinnedColumns,
791
- isGanttView,
792
- isTreeView,
793
- isDependencyGraphEnabled,
794
- numberedRowWidth,
795
- zeroColumn,
796
- // resolver
797
- linkResolver,
798
- // actions
799
- addRow,
800
- clearFilter,
801
- closeModal,
802
- createConnection,
803
- deleteConnection,
804
- deleteRow,
805
- duplicateRow,
806
- getCellData,
807
- getCellDisplayValue,
808
- getConnectionsForBar,
809
- getFormattedValue,
810
- getHandlesForBar,
811
- getHeaderCellStyle,
812
- getRowExpandSymbol,
813
- insertRowAbove,
814
- insertRowBelow,
815
- isRowGantt,
816
- isRowVisible,
817
- moveRow,
818
- registerConnectionHandle,
819
- registerGanttBar,
820
- resizeColumn,
821
- setCellData,
822
- setCellText,
823
- setFilter,
824
- sortByColumn,
825
- toggleRowExpand,
826
- unregisterConnectionHandle,
827
- unregisterGanttBar,
828
- updateGanttBar,
829
- updateRows,
830
- };
831
- });
832
- return createStore();
833
- };