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