@hina-ui/vue 1.7.0 → 1.7.2
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 +3086 -444
- package/dist/index.js.map +1 -1
- package/dist/types/components/anchor/composables/useScrollSpy.d.ts.map +1 -1
- 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-styles.d.ts +4 -0
- package/dist/types/components/data-table/column-sizing-styles.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 +52 -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/table/TableHead.vue.d.ts +2 -2
- package/dist/types/components/table/TableHead.vue.d.ts.map +1 -1
- package/dist/types/components/table/table.variants.d.ts +1 -1
- package/dist/types/components/table/table.variants.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- 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/composables/useScrollSpy.ts +8 -11
- package/src/components/combobox/Combobox.vue +11 -1
- package/src/components/data-table/DataTable.vue +341 -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-styles.ts +40 -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 +251 -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/table/TableHead.vue +1 -1
- package/src/components/table/table.variants.ts +1 -2
- package/src/index.ts +25 -0
- 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 +330 -0
- package/src/styles/tokens.css +1 -0
- package/src/styles/typography.css +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hina-ui/vue",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"description": "An elegant, restrained Vue 3 component library built on Reka UI and Tailwind CSS v4.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vue",
|
|
@@ -41,9 +41,18 @@
|
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "vite build && vue-tsc -p tsconfig.build.json",
|
|
46
|
+
"typecheck": "vue-tsc --noEmit -p tsconfig.json",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"test:browser": "vitest run -c vitest.browser.config.ts"
|
|
50
|
+
},
|
|
44
51
|
"dependencies": {
|
|
45
52
|
"@internationalized/date": "^3.12.4",
|
|
46
53
|
"@lucide/vue": "^1.37.0",
|
|
54
|
+
"@tanstack/vue-table": "^9.2.4",
|
|
55
|
+
"@tanstack/vue-virtual": "^3.13.36",
|
|
47
56
|
"@vueuse/core": "^12.8.2",
|
|
48
57
|
"clsx": "^2.1.1",
|
|
49
58
|
"motion-v": "^2.2.1",
|
|
@@ -51,7 +60,8 @@
|
|
|
51
60
|
"reka-ui": "^2.9.10",
|
|
52
61
|
"shiki": "4.0.2",
|
|
53
62
|
"tailwind-merge": "^3.6.0",
|
|
54
|
-
"tailwind-variants": "^3.3.1"
|
|
63
|
+
"tailwind-variants": "^3.3.1",
|
|
64
|
+
"vue-component-type-helpers": "^3.3.11"
|
|
55
65
|
},
|
|
56
66
|
"peerDependencies": {
|
|
57
67
|
"vue": "^3.5.0"
|
|
@@ -59,7 +69,6 @@
|
|
|
59
69
|
"devDependencies": {
|
|
60
70
|
"@floating-ui/dom": "1.7.6",
|
|
61
71
|
"@tailwindcss/vite": "^4.3.0",
|
|
62
|
-
"@tanstack/vue-virtual": "^3.13.36",
|
|
63
72
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
64
73
|
"@vitest/browser": "^4.1.11",
|
|
65
74
|
"@vitest/browser-playwright": "^4.1.11",
|
|
@@ -74,12 +83,5 @@
|
|
|
74
83
|
"vue": "^3.5.33",
|
|
75
84
|
"vue-tsc": "^3.3.1"
|
|
76
85
|
},
|
|
77
|
-
"license": "MIT"
|
|
78
|
-
|
|
79
|
-
"build": "vite build && vue-tsc -p tsconfig.build.json",
|
|
80
|
-
"typecheck": "vue-tsc --noEmit -p tsconfig.json",
|
|
81
|
-
"test": "vitest run",
|
|
82
|
-
"test:watch": "vitest",
|
|
83
|
-
"test:browser": "vitest run -c vitest.browser.config.ts"
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
+
"license": "MIT"
|
|
87
|
+
}
|
|
@@ -11,7 +11,7 @@ export interface SpyEntry {
|
|
|
11
11
|
export function useScrollSpy(entries: () => SpyEntry[]) {
|
|
12
12
|
const visible = shallowRef<ReadonlySet<string>>(new Set())
|
|
13
13
|
const targets = shallowRef<Array<HTMLElement | null>>([])
|
|
14
|
-
const
|
|
14
|
+
const intersecting = new Set<string>()
|
|
15
15
|
|
|
16
16
|
const covered = computed(() => entries().filter(entry => visible.value.has(entry.id)))
|
|
17
17
|
const current = computed(() => covered.value[0]?.id)
|
|
@@ -23,18 +23,15 @@ export function useScrollSpy(entries: () => SpyEntry[]) {
|
|
|
23
23
|
useIntersectionObserver(
|
|
24
24
|
targets,
|
|
25
25
|
observed => {
|
|
26
|
-
const next = new Set(visible.value)
|
|
27
26
|
for (const entry of observed) {
|
|
28
|
-
if (entry.isIntersecting)
|
|
29
|
-
|
|
30
|
-
next.add(entry.target.id)
|
|
31
|
-
} else if (seen.has(entry.target.id)) {
|
|
32
|
-
next.delete(entry.target.id)
|
|
33
|
-
}
|
|
27
|
+
if (entry.isIntersecting && entry.intersectionRatio > 0) intersecting.add(entry.target.id)
|
|
28
|
+
else intersecting.delete(entry.target.id)
|
|
34
29
|
}
|
|
35
|
-
|
|
30
|
+
visible.value = intersecting.size
|
|
31
|
+
? new Set(intersecting)
|
|
32
|
+
: new Set(current.value ? [current.value] : [])
|
|
36
33
|
},
|
|
37
|
-
{ rootMargin: '0px 0px -15% 0px' },
|
|
34
|
+
{ rootMargin: '0px 0px -15% 0px', threshold: [0, Number.EPSILON] },
|
|
38
35
|
)
|
|
39
36
|
|
|
40
37
|
function targetOf(id: string) {
|
|
@@ -42,7 +39,7 @@ export function useScrollSpy(entries: () => SpyEntry[]) {
|
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
function locate() {
|
|
45
|
-
|
|
42
|
+
intersecting.clear()
|
|
46
43
|
targets.value = entries().map(({ id }) => targetOf(id))
|
|
47
44
|
}
|
|
48
45
|
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { useUiLocale } from '../../locale'
|
|
6
6
|
import { X } from '@lucide/vue'
|
|
7
7
|
import InputAction from '../input/InputAction.vue'
|
|
8
|
+
import IconSlot from '../button/IconSlot.vue'
|
|
9
|
+
import { buttonIconBox } from '../button/button.variants'
|
|
8
10
|
import DisclosureIcon from '../disclosure-icon/DisclosureIcon.vue'
|
|
9
11
|
import { useFieldControl } from '../form-field/context'
|
|
10
12
|
import { injectInputGroup } from '../input-group/context'
|
|
@@ -28,6 +30,7 @@
|
|
|
28
30
|
selectedOption?: T | null
|
|
29
31
|
placeholder?: string
|
|
30
32
|
ignoreFilter?: boolean
|
|
33
|
+
loading?: boolean
|
|
31
34
|
clearable?: boolean
|
|
32
35
|
variant?: InputVariants['variant']
|
|
33
36
|
size?: InputVariants['size']
|
|
@@ -84,6 +87,7 @@
|
|
|
84
87
|
data-hn-combobox
|
|
85
88
|
:data-invalid="invalid ? '' : undefined"
|
|
86
89
|
:data-disabled="disabled ? '' : undefined"
|
|
90
|
+
:aria-busy="props.loading || undefined"
|
|
87
91
|
:class="
|
|
88
92
|
cn(
|
|
89
93
|
group ? inputEmbedded() : inputHost({ variant: props.variant, size: props.size }),
|
|
@@ -124,7 +128,13 @@
|
|
|
124
128
|
:class="cn(inputAdornment(), inputIndicator())"
|
|
125
129
|
@mousedown.prevent
|
|
126
130
|
>
|
|
127
|
-
<
|
|
131
|
+
<IconSlot
|
|
132
|
+
:box-class="buttonIconBox({ size })"
|
|
133
|
+
:swapped="!!props.loading"
|
|
134
|
+
spinner-size="sm"
|
|
135
|
+
>
|
|
136
|
+
<DisclosureIcon :open="open" />
|
|
137
|
+
</IconSlot>
|
|
128
138
|
</ComboboxTrigger>
|
|
129
139
|
</div>
|
|
130
140
|
</ComboboxAnchor>
|
|
@@ -0,0 +1,341 @@
|
|
|
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
|
+
layout.constrained.value && '@container',
|
|
172
|
+
(props.fill || props.height) && 'h-full',
|
|
173
|
+
props.stickyHeader && '[&_thead_th]:sticky [&_thead_th]:z-[3]',
|
|
174
|
+
props.stickyFooter &&
|
|
175
|
+
'[&_tfoot_td]:sticky [&_tfoot_td]:bottom-0 [&_tfoot_td]:z-[2] [&_tfoot_td]:bg-(--hn-table-head-bg)',
|
|
176
|
+
props.tableClass,
|
|
177
|
+
)
|
|
178
|
+
"
|
|
179
|
+
>
|
|
180
|
+
<table
|
|
181
|
+
ref="element"
|
|
182
|
+
class="hn-table"
|
|
183
|
+
:style="layout.tableStyle.value"
|
|
184
|
+
:aria-label="props.label"
|
|
185
|
+
:aria-rowcount="
|
|
186
|
+
props.virtualize
|
|
187
|
+
? virtual.entries.value.length + layout.headerRows.value.length + (hasSummary ? 1 : 0)
|
|
188
|
+
: undefined
|
|
189
|
+
"
|
|
190
|
+
:inert="ctl.blocked.value || undefined"
|
|
191
|
+
>
|
|
192
|
+
<caption v-if="props.caption">
|
|
193
|
+
<span>{{ props.caption }}</span>
|
|
194
|
+
</caption>
|
|
195
|
+
<colgroup>
|
|
196
|
+
<col v-for="control in controls" :key="control" style="width: 48px" />
|
|
197
|
+
<col
|
|
198
|
+
v-for="column in ctl.visibleColumns.value"
|
|
199
|
+
:key="column.key"
|
|
200
|
+
:style="{ width: layout.cellStyle(column).width }"
|
|
201
|
+
/>
|
|
202
|
+
<col v-if="trailing" style="width: 72px" />
|
|
203
|
+
</colgroup>
|
|
204
|
+
<DataTableHead
|
|
205
|
+
:config="props"
|
|
206
|
+
:ctl="ctl"
|
|
207
|
+
:models="models"
|
|
208
|
+
:layout="layout"
|
|
209
|
+
:drag="drag"
|
|
210
|
+
:controls="controls"
|
|
211
|
+
:trailing="trailing"
|
|
212
|
+
:slots="slots"
|
|
213
|
+
/>
|
|
214
|
+
<tbody>
|
|
215
|
+
<tr
|
|
216
|
+
v-if="
|
|
217
|
+
!ctl.rows.value.length || (!ctl.visibleColumns.value.length && !controls.length)
|
|
218
|
+
"
|
|
219
|
+
>
|
|
220
|
+
<TableCell :colspan="colspan">
|
|
221
|
+
<div class="text-muted flex min-h-32 items-center justify-center px-3 py-6 text-sm">
|
|
222
|
+
<template v-if="props.loading">{{ t.table.loading }}</template>
|
|
223
|
+
<template v-else-if="!ctl.visibleColumns.value.length && !controls.length">
|
|
224
|
+
{{ t.table.noColumns }}
|
|
225
|
+
</template>
|
|
226
|
+
<slot v-else name="empty">{{ props.emptyText ?? t.table.empty }}</slot>
|
|
227
|
+
</div>
|
|
228
|
+
</TableCell>
|
|
229
|
+
</tr>
|
|
230
|
+
<template v-else>
|
|
231
|
+
<tr v-if="virtual.before.value" aria-hidden="true">
|
|
232
|
+
<td
|
|
233
|
+
:colspan="colspan"
|
|
234
|
+
:style="{ height: `${virtual.before.value}px`, padding: 0, border: 0 }"
|
|
235
|
+
/>
|
|
236
|
+
</tr>
|
|
237
|
+
<DataTableRow
|
|
238
|
+
v-for="item in virtual.visible.value"
|
|
239
|
+
:key="item.id"
|
|
240
|
+
:item="item"
|
|
241
|
+
:config="props"
|
|
242
|
+
:ctl="ctl"
|
|
243
|
+
:layout="layout"
|
|
244
|
+
:drag="drag"
|
|
245
|
+
:editing="editing"
|
|
246
|
+
:slots="slots"
|
|
247
|
+
:controls="controls"
|
|
248
|
+
:colspan="colspan"
|
|
249
|
+
:name="name"
|
|
250
|
+
:measure="virtual.measure"
|
|
251
|
+
@row-contextmenu="onRowContextmenu"
|
|
252
|
+
/>
|
|
253
|
+
<tr v-if="virtual.after.value" aria-hidden="true">
|
|
254
|
+
<td
|
|
255
|
+
:colspan="colspan"
|
|
256
|
+
:style="{ height: `${virtual.after.value}px`, padding: 0, border: 0 }"
|
|
257
|
+
/>
|
|
258
|
+
</tr>
|
|
259
|
+
</template>
|
|
260
|
+
</tbody>
|
|
261
|
+
<tfoot v-if="hasSummary">
|
|
262
|
+
<Slot :render="slots.summary" :context="ctl.state.value">
|
|
263
|
+
<tr>
|
|
264
|
+
<TableCell
|
|
265
|
+
v-for="(control, index) in controls"
|
|
266
|
+
:key="control"
|
|
267
|
+
:style="layout.controlStyle('start', index)"
|
|
268
|
+
/>
|
|
269
|
+
<TableCell
|
|
270
|
+
v-for="column in ctl.visibleColumns.value"
|
|
271
|
+
:key="column.key"
|
|
272
|
+
:style="layout.cellStyle(column)"
|
|
273
|
+
:align="column.align"
|
|
274
|
+
class="font-medium"
|
|
275
|
+
>
|
|
276
|
+
<Slot
|
|
277
|
+
:render="slots[`footer-${column.key}`]"
|
|
278
|
+
:context="{ column, rows: summaryRows }"
|
|
279
|
+
>
|
|
280
|
+
{{
|
|
281
|
+
typeof column.footer === 'function'
|
|
282
|
+
? column.footer(summaryRows)
|
|
283
|
+
: column.footer === true
|
|
284
|
+
? aggregate(summaryRows, column, ctl.valueOf)
|
|
285
|
+
: column.footer
|
|
286
|
+
}}
|
|
287
|
+
</Slot>
|
|
288
|
+
</TableCell>
|
|
289
|
+
<TableCell v-if="trailing" :style="layout.controlStyle('end', 0)" />
|
|
290
|
+
</tr>
|
|
291
|
+
</Slot>
|
|
292
|
+
</tfoot>
|
|
293
|
+
</table>
|
|
294
|
+
</ScrollArea>
|
|
295
|
+
<LoadingOverlay :visible="props.loading" :text="t.table.loading" class="rounded-lg">
|
|
296
|
+
<template v-if="slots.loading" #default><slot name="loading" /></template>
|
|
297
|
+
</LoadingOverlay>
|
|
298
|
+
</div>
|
|
299
|
+
<slot name="footer" v-bind="ctl.state.value">
|
|
300
|
+
<Pagination
|
|
301
|
+
v-if="props.pagination && ctl.knownTotal.value"
|
|
302
|
+
:model-value="ctl.page.value"
|
|
303
|
+
:page-size="ctl.pageSize.value"
|
|
304
|
+
:total="ctl.total.value"
|
|
305
|
+
:item-count="ctl.api.getRows().length"
|
|
306
|
+
:pending="props.loading"
|
|
307
|
+
:disabled="props.disabled"
|
|
308
|
+
@update:model-value="models.page.value = $event"
|
|
309
|
+
@update:page-size="models.pageSize.value = $event"
|
|
310
|
+
/>
|
|
311
|
+
<nav
|
|
312
|
+
v-else-if="props.pagination"
|
|
313
|
+
class="flex items-center gap-2"
|
|
314
|
+
:aria-label="t.pagination.navLabel"
|
|
315
|
+
>
|
|
316
|
+
<Button
|
|
317
|
+
variant="ghost"
|
|
318
|
+
size="sm"
|
|
319
|
+
icon-only
|
|
320
|
+
:disabled="ctl.blocked.value || ctl.page.value <= 1"
|
|
321
|
+
:aria-label="t.pagination.prev"
|
|
322
|
+
@click="models.page.value--"
|
|
323
|
+
>
|
|
324
|
+
<ChevronLeft class="rtl:rotate-180" />
|
|
325
|
+
</Button>
|
|
326
|
+
<span class="text-muted text-sm tabular-nums">{{ ctl.page.value }}</span>
|
|
327
|
+
<Button
|
|
328
|
+
variant="ghost"
|
|
329
|
+
size="sm"
|
|
330
|
+
icon-only
|
|
331
|
+
:disabled="ctl.blocked.value || !ctl.canNext.value"
|
|
332
|
+
:aria-label="t.pagination.next"
|
|
333
|
+
@click="models.page.value++"
|
|
334
|
+
>
|
|
335
|
+
<ChevronRight class="rtl:rotate-180" />
|
|
336
|
+
</Button>
|
|
337
|
+
</nav>
|
|
338
|
+
</slot>
|
|
339
|
+
<DataTableDragPreview :drag="drag" :layout="layout" />
|
|
340
|
+
</div>
|
|
341
|
+
</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>
|