@stonecrop/atable 0.6.1 → 0.6.3

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.
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
3
+ <path d="M32,64C14.35,64,0,49.65,0,32S14.35,0,32,0s32,14.35,32,32-14.35,32-32,32ZM32,4c-15.44,0-28,12.56-28,28s12.56,28,28,28,28-12.56,28-28S47.44,4,32,4Z" style="fill: #000; stroke-width: 0px;"/>
4
+ <polygon points="46.84 19.99 44.01 17.16 32 29.17 19.99 17.16 17.16 19.99 29.17 32 17.16 44.01 19.99 46.84 32 34.83 44.01 46.84 46.84 44.01 34.83 32 46.84 19.99" style="fill: #000; stroke-width: 0px;"/>
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70.67 70.67">
3
+ <path d="M68.67,16.67h-14.67V2c0-1.1-.9-2-2-2H2C.9,0,0,.9,0,2v50c0,1.1.9,2,2,2h14.67v14.67c0,1.1.9,2,2,2h50c1.1,0,2-.9,2-2V18.67c0-1.1-.9-2-2-2ZM4,4h46v46H4V4ZM66.67,66.67H20.67v-12.67h31.33c1.1,0,2-.9,2-2v-31.33h12.67v46Z" style="fill: #000; stroke-width: 0px;"/>
4
+ <polygon points="41 25 29 25 29 13 25 13 25 25 13 25 13 29 25 29 25 41 29 41 29 29 41 29 41 25" style="fill: #000; stroke-width: 0px;"/>
5
+ </svg>
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70.6 61.51">
3
+ <rect y="43.84" width="70.6" height="17.67" style="fill: #000; stroke-width: 0px;"/>
4
+ <rect y="22.17" width="70.6" height="17.67" style="fill: #000; stroke-width: 0px;"/>
5
+ <g>
6
+ <polygon points="70.6 2.5 68.63 2.5 68.63 2 68.14 2 68.14 0 70.6 0 70.6 2.5" style="fill: #000; stroke-width: 0px;"/>
7
+ <path d="M65.28,2h-2.86V0h2.86v2ZM59.57,2h-2.86V0h2.86v2ZM53.86,2h-2.86V0h2.86v2ZM48.15,2h-2.86V0h2.86v2ZM42.44,2h-2.86V0h2.86v2ZM36.73,2h-2.86V0h2.86v2ZM31.02,2h-2.86V0h2.86v2ZM25.31,2h-2.86V0h2.86v2ZM19.6,2h-2.86V0h2.86v2ZM13.89,2h-2.86V0h2.86v2ZM8.18,2h-2.86V0h2.86v2Z" style="fill: #000; stroke-width: 0px;"/>
8
+ <polygon points="1.97 2.5 0 2.5 0 0 2.47 0 2.47 2 1.97 2 1.97 2.5" style="fill: #000; stroke-width: 0px;"/>
9
+ <path d="M1.97,13.43H0v-2.73h1.97v2.73ZM1.97,7.97H0v-2.73h1.97v2.73Z" style="fill: #000; stroke-width: 0px;"/>
10
+ <polygon points="2.47 18.67 0 18.67 0 16.17 1.97 16.17 1.97 16.67 2.47 16.67 2.47 18.67" style="fill: #000; stroke-width: 0px;"/>
11
+ <path d="M65.28,18.67h-2.86v-2h2.86v2ZM59.57,18.67h-2.86v-2h2.86v2ZM53.86,18.67h-2.86v-2h2.86v2ZM48.15,18.67h-2.86v-2h2.86v2ZM42.44,18.67h-2.86v-2h2.86v2ZM36.73,18.67h-2.86v-2h2.86v2ZM31.02,18.67h-2.86v-2h2.86v2ZM25.31,18.67h-2.86v-2h2.86v2ZM19.6,18.67h-2.86v-2h2.86v2ZM13.89,18.67h-2.86v-2h2.86v2ZM8.18,18.67h-2.86v-2h2.86v2Z" style="fill: #000; stroke-width: 0px;"/>
12
+ <polygon points="70.6 18.67 68.14 18.67 68.14 16.67 68.63 16.67 68.63 16.17 70.6 16.17 70.6 18.67" style="fill: #000; stroke-width: 0px;"/>
13
+ <path d="M70.6,13.43h-1.97v-2.73h1.97v2.73ZM70.6,7.97h-1.97v-2.73h1.97v2.73Z" style="fill: #000; stroke-width: 0px;"/>
14
+ </g>
15
+ </svg>
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70.6 61.51">
3
+ <g>
4
+ <polygon points="70.6 45.34 68.63 45.34 68.63 44.84 68.14 44.84 68.14 42.84 70.6 42.84 70.6 45.34" style="fill: #000; stroke-width: 0px;"/>
5
+ <path d="M65.28,44.84h-2.86v-2h2.86v2ZM59.57,44.84h-2.86v-2h2.86v2ZM53.86,44.84h-2.86v-2h2.86v2ZM48.15,44.84h-2.86v-2h2.86v2ZM42.44,44.84h-2.86v-2h2.86v2ZM36.73,44.84h-2.86v-2h2.86v2ZM31.02,44.84h-2.86v-2h2.86v2ZM25.31,44.84h-2.86v-2h2.86v2ZM19.6,44.84h-2.86v-2h2.86v2ZM13.89,44.84h-2.86v-2h2.86v2ZM8.18,44.84h-2.86v-2h2.86v2Z" style="fill: #000; stroke-width: 0px;"/>
6
+ <polygon points="1.97 45.34 0 45.34 0 42.84 2.47 42.84 2.47 44.84 1.97 44.84 1.97 45.34" style="fill: #000; stroke-width: 0px;"/>
7
+ <path d="M1.97,56.27H0v-2.73h1.97v2.73ZM1.97,50.81H0v-2.73h1.97v2.73Z" style="fill: #000; stroke-width: 0px;"/>
8
+ <polygon points="2.47 61.51 0 61.51 0 59.01 1.97 59.01 1.97 59.51 2.47 59.51 2.47 61.51" style="fill: #000; stroke-width: 0px;"/>
9
+ <path d="M65.28,61.51h-2.86v-2h2.86v2ZM59.57,61.51h-2.86v-2h2.86v2ZM53.86,61.51h-2.86v-2h2.86v2ZM48.15,61.51h-2.86v-2h2.86v2ZM42.44,61.51h-2.86v-2h2.86v2ZM36.73,61.51h-2.86v-2h2.86v2ZM31.02,61.51h-2.86v-2h2.86v2ZM25.31,61.51h-2.86v-2h2.86v2ZM19.6,61.51h-2.86v-2h2.86v2ZM13.89,61.51h-2.86v-2h2.86v2ZM8.18,61.51h-2.86v-2h2.86v2Z" style="fill: #000; stroke-width: 0px;"/>
10
+ <polygon points="70.6 61.51 68.14 61.51 68.14 59.51 68.63 59.51 68.63 59.01 70.6 59.01 70.6 61.51" style="fill: #000; stroke-width: 0px;"/>
11
+ <path d="M70.6,56.27h-1.97v-2.73h1.97v2.73ZM70.6,50.81h-1.97v-2.73h1.97v2.73Z" style="fill: #000; stroke-width: 0px;"/>
12
+ </g>
13
+ <rect y="21.67" width="70.6" height="17.67" style="fill: #000; stroke-width: 0px;"/>
14
+ <rect width="70.6" height="17.67" style="fill: #000; stroke-width: 0px;"/>
15
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70.67 70.64">
3
+ <path d="M70.08,33.9l-9.75-9.75-2.83,2.83,6.33,6.33h-26.51V6.81l6.33,6.33,2.83-2.83L36.75.56c-.75-.75-2.08-.75-2.83,0l-9.75,9.75,2.83,2.83,6.33-6.33v26.5H6.83l6.33-6.33-2.83-2.83L.59,33.9c-.38.38-.59.88-.59,1.41s.21,1.04.59,1.41l9.75,9.75,2.83-2.83-6.33-6.33h26.5v26.51l-6.33-6.33-2.83,2.83,9.75,9.75c.38.38.88.59,1.41.59s1.04-.21,1.41-.59l9.75-9.75-2.83-2.83-6.33,6.33v-26.51h26.51l-6.33,6.33,2.83,2.83,9.75-9.75c.38-.38.59-.88.59-1.41s-.21-1.04-.59-1.41Z" style="fill: #000; stroke-width: 0px;"/>
4
+ </svg>
@@ -0,0 +1,27 @@
1
+ import ACell from './components/ACell.vue';
2
+ import AExpansionRow from './components/AExpansionRow.vue';
3
+ import AGanttCell from './components/AGanttCell.vue';
4
+ import ARow from './components/ARow.vue';
5
+ import ATable from './components/ATable.vue';
6
+ import ATableHeader from './components/ATableHeader.vue';
7
+ import ATableLoading from './components/ATableLoading.vue';
8
+ import ATableLoadingBar from './components/ATableLoadingBar.vue';
9
+ import ATableModal from './components/ATableModal.vue';
10
+ export { createTableStore } from './stores/table';
11
+ /**
12
+ * Install all ATable components
13
+ * @param app - Vue app instance
14
+ * @public
15
+ */
16
+ function install(app /* options */) {
17
+ app.component('ACell', ACell);
18
+ app.component('AExpansionRow', AExpansionRow);
19
+ app.component('AGanttCell', AGanttCell);
20
+ app.component('ARow', ARow);
21
+ app.component('ATable', ATable);
22
+ app.component('ATableHeader', ATableHeader);
23
+ app.component('ATableLoading', ATableLoading);
24
+ app.component('ATableLoadingBar', ATableLoadingBar);
25
+ app.component('ATableModal', ATableModal);
26
+ }
27
+ export { ACell, AExpansionRow, AGanttCell, ARow, ATable, ATableHeader, ATableLoading, ATableLoadingBar, ATableModal, install, };
@@ -0,0 +1,403 @@
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
+ // util functions
14
+ const createTableObject = () => {
15
+ const table = {};
16
+ for (const [colIndex, column] of columns.value.entries()) {
17
+ for (const [rowIndex, row] of rows.value.entries()) {
18
+ table[`${colIndex}:${rowIndex}`] = row[column.name];
19
+ }
20
+ }
21
+ return table;
22
+ };
23
+ const createDisplayObject = (forceRecalculate = false) => {
24
+ const defaultDisplay = [Object.assign({}, { rowModified: false })];
25
+ // Only use provided display on initial load, not on reactive updates
26
+ if (!forceRecalculate && initData.display) {
27
+ if ('0:0' in initData.display) {
28
+ return initData.display;
29
+ }
30
+ // else if ('default' in display) {
31
+ // // TODO: (typing) what is the possible input here for 'default'?
32
+ // defaultDisplay = display.default
33
+ // }
34
+ }
35
+ // TODO: (typing) is this type correct for the parent set?
36
+ const parents = new Set();
37
+ for (let rowIndex = 0; rowIndex < rows.value.length; rowIndex++) {
38
+ const row = rows.value[rowIndex];
39
+ if (row.parent !== null && row.parent !== undefined) {
40
+ parents.add(row.parent);
41
+ }
42
+ }
43
+ for (let rowIndex = 0; rowIndex < rows.value.length; rowIndex++) {
44
+ const row = rows.value[rowIndex];
45
+ defaultDisplay[rowIndex] = {
46
+ childrenOpen: false,
47
+ expanded: false,
48
+ indent: row.indent || 0,
49
+ isParent: parents.has(rowIndex),
50
+ isRoot: row.parent === null || row.parent === undefined,
51
+ rowModified: false,
52
+ open: row.parent === null || row.parent === undefined,
53
+ parent: row.parent,
54
+ };
55
+ }
56
+ return defaultDisplay;
57
+ };
58
+ // state
59
+ const columns = ref(initData.columns);
60
+ const rows = ref(initData.rows);
61
+ const config = ref(initData.config || {});
62
+ const table = ref(initData.table || createTableObject());
63
+ // Track row modifications and expand states separately from the computed display
64
+ const rowModifications = ref({});
65
+ const rowExpandStates = ref({});
66
+ // Use a ref instead of computed to have more control over reactivity
67
+ const displayData = ref([]);
68
+ const calculateDisplay = () => {
69
+ // Always force recalculation when this method is called
70
+ const baseDisplay = createDisplayObject(true);
71
+ // Apply persistent modifications and expand states
72
+ for (let i = 0; i < baseDisplay.length; i++) {
73
+ if (rowModifications.value[i]) {
74
+ baseDisplay[i].rowModified = rowModifications.value[i];
75
+ }
76
+ if (rowExpandStates.value[i]) {
77
+ if (rowExpandStates.value[i].childrenOpen !== undefined) {
78
+ baseDisplay[i].childrenOpen = rowExpandStates.value[i].childrenOpen;
79
+ }
80
+ if (rowExpandStates.value[i].expanded !== undefined) {
81
+ baseDisplay[i].expanded = rowExpandStates.value[i].expanded;
82
+ }
83
+ }
84
+ }
85
+ // Calculate 'open' property for tree view based on parent's childrenOpen state
86
+ if (isTreeView.value) {
87
+ for (let i = 0; i < baseDisplay.length; i++) {
88
+ const row = baseDisplay[i];
89
+ if (!row.isRoot && row.parent !== null && row.parent !== undefined) {
90
+ // Child row is 'open' if its parent's childrenOpen is true
91
+ const parentIndex = row.parent;
92
+ if (parentIndex >= 0 && parentIndex < baseDisplay.length) {
93
+ baseDisplay[i].open = baseDisplay[parentIndex].childrenOpen || false;
94
+ }
95
+ }
96
+ }
97
+ }
98
+ displayData.value = baseDisplay;
99
+ return baseDisplay;
100
+ };
101
+ const display = computed(() => displayData.value);
102
+ const modal = ref(initData.modal || { visible: false });
103
+ const updates = ref({});
104
+ const ganttBars = ref([]);
105
+ const connectionHandles = ref([]);
106
+ const connectionPaths = ref([]);
107
+ // getters
108
+ const hasPinnedColumns = computed(() => columns.value.some(col => col.pinned));
109
+ const isGanttView = computed(() => config.value.view === 'gantt' || config.value.view === 'tree-gantt');
110
+ const isTreeView = computed(() => config.value.view === 'tree' || config.value.view === 'tree-gantt');
111
+ const numberedRowWidth = computed(() => {
112
+ const indent = Math.ceil(rows.value.length / 100 + 1);
113
+ return `${indent}ch`;
114
+ });
115
+ const zeroColumn = computed(() => config.value.view ? ['list', 'tree', 'tree-gantt', 'list-expansion'].includes(config.value.view) : false);
116
+ // Initialize display data after all computed properties are defined
117
+ calculateDisplay();
118
+ // actions
119
+ const getCellData = (colIndex, rowIndex) => table.value[`${colIndex}:${rowIndex}`];
120
+ const setCellData = (colIndex, rowIndex, value) => {
121
+ const index = `${colIndex}:${rowIndex}`;
122
+ const col = columns.value[colIndex];
123
+ if (table.value[index] !== value) {
124
+ rowModifications.value[rowIndex] = true;
125
+ }
126
+ table.value[index] = value;
127
+ // Create a new row object to ensure reactivity
128
+ rows.value[rowIndex] = {
129
+ ...rows.value[rowIndex],
130
+ [col.name]: value,
131
+ };
132
+ // Recalculate display when rows change
133
+ calculateDisplay();
134
+ };
135
+ const updateRows = (newRows) => {
136
+ rows.value = newRows;
137
+ calculateDisplay();
138
+ };
139
+ const setCellText = (colIndex, rowIndex, value) => {
140
+ const index = `${colIndex}:${rowIndex}`;
141
+ if (table.value[index] !== value) {
142
+ rowModifications.value[rowIndex] = true;
143
+ updates.value[index] = value;
144
+ }
145
+ };
146
+ const getHeaderCellStyle = (column) => {
147
+ const isLastCol = columns.value.indexOf(column) === columns.value.length - 1;
148
+ // if the table is full width, the last column should not be resizable;
149
+ // ref: https://github.com/agritheory/stonecrop/pull/196#issuecomment-2503762641
150
+ const isResizable = config.value.fullWidth ? column.resizable && !isLastCol : column.resizable;
151
+ return {
152
+ width: column.width || '40ch',
153
+ textAlign: column.align || 'center',
154
+ ...(isResizable && {
155
+ resize: 'horizontal',
156
+ overflow: 'hidden',
157
+ whiteSpace: 'nowrap',
158
+ }),
159
+ };
160
+ };
161
+ const resizeColumn = (colIndex, newWidth) => {
162
+ if (colIndex < 0 || colIndex >= columns.value.length)
163
+ return;
164
+ const minWidth = 40;
165
+ const finalWidth = Math.max(newWidth, minWidth);
166
+ columns.value[colIndex] = {
167
+ ...columns.value[colIndex],
168
+ width: `${finalWidth}px`,
169
+ };
170
+ };
171
+ const isRowGantt = (rowIndex) => {
172
+ const row = rows.value[rowIndex];
173
+ return isGanttView.value && row.gantt !== undefined;
174
+ };
175
+ const isRowVisible = (rowIndex) => {
176
+ return !isTreeView.value || display.value[rowIndex].isRoot || display.value[rowIndex].open;
177
+ };
178
+ const getRowExpandSymbol = (rowIndex) => {
179
+ if (!isTreeView.value && config.value.view !== 'list-expansion') {
180
+ return '';
181
+ }
182
+ if (isTreeView.value && (display.value[rowIndex].isRoot || display.value[rowIndex].isParent)) {
183
+ return display.value[rowIndex].childrenOpen ? '▼' : '►';
184
+ }
185
+ if (config.value.view === 'list-expansion') {
186
+ return display.value[rowIndex].expanded ? '▼' : '►';
187
+ }
188
+ return '';
189
+ };
190
+ const toggleRowExpand = (rowIndex) => {
191
+ if (isTreeView.value) {
192
+ const currentState = rowExpandStates.value[rowIndex] || {};
193
+ const currentChildrenOpen = currentState.childrenOpen ?? display.value[rowIndex].childrenOpen;
194
+ const newChildrenOpen = !currentChildrenOpen;
195
+ rowExpandStates.value[rowIndex] = {
196
+ ...currentState,
197
+ childrenOpen: newChildrenOpen
198
+ };
199
+ // If we're closing, recursively close all descendant nodes
200
+ if (!newChildrenOpen) {
201
+ for (let index = 0; index < rows.value.length; index++) {
202
+ if (display.value[index].parent === rowIndex) {
203
+ const childState = rowExpandStates.value[index] || {};
204
+ rowExpandStates.value[index] = {
205
+ ...childState,
206
+ childrenOpen: false
207
+ };
208
+ toggleRowExpand(index);
209
+ }
210
+ }
211
+ }
212
+ }
213
+ else if (config.value.view === 'list-expansion') {
214
+ const currentState = rowExpandStates.value[rowIndex] || {};
215
+ const currentExpanded = currentState.expanded ?? display.value[rowIndex].expanded;
216
+ rowExpandStates.value[rowIndex] = {
217
+ ...currentState,
218
+ expanded: !currentExpanded
219
+ };
220
+ }
221
+ };
222
+ const getCellDisplayValue = (colIndex, rowIndex) => {
223
+ const cellData = getCellData(colIndex, rowIndex);
224
+ return getFormattedValue(colIndex, rowIndex, cellData);
225
+ };
226
+ const getFormattedValue = (colIndex, rowIndex, value) => {
227
+ const column = columns.value[colIndex];
228
+ const row = rows.value[rowIndex];
229
+ const format = column.format;
230
+ if (!format) {
231
+ return value;
232
+ }
233
+ if (typeof format === 'function') {
234
+ return format(value, { table: table.value, row, column });
235
+ }
236
+ else if (typeof format === 'string') {
237
+ // parse format function from string
238
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
239
+ const formatFn = Function(`"use strict";return (${format})`)();
240
+ return formatFn(value, { table: table.value, row, column });
241
+ }
242
+ return value;
243
+ };
244
+ const closeModal = (event) => {
245
+ if (!(event.target instanceof Node)) {
246
+ // if the target is not a node, it's probably a custom click event to Document or Window
247
+ // err on the side of closing the modal in that case
248
+ if (modal.value.visible)
249
+ modal.value.visible = false;
250
+ }
251
+ else if (!modal.value.parent?.contains(event.target)) {
252
+ if (modal.value.visible)
253
+ modal.value.visible = false;
254
+ }
255
+ };
256
+ const getIndent = (colIndex, indentLevel) => {
257
+ if (indentLevel && colIndex === 0 && indentLevel > 0) {
258
+ return `${indentLevel}ch`;
259
+ }
260
+ else {
261
+ return 'inherit';
262
+ }
263
+ };
264
+ const updateGanttBar = (event) => {
265
+ // update the local gantt bar cache
266
+ const ganttBar = rows.value[event.rowIndex]?.gantt;
267
+ if (ganttBar) {
268
+ if (event.type === 'resize') {
269
+ if (event.edge === 'start') {
270
+ ganttBar.startIndex = event.newStart;
271
+ ganttBar.endIndex = event.end;
272
+ ganttBar.colspan = ganttBar.endIndex - ganttBar.startIndex;
273
+ }
274
+ else if (event.edge === 'end') {
275
+ ganttBar.startIndex = event.start;
276
+ ganttBar.endIndex = event.newEnd;
277
+ ganttBar.colspan = ganttBar.endIndex - ganttBar.startIndex;
278
+ }
279
+ }
280
+ else if (event.type === 'bar') {
281
+ ganttBar.startIndex = event.newStart;
282
+ ganttBar.endIndex = event.newEnd;
283
+ ganttBar.colspan = ganttBar.endIndex - ganttBar.startIndex;
284
+ }
285
+ }
286
+ };
287
+ const registerGanttBar = (barInfo) => {
288
+ const existingIndex = ganttBars.value.findIndex(bar => bar.id === barInfo.id);
289
+ if (existingIndex >= 0) {
290
+ // @ts-expect-error TODO: for some reason, the IDE is expecting an unref'd value
291
+ ganttBars.value[existingIndex] = barInfo;
292
+ }
293
+ else {
294
+ // @ts-expect-error TODO: for some reason, the IDE is expecting an unref'd value
295
+ ganttBars.value.push(barInfo);
296
+ }
297
+ };
298
+ const unregisterGanttBar = (barId) => {
299
+ const index = ganttBars.value.findIndex(bar => bar.id === barId);
300
+ if (index >= 0) {
301
+ ganttBars.value.splice(index, 1);
302
+ }
303
+ };
304
+ const registerConnectionHandle = (handleInfo) => {
305
+ const existingIndex = connectionHandles.value.findIndex(handle => handle.id === handleInfo.id);
306
+ if (existingIndex >= 0) {
307
+ // @ts-expect-error TODO: for some reason, the IDE is expecting an unref'd value
308
+ connectionHandles.value[existingIndex] = handleInfo;
309
+ }
310
+ else {
311
+ // @ts-expect-error TODO: for some reason, the IDE is expecting an unref'd value
312
+ connectionHandles.value.push(handleInfo);
313
+ }
314
+ };
315
+ const unregisterConnectionHandle = (handleId) => {
316
+ const index = connectionHandles.value.findIndex(handle => handle.id === handleId);
317
+ if (index >= 0) {
318
+ connectionHandles.value.splice(index, 1);
319
+ }
320
+ };
321
+ const createConnection = (fromHandleId, toHandleId, options) => {
322
+ const fromHandle = connectionHandles.value.find(h => h.id === fromHandleId);
323
+ const toHandle = connectionHandles.value.find(h => h.id === toHandleId);
324
+ if (!fromHandle || !toHandle) {
325
+ console.warn('Cannot create connection: handle not found');
326
+ return null;
327
+ }
328
+ const connection = {
329
+ id: `connection-${fromHandleId}-${toHandleId}`,
330
+ from: {
331
+ barId: fromHandle.barId,
332
+ side: fromHandle.side,
333
+ },
334
+ to: {
335
+ barId: toHandle.barId,
336
+ side: toHandle.side,
337
+ },
338
+ style: options?.style,
339
+ label: options?.label,
340
+ };
341
+ connectionPaths.value.push(connection);
342
+ return connection;
343
+ };
344
+ const deleteConnection = (connectionId) => {
345
+ const index = connectionPaths.value.findIndex(conn => conn.id === connectionId);
346
+ if (index >= 0) {
347
+ connectionPaths.value.splice(index, 1);
348
+ return true;
349
+ }
350
+ return false;
351
+ };
352
+ const getConnectionsForBar = (barId) => {
353
+ return connectionPaths.value.filter(conn => conn.from.barId === barId || conn.to.barId === barId);
354
+ };
355
+ const getHandlesForBar = (barId) => {
356
+ return connectionHandles.value.filter(handle => handle.barId === barId);
357
+ };
358
+ return {
359
+ // state
360
+ columns,
361
+ config,
362
+ connectionHandles,
363
+ connectionPaths,
364
+ display,
365
+ ganttBars,
366
+ modal,
367
+ rows,
368
+ table,
369
+ updates,
370
+ // getters
371
+ hasPinnedColumns,
372
+ isGanttView,
373
+ isTreeView,
374
+ numberedRowWidth,
375
+ zeroColumn,
376
+ // actions
377
+ closeModal,
378
+ createConnection,
379
+ deleteConnection,
380
+ getCellData,
381
+ getCellDisplayValue,
382
+ getConnectionsForBar,
383
+ getFormattedValue,
384
+ getHandlesForBar,
385
+ getHeaderCellStyle,
386
+ getIndent,
387
+ getRowExpandSymbol,
388
+ isRowGantt,
389
+ isRowVisible,
390
+ registerConnectionHandle,
391
+ registerGanttBar,
392
+ resizeColumn,
393
+ setCellData,
394
+ setCellText,
395
+ toggleRowExpand,
396
+ unregisterConnectionHandle,
397
+ unregisterGanttBar,
398
+ updateGanttBar,
399
+ updateRows,
400
+ };
401
+ });
402
+ return createStore();
403
+ };
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.52.10"
8
+ "packageVersion": "7.55.2"
9
9
  }
10
10
  ]
11
11
  }
File without changes
@@ -0,0 +1,7 @@
1
+ export const isHtmlString = (htmlString) => {
2
+ const $document = new DOMParser().parseFromString(htmlString, 'text/html');
3
+ return Array.from($document.body.childNodes).some(node => node.nodeType === 1);
4
+ };
5
+ export const generateHash = (length = 8) => {
6
+ return Array.from({ length }, () => Math.floor(Math.random() * 16).toString(16)).join('');
7
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=display.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../../tests/display.ts"],"names":[],"mappings":""}
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { setActivePinia, createPinia } from 'pinia';
3
+ import { createTableStore } from '../src/stores/table';
4
+ describe('Display reactivity fix', () => {
5
+ const mockColumns = [
6
+ { name: 'id', label: 'ID' },
7
+ { name: 'name', label: 'Name' }
8
+ ];
9
+ beforeEach(() => {
10
+ setActivePinia(createPinia());
11
+ });
12
+ it('should update display properties when rows change', () => {
13
+ // Initial data with parent-child relationships
14
+ const initialRows = [
15
+ { id: 1, name: 'Parent 1', parent: undefined },
16
+ { id: 2, name: 'Child 1.1', parent: 0 },
17
+ { id: 3, name: 'Child 1.2', parent: 0 }
18
+ ];
19
+ const store = createTableStore({ columns: mockColumns, rows: initialRows });
20
+ // Check initial display state
21
+ expect(store.display[0].isRoot).toBe(true);
22
+ expect(store.display[0].isParent).toBe(true);
23
+ expect(store.display[1].isRoot).toBe(false);
24
+ expect(store.display[1].parent).toBe(0);
25
+ expect(store.display[2].isRoot).toBe(false);
26
+ expect(store.display[2].parent).toBe(0);
27
+ // Change the rows data - modify parent relationships
28
+ const newRows = [
29
+ { id: 1, name: 'Parent 1', parent: undefined },
30
+ { id: 2, name: 'Child 1.1', parent: 0 },
31
+ { id: 3, name: 'New Root', parent: undefined }, // Changed from child to root
32
+ { id: 4, name: 'New Child', parent: 2 } // New child of previous child
33
+ ];
34
+ // Update rows using the new method
35
+ store.updateRows(newRows);
36
+ // Check that display state has been recalculated
37
+ expect(store.display[0].isRoot).toBe(true);
38
+ expect(store.display[0].isParent).toBe(true);
39
+ expect(store.display[1].isRoot).toBe(false);
40
+ expect(store.display[1].isParent).toBe(false); // No children
41
+ expect(store.display[1].parent).toBe(0);
42
+ expect(store.display[2].isRoot).toBe(true); // Now a root
43
+ expect(store.display[2].isParent).toBe(true); // Has a child (row 3)
44
+ expect(store.display[2].parent).toBeUndefined(); // No parent
45
+ expect(store.display[3].isRoot).toBe(false);
46
+ expect(store.display[3].parent).toBe(2);
47
+ });
48
+ it('should preserve row modifications and expand states across data changes', () => {
49
+ const initialRows = [
50
+ { id: 1, name: 'Parent 1', parent: undefined },
51
+ { id: 2, name: 'Child 1.1', parent: 0 }
52
+ ];
53
+ const store = createTableStore({ columns: mockColumns, rows: initialRows });
54
+ // Modify a cell to mark row as modified
55
+ store.setCellData(1, 0, 'Modified Parent');
56
+ expect(store.display[0].rowModified).toBe(true);
57
+ // Toggle row expansion (for tree view)
58
+ store.toggleRowExpand(0);
59
+ // Now change the rows data but keep the same structure
60
+ const newRows = [
61
+ { id: 1, name: 'Parent 1 Updated', parent: undefined },
62
+ { id: 2, name: 'Child 1.1 Updated', parent: 0 }
63
+ ];
64
+ store.updateRows(newRows);
65
+ // Row modifications and expand states should be preserved
66
+ expect(store.display[0].rowModified).toBe(true);
67
+ // The expansion state should be preserved too
68
+ expect(store.display[0].childrenOpen).toBeDefined();
69
+ });
70
+ });
@@ -0,0 +1 @@
1
+ /* old table styles */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/atable",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "author": {
@@ -42,8 +42,8 @@
42
42
  "@vueuse/core": "^14.0.0",
43
43
  "pinia": "^3.0.3",
44
44
  "vue": "^3.5.22",
45
- "@stonecrop/themes": "0.6.1",
46
- "@stonecrop/utilities": "0.6.1"
45
+ "@stonecrop/themes": "0.6.3",
46
+ "@stonecrop/utilities": "0.6.3"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@eslint/js": "^9.38.0",