@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.
- package/dist/index.js +3170 -461
- package/dist/index.js.map +1 -1
- package/dist/types/components/anchor/Anchor.vue.d.ts +26 -10
- package/dist/types/components/anchor/Anchor.vue.d.ts.map +1 -1
- package/dist/types/components/anchor/composables/useAnchor.d.ts +15 -0
- package/dist/types/components/anchor/composables/useAnchor.d.ts.map +1 -0
- package/dist/types/components/anchor/composables/useScrollSpy.d.ts.map +1 -1
- package/dist/types/components/anchor/types.d.ts +9 -0
- package/dist/types/components/anchor/types.d.ts.map +1 -0
- package/dist/types/components/app-shell/AppShell.vue.d.ts.map +1 -1
- package/dist/types/components/app-shell/composables/useSidebarScrollUpdates.d.ts +4 -0
- package/dist/types/components/app-shell/composables/useSidebarScrollUpdates.d.ts.map +1 -0
- package/dist/types/components/combobox/Combobox.vue.d.ts +1 -0
- package/dist/types/components/combobox/Combobox.vue.d.ts.map +1 -1
- package/dist/types/components/data-table/DataTable.vue.d.ts +58 -0
- package/dist/types/components/data-table/DataTable.vue.d.ts.map +1 -0
- package/dist/types/components/data-table/DataTableAction.vue.d.ts +18 -0
- package/dist/types/components/data-table/DataTableAction.vue.d.ts.map +1 -0
- package/dist/types/components/data-table/DataTableDragPreview.vue.d.ts +24 -0
- package/dist/types/components/data-table/DataTableDragPreview.vue.d.ts.map +1 -0
- package/dist/types/components/data-table/DataTableEditor.vue.d.ts +24 -0
- package/dist/types/components/data-table/DataTableEditor.vue.d.ts.map +1 -0
- package/dist/types/components/data-table/DataTableHead.vue.d.ts +32 -0
- package/dist/types/components/data-table/DataTableHead.vue.d.ts.map +1 -0
- package/dist/types/components/data-table/DataTableRow.vue.d.ts +39 -0
- package/dist/types/components/data-table/DataTableRow.vue.d.ts.map +1 -0
- package/dist/types/components/data-table/DataTableSelection.vue.d.ts +15 -0
- package/dist/types/components/data-table/DataTableSelection.vue.d.ts.map +1 -0
- package/dist/types/components/data-table/DataTableText.vue.d.ts +8 -0
- package/dist/types/components/data-table/DataTableText.vue.d.ts.map +1 -0
- package/dist/types/components/data-table/Slot.d.ts +8 -0
- package/dist/types/components/data-table/Slot.d.ts.map +1 -0
- package/dist/types/components/data-table/column-sizing.d.ts +22 -0
- package/dist/types/components/data-table/column-sizing.d.ts.map +1 -0
- package/dist/types/components/data-table/composables/useDataTable.d.ts +80 -0
- package/dist/types/components/data-table/composables/useDataTable.d.ts.map +1 -0
- package/dist/types/components/data-table/composables/useTableColumns.d.ts +51 -0
- package/dist/types/components/data-table/composables/useTableColumns.d.ts.map +1 -0
- package/dist/types/components/data-table/composables/useTableDrag.d.ts +53 -0
- package/dist/types/components/data-table/composables/useTableDrag.d.ts.map +1 -0
- package/dist/types/components/data-table/composables/useTableEditing.d.ts +27 -0
- package/dist/types/components/data-table/composables/useTableEditing.d.ts.map +1 -0
- package/dist/types/components/data-table/composables/useTableExport.d.ts +3 -0
- package/dist/types/components/data-table/composables/useTableExport.d.ts.map +1 -0
- package/dist/types/components/data-table/composables/useTableResize.d.ts +42 -0
- package/dist/types/components/data-table/composables/useTableResize.d.ts.map +1 -0
- package/dist/types/components/data-table/composables/useTableResizeHandles.d.ts +8 -0
- package/dist/types/components/data-table/composables/useTableResizeHandles.d.ts.map +1 -0
- package/dist/types/components/data-table/composables/useTableText.d.ts +6 -0
- package/dist/types/components/data-table/composables/useTableText.d.ts.map +1 -0
- package/dist/types/components/data-table/composables/useTableVirtual.d.ts +55 -0
- package/dist/types/components/data-table/composables/useTableVirtual.d.ts.map +1 -0
- package/dist/types/components/data-table/features.d.ts +16 -0
- package/dist/types/components/data-table/features.d.ts.map +1 -0
- package/dist/types/components/data-table/types.d.ts +225 -0
- package/dist/types/components/data-table/types.d.ts.map +1 -0
- package/dist/types/components/data-table/utils.d.ts +10 -0
- package/dist/types/components/data-table/utils.d.ts.map +1 -0
- package/dist/types/components/form/composables/useForm.d.ts.map +1 -1
- package/dist/types/components/sidebar/DrawerScope.d.ts +13 -2
- package/dist/types/components/sidebar/DrawerScope.d.ts.map +1 -1
- package/dist/types/components/sidebar/Sidebar.vue.d.ts +4 -1
- package/dist/types/components/sidebar/Sidebar.vue.d.ts.map +1 -1
- package/dist/types/components/sidebar/context.d.ts +1 -0
- package/dist/types/components/sidebar/context.d.ts.map +1 -1
- package/dist/types/components/sidebar/sidebar.variants.d.ts +3 -3
- package/dist/types/components/table/TableHead.vue.d.ts +2 -2
- package/dist/types/components/table/TableHead.vue.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/locale/en-US.d.ts.map +1 -1
- package/dist/types/locale/types.d.ts +12 -0
- package/dist/types/locale/types.d.ts.map +1 -1
- package/dist/types/locale/zh-CN.d.ts.map +1 -1
- package/package.json +14 -12
- package/src/components/anchor/Anchor.vue +15 -19
- package/src/components/anchor/composables/useAnchor.ts +19 -0
- package/src/components/anchor/composables/useScrollSpy.ts +6 -1
- package/src/components/anchor/types.ts +8 -0
- package/src/components/app-shell/AppShell.vue +5 -2
- package/src/components/app-shell/composables/useSidebarScrollUpdates.ts +52 -0
- package/src/components/combobox/Combobox.vue +11 -1
- package/src/components/data-table/DataTable.vue +340 -0
- package/src/components/data-table/DataTableAction.vue +15 -0
- package/src/components/data-table/DataTableDragPreview.vue +72 -0
- package/src/components/data-table/DataTableEditor.vue +59 -0
- package/src/components/data-table/DataTableHead.vue +181 -0
- package/src/components/data-table/DataTableRow.vue +255 -0
- package/src/components/data-table/DataTableSelection.vue +51 -0
- package/src/components/data-table/DataTableText.vue +20 -0
- package/src/components/data-table/Slot.ts +12 -0
- package/src/components/data-table/column-sizing.ts +101 -0
- package/src/components/data-table/composables/useDataTable.ts +425 -0
- package/src/components/data-table/composables/useTableColumns.ts +253 -0
- package/src/components/data-table/composables/useTableDrag.ts +345 -0
- package/src/components/data-table/composables/useTableEditing.ts +184 -0
- package/src/components/data-table/composables/useTableExport.ts +44 -0
- package/src/components/data-table/composables/useTableResize.ts +292 -0
- package/src/components/data-table/composables/useTableResizeHandles.ts +72 -0
- package/src/components/data-table/composables/useTableText.ts +13 -0
- package/src/components/data-table/composables/useTableVirtual.ts +109 -0
- package/src/components/data-table/features.ts +32 -0
- package/src/components/data-table/types.ts +218 -0
- package/src/components/data-table/utils.ts +75 -0
- package/src/components/form/composables/useForm.ts +44 -8
- package/src/components/sidebar/DrawerScope.ts +12 -4
- package/src/components/sidebar/Sidebar.vue +27 -7
- package/src/components/sidebar/context.ts +1 -0
- package/src/components/sidebar/sidebar.variants.ts +1 -1
- package/src/components/table/TableHead.vue +1 -1
- package/src/index.ts +26 -1
- package/src/locale/en-US.ts +13 -0
- package/src/locale/types.ts +13 -0
- package/src/locale/zh-CN.ts +13 -0
- package/src/styles/data-table.css +331 -0
- package/src/styles/tokens.css +1 -0
- 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>
|