@hina-ui/vue 1.6.0 → 1.7.1

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 (117) hide show
  1. package/dist/index.js +3170 -461
  2. package/dist/index.js.map +1 -1
  3. package/dist/types/components/anchor/Anchor.vue.d.ts +26 -10
  4. package/dist/types/components/anchor/Anchor.vue.d.ts.map +1 -1
  5. package/dist/types/components/anchor/composables/useAnchor.d.ts +15 -0
  6. package/dist/types/components/anchor/composables/useAnchor.d.ts.map +1 -0
  7. package/dist/types/components/anchor/composables/useScrollSpy.d.ts.map +1 -1
  8. package/dist/types/components/anchor/types.d.ts +9 -0
  9. package/dist/types/components/anchor/types.d.ts.map +1 -0
  10. package/dist/types/components/app-shell/AppShell.vue.d.ts.map +1 -1
  11. package/dist/types/components/app-shell/composables/useSidebarScrollUpdates.d.ts +4 -0
  12. package/dist/types/components/app-shell/composables/useSidebarScrollUpdates.d.ts.map +1 -0
  13. package/dist/types/components/combobox/Combobox.vue.d.ts +1 -0
  14. package/dist/types/components/combobox/Combobox.vue.d.ts.map +1 -1
  15. package/dist/types/components/data-table/DataTable.vue.d.ts +58 -0
  16. package/dist/types/components/data-table/DataTable.vue.d.ts.map +1 -0
  17. package/dist/types/components/data-table/DataTableAction.vue.d.ts +18 -0
  18. package/dist/types/components/data-table/DataTableAction.vue.d.ts.map +1 -0
  19. package/dist/types/components/data-table/DataTableDragPreview.vue.d.ts +24 -0
  20. package/dist/types/components/data-table/DataTableDragPreview.vue.d.ts.map +1 -0
  21. package/dist/types/components/data-table/DataTableEditor.vue.d.ts +24 -0
  22. package/dist/types/components/data-table/DataTableEditor.vue.d.ts.map +1 -0
  23. package/dist/types/components/data-table/DataTableHead.vue.d.ts +32 -0
  24. package/dist/types/components/data-table/DataTableHead.vue.d.ts.map +1 -0
  25. package/dist/types/components/data-table/DataTableRow.vue.d.ts +39 -0
  26. package/dist/types/components/data-table/DataTableRow.vue.d.ts.map +1 -0
  27. package/dist/types/components/data-table/DataTableSelection.vue.d.ts +15 -0
  28. package/dist/types/components/data-table/DataTableSelection.vue.d.ts.map +1 -0
  29. package/dist/types/components/data-table/DataTableText.vue.d.ts +8 -0
  30. package/dist/types/components/data-table/DataTableText.vue.d.ts.map +1 -0
  31. package/dist/types/components/data-table/Slot.d.ts +8 -0
  32. package/dist/types/components/data-table/Slot.d.ts.map +1 -0
  33. package/dist/types/components/data-table/column-sizing.d.ts +22 -0
  34. package/dist/types/components/data-table/column-sizing.d.ts.map +1 -0
  35. package/dist/types/components/data-table/composables/useDataTable.d.ts +80 -0
  36. package/dist/types/components/data-table/composables/useDataTable.d.ts.map +1 -0
  37. package/dist/types/components/data-table/composables/useTableColumns.d.ts +51 -0
  38. package/dist/types/components/data-table/composables/useTableColumns.d.ts.map +1 -0
  39. package/dist/types/components/data-table/composables/useTableDrag.d.ts +53 -0
  40. package/dist/types/components/data-table/composables/useTableDrag.d.ts.map +1 -0
  41. package/dist/types/components/data-table/composables/useTableEditing.d.ts +27 -0
  42. package/dist/types/components/data-table/composables/useTableEditing.d.ts.map +1 -0
  43. package/dist/types/components/data-table/composables/useTableExport.d.ts +3 -0
  44. package/dist/types/components/data-table/composables/useTableExport.d.ts.map +1 -0
  45. package/dist/types/components/data-table/composables/useTableResize.d.ts +42 -0
  46. package/dist/types/components/data-table/composables/useTableResize.d.ts.map +1 -0
  47. package/dist/types/components/data-table/composables/useTableResizeHandles.d.ts +8 -0
  48. package/dist/types/components/data-table/composables/useTableResizeHandles.d.ts.map +1 -0
  49. package/dist/types/components/data-table/composables/useTableText.d.ts +6 -0
  50. package/dist/types/components/data-table/composables/useTableText.d.ts.map +1 -0
  51. package/dist/types/components/data-table/composables/useTableVirtual.d.ts +55 -0
  52. package/dist/types/components/data-table/composables/useTableVirtual.d.ts.map +1 -0
  53. package/dist/types/components/data-table/features.d.ts +16 -0
  54. package/dist/types/components/data-table/features.d.ts.map +1 -0
  55. package/dist/types/components/data-table/types.d.ts +225 -0
  56. package/dist/types/components/data-table/types.d.ts.map +1 -0
  57. package/dist/types/components/data-table/utils.d.ts +10 -0
  58. package/dist/types/components/data-table/utils.d.ts.map +1 -0
  59. package/dist/types/components/form/composables/useForm.d.ts.map +1 -1
  60. package/dist/types/components/sidebar/DrawerScope.d.ts +13 -2
  61. package/dist/types/components/sidebar/DrawerScope.d.ts.map +1 -1
  62. package/dist/types/components/sidebar/Sidebar.vue.d.ts +4 -1
  63. package/dist/types/components/sidebar/Sidebar.vue.d.ts.map +1 -1
  64. package/dist/types/components/sidebar/context.d.ts +1 -0
  65. package/dist/types/components/sidebar/context.d.ts.map +1 -1
  66. package/dist/types/components/sidebar/sidebar.variants.d.ts +3 -3
  67. package/dist/types/components/table/TableHead.vue.d.ts +2 -2
  68. package/dist/types/components/table/TableHead.vue.d.ts.map +1 -1
  69. package/dist/types/index.d.ts +3 -1
  70. package/dist/types/index.d.ts.map +1 -1
  71. package/dist/types/locale/en-US.d.ts.map +1 -1
  72. package/dist/types/locale/types.d.ts +12 -0
  73. package/dist/types/locale/types.d.ts.map +1 -1
  74. package/dist/types/locale/zh-CN.d.ts.map +1 -1
  75. package/package.json +14 -12
  76. package/src/components/anchor/Anchor.vue +15 -19
  77. package/src/components/anchor/composables/useAnchor.ts +19 -0
  78. package/src/components/anchor/composables/useScrollSpy.ts +6 -1
  79. package/src/components/anchor/types.ts +8 -0
  80. package/src/components/app-shell/AppShell.vue +5 -2
  81. package/src/components/app-shell/composables/useSidebarScrollUpdates.ts +52 -0
  82. package/src/components/combobox/Combobox.vue +11 -1
  83. package/src/components/data-table/DataTable.vue +340 -0
  84. package/src/components/data-table/DataTableAction.vue +15 -0
  85. package/src/components/data-table/DataTableDragPreview.vue +72 -0
  86. package/src/components/data-table/DataTableEditor.vue +59 -0
  87. package/src/components/data-table/DataTableHead.vue +181 -0
  88. package/src/components/data-table/DataTableRow.vue +255 -0
  89. package/src/components/data-table/DataTableSelection.vue +51 -0
  90. package/src/components/data-table/DataTableText.vue +20 -0
  91. package/src/components/data-table/Slot.ts +12 -0
  92. package/src/components/data-table/column-sizing.ts +101 -0
  93. package/src/components/data-table/composables/useDataTable.ts +425 -0
  94. package/src/components/data-table/composables/useTableColumns.ts +253 -0
  95. package/src/components/data-table/composables/useTableDrag.ts +345 -0
  96. package/src/components/data-table/composables/useTableEditing.ts +184 -0
  97. package/src/components/data-table/composables/useTableExport.ts +44 -0
  98. package/src/components/data-table/composables/useTableResize.ts +292 -0
  99. package/src/components/data-table/composables/useTableResizeHandles.ts +72 -0
  100. package/src/components/data-table/composables/useTableText.ts +13 -0
  101. package/src/components/data-table/composables/useTableVirtual.ts +109 -0
  102. package/src/components/data-table/features.ts +32 -0
  103. package/src/components/data-table/types.ts +218 -0
  104. package/src/components/data-table/utils.ts +75 -0
  105. package/src/components/form/composables/useForm.ts +44 -8
  106. package/src/components/sidebar/DrawerScope.ts +12 -4
  107. package/src/components/sidebar/Sidebar.vue +27 -7
  108. package/src/components/sidebar/context.ts +1 -0
  109. package/src/components/sidebar/sidebar.variants.ts +1 -1
  110. package/src/components/table/TableHead.vue +1 -1
  111. package/src/index.ts +26 -1
  112. package/src/locale/en-US.ts +13 -0
  113. package/src/locale/types.ts +13 -0
  114. package/src/locale/zh-CN.ts +13 -0
  115. package/src/styles/data-table.css +331 -0
  116. package/src/styles/tokens.css +1 -0
  117. package/src/styles/typography.css +3 -0
@@ -0,0 +1,340 @@
1
+ <script setup lang="ts" generic="T extends object">
2
+ import { computed, shallowRef, useId } from 'vue'
3
+ import { ChevronLeft, ChevronRight } from '@lucide/vue'
4
+ import { cn } from '../../lib/cn'
5
+ import { useUiLocale } from '../../locale'
6
+ import Button from '../button/Button.vue'
7
+ import LoadingOverlay from '../loading-overlay/LoadingOverlay.vue'
8
+ import Pagination from '../pagination/Pagination.vue'
9
+ import ScrollArea from '../scroll-area/ScrollArea.vue'
10
+ import TableCell from '../table/TableCell.vue'
11
+ import { tableWrapper } from '../table/table.variants'
12
+ import DataTableHead from './DataTableHead.vue'
13
+ import DataTableRow from './DataTableRow.vue'
14
+ import DataTableDragPreview from './DataTableDragPreview.vue'
15
+ import Slot from './Slot'
16
+ import { aggregate, cssSize } from './utils'
17
+ import { useDataTable } from './composables/useDataTable'
18
+ import { useTableColumns } from './composables/useTableColumns'
19
+ import { useTableDrag } from './composables/useTableDrag'
20
+ import { useTableEditing } from './composables/useTableEditing'
21
+ import { useTableExport } from './composables/useTableExport'
22
+ import { useTableVirtual } from './composables/useTableVirtual'
23
+ import type {
24
+ DataTableEdit,
25
+ DataTableFilter,
26
+ DataTableKey,
27
+ DataTableProps,
28
+ DataTableQuery,
29
+ DataTableReorder,
30
+ DataTableSlots,
31
+ DataTableSort,
32
+ } from './types'
33
+
34
+ defineOptions({ name: 'HnDataTable' })
35
+ const props = withDefaults(defineProps<DataTableProps<T>>(), {
36
+ variant: 'primary',
37
+ hover: true,
38
+ autoResetPage: true,
39
+ selectChildren: true,
40
+ hasNextPage: undefined,
41
+ })
42
+ const slots = defineSlots<DataTableSlots<T>>()
43
+ const page = defineModel<number>('page', { default: 1 })
44
+ const pageSize = defineModel<number>('pageSize', { default: 10 })
45
+ const sorting = defineModel<DataTableSort[]>('sorting', { default: () => [] })
46
+ const filter = defineModel<string>('filter', { default: '' })
47
+ const columnFilters = defineModel<DataTableFilter[]>('columnFilters', { default: () => [] })
48
+ const grouping = defineModel<string[]>('grouping', { default: () => [] })
49
+ const selected = defineModel<DataTableKey[]>('selected', { default: () => [] })
50
+ const expanded = defineModel<DataTableKey[]>('expanded', { default: () => [] })
51
+ const expandedGroups = defineModel<string[]>('expandedGroups', { default: () => [] })
52
+ const hiddenColumns = defineModel<string[]>('hiddenColumns', { default: () => [] })
53
+ const columnOrder = defineModel<string[]>('columnOrder', { default: () => [] })
54
+ const columnWidths = defineModel<Record<string, number>>('columnWidths', { default: () => ({}) })
55
+ const models = {
56
+ page,
57
+ pageSize,
58
+ sorting,
59
+ filter,
60
+ columnFilters,
61
+ grouping,
62
+ selected,
63
+ expanded,
64
+ expandedGroups,
65
+ hiddenColumns,
66
+ columnOrder,
67
+ columnWidths,
68
+ }
69
+ const emit = defineEmits<{
70
+ change: [query: DataTableQuery]
71
+ rowClick: [row: T, event: MouseEvent | KeyboardEvent]
72
+ rowReorder: [change: DataTableReorder<T>]
73
+ 'update:rows': [rows: T[]]
74
+ edit: [edit: DataTableEdit<T>]
75
+ editError: [error: unknown, edit: DataTableEdit<T>]
76
+ rowContextmenu: [row: T, event: MouseEvent]
77
+ }>()
78
+ function onRowContextmenu(row: T, event: MouseEvent) {
79
+ emit('rowContextmenu', row, event)
80
+ }
81
+ const t = useUiLocale()
82
+ const name = useId()
83
+ const area = shallowRef<InstanceType<typeof ScrollArea>>()
84
+ const element = shallowRef<HTMLTableElement>()
85
+ const viewport = computed(() => area.value?.viewport)
86
+ const ctl = useDataTable(
87
+ props,
88
+ models,
89
+ query => emit('change', query),
90
+ (row, event) => emit('rowClick', row, event),
91
+ )
92
+ const controls = computed(() =>
93
+ [
94
+ props.reorderable && 'drag',
95
+ props.selectable && 'select',
96
+ (props.expandable || props.getChildren) && 'expand',
97
+ ].filter((value): value is string => typeof value === 'string'),
98
+ )
99
+ const trailing = computed(() => (props.editMode === 'row' ? 1 : 0))
100
+ const layout = useTableColumns(
101
+ props,
102
+ models,
103
+ ctl,
104
+ element,
105
+ viewport,
106
+ computed(() => controls.value.length),
107
+ trailing,
108
+ )
109
+ const editing = useTableEditing(
110
+ props,
111
+ ctl,
112
+ edit => emit('edit', edit),
113
+ (error, edit) => emit('editError', error, edit),
114
+ element,
115
+ )
116
+ const drag = useTableDrag(props, models, ctl, element, viewport, change => {
117
+ if (!change.parent) emit('update:rows', change.rows)
118
+ emit('rowReorder', change)
119
+ })
120
+ const virtual = useTableVirtual(
121
+ props,
122
+ ctl,
123
+ element,
124
+ viewport,
125
+ () => !!slots.expansion,
126
+ editing.rowError,
127
+ )
128
+ useTableExport(ctl)
129
+ const colspan = computed(() =>
130
+ Math.max(1, ctl.visibleColumns.value.length + controls.value.length + trailing.value),
131
+ )
132
+ const summaryRows = computed(() => ctl.api.getRows('filtered'))
133
+ const hasSummary = computed(
134
+ () =>
135
+ !!slots.summary ||
136
+ ctl.visibleColumns.value.some(
137
+ column => column.footer !== undefined || slots[`footer-${column.key}`],
138
+ ),
139
+ )
140
+ defineExpose({ viewport, element, state: ctl.state, api: ctl.api })
141
+ </script>
142
+ <template>
143
+ <div
144
+ :aria-busy="props.loading || undefined"
145
+ :style="{ height: cssSize(props.height) }"
146
+ :class="
147
+ cn(
148
+ 'flex min-h-0 min-w-0 w-full max-w-full flex-col gap-3',
149
+ props.fill && 'h-full',
150
+ props.class,
151
+ )
152
+ "
153
+ >
154
+ <slot name="toolbar" v-bind="ctl.state.value" />
155
+ <div :class="['relative min-h-0', (props.fill || props.height) && 'flex-1']">
156
+ <ScrollArea
157
+ ref="area"
158
+ :direction="
159
+ props.stickyHeader || props.maxHeight || props.height || props.fill || props.virtualize
160
+ ? 'both'
161
+ : 'horizontal'
162
+ "
163
+ :style="{
164
+ maxHeight: cssSize(
165
+ props.maxHeight ?? (props.virtualize && !props.fill && !props.height ? 400 : undefined),
166
+ ),
167
+ }"
168
+ :class="
169
+ cn(
170
+ tableWrapper({ variant: props.variant, hover: false }),
171
+ (props.fill || props.height) && 'h-full',
172
+ props.stickyHeader && '[&_thead_th]:sticky [&_thead_th]:z-[3]',
173
+ props.stickyFooter &&
174
+ '[&_tfoot_td]:sticky [&_tfoot_td]:bottom-0 [&_tfoot_td]:z-[2] [&_tfoot_td]:bg-(--hn-table-head-bg)',
175
+ props.tableClass,
176
+ )
177
+ "
178
+ >
179
+ <table
180
+ ref="element"
181
+ class="hn-table"
182
+ :style="layout.tableStyle.value"
183
+ :aria-label="props.label"
184
+ :aria-rowcount="
185
+ props.virtualize
186
+ ? virtual.entries.value.length + layout.headerRows.value.length + (hasSummary ? 1 : 0)
187
+ : undefined
188
+ "
189
+ :inert="ctl.blocked.value || undefined"
190
+ >
191
+ <caption v-if="props.caption">
192
+ <span>{{ props.caption }}</span>
193
+ </caption>
194
+ <colgroup>
195
+ <col v-for="control in controls" :key="control" style="width: 48px" />
196
+ <col
197
+ v-for="column in ctl.visibleColumns.value"
198
+ :key="column.key"
199
+ :style="{ width: layout.cellStyle(column).width }"
200
+ />
201
+ <col v-if="trailing" style="width: 72px" />
202
+ </colgroup>
203
+ <DataTableHead
204
+ :config="props"
205
+ :ctl="ctl"
206
+ :models="models"
207
+ :layout="layout"
208
+ :drag="drag"
209
+ :controls="controls"
210
+ :trailing="trailing"
211
+ :slots="slots"
212
+ />
213
+ <tbody>
214
+ <tr
215
+ v-if="
216
+ !ctl.rows.value.length || (!ctl.visibleColumns.value.length && !controls.length)
217
+ "
218
+ >
219
+ <TableCell :colspan="colspan">
220
+ <div class="text-muted flex min-h-32 items-center justify-center px-3 py-6 text-sm">
221
+ <template v-if="props.loading">{{ t.table.loading }}</template>
222
+ <template v-else-if="!ctl.visibleColumns.value.length && !controls.length">
223
+ {{ t.table.noColumns }}
224
+ </template>
225
+ <slot v-else name="empty">{{ props.emptyText ?? t.table.empty }}</slot>
226
+ </div>
227
+ </TableCell>
228
+ </tr>
229
+ <template v-else>
230
+ <tr v-if="virtual.before.value" aria-hidden="true">
231
+ <td
232
+ :colspan="colspan"
233
+ :style="{ height: `${virtual.before.value}px`, padding: 0, border: 0 }"
234
+ />
235
+ </tr>
236
+ <DataTableRow
237
+ v-for="item in virtual.visible.value"
238
+ :key="item.id"
239
+ :item="item"
240
+ :config="props"
241
+ :ctl="ctl"
242
+ :layout="layout"
243
+ :drag="drag"
244
+ :editing="editing"
245
+ :slots="slots"
246
+ :controls="controls"
247
+ :colspan="colspan"
248
+ :name="name"
249
+ :measure="virtual.measure"
250
+ @row-contextmenu="onRowContextmenu"
251
+ />
252
+ <tr v-if="virtual.after.value" aria-hidden="true">
253
+ <td
254
+ :colspan="colspan"
255
+ :style="{ height: `${virtual.after.value}px`, padding: 0, border: 0 }"
256
+ />
257
+ </tr>
258
+ </template>
259
+ </tbody>
260
+ <tfoot v-if="hasSummary">
261
+ <Slot :render="slots.summary" :context="ctl.state.value">
262
+ <tr>
263
+ <TableCell
264
+ v-for="(control, index) in controls"
265
+ :key="control"
266
+ :style="layout.controlStyle('start', index)"
267
+ />
268
+ <TableCell
269
+ v-for="column in ctl.visibleColumns.value"
270
+ :key="column.key"
271
+ :style="layout.cellStyle(column)"
272
+ :align="column.align"
273
+ class="font-medium"
274
+ >
275
+ <Slot
276
+ :render="slots[`footer-${column.key}`]"
277
+ :context="{ column, rows: summaryRows }"
278
+ >
279
+ {{
280
+ typeof column.footer === 'function'
281
+ ? column.footer(summaryRows)
282
+ : column.footer === true
283
+ ? aggregate(summaryRows, column, ctl.valueOf)
284
+ : column.footer
285
+ }}
286
+ </Slot>
287
+ </TableCell>
288
+ <TableCell v-if="trailing" :style="layout.controlStyle('end', 0)" />
289
+ </tr>
290
+ </Slot>
291
+ </tfoot>
292
+ </table>
293
+ </ScrollArea>
294
+ <LoadingOverlay :visible="props.loading" :text="t.table.loading" class="rounded-lg">
295
+ <template v-if="slots.loading" #default><slot name="loading" /></template>
296
+ </LoadingOverlay>
297
+ </div>
298
+ <slot name="footer" v-bind="ctl.state.value">
299
+ <Pagination
300
+ v-if="props.pagination && ctl.knownTotal.value"
301
+ :model-value="ctl.page.value"
302
+ :page-size="ctl.pageSize.value"
303
+ :total="ctl.total.value"
304
+ :item-count="ctl.api.getRows().length"
305
+ :pending="props.loading"
306
+ :disabled="props.disabled"
307
+ @update:model-value="models.page.value = $event"
308
+ @update:page-size="models.pageSize.value = $event"
309
+ />
310
+ <nav
311
+ v-else-if="props.pagination"
312
+ class="flex items-center gap-2"
313
+ :aria-label="t.pagination.navLabel"
314
+ >
315
+ <Button
316
+ variant="ghost"
317
+ size="sm"
318
+ icon-only
319
+ :disabled="ctl.blocked.value || ctl.page.value <= 1"
320
+ :aria-label="t.pagination.prev"
321
+ @click="models.page.value--"
322
+ >
323
+ <ChevronLeft class="rtl:rotate-180" />
324
+ </Button>
325
+ <span class="text-muted text-sm tabular-nums">{{ ctl.page.value }}</span>
326
+ <Button
327
+ variant="ghost"
328
+ size="sm"
329
+ icon-only
330
+ :disabled="ctl.blocked.value || !ctl.canNext.value"
331
+ :aria-label="t.pagination.next"
332
+ @click="models.page.value++"
333
+ >
334
+ <ChevronRight class="rtl:rotate-180" />
335
+ </Button>
336
+ </nav>
337
+ </slot>
338
+ <DataTableDragPreview :drag="drag" :layout="layout" />
339
+ </div>
340
+ </template>
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+ import Spinner from '../spinner/Spinner.vue'
3
+ defineProps<{ disabled?: boolean; loading?: boolean }>()
4
+ </script>
5
+ <template>
6
+ <button
7
+ type="button"
8
+ class="hn-table-action hn-focus-ring"
9
+ :disabled="disabled || loading"
10
+ :aria-busy="loading || undefined"
11
+ >
12
+ <Spinner v-if="loading" size="sm" />
13
+ <slot v-else />
14
+ </button>
15
+ </template>
@@ -0,0 +1,72 @@
1
+ <script setup lang="ts" generic="T extends object">
2
+ import { DialogPortal } from 'reka-ui'
3
+ import type { DataTableDrag } from './composables/useTableDrag'
4
+ import type { DataTableLayout } from './composables/useTableColumns'
5
+ defineProps<{ drag: DataTableDrag<T>; layout: DataTableLayout<T> }>()
6
+ </script>
7
+ <template>
8
+ <DialogPortal>
9
+ <div
10
+ v-if="layout.guide.value"
11
+ class="hn-table-drop-line"
12
+ aria-hidden="true"
13
+ data-hn-resize-guide
14
+ :style="{
15
+ left: `${layout.guide.value.x}px`,
16
+ top: `${layout.guide.value.y}px`,
17
+ height: `${layout.guide.value.height}px`,
18
+ width: '2px',
19
+ }"
20
+ />
21
+ <div
22
+ v-if="layout.guide.value"
23
+ class="hn-table-resize-label"
24
+ data-hn-resize-label
25
+ aria-hidden="true"
26
+ :style="{
27
+ left: `${layout.guide.value.labelX}px`,
28
+ top: `${Math.max(4, layout.guide.value.y - 12 - layout.guide.value.columns.length * 18)}px`,
29
+ }"
30
+ >
31
+ <div v-for="column in layout.guide.value.columns" :key="column.key">
32
+ <span>{{ column.label }}</span>
33
+ <span>{{ column.width }}px</span>
34
+ </div>
35
+ </div>
36
+ <template v-if="drag.dragging.value">
37
+ <div
38
+ v-if="drag.dragging.value.marker"
39
+ class="hn-table-drop-line"
40
+ aria-hidden="true"
41
+ data-hn-drop-line
42
+ :style="{
43
+ left: `${drag.dragging.value.marker.x}px`,
44
+ top: `${drag.dragging.value.marker.y}px`,
45
+ width: `${drag.dragging.value.marker.width}px`,
46
+ height: `${drag.dragging.value.marker.height}px`,
47
+ }"
48
+ />
49
+ <div
50
+ class="hn-table-drag-preview"
51
+ aria-hidden="true"
52
+ data-hn-drag-preview
53
+ :style="{
54
+ ...drag.dragging.value.appearance,
55
+ left: `${drag.dragging.value.x - drag.dragging.value.offsetX}px`,
56
+ top: `${drag.dragging.value.y - drag.dragging.value.offsetY}px`,
57
+ width:
58
+ drag.dragging.value.kind === 'column' ? `${drag.dragging.value.width}px` : undefined,
59
+ }"
60
+ >
61
+ <div class="hn-table-drag-label">{{ drag.dragging.value.label }}</div>
62
+ <div
63
+ v-for="(sample, index) in drag.dragging.value.samples"
64
+ :key="index"
65
+ class="hn-table-drag-cell"
66
+ >
67
+ {{ sample }}
68
+ </div>
69
+ </div>
70
+ </template>
71
+ </DialogPortal>
72
+ </template>
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts" generic="T extends object">
2
+ import { useId } from 'vue'
3
+ import { Check, X } from '@lucide/vue'
4
+ import { useUiLocale } from '../../locale'
5
+ import Input from '../input/Input.vue'
6
+ import InputGroup from '../input-group/InputGroup.vue'
7
+ import DataTableAction from './DataTableAction.vue'
8
+ import Slot from './Slot'
9
+ import type { DataTableEditorContext, DataTableSlots } from './types'
10
+ defineProps<{ context: DataTableEditorContext<T>; slots: DataTableSlots<T>; cell: boolean }>()
11
+ const t = useUiLocale()
12
+ const errorId = useId()
13
+ </script>
14
+ <template>
15
+ <div class="hn-table-editor" @click.stop @keydown.stop>
16
+ <div class="hn-table-editor-control">
17
+ <InputGroup
18
+ variant="bare"
19
+ size="sm"
20
+ :disabled="context.pending"
21
+ :invalid="!!context.error"
22
+ class="hn-table-editor-field"
23
+ role="group"
24
+ :aria-label="`${t.table.edit}: ${context.column.label}`"
25
+ :aria-describedby="context.error ? errorId : undefined"
26
+ >
27
+ <Slot :render="slots[`editor-${context.column.key}`] ?? slots.editor" :context="context">
28
+ <Input
29
+ :model-value="String(context.value ?? '')"
30
+ :aria-label="`${t.table.edit}: ${context.column.label}`"
31
+ :aria-describedby="context.error ? errorId : undefined"
32
+ @update:model-value="context.updateValue"
33
+ @keydown.enter.prevent="context.commit"
34
+ @keydown.esc.prevent="context.cancel"
35
+ />
36
+ </Slot>
37
+ </InputGroup>
38
+ <template v-if="cell">
39
+ <DataTableAction
40
+ :loading="context.pending"
41
+ :aria-label="t.table.save"
42
+ @click="context.commit"
43
+ >
44
+ <Check />
45
+ </DataTableAction>
46
+ <DataTableAction
47
+ :disabled="context.pending"
48
+ :aria-label="t.table.cancel"
49
+ @click="context.cancel"
50
+ >
51
+ <X />
52
+ </DataTableAction>
53
+ </template>
54
+ </div>
55
+ <p v-if="context.error" :id="errorId" role="alert" class="hn-table-field-error">
56
+ {{ context.error }}
57
+ </p>
58
+ </div>
59
+ </template>
@@ -0,0 +1,181 @@
1
+ <script setup lang="ts" generic="T extends object">
2
+ import { ArrowDown, ArrowUp, ChevronsUpDown } from '@lucide/vue'
3
+ import { cn } from '../../lib/cn'
4
+ import { useUiLocale } from '../../locale'
5
+ import Checkbox from '../checkbox/Checkbox.vue'
6
+ import { vTooltip } from '../tooltip/directive'
7
+ import TableHead from '../table/TableHead.vue'
8
+ import Slot from './Slot'
9
+ import type { DataTableProps, DataTableSlots } from './types'
10
+ import type { DataTableController, DataTableModels } from './composables/useDataTable'
11
+ import type { DataTableLayout } from './composables/useTableColumns'
12
+ import type { DataTableDrag } from './composables/useTableDrag'
13
+ defineProps<{
14
+ config: DataTableProps<T>
15
+ ctl: DataTableController<T>
16
+ models: DataTableModels
17
+ layout: DataTableLayout<T>
18
+ drag: DataTableDrag<T>
19
+ controls: string[]
20
+ trailing: number
21
+ slots: DataTableSlots<T>
22
+ }>()
23
+ const t = useUiLocale()
24
+ </script>
25
+ <template>
26
+ <thead>
27
+ <tr v-for="(headers, level) in layout.headerRows.value" :key="level">
28
+ <template v-if="level === 0">
29
+ <TableHead
30
+ v-for="(control, index) in controls"
31
+ :key="control"
32
+ :rowspan="layout.headerRows.value.length"
33
+ :style="{ ...layout.controlStyle('start', index, true), top: 0 }"
34
+ >
35
+ <Checkbox
36
+ v-if="control === 'select' && config.selectionMode !== 'single'"
37
+ class="mx-auto"
38
+ :model-value="ctl.pageSelection.value"
39
+ :disabled="ctl.selectionDisabled.value"
40
+ :aria-label="
41
+ config.selectAll === 'filtered' ? t.table.selectFiltered : t.table.selectPage
42
+ "
43
+ @update:model-value="ctl.togglePage"
44
+ />
45
+ <span v-else class="sr-only">
46
+ {{
47
+ control === 'select'
48
+ ? t.table.selectRow
49
+ : control === 'drag'
50
+ ? t.table.moveRow
51
+ : t.table.expand
52
+ }}
53
+ </span>
54
+ </TableHead>
55
+ </template>
56
+ <TableHead
57
+ v-for="header in headers"
58
+ :key="header.id"
59
+ :data-hn-column="header.leaf ? header.column.key : undefined"
60
+ :scope="header.leaf ? 'col' : 'colgroup'"
61
+ :colspan="header.colspan"
62
+ :rowspan="header.rowspan"
63
+ :align="header.column.align"
64
+ :aria-sort="header.ariaSort"
65
+ :style="header.style"
66
+ :class="
67
+ cn(
68
+ 'hn-table-header relative',
69
+ header.column.headerClass,
70
+ drag.kind.value === 'column' && drag.key.value === header.column.key && 'opacity-40',
71
+ )
72
+ "
73
+ :data-reorderable="
74
+ header.leaf &&
75
+ config.reorderColumns &&
76
+ header.column.reorderable !== false &&
77
+ !ctl.blocked.value
78
+ ? ''
79
+ : undefined
80
+ "
81
+ :data-align="header.column.align"
82
+ @pointerdown="
83
+ header.leaf && drag.start('column', header.column.key, header.column.label, $event)
84
+ "
85
+ >
86
+ <Slot :render="slots[`header-${header.column.key}`] ?? slots.header" :context="header">
87
+ <component
88
+ :is="
89
+ header.leaf &&
90
+ (header.column.sortable ||
91
+ (config.reorderColumns && header.column.reorderable !== false))
92
+ ? 'button'
93
+ : 'span'
94
+ "
95
+ data-hn-table-heading
96
+ class="hn-table-heading hn-focus-ring"
97
+ :type="
98
+ header.leaf && (header.column.sortable || config.reorderColumns)
99
+ ? 'button'
100
+ : undefined
101
+ "
102
+ :disabled="ctl.blocked.value"
103
+ :data-sorted="header.sorting || undefined"
104
+ :aria-label="
105
+ header.column.sortable ? `${header.column.label}: ${header.nextLabel}` : undefined
106
+ "
107
+ :aria-description="
108
+ config.reorderColumns && header.column.reorderable !== false
109
+ ? `${t.table.moveColumn}: Alt + ← / →`
110
+ : undefined
111
+ "
112
+ :aria-keyshortcuts="
113
+ config.reorderColumns && header.column.reorderable !== false
114
+ ? 'Alt+ArrowLeft Alt+ArrowRight'
115
+ : undefined
116
+ "
117
+ @click="header.leaf && header.column.sortable && header.toggleSort($event.shiftKey)"
118
+ @keydown="
119
+ header.leaf &&
120
+ config.reorderColumns &&
121
+ header.column.reorderable !== false &&
122
+ drag.columnKeydown(header.column.key, $event)
123
+ "
124
+ >
125
+ <span
126
+ class="min-w-0"
127
+ :class="(header.column.truncate || config.resizable) && 'truncate'"
128
+ >
129
+ {{ header.column.label }}
130
+ </span>
131
+ <span
132
+ v-if="header.leaf && header.column.sortable"
133
+ class="hn-table-sort"
134
+ aria-hidden="true"
135
+ >
136
+ <ArrowUp v-if="header.sorting === 'asc'" />
137
+ <ArrowDown v-else-if="header.sorting === 'desc'" />
138
+ <ChevronsUpDown v-else />
139
+ <span
140
+ v-if="config.multiSort && models.sorting.value.length > 1 && header.sortIndex >= 0"
141
+ class="text-xs"
142
+ >
143
+ {{ header.sortIndex + 1 }}
144
+ </span>
145
+ </span>
146
+ </component>
147
+ </Slot>
148
+ <div
149
+ v-if="header.leaf && layout.canResize(header.column) && !ctl.blocked.value"
150
+ v-tooltip="
151
+ !layout.resizing.value && layout.handleVisible(header.column)
152
+ ? `${t.table.resizeColumn}: ${layout.resizeLabel(header.column)}`
153
+ : false
154
+ "
155
+ role="separator"
156
+ aria-orientation="vertical"
157
+ :tabindex="layout.handleVisible(header.column) ? 0 : -1"
158
+ :style="layout.handleStyle(header.column)"
159
+ :aria-label="`${t.table.resizeColumn}: ${layout.resizeLabel(header.column)}`"
160
+ :aria-valuenow="Math.round(layout.width(header.column))"
161
+ :aria-valuetext="layout.resizeValueText(header.column)"
162
+ :aria-valuemin="Math.round(layout.bounds(header.column).min)"
163
+ :aria-valuemax="Math.round(layout.bounds(header.column).max)"
164
+ :data-side="layout.boundary(header.column)?.side"
165
+ class="hn-table-resize hn-focus-ring"
166
+ :data-resizing="layout.resizing.value === header.column.key ? '' : undefined"
167
+ @click.stop
168
+ @pointerdown="layout.resize(header.column, $event)"
169
+ @keydown="layout.resizeKey(header.column, $event)"
170
+ />
171
+ </TableHead>
172
+ <TableHead
173
+ v-if="level === 0 && trailing"
174
+ :rowspan="layout.headerRows.value.length"
175
+ :style="{ ...layout.controlStyle('end', 0, true), top: 0 }"
176
+ >
177
+ <span class="sr-only">{{ t.table.edit }}</span>
178
+ </TableHead>
179
+ </tr>
180
+ </thead>
181
+ </template>