@stonecrop/atable 0.31.0 → 0.32.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.
Files changed (79) hide show
  1. package/dist/assets/index.css +804 -1
  2. package/dist/atable.js +2809 -1944
  3. package/dist/atable.js.map +1 -1
  4. package/dist/src/components/ACell.vue.d.ts +17 -0
  5. package/dist/src/components/ACell.vue.d.ts.map +1 -0
  6. package/dist/src/components/AGanttCell.vue.d.ts +34 -0
  7. package/dist/src/components/AGanttCell.vue.d.ts.map +1 -0
  8. package/dist/src/components/AGanttConnection.vue.d.ts +13 -0
  9. package/dist/src/components/AGanttConnection.vue.d.ts.map +1 -0
  10. package/dist/src/components/ARow.vue.d.ts +2693 -0
  11. package/dist/src/components/ARow.vue.d.ts.map +1 -0
  12. package/dist/src/components/ARowActions.vue.d.ts +16 -0
  13. package/dist/src/components/ARowActions.vue.d.ts.map +1 -0
  14. package/dist/src/components/ATable.vue.d.ts +13358 -0
  15. package/dist/src/components/ATable.vue.d.ts.map +1 -0
  16. package/dist/src/components/ATableColumnFilter.vue.d.ts +11 -0
  17. package/dist/src/components/ATableColumnFilter.vue.d.ts.map +1 -0
  18. package/dist/src/components/ATableHeader.vue.d.ts +20 -0
  19. package/dist/src/components/ATableHeader.vue.d.ts.map +1 -0
  20. package/dist/src/components/ATableLoading.vue.d.ts +14 -0
  21. package/dist/src/components/ATableLoading.vue.d.ts.map +1 -0
  22. package/dist/src/components/ATableLoadingBar.vue.d.ts +14 -0
  23. package/dist/src/components/ATableLoadingBar.vue.d.ts.map +1 -0
  24. package/dist/src/components/ATableModal.vue.d.ts +18 -0
  25. package/dist/src/components/ATableModal.vue.d.ts.map +1 -0
  26. package/dist/src/components/ATablePaginationFooter.vue.d.ts +17 -0
  27. package/dist/src/components/ATablePaginationFooter.vue.d.ts.map +1 -0
  28. package/dist/src/icons/index.d.ts +14 -7
  29. package/dist/src/icons/index.d.ts.map +1 -1
  30. package/dist/{tsdoc-metadata.json → src/tsdoc-metadata.json} +1 -1
  31. package/dist/src/types/index.d.ts.map +1 -1
  32. package/package.json +35 -29
  33. package/dist/atable.d.ts +0 -3560
  34. package/dist/atable.tsbuildinfo +0 -1
  35. package/dist/icons/stonecrop-ui-icon-add.svg +0 -5
  36. package/dist/icons/stonecrop-ui-icon-delete.svg +0 -5
  37. package/dist/icons/stonecrop-ui-icon-duplicate.svg +0 -5
  38. package/dist/icons/stonecrop-ui-icon-insert-above.svg +0 -15
  39. package/dist/icons/stonecrop-ui-icon-insert-below.svg +0 -15
  40. package/dist/icons/stonecrop-ui-icon-move.svg +0 -4
  41. package/dist/icons/stonecrop-ui-icon-open.svg +0 -5
  42. package/dist/src/composables/table-pagination.js +0 -108
  43. package/dist/src/icons/index.js +0 -32
  44. package/dist/src/index.js +0 -33
  45. package/dist/src/linkSearchableText.js +0 -44
  46. package/dist/src/resolveFilterType.js +0 -27
  47. package/dist/src/schemaToColumns.js +0 -59
  48. package/dist/src/stores/table.js +0 -833
  49. package/dist/src/types/index.js +0 -0
  50. package/dist/src/utils.js +0 -39
  51. package/src/components/ACell.vue +0 -410
  52. package/src/components/AGanttCell.vue +0 -638
  53. package/src/components/AGanttConnection.vue +0 -164
  54. package/src/components/ARow.vue +0 -251
  55. package/src/components/ARowActions.vue +0 -407
  56. package/src/components/ATable.vue +0 -538
  57. package/src/components/ATableColumnFilter.vue +0 -216
  58. package/src/components/ATableHeader.vue +0 -141
  59. package/src/components/ATableLoading.vue +0 -86
  60. package/src/components/ATableLoadingBar.vue +0 -81
  61. package/src/components/ATableModal.vue +0 -52
  62. package/src/components/ATablePaginationFooter.vue +0 -100
  63. package/src/composables/table-pagination.ts +0 -172
  64. package/src/icons/index.ts +0 -38
  65. package/src/icons/stonecrop-ui-icon-add.svg +0 -5
  66. package/src/icons/stonecrop-ui-icon-delete.svg +0 -5
  67. package/src/icons/stonecrop-ui-icon-duplicate.svg +0 -5
  68. package/src/icons/stonecrop-ui-icon-insert-above.svg +0 -15
  69. package/src/icons/stonecrop-ui-icon-insert-below.svg +0 -15
  70. package/src/icons/stonecrop-ui-icon-move.svg +0 -4
  71. package/src/icons/stonecrop-ui-icon-open.svg +0 -5
  72. package/src/index.ts +0 -62
  73. package/src/linkSearchableText.ts +0 -42
  74. package/src/resolveFilterType.ts +0 -32
  75. package/src/schemaToColumns.ts +0 -67
  76. package/src/shims-vue.d.ts +0 -10
  77. package/src/stores/table.ts +0 -978
  78. package/src/types/index.ts +0 -794
  79. package/src/utils.ts +0 -40
@@ -1,538 +0,0 @@
1
- <template>
2
- <div class="atable-container" style="position: relative">
3
- <!-- Main table view -->
4
- <table
5
- ref="table"
6
- v-on-click-outside="store.closeModal"
7
- class="atable"
8
- :style="{
9
- width: store.config.fullWidth ? '100%' : 'auto',
10
- }">
11
- <slot name="header" :data="store">
12
- <ATableHeader :columns="store.columns" :store="store" />
13
- </slot>
14
- <tbody>
15
- <slot name="body" :data="store">
16
- <ARow
17
- v-for="(row, filteredIndex) in visibleRows"
18
- :key="`${row.originalIndex}-${filteredIndex}`"
19
- :row="row"
20
- :row-index="row.originalIndex"
21
- :store="store"
22
- @row:action="handleRowAction"
23
- @row:click="handleRowClick">
24
- <template v-for="(column, colIndex) in getProcessedColumnsForRow(row)" :key="column.name">
25
- <component
26
- :is="column.ganttComponent || 'AGanttCell'"
27
- v-if="column.isGantt"
28
- :store="store"
29
- :columns-count="store.columns.length - pinnedColumnCount"
30
- :color="row.gantt?.color"
31
- :start="row.gantt?.startIndex"
32
- :end="row.gantt?.endIndex"
33
- :colspan="column.colspan"
34
- :pinned="column.pinned"
35
- :row-index="row.originalIndex"
36
- :col-index="column.originalIndex ?? colIndex"
37
- :style="{
38
- textAlign: column?.align || 'center',
39
- minWidth: column?.width || '40ch',
40
- width: store.config.fullWidth ? 'auto' : null,
41
- }"
42
- @connection:create="handleConnectionCreate" />
43
- <component
44
- :is="column.cellComponent || 'ACell'"
45
- v-else
46
- :store="store"
47
- :pinned="column.pinned"
48
- :row-index="row.originalIndex"
49
- :col-index="colIndex"
50
- :style="{
51
- textAlign: column?.align || 'center',
52
- width: store.config.fullWidth ? 'auto' : null,
53
- }"
54
- spellcheck="false" />
55
- </template>
56
- </ARow>
57
- </slot>
58
- </tbody>
59
-
60
- <slot name="footer" :data="store" :page="pagination">
61
- <ATablePaginationFooter
62
- :column-count="footerColumnCount"
63
- :show-footer="showFooter"
64
- :has-more="hasMore"
65
- :has-prev="hasPrev"
66
- :has-next="hasNext"
67
- :has-local-next="hasLocalNext"
68
- :has-ever-had-more="hasEverHadMore"
69
- :loaded-count="loadedCount"
70
- :loading="paginationLoading"
71
- :next="paginationNext"
72
- :prev="paginationPrev" />
73
- </slot>
74
-
75
- <!-- Modal overlay -->
76
- <slot name="modal" :data="store">
77
- <ATableModal v-show="store.modal.visible" :store="store">
78
- <template #default>
79
- <component
80
- :is="store.modal.component"
81
- :key="`${store.modal.rowIndex}:${store.modal.colIndex}`"
82
- :col-index="store.modal.colIndex"
83
- :row-index="store.modal.rowIndex"
84
- :store="store"
85
- v-bind="store.modal.componentProps" />
86
- </template>
87
- </ATableModal>
88
- </slot>
89
- </table>
90
-
91
- <!-- Connection overlay for gantt connections -->
92
- <AGanttConnection
93
- v-if="store.isGanttView && store.isDependencyGraphEnabled"
94
- :store="store"
95
- @connection:delete="handleConnectionDelete" />
96
- </div>
97
- </template>
98
-
99
- <script setup lang="ts">
100
- import { vOnClickOutside } from '@vueuse/components'
101
- import { useMutationObserver } from '@vueuse/core'
102
- import { computed, inject, nextTick, onMounted, useTemplateRef, watch } from 'vue'
103
-
104
- import AGanttConnection from './AGanttConnection.vue'
105
- import ARow from './ARow.vue'
106
- import ATableHeader from './ATableHeader.vue'
107
- import ATableModal from './ATableModal.vue'
108
- import ATablePaginationFooter from './ATablePaginationFooter.vue'
109
- import { useTablePagination } from '../composables/table-pagination'
110
- import { createTableStore } from '../stores/table'
111
- import type { ColumnSchema, GetRecordsOptions, GetRecordsResult } from '@stonecrop/schema'
112
-
113
- import { schemaToColumns } from '../schemaToColumns'
114
- import type {
115
- ConnectionEvent,
116
- ConnectionPath,
117
- GanttDragEvent,
118
- RowActionType,
119
- RowAddEvent,
120
- RowClickEvent,
121
- RowDeleteEvent,
122
- RowDuplicateEvent,
123
- RowInsertEvent,
124
- RowMoveEvent,
125
- TableColumn,
126
- TableConfig,
127
- TableRow,
128
- } from '../types'
129
-
130
- const rows = defineModel<TableRow[]>('rows', { required: true })
131
- // TODO: convert columns from defineModel to a regular prop
132
- const columns = defineModel<TableColumn[]>('columns')
133
-
134
- type LinkResolverFn = (doctype: string, id: string) => Promise<string | undefined>
135
-
136
- const {
137
- id = '',
138
- config = new Object(),
139
- schema = [],
140
- linkResolver = undefined,
141
- getRecords = undefined,
142
- sourceKey = undefined,
143
- } = defineProps<{
144
- id?: string
145
- config?: TableConfig
146
- schema?: ColumnSchema[]
147
- linkResolver?: LinkResolverFn
148
- getRecords?: (options?: GetRecordsOptions) => Promise<GetRecordsResult>
149
- sourceKey?: string
150
- }>()
151
-
152
- const emit = defineEmits<{
153
- cellUpdate: [{ colIndex: number; rowIndex: number; newValue: any; oldValue: any }]
154
- 'gantt:drag': [event: GanttDragEvent]
155
- 'connection:event': [event: ConnectionEvent]
156
- 'columns:update': [columns: TableColumn[]]
157
- 'row:add': [event: RowAddEvent]
158
- 'row:click': [event: RowClickEvent]
159
- 'row:delete': [event: RowDeleteEvent]
160
- 'row:duplicate': [event: RowDuplicateEvent]
161
- 'row:insert-above': [event: RowInsertEvent]
162
- 'row:insert-below': [event: RowInsertEvent]
163
- 'row:move': [event: RowMoveEvent]
164
- 'row:open': [event: RowClickEvent]
165
- }>()
166
-
167
- const tableRef = useTemplateRef<HTMLTableElement>('table')
168
- const resolvedColumns = columns.value?.length ? columns.value : schemaToColumns(schema ?? [])
169
- const injectedLinkResolver = inject<LinkResolverFn | null>('aformLinkResolver', null)
170
- const store = createTableStore({
171
- columns: resolvedColumns,
172
- rows: rows.value,
173
- id,
174
- config,
175
- linkResolver: linkResolver ?? injectedLinkResolver,
176
- })
177
-
178
- const pagination = useTablePagination({
179
- rows: () => store.filteredRows,
180
- pageSize: () => store.config.pageSize,
181
- getRecords,
182
- sourceKey: () => sourceKey,
183
- })
184
-
185
- const {
186
- visibleRows,
187
- showFooter,
188
- hasMore,
189
- hasPrev,
190
- hasNext,
191
- hasLocalNext,
192
- hasEverHadMore,
193
- loadedCount,
194
- loading: paginationLoading,
195
- next: paginationNext,
196
- prev: paginationPrev,
197
- } = pagination
198
-
199
- const footerColumnCount = computed(() => store.columns.length + (store.zeroColumn ? 1 : 0))
200
-
201
- store.$onAction(({ name, store: storeInstance, args, after }) => {
202
- if (name === 'setCellData' || name === 'setCellText') {
203
- const [colIndex, rowIndex, newValue] = args
204
- const oldValue = storeInstance.getCellData(colIndex, rowIndex)
205
- after(() => {
206
- // Update rows model to trigger update:rows event
207
- rows.value = [...store.rows]
208
- emit('cellUpdate', { colIndex, rowIndex, newValue, oldValue })
209
- })
210
- } else if (name === 'updateGanttBar') {
211
- const [event] = args
212
- let shouldEmit = false
213
- if (event.type === 'resize') {
214
- shouldEmit = event.oldColspan !== event.newColspan
215
- } else if (event.type === 'bar') {
216
- shouldEmit = event.oldStart !== event.newStart || event.oldEnd !== event.newEnd
217
- }
218
-
219
- if (shouldEmit) {
220
- after(() => {
221
- emit('gantt:drag', event)
222
- })
223
- }
224
- } else if (name === 'resizeColumn') {
225
- after(() => {
226
- // Update columns model
227
- columns.value = [...store.columns]
228
- emit('columns:update', [...store.columns])
229
- })
230
- }
231
- })
232
-
233
- // Watch for external changes to rows and sync to store
234
- watch(
235
- () => rows.value,
236
- newRows => {
237
- // Only update if the rows have actually changed (avoid infinite loops)
238
- if (JSON.stringify(newRows) !== JSON.stringify(store.rows)) {
239
- store.rows = [...newRows]
240
- }
241
- },
242
- { deep: true }
243
- )
244
-
245
- // Watch for changes to columns and sync to store
246
- watch(
247
- columns,
248
- newColumns => {
249
- // The model is optional, so it can be absent — there is nothing to sync from, and the store
250
- // keeps the columns it resolved from the schema.
251
- if (!newColumns) return
252
- // Only update if the columns have actually changed (avoid infinite loops)
253
- if (JSON.stringify(newColumns) !== JSON.stringify(store.columns)) {
254
- store.columns = [...newColumns]
255
- emit('columns:update', [...newColumns])
256
- }
257
- },
258
- { deep: true }
259
- )
260
-
261
- onMounted(() => {
262
- if (columns.value?.some(column => column.pinned)) {
263
- assignStickyCellWidths()
264
-
265
- // in tree views, also add a mutation observer to capture and adjust expanded rows
266
- if (store.isTreeView) {
267
- useMutationObserver(tableRef, assignStickyCellWidths, { childList: true, subtree: true })
268
- }
269
- }
270
- })
271
-
272
- const pinnedColumnCount = computed(() => {
273
- return store.columns.filter(column => column.pinned).length
274
- })
275
-
276
- const assignStickyCellWidths = () => {
277
- const table = tableRef.value
278
-
279
- // set header cell width to match sticky cells' width
280
- const headerRow = table?.rows[0]
281
- const firstDataRow = table?.rows[1]
282
- const headerCells = headerRow ? Array.from(headerRow.cells) : []
283
- for (const [index, headerCell] of headerCells.entries()) {
284
- const rowCell = firstDataRow?.cells[index]
285
- if (rowCell) {
286
- headerCell.style.width = `${rowCell.offsetWidth}px`
287
- }
288
- }
289
-
290
- // pin cells in row that are sticky
291
- for (const row of table?.rows || []) {
292
- let totalWidth = 0
293
- const pinnedCells: HTMLTableCellElement[] = []
294
-
295
- for (const column of row.cells) {
296
- if (column.classList.contains('sticky-column') || column.classList.contains('sticky-index')) {
297
- column.style.left = `${totalWidth}px`
298
- totalWidth += column.offsetWidth
299
- pinnedCells.push(column)
300
- }
301
- }
302
-
303
- if (pinnedCells.length > 0) {
304
- const lastColumn = pinnedCells[pinnedCells.length - 1]
305
- lastColumn.classList.add('sticky-column-edge')
306
- }
307
- }
308
- }
309
-
310
- window.addEventListener('keydown', (event: KeyboardEvent) => {
311
- if (event.key === 'Escape') {
312
- if (store.modal.visible) {
313
- store.modal.visible = false
314
-
315
- // focus on the parent cell again
316
- const $parent = store.modal.parent
317
- if ($parent) {
318
- // wait for the modal to close before focusing
319
- void nextTick().then(() => {
320
- return $parent.focus()
321
- })
322
- }
323
- }
324
- }
325
- })
326
-
327
- const getProcessedColumnsForRow = (row: TableRow) => {
328
- if (!row.gantt || pinnedColumnCount.value === 0) {
329
- return store.columns
330
- }
331
-
332
- // For Gantt views, first add the pinned columns
333
- const result: TableColumn[] = []
334
- for (let i = 0; i < pinnedColumnCount.value; i++) {
335
- const column = { ...store.columns[i] }
336
- column.originalIndex = i // Preserve original index
337
- result.push(column)
338
- }
339
-
340
- // Finally, add the Gantt bar column with either a provided colspan or default to full-width colspan
341
- result.push({
342
- ...store.columns[pinnedColumnCount.value],
343
- isGantt: true,
344
- colspan: row.gantt?.colspan || store.columns.length - pinnedColumnCount.value,
345
- originalIndex: pinnedColumnCount.value,
346
- width: 'auto', // TODO: refactor to API that can detect when data exists in a cell. Might have be custom and not generalizable
347
- })
348
-
349
- return result
350
- }
351
-
352
- const handleConnectionCreate = (connection: ConnectionPath) => {
353
- emit('connection:event', { type: 'create', connection })
354
- }
355
-
356
- const handleConnectionDelete = (connection: ConnectionPath) => {
357
- emit('connection:event', { type: 'delete', connection })
358
- }
359
-
360
- /**
361
- * Handle row click events from ARow components.
362
- */
363
- const handleRowClick = (rowIndex: number, event: MouseEvent) => {
364
- const row = store.rows[rowIndex]
365
- emit('row:click', { row, rowIndex, event })
366
- }
367
-
368
- /**
369
- * Handle row action events from ARow components.
370
- * Performs the default action and emits the appropriate event.
371
- */
372
- const handleRowAction = (actionType: RowActionType, rowIndex: number, event?: MouseEvent) => {
373
- switch (actionType) {
374
- case 'add': {
375
- // Add a new row after the current row
376
- const newIndex = store.addRow({}, rowIndex + 1)
377
- const newRow = store.rows[newIndex]
378
- rows.value = [...store.rows]
379
- emit('row:add', { rowIndex: newIndex, row: newRow })
380
- break
381
- }
382
- case 'delete': {
383
- const deletedRow = store.deleteRow(rowIndex)
384
- if (deletedRow) {
385
- rows.value = [...store.rows]
386
- emit('row:delete', { rowIndex, row: deletedRow })
387
- }
388
- break
389
- }
390
- case 'duplicate': {
391
- const newIndex = store.duplicateRow(rowIndex)
392
- if (newIndex >= 0) {
393
- const newRow = store.rows[newIndex]
394
- rows.value = [...store.rows]
395
- emit('row:duplicate', { sourceIndex: rowIndex, newIndex, row: newRow })
396
- }
397
- break
398
- }
399
- case 'insertAbove': {
400
- const newIndex = store.insertRowAbove(rowIndex)
401
- const newRow = store.rows[newIndex]
402
- rows.value = [...store.rows]
403
- emit('row:insert-above', { targetIndex: rowIndex, newIndex, row: newRow })
404
- break
405
- }
406
- case 'insertBelow': {
407
- const newIndex = store.insertRowBelow(rowIndex)
408
- const newRow = store.rows[newIndex]
409
- rows.value = [...store.rows]
410
- emit('row:insert-below', { targetIndex: rowIndex, newIndex, row: newRow })
411
- break
412
- }
413
- case 'move': {
414
- // Move action requires a target index - for now, emit an event
415
- // The consumer should handle showing a UI for selecting the target
416
- emit('row:move', { fromIndex: rowIndex, toIndex: -1 })
417
- break
418
- }
419
- case 'moveUp': {
420
- if (rowIndex > 0 && store.moveRow(rowIndex, rowIndex - 1)) {
421
- rows.value = [...store.rows]
422
- emit('row:move', { fromIndex: rowIndex, toIndex: rowIndex - 1 })
423
- }
424
- break
425
- }
426
- case 'moveDown': {
427
- if (rowIndex < store.rows.length - 1 && store.moveRow(rowIndex, rowIndex + 1)) {
428
- rows.value = [...store.rows]
429
- emit('row:move', { fromIndex: rowIndex, toIndex: rowIndex + 1 })
430
- }
431
- break
432
- }
433
- case 'open': {
434
- const row = store.rows[rowIndex]
435
- emit('row:open', { row, rowIndex, event })
436
- break
437
- }
438
- }
439
- }
440
-
441
- defineExpose({
442
- store,
443
- pagination,
444
- createConnection: store.createConnection,
445
- deleteConnection: store.deleteConnection,
446
- getConnectionsForBar: store.getConnectionsForBar,
447
- getHandlesForBar: store.getHandlesForBar,
448
- // Row action methods
449
- addRow: store.addRow,
450
- deleteRow: store.deleteRow,
451
- duplicateRow: store.duplicateRow,
452
- insertRowAbove: store.insertRowAbove,
453
- insertRowBelow: store.insertRowBelow,
454
- moveRow: store.moveRow,
455
- })
456
- </script>
457
-
458
- <style>
459
- .atable-container {
460
- position: relative;
461
- }
462
-
463
- .sticky-index {
464
- position: sticky;
465
- left: 0px;
466
- z-index: 100;
467
- order: 0;
468
- }
469
-
470
- .sticky-column,
471
- th.sticky-column,
472
- td.sticky-column,
473
- th.sticky-index,
474
- td.sticky-index {
475
- position: sticky;
476
- z-index: 100;
477
- order: 0;
478
- background: white;
479
- }
480
-
481
- .sticky-column-edge,
482
- .atable th.sticky-column-edge {
483
- border-right: 1px solid var(--sc-row-border-color);
484
- }
485
- </style>
486
-
487
- <style scoped>
488
- .atable {
489
- position: relative;
490
- font-family: var(--sc-atable-font-family);
491
- -webkit-font-smoothing: antialiased;
492
- -moz-osx-font-smoothing: grayscale;
493
- font-size: var(--sc-table-font-size);
494
- border-collapse: collapse;
495
- box-sizing: border-box;
496
- table-layout: auto;
497
- width: auto;
498
- overflow: clip;
499
- height: 1px;
500
- /* border-left:4px solid var(--sc-form-border); */
501
- }
502
- .atable th {
503
- border-width: 0px;
504
- border-style: solid;
505
- border-radius: 0px;
506
- padding-left: 0.5ch;
507
- padding-right: 0.5ch;
508
- padding-top: var(--sc-atable-row-padding);
509
- padding-bottom: var(--sc-atable-row-padding);
510
- color: var(--sc-gray-60);
511
- height: var(--sc-atable-row-height);
512
- font-weight: 300;
513
- letter-spacing: 0.05rem;
514
- order: 1;
515
- box-sizing: border-box;
516
- }
517
- .atable th:focus {
518
- outline: none;
519
- }
520
-
521
- /* Make sure the vertical indicator doesn't extend into the header */
522
- .atable:tbody {
523
- overflow: hidden; /* This ensures the indicator is clipped */
524
- position: relative; /* Create a stacking context */
525
- }
526
-
527
- /* Ensure the indicator stays within the tbody */
528
- .atable:tbody::before {
529
- content: '';
530
- position: absolute;
531
- top: 0;
532
- left: 0;
533
- right: 0;
534
- height: 1px;
535
- background-color: transparent;
536
- z-index: 100;
537
- }
538
- </style>