@stonecrop/atable 0.13.12 → 0.14.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/README.md +12 -11
- package/dist/assets/index.css +1 -1
- package/dist/atable.d.ts +741 -32
- package/dist/atable.js +1494 -1470
- package/dist/atable.js.map +1 -1
- package/dist/probe.tsbuildinfo +1 -0
- package/dist/src/icons/index.d.ts +2 -1
- package/dist/src/icons/index.d.ts.map +1 -1
- package/dist/src/icons/index.js +8 -1
- package/dist/src/index.d.ts +2 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -4
- package/dist/src/schemaToColumns.d.ts +2 -2
- package/dist/src/schemaToColumns.d.ts.map +1 -1
- package/dist/src/schemaToColumns.js +7 -10
- package/dist/src/stores/table.d.ts +691 -12
- package/dist/src/stores/table.d.ts.map +1 -1
- package/dist/src/stores/table.js +17 -12
- package/dist/src/types/index.d.ts +50 -15
- package/dist/src/types/index.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/components/ACell.vue +12 -10
- package/src/components/AGanttCell.vue +9 -11
- package/src/components/ARow.vue +86 -13
- package/src/components/ARowActions.vue +53 -24
- package/src/components/ATable.vue +37 -4
- package/src/components/ATableColumnFilter.vue +19 -18
- package/src/components/ATableHeader.vue +0 -2
- package/src/components/ATableModal.vue +0 -2
- package/src/icons/index.ts +11 -1
- package/src/icons/stonecrop-ui-icon-open.svg +5 -0
- package/src/index.ts +10 -4
- package/src/schemaToColumns.ts +7 -9
- package/src/stores/table.ts +14 -12
- package/src/types/index.ts +54 -15
- package/src/components/AExpansionRow.vue +0 -105
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
type="button"
|
|
27
27
|
class="row-action-menu-item"
|
|
28
28
|
role="menuitem"
|
|
29
|
-
|
|
29
|
+
:disabled="action.disabled"
|
|
30
|
+
@click.stop="executeAction(action.type, $event)">
|
|
30
31
|
<span class="action-icon" v-html="action.icon" />
|
|
31
32
|
<span class="action-label">{{ action.label }}</span>
|
|
32
33
|
</button>
|
|
@@ -42,7 +43,8 @@
|
|
|
42
43
|
class="row-action-btn"
|
|
43
44
|
:title="action.label"
|
|
44
45
|
:aria-label="action.label"
|
|
45
|
-
|
|
46
|
+
:disabled="action.disabled"
|
|
47
|
+
@click.stop="executeAction(action.type, $event)">
|
|
46
48
|
<span class="action-icon" v-html="action.icon" />
|
|
47
49
|
</button>
|
|
48
50
|
</div>
|
|
@@ -65,7 +67,7 @@ const props = defineProps<{
|
|
|
65
67
|
}>()
|
|
66
68
|
|
|
67
69
|
const emit = defineEmits<{
|
|
68
|
-
action: [type: RowActionType, rowIndex: number]
|
|
70
|
+
action: [type: RowActionType, rowIndex: number, event?: MouseEvent]
|
|
69
71
|
}>()
|
|
70
72
|
|
|
71
73
|
const actionsCellRef = useTemplateRef<HTMLTableCellElement>('actionsCell')
|
|
@@ -77,20 +79,33 @@ const menuPosition = ref({ top: 0, left: 0 })
|
|
|
77
79
|
|
|
78
80
|
// Default labels for actions
|
|
79
81
|
const defaultLabels: Record<RowActionType, string> = {
|
|
80
|
-
add: 'Add
|
|
81
|
-
delete: 'Delete
|
|
82
|
-
duplicate: 'Duplicate
|
|
82
|
+
add: 'Add Record',
|
|
83
|
+
delete: 'Delete Record',
|
|
84
|
+
duplicate: 'Duplicate Record',
|
|
83
85
|
insertAbove: 'Insert Above',
|
|
84
86
|
insertBelow: 'Insert Below',
|
|
85
|
-
move: 'Move
|
|
87
|
+
move: 'Move Record',
|
|
88
|
+
moveUp: 'Move Up',
|
|
89
|
+
moveDown: 'Move Down',
|
|
90
|
+
open: 'Open Record',
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
// Determine which actions are enabled
|
|
89
94
|
const enabledActions = computed(() => {
|
|
90
|
-
const actions: Array<{ type: RowActionType; label: string; icon: string }> = []
|
|
95
|
+
const actions: Array<{ type: RowActionType; label: string; icon: string; disabled: boolean }> = []
|
|
91
96
|
const configActions = props.config.actions || {}
|
|
92
97
|
|
|
93
|
-
const actionTypes: RowActionType[] = [
|
|
98
|
+
const actionTypes: RowActionType[] = [
|
|
99
|
+
'open',
|
|
100
|
+
'moveUp',
|
|
101
|
+
'moveDown',
|
|
102
|
+
'duplicate',
|
|
103
|
+
'insertAbove',
|
|
104
|
+
'insertBelow',
|
|
105
|
+
'add',
|
|
106
|
+
'delete',
|
|
107
|
+
'move',
|
|
108
|
+
]
|
|
94
109
|
|
|
95
110
|
for (const type of actionTypes) {
|
|
96
111
|
const actionConfig = configActions[type]
|
|
@@ -104,15 +119,18 @@ const enabledActions = computed(() => {
|
|
|
104
119
|
let enabled = true
|
|
105
120
|
let label = defaultLabels[type]
|
|
106
121
|
let icon = actionIcons[type]
|
|
122
|
+
let disabled = false
|
|
107
123
|
|
|
108
124
|
if (typeof actionConfig === 'object') {
|
|
109
125
|
enabled = actionConfig.enabled !== false
|
|
110
126
|
label = actionConfig.label || label
|
|
111
127
|
icon = actionConfig.icon || icon
|
|
128
|
+
// Per-row predicate; reading store state here keeps this computed reactive to row changes.
|
|
129
|
+
if (actionConfig.disabled) disabled = actionConfig.disabled(props.rowIndex, props.store)
|
|
112
130
|
}
|
|
113
131
|
|
|
114
132
|
if (enabled) {
|
|
115
|
-
actions.push({ type, label, icon })
|
|
133
|
+
actions.push({ type, label, icon, disabled })
|
|
116
134
|
}
|
|
117
135
|
}
|
|
118
136
|
|
|
@@ -171,7 +189,9 @@ const checkDropdownPosition = () => {
|
|
|
171
189
|
|
|
172
190
|
const buttonRect = toggleButtonRef.value.getBoundingClientRect()
|
|
173
191
|
const viewportHeight = window.innerHeight
|
|
192
|
+
const viewportWidth = window.innerWidth
|
|
174
193
|
const estimatedMenuHeight = enabledActions.value.length * 40 + 16 // ~40px per item + padding
|
|
194
|
+
const estimatedMenuWidth = 160 // matches min-width: 10rem
|
|
175
195
|
|
|
176
196
|
// Check if menu would extend beyond viewport bottom
|
|
177
197
|
const spaceBelow = viewportHeight - buttonRect.bottom
|
|
@@ -180,17 +200,17 @@ const checkDropdownPosition = () => {
|
|
|
180
200
|
// Flip if not enough space below but enough space above
|
|
181
201
|
dropdownFlipped.value = spaceBelow < estimatedMenuHeight && spaceAbove > estimatedMenuHeight
|
|
182
202
|
|
|
203
|
+
// Horizontal: anchor the menu's left edge to the button, but right-align it to the button when
|
|
204
|
+
// it would overflow the viewport's right edge (e.g. an end-positioned actions column).
|
|
205
|
+
let left = buttonRect.left
|
|
206
|
+
if (left + estimatedMenuWidth > viewportWidth) {
|
|
207
|
+
left = Math.max(0, buttonRect.right - estimatedMenuWidth)
|
|
208
|
+
}
|
|
209
|
+
|
|
183
210
|
// Calculate fixed position
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
left: buttonRect.left,
|
|
188
|
-
}
|
|
189
|
-
} else {
|
|
190
|
-
menuPosition.value = {
|
|
191
|
-
top: buttonRect.bottom,
|
|
192
|
-
left: buttonRect.left,
|
|
193
|
-
}
|
|
211
|
+
menuPosition.value = {
|
|
212
|
+
top: dropdownFlipped.value ? buttonRect.top : buttonRect.bottom,
|
|
213
|
+
left,
|
|
194
214
|
}
|
|
195
215
|
}
|
|
196
216
|
|
|
@@ -199,7 +219,7 @@ onClickOutside(actionsCellRef, () => {
|
|
|
199
219
|
})
|
|
200
220
|
|
|
201
221
|
// Execute an action
|
|
202
|
-
const executeAction = (actionType: RowActionType) => {
|
|
222
|
+
const executeAction = (actionType: RowActionType, event?: MouseEvent) => {
|
|
203
223
|
dropdownOpen.value = false
|
|
204
224
|
|
|
205
225
|
// Check for custom handler
|
|
@@ -213,13 +233,11 @@ const executeAction = (actionType: RowActionType) => {
|
|
|
213
233
|
}
|
|
214
234
|
|
|
215
235
|
// Emit the action event for parent to handle
|
|
216
|
-
emit('action', actionType, props.rowIndex)
|
|
236
|
+
emit('action', actionType, props.rowIndex, event)
|
|
217
237
|
}
|
|
218
238
|
</script>
|
|
219
239
|
|
|
220
240
|
<style>
|
|
221
|
-
@import url('@stonecrop/themes/default.css');
|
|
222
|
-
|
|
223
241
|
.atable-row-actions {
|
|
224
242
|
width: 2rem;
|
|
225
243
|
min-width: 2rem;
|
|
@@ -358,6 +376,17 @@ const executeAction = (actionType: RowActionType) => {
|
|
|
358
376
|
background-color: var(--sc-gray-10, #f5f5f5);
|
|
359
377
|
}
|
|
360
378
|
|
|
379
|
+
.row-action-menu-item:disabled,
|
|
380
|
+
.row-action-btn:disabled {
|
|
381
|
+
opacity: 0.4;
|
|
382
|
+
cursor: not-allowed;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.row-action-menu-item:disabled:hover,
|
|
386
|
+
.row-action-btn:disabled:hover {
|
|
387
|
+
background-color: transparent;
|
|
388
|
+
}
|
|
389
|
+
|
|
361
390
|
.row-action-menu-item .action-icon {
|
|
362
391
|
display: flex;
|
|
363
392
|
align-items: center;
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
:row="row"
|
|
20
20
|
:row-index="row.originalIndex"
|
|
21
21
|
:store="store"
|
|
22
|
-
@row:action="handleRowAction"
|
|
22
|
+
@row:action="handleRowAction"
|
|
23
|
+
@row:click="handleRowClick">
|
|
23
24
|
<template v-for="(column, colIndex) in getProcessedColumnsForRow(row)" :key="column.name">
|
|
24
25
|
<component
|
|
25
26
|
:is="column.ganttComponent || 'AGanttCell'"
|
|
@@ -101,6 +102,7 @@ import type {
|
|
|
101
102
|
GanttDragEvent,
|
|
102
103
|
RowActionType,
|
|
103
104
|
RowAddEvent,
|
|
105
|
+
RowClickEvent,
|
|
104
106
|
RowDeleteEvent,
|
|
105
107
|
RowDuplicateEvent,
|
|
106
108
|
RowInsertEvent,
|
|
@@ -134,11 +136,13 @@ const emit = defineEmits<{
|
|
|
134
136
|
'connection:event': [event: ConnectionEvent]
|
|
135
137
|
'columns:update': [columns: TableColumn[]]
|
|
136
138
|
'row:add': [event: RowAddEvent]
|
|
139
|
+
'row:click': [event: RowClickEvent]
|
|
137
140
|
'row:delete': [event: RowDeleteEvent]
|
|
138
141
|
'row:duplicate': [event: RowDuplicateEvent]
|
|
139
142
|
'row:insert-above': [event: RowInsertEvent]
|
|
140
143
|
'row:insert-below': [event: RowInsertEvent]
|
|
141
144
|
'row:move': [event: RowMoveEvent]
|
|
145
|
+
'row:open': [event: RowClickEvent]
|
|
142
146
|
}>()
|
|
143
147
|
|
|
144
148
|
const tableRef = useTemplateRef<HTMLTableElement>('table')
|
|
@@ -200,10 +204,13 @@ watch(
|
|
|
200
204
|
watch(
|
|
201
205
|
columns,
|
|
202
206
|
newColumns => {
|
|
207
|
+
// The model is optional, so it can be absent — there is nothing to sync from, and the store
|
|
208
|
+
// keeps the columns it resolved from the schema.
|
|
209
|
+
if (!newColumns) return
|
|
203
210
|
// Only update if the columns have actually changed (avoid infinite loops)
|
|
204
211
|
if (JSON.stringify(newColumns) !== JSON.stringify(store.columns)) {
|
|
205
212
|
store.columns = [...newColumns]
|
|
206
|
-
emit('columns:update', [...newColumns]
|
|
213
|
+
emit('columns:update', [...newColumns])
|
|
207
214
|
}
|
|
208
215
|
},
|
|
209
216
|
{ deep: true }
|
|
@@ -308,11 +315,19 @@ const handleConnectionDelete = (connection: ConnectionPath) => {
|
|
|
308
315
|
emit('connection:event', { type: 'delete', connection })
|
|
309
316
|
}
|
|
310
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Handle row click events from ARow components.
|
|
320
|
+
*/
|
|
321
|
+
const handleRowClick = (rowIndex: number, event: MouseEvent) => {
|
|
322
|
+
const row = store.rows[rowIndex]
|
|
323
|
+
emit('row:click', { row, rowIndex, event })
|
|
324
|
+
}
|
|
325
|
+
|
|
311
326
|
/**
|
|
312
327
|
* Handle row action events from ARow components.
|
|
313
328
|
* Performs the default action and emits the appropriate event.
|
|
314
329
|
*/
|
|
315
|
-
const handleRowAction = (actionType: RowActionType, rowIndex: number) => {
|
|
330
|
+
const handleRowAction = (actionType: RowActionType, rowIndex: number, event?: MouseEvent) => {
|
|
316
331
|
switch (actionType) {
|
|
317
332
|
case 'add': {
|
|
318
333
|
// Add a new row after the current row
|
|
@@ -359,6 +374,25 @@ const handleRowAction = (actionType: RowActionType, rowIndex: number) => {
|
|
|
359
374
|
emit('row:move', { fromIndex: rowIndex, toIndex: -1 })
|
|
360
375
|
break
|
|
361
376
|
}
|
|
377
|
+
case 'moveUp': {
|
|
378
|
+
if (rowIndex > 0 && store.moveRow(rowIndex, rowIndex - 1)) {
|
|
379
|
+
rows.value = [...store.rows]
|
|
380
|
+
emit('row:move', { fromIndex: rowIndex, toIndex: rowIndex - 1 })
|
|
381
|
+
}
|
|
382
|
+
break
|
|
383
|
+
}
|
|
384
|
+
case 'moveDown': {
|
|
385
|
+
if (rowIndex < store.rows.length - 1 && store.moveRow(rowIndex, rowIndex + 1)) {
|
|
386
|
+
rows.value = [...store.rows]
|
|
387
|
+
emit('row:move', { fromIndex: rowIndex, toIndex: rowIndex + 1 })
|
|
388
|
+
}
|
|
389
|
+
break
|
|
390
|
+
}
|
|
391
|
+
case 'open': {
|
|
392
|
+
const row = store.rows[rowIndex]
|
|
393
|
+
emit('row:open', { row, rowIndex, event })
|
|
394
|
+
break
|
|
395
|
+
}
|
|
362
396
|
}
|
|
363
397
|
}
|
|
364
398
|
|
|
@@ -408,7 +442,6 @@ td.sticky-index {
|
|
|
408
442
|
</style>
|
|
409
443
|
|
|
410
444
|
<style scoped>
|
|
411
|
-
@import url('@stonecrop/themes/default.css');
|
|
412
445
|
.atable {
|
|
413
446
|
position: relative;
|
|
414
447
|
font-family: var(--sc-atable-font-family);
|
|
@@ -66,9 +66,24 @@
|
|
|
66
66
|
|
|
67
67
|
<script setup lang="ts">
|
|
68
68
|
import { ref, reactive, computed } from 'vue'
|
|
69
|
+
import { componentCategory, type ComponentCategory } from '@stonecrop/schema'
|
|
70
|
+
|
|
69
71
|
import { createTableStore } from '../stores/table'
|
|
70
72
|
import type { TableColumn } from '../types'
|
|
71
73
|
|
|
74
|
+
// The filter widget is chosen by the component's semantic category.
|
|
75
|
+
const CATEGORY_FILTER: Record<ComponentCategory, 'text' | 'select' | 'number' | 'date' | 'dateRange' | 'checkbox'> = {
|
|
76
|
+
text: 'text',
|
|
77
|
+
number: 'number',
|
|
78
|
+
boolean: 'checkbox',
|
|
79
|
+
date: 'date',
|
|
80
|
+
datetime: 'dateRange',
|
|
81
|
+
select: 'select',
|
|
82
|
+
code: 'text',
|
|
83
|
+
link: 'text',
|
|
84
|
+
attach: 'text',
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
const { column, colIndex, store } = defineProps<{
|
|
73
88
|
column: TableColumn
|
|
74
89
|
colIndex: number
|
|
@@ -83,24 +98,10 @@ const dateFilter = reactive({
|
|
|
83
98
|
|
|
84
99
|
const resolvedFilterType = computed(() => {
|
|
85
100
|
if (column.filterType) return column.filterType
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return 'date'
|
|
91
|
-
case 'Datetime':
|
|
92
|
-
return 'dateRange'
|
|
93
|
-
case 'Select':
|
|
94
|
-
return 'select'
|
|
95
|
-
case 'Int':
|
|
96
|
-
case 'Float':
|
|
97
|
-
case 'Currency':
|
|
98
|
-
case 'Decimal':
|
|
99
|
-
case 'Quantity':
|
|
100
|
-
return 'number'
|
|
101
|
-
default:
|
|
102
|
-
return 'text'
|
|
103
|
-
}
|
|
101
|
+
const category = componentCategory(column.component)
|
|
102
|
+
if (category) return CATEGORY_FILTER[category]
|
|
103
|
+
// An unknown (custom) component gets the widget that can filter anything.
|
|
104
|
+
return 'text'
|
|
104
105
|
})
|
|
105
106
|
|
|
106
107
|
const getSelectOptions = (col: TableColumn): any[] => {
|
package/src/icons/index.ts
CHANGED
|
@@ -10,8 +10,15 @@ import DuplicateIcon from './stonecrop-ui-icon-duplicate.svg?raw'
|
|
|
10
10
|
import InsertAboveIcon from './stonecrop-ui-icon-insert-above.svg?raw'
|
|
11
11
|
import InsertBelowIcon from './stonecrop-ui-icon-insert-below.svg?raw'
|
|
12
12
|
import MoveIcon from './stonecrop-ui-icon-move.svg?raw'
|
|
13
|
+
import OpenIcon from './stonecrop-ui-icon-open.svg?raw'
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
// Directional move icons are inline (no dedicated SVG asset): simple up/down chevrons.
|
|
16
|
+
const MoveUpIcon =
|
|
17
|
+
'<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 13V3.5M4.5 7 8 3.5 11.5 7" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
|
18
|
+
const MoveDownIcon =
|
|
19
|
+
'<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 3v9.5M4.5 9 8 12.5 11.5 9" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
|
20
|
+
|
|
21
|
+
export { AddIcon, DeleteIcon, DuplicateIcon, InsertAboveIcon, InsertBelowIcon, MoveIcon, OpenIcon }
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Map of action types to their default icons.
|
|
@@ -25,4 +32,7 @@ export const actionIcons: Record<string, string> = {
|
|
|
25
32
|
insertAbove: InsertAboveIcon,
|
|
26
33
|
insertBelow: InsertBelowIcon,
|
|
27
34
|
move: MoveIcon,
|
|
35
|
+
moveUp: MoveUpIcon,
|
|
36
|
+
moveDown: MoveDownIcon,
|
|
37
|
+
open: OpenIcon,
|
|
28
38
|
}
|
|
@@ -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="18,20 18,44 46,32" style="fill: #000; stroke-width: 0px;"/>
|
|
5
|
+
</svg>
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { App } from 'vue'
|
|
2
2
|
|
|
3
3
|
import ACell from './components/ACell.vue'
|
|
4
|
-
import AExpansionRow from './components/AExpansionRow.vue'
|
|
5
4
|
import AGanttCell from './components/AGanttCell.vue'
|
|
6
5
|
import ARow from './components/ARow.vue'
|
|
7
6
|
import ARowActions from './components/ARowActions.vue'
|
|
@@ -16,7 +15,16 @@ export type * from './types'
|
|
|
16
15
|
export { schemaToColumns } from './schemaToColumns'
|
|
17
16
|
|
|
18
17
|
// Icon exports
|
|
19
|
-
export {
|
|
18
|
+
export {
|
|
19
|
+
AddIcon,
|
|
20
|
+
DeleteIcon,
|
|
21
|
+
DuplicateIcon,
|
|
22
|
+
InsertAboveIcon,
|
|
23
|
+
InsertBelowIcon,
|
|
24
|
+
MoveIcon,
|
|
25
|
+
OpenIcon,
|
|
26
|
+
actionIcons,
|
|
27
|
+
} from './icons'
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
30
|
* Install all ATable components
|
|
@@ -25,7 +33,6 @@ export { AddIcon, DeleteIcon, DuplicateIcon, InsertAboveIcon, InsertBelowIcon, M
|
|
|
25
33
|
*/
|
|
26
34
|
function install(app: App /* options */) {
|
|
27
35
|
app.component('ACell', ACell)
|
|
28
|
-
app.component('AExpansionRow', AExpansionRow)
|
|
29
36
|
app.component('AGanttCell', AGanttCell)
|
|
30
37
|
app.component('ARow', ARow)
|
|
31
38
|
app.component('ARowActions', ARowActions)
|
|
@@ -38,7 +45,6 @@ function install(app: App /* options */) {
|
|
|
38
45
|
|
|
39
46
|
export {
|
|
40
47
|
ACell,
|
|
41
|
-
AExpansionRow,
|
|
42
48
|
AGanttCell,
|
|
43
49
|
ARow,
|
|
44
50
|
ARowActions,
|
package/src/schemaToColumns.ts
CHANGED
|
@@ -7,12 +7,12 @@ import type { TableColumn } from './types'
|
|
|
7
7
|
*
|
|
8
8
|
* Fields are excluded when:
|
|
9
9
|
* - `hidden: true` — field should not be visible in any view
|
|
10
|
-
* - no `
|
|
10
|
+
* - no `component` — non-scalar entry (nested table or fieldset), no column equivalent
|
|
11
11
|
*
|
|
12
12
|
* `fieldname` is renamed to `name`; `hidden` is stripped. All other `ColumnSchema` properties
|
|
13
13
|
* spread through automatically.
|
|
14
14
|
*
|
|
15
|
-
* For
|
|
15
|
+
* For link fields (those carrying `doctype`) without an explicit `cellComponent`:
|
|
16
16
|
* - `linkDoctype` is set from the field's `doctype` property (used by ACell's async resolver).
|
|
17
17
|
* - A synchronous `format` function is added (unless the field already has one) that handles
|
|
18
18
|
* both bare ID strings and pre-resolved `{ id, displayText }` objects.
|
|
@@ -21,16 +21,14 @@ import type { TableColumn } from './types'
|
|
|
21
21
|
*/
|
|
22
22
|
export function schemaToColumns(schema: ColumnSchema[]): TableColumn[] {
|
|
23
23
|
return schema
|
|
24
|
-
.filter(f => !f.hidden && f.
|
|
24
|
+
.filter(f => !f.hidden && f.component)
|
|
25
25
|
.map(({ fieldname, hidden: _hidden, ...rest }) => {
|
|
26
26
|
const col: TableColumn = Object.assign({ name: fieldname }, rest)
|
|
27
27
|
|
|
28
|
-
// Link fields: store the linked doctype for async resolution by ACell,
|
|
29
|
-
//
|
|
30
|
-
if (rest.
|
|
31
|
-
|
|
32
|
-
const linkedDoctype = typeof fields.doctype === 'string' ? fields.doctype : undefined
|
|
33
|
-
if (linkedDoctype) col.linkDoctype = linkedDoctype
|
|
28
|
+
// Link fields: store the linked doctype for async resolution by ACell, and add a sync
|
|
29
|
+
// format that handles pre-resolved AFormLinkValue objects.
|
|
30
|
+
if (rest.doctype && !rest.cellComponent) {
|
|
31
|
+
col.linkDoctype = rest.doctype
|
|
34
32
|
|
|
35
33
|
if (!rest.format) {
|
|
36
34
|
col.format = (v: any): string => {
|
package/src/stores/table.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
|
+
import { componentCategory } from '@stonecrop/schema'
|
|
2
3
|
import { type CSSProperties, computed, ref } from 'vue'
|
|
3
4
|
|
|
4
5
|
import type {
|
|
@@ -127,7 +128,12 @@ function applyFilter(cellValue: any, filter: FilterState, column: TableColumn):
|
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Compute the CSS indentation for a tree-table cell. Pure helper (captures no store state);
|
|
133
|
+
* imported directly by cell components rather than exposed on the store's public return.
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
export function getIndent(colIndex: number, indentLevel?: number): string {
|
|
131
137
|
if (indentLevel && colIndex === 0 && indentLevel > 0) {
|
|
132
138
|
return `${indentLevel}ch`
|
|
133
139
|
} else {
|
|
@@ -509,16 +515,13 @@ export const createTableStore = (initData: {
|
|
|
509
515
|
const format = column.format
|
|
510
516
|
|
|
511
517
|
if (!format) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
default:
|
|
520
|
-
return value
|
|
521
|
-
}
|
|
518
|
+
// Default cell formatting comes from the component's category; anything without an
|
|
519
|
+
// opinion (including an unknown component) renders the raw value.
|
|
520
|
+
const category = componentCategory(column.component)
|
|
521
|
+
if (category === 'boolean') return value ? '✓' : '✗'
|
|
522
|
+
if (category === 'date') return value != null ? new Date(String(value)).toLocaleDateString() : value
|
|
523
|
+
if (category === 'datetime') return value != null ? new Date(String(value)).toLocaleString() : value
|
|
524
|
+
return value
|
|
522
525
|
}
|
|
523
526
|
|
|
524
527
|
if (typeof format === 'function') {
|
|
@@ -922,7 +925,6 @@ export const createTableStore = (initData: {
|
|
|
922
925
|
getFormattedValue,
|
|
923
926
|
getHandlesForBar,
|
|
924
927
|
getHeaderCellStyle,
|
|
925
|
-
getIndent,
|
|
926
928
|
getRowExpandSymbol,
|
|
927
929
|
insertRowAbove,
|
|
928
930
|
insertRowBelow,
|
package/src/types/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { createTableStore } from '../stores/table'
|
|
|
8
8
|
/**
|
|
9
9
|
* Runtime column definition for ATable.
|
|
10
10
|
*
|
|
11
|
-
* Extends `ColumnSchema` from `@stonecrop/schema` — all authoring properties (`label`, `
|
|
11
|
+
* Extends `ColumnSchema` from `@stonecrop/schema` — all authoring properties (`label`, `component`, `width`,
|
|
12
12
|
* `pinned`, filter config, cell/modal component names, etc.) are inherited. The overrides
|
|
13
13
|
* below widen three properties for runtime use (live functions, broader alignment values)
|
|
14
14
|
* and add two runtime-only additions (`mask`, `originalIndex`).
|
|
@@ -45,7 +45,7 @@ export interface TableColumn extends Omit<ColumnSchema, 'fieldname' | 'hidden' |
|
|
|
45
45
|
mask?: (value: any) => any
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* For
|
|
48
|
+
* For link columns (those carrying `doctype`): the target doctype slug used by the `linkResolver`
|
|
49
49
|
* to look up display text for bare ID values. Set automatically by `schemaToColumns`
|
|
50
50
|
* from the field's `doctype` property.
|
|
51
51
|
*/
|
|
@@ -83,7 +83,8 @@ export interface CellContext {
|
|
|
83
83
|
* Row action type identifiers.
|
|
84
84
|
* @public
|
|
85
85
|
*/
|
|
86
|
-
export type RowActionType =
|
|
86
|
+
export type RowActionType =
|
|
87
|
+
'open' | 'add' | 'delete' | 'duplicate' | 'insertAbove' | 'insertBelow' | 'move' | 'moveUp' | 'moveDown'
|
|
87
88
|
|
|
88
89
|
/**
|
|
89
90
|
* Options for configuring individual row actions.
|
|
@@ -115,6 +116,16 @@ export interface RowActionOptions {
|
|
|
115
116
|
* @returns void or false to prevent default behavior
|
|
116
117
|
*/
|
|
117
118
|
handler?: (rowIndex: number, store: ReturnType<typeof createTableStore>) => void | boolean
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Per-row predicate to disable this action for specific rows (e.g. a lock-aware delete, or
|
|
122
|
+
* move-up on the first row). Returns true to render the action disabled. Evaluated reactively
|
|
123
|
+
* against the store, so it updates as rows change.
|
|
124
|
+
*
|
|
125
|
+
* @param rowIndex - The index of the row
|
|
126
|
+
* @param store - The table store instance
|
|
127
|
+
*/
|
|
128
|
+
disabled?: (rowIndex: number, store: ReturnType<typeof createTableStore>) => boolean
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
/**
|
|
@@ -156,12 +167,15 @@ export interface RowActionsConfig {
|
|
|
156
167
|
* false to disable, or provide RowActionOptions for custom configuration.
|
|
157
168
|
*/
|
|
158
169
|
actions?: {
|
|
170
|
+
open?: boolean | RowActionOptions
|
|
159
171
|
add?: boolean | RowActionOptions
|
|
160
172
|
delete?: boolean | RowActionOptions
|
|
161
173
|
duplicate?: boolean | RowActionOptions
|
|
162
174
|
insertAbove?: boolean | RowActionOptions
|
|
163
175
|
insertBelow?: boolean | RowActionOptions
|
|
164
176
|
move?: boolean | RowActionOptions
|
|
177
|
+
moveUp?: boolean | RowActionOptions
|
|
178
|
+
moveDown?: boolean | RowActionOptions
|
|
165
179
|
}
|
|
166
180
|
}
|
|
167
181
|
|
|
@@ -177,6 +191,14 @@ export interface BaseTableConfig {
|
|
|
177
191
|
*/
|
|
178
192
|
fullWidth?: boolean
|
|
179
193
|
|
|
194
|
+
/**
|
|
195
|
+
* When true, rows show a pointer cursor and a hover highlight, signalling they are
|
|
196
|
+
* clickable. Emits `row:click` on every row click.
|
|
197
|
+
*
|
|
198
|
+
* @defaultValue false
|
|
199
|
+
*/
|
|
200
|
+
clickable?: boolean
|
|
201
|
+
|
|
180
202
|
/**
|
|
181
203
|
* Configuration for row-level actions (add, delete, duplicate, etc.).
|
|
182
204
|
*/
|
|
@@ -692,9 +714,9 @@ export type ConnectionEvent = {
|
|
|
692
714
|
* @public
|
|
693
715
|
*/
|
|
694
716
|
export interface RowAddEvent {
|
|
695
|
-
/** The index
|
|
717
|
+
/** The index of the newly added row. */
|
|
696
718
|
rowIndex: number
|
|
697
|
-
/** The
|
|
719
|
+
/** The data for the newly added row. */
|
|
698
720
|
row: TableRow
|
|
699
721
|
}
|
|
700
722
|
|
|
@@ -703,9 +725,9 @@ export interface RowAddEvent {
|
|
|
703
725
|
* @public
|
|
704
726
|
*/
|
|
705
727
|
export interface RowDeleteEvent {
|
|
706
|
-
/** The index of the row
|
|
728
|
+
/** The index of the deleted row (before deletion). */
|
|
707
729
|
rowIndex: number
|
|
708
|
-
/** The
|
|
730
|
+
/** The data of the deleted row. */
|
|
709
731
|
row: TableRow
|
|
710
732
|
}
|
|
711
733
|
|
|
@@ -714,11 +736,11 @@ export interface RowDeleteEvent {
|
|
|
714
736
|
* @public
|
|
715
737
|
*/
|
|
716
738
|
export interface RowDuplicateEvent {
|
|
717
|
-
/** The index of the original row that was duplicated */
|
|
739
|
+
/** The index of the original row that was duplicated. */
|
|
718
740
|
sourceIndex: number
|
|
719
|
-
/** The index of the newly created duplicate row */
|
|
741
|
+
/** The index of the newly created duplicate row. */
|
|
720
742
|
newIndex: number
|
|
721
|
-
/** The
|
|
743
|
+
/** The data of the newly created duplicate row. */
|
|
722
744
|
row: TableRow
|
|
723
745
|
}
|
|
724
746
|
|
|
@@ -727,11 +749,11 @@ export interface RowDuplicateEvent {
|
|
|
727
749
|
* @public
|
|
728
750
|
*/
|
|
729
751
|
export interface RowInsertEvent {
|
|
730
|
-
/** The index of the row relative to which the
|
|
752
|
+
/** The index of the reference row relative to which the new row was inserted. */
|
|
731
753
|
targetIndex: number
|
|
732
|
-
/** The index
|
|
754
|
+
/** The index at which the new row was inserted. */
|
|
733
755
|
newIndex: number
|
|
734
|
-
/** The newly inserted row
|
|
756
|
+
/** The data of the newly inserted row. */
|
|
735
757
|
row: TableRow
|
|
736
758
|
}
|
|
737
759
|
|
|
@@ -740,8 +762,25 @@ export interface RowInsertEvent {
|
|
|
740
762
|
* @public
|
|
741
763
|
*/
|
|
742
764
|
export interface RowMoveEvent {
|
|
743
|
-
/** The
|
|
765
|
+
/** The index the row was moved from. */
|
|
744
766
|
fromIndex: number
|
|
745
|
-
/** The
|
|
767
|
+
/** The index the row was moved to. */
|
|
746
768
|
toIndex: number
|
|
747
769
|
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Event payload for row:click and row:open events.
|
|
773
|
+
* @public
|
|
774
|
+
*/
|
|
775
|
+
export interface RowClickEvent {
|
|
776
|
+
/** The data of the clicked row. */
|
|
777
|
+
row: TableRow
|
|
778
|
+
/** The index of the clicked row. */
|
|
779
|
+
rowIndex: number
|
|
780
|
+
/**
|
|
781
|
+
* The originating DOM MouseEvent. Present for all real user interactions.
|
|
782
|
+
* Inspect `event.ctrlKey` / `event.metaKey` for new-tab navigation,
|
|
783
|
+
* `event.button === 1` for middle-click, etc.
|
|
784
|
+
*/
|
|
785
|
+
event?: MouseEvent
|
|
786
|
+
}
|