@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.
@@ -26,7 +26,8 @@
26
26
  type="button"
27
27
  class="row-action-menu-item"
28
28
  role="menuitem"
29
- @click.stop="executeAction(action.type)">
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
- @click.stop="executeAction(action.type)">
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 Row',
81
- delete: 'Delete Row',
82
- duplicate: 'Duplicate Row',
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 Row',
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[] = ['add', 'delete', 'duplicate', 'insertAbove', 'insertBelow', 'move']
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
- if (dropdownFlipped.value) {
185
- menuPosition.value = {
186
- top: buttonRect.top,
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] as TableColumn[])
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
- switch (column.fieldtype) {
87
- case 'Check':
88
- return 'checkbox'
89
- case 'Date':
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[] => {
@@ -98,8 +98,6 @@ const onResize = (entries: ReadonlyArray<ResizeObserverEntry>) => {
98
98
  </script>
99
99
 
100
100
  <style>
101
- @import url('@stonecrop/themes/default.css');
102
-
103
101
  .atable-header-row th {
104
102
  padding-left: 0.5ch !important;
105
103
  font-weight: 700;
@@ -44,8 +44,6 @@ const amodalStyles = computed((): StyleValue => {
44
44
  </script>
45
45
 
46
46
  <style>
47
- @import url('@stonecrop/themes/default.css');
48
-
49
47
  .amodal {
50
48
  position: absolute;
51
49
  background-color: var(--sc-row-color-zebra-dark);
@@ -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
- export { AddIcon, DeleteIcon, DuplicateIcon, InsertAboveIcon, InsertBelowIcon, MoveIcon }
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 { AddIcon, DeleteIcon, DuplicateIcon, InsertAboveIcon, InsertBelowIcon, MoveIcon, actionIcons } from './icons'
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,
@@ -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 `fieldtype` — non-scalar entry (nested table or fieldset), has no column equivalent
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 `fieldtype: 'Link'` fields without an explicit `cellComponent`:
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.fieldtype)
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
- // and add a sync format that handles pre-resolved AFormLinkValue objects.
30
- if (rest.fieldtype === 'Link' && !rest.cellComponent) {
31
- const fields = rest as Record<string, unknown>
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 => {
@@ -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
- function getIndent(colIndex: number, indentLevel?: number): string {
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
- switch (column.fieldtype) {
513
- case 'Check':
514
- return value ? '✓' : '✗'
515
- case 'Date':
516
- return value != null ? new Date(String(value)).toLocaleDateString() : value
517
- case 'Datetime':
518
- return value != null ? new Date(String(value)).toLocaleString() : value
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,
@@ -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`, `fieldtype`, `width`,
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 `fieldtype: 'Link'` columns: the target doctype slug used by the `linkResolver`
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 = 'add' | 'delete' | 'duplicate' | 'insertAbove' | 'insertBelow' | 'move'
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 at which the row was added */
717
+ /** The index of the newly added row. */
696
718
  rowIndex: number
697
- /** The row object that was added */
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 that was deleted */
728
+ /** The index of the deleted row (before deletion). */
707
729
  rowIndex: number
708
- /** The row object that was deleted */
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 new duplicate row object */
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 insertion was made */
752
+ /** The index of the reference row relative to which the new row was inserted. */
731
753
  targetIndex: number
732
- /** The index of the newly inserted row */
754
+ /** The index at which the new row was inserted. */
733
755
  newIndex: number
734
- /** The newly inserted row object */
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 original index of the row before the move */
765
+ /** The index the row was moved from. */
744
766
  fromIndex: number
745
- /** The destination index of the row after the move */
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
+ }