@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.
- package/dist/atable.js +775 -779
- package/dist/atable.js.map +1 -1
- package/dist/atable.umd.cjs +2 -2
- package/dist/atable.umd.cjs.map +1 -1
- 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/src/index.js +27 -0
- package/dist/src/stores/table.js +403 -0
- package/dist/src/tsdoc-metadata.json +1 -1
- package/dist/src/types/index.js +0 -0
- package/dist/src/utils.js +7 -0
- package/dist/tests/display.d.ts +2 -0
- package/dist/tests/display.d.ts.map +1 -0
- package/dist/tests/display.js +70 -0
- package/dist/themes/default.css +1 -0
- package/package.json +3 -3
|
@@ -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
|
+
};
|
|
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 @@
|
|
|
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.
|
|
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.
|
|
46
|
-
"@stonecrop/utilities": "0.6.
|
|
45
|
+
"@stonecrop/themes": "0.6.3",
|
|
46
|
+
"@stonecrop/utilities": "0.6.3"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@eslint/js": "^9.38.0",
|