@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,255 @@
1
+ <script setup lang="ts" generic="T extends object">
2
+ import { computed, shallowRef, watch } from 'vue'
3
+ import { Check, ChevronRight, CircleAlert, GripVertical, Pencil, X } from '@lucide/vue'
4
+ import { cn } from '../../lib/cn'
5
+ import { useUiLocale } from '../../locale'
6
+ import DataTableAction from './DataTableAction.vue'
7
+ import TableCell from '../table/TableCell.vue'
8
+ import DataTableSelection from './DataTableSelection.vue'
9
+ import DataTableEditor from './DataTableEditor.vue'
10
+ import DataTableText from './DataTableText.vue'
11
+ import Slot from './Slot'
12
+ import { cssSize, isRowAction } from './utils'
13
+ import type { DataTableProps, DataTableSlots } from './types'
14
+ import type { DataTableController } from './composables/useDataTable'
15
+ import type { DataTableLayout } from './composables/useTableColumns'
16
+ import type { DataTableDrag } from './composables/useTableDrag'
17
+ import type { DataTableEditing } from './composables/useTableEditing'
18
+ import type { DataTableRenderEntry } from './composables/useTableVirtual'
19
+ const props = defineProps<{
20
+ item: DataTableRenderEntry<T>
21
+ config: DataTableProps<T>
22
+ ctl: DataTableController<T>
23
+ layout: DataTableLayout<T>
24
+ drag: DataTableDrag<T>
25
+ editing: DataTableEditing<T>
26
+ slots: DataTableSlots<T>
27
+ controls: string[]
28
+ colspan: number
29
+ name: string
30
+ measure: (element: unknown) => void
31
+ }>()
32
+ const emit = defineEmits<{ rowContextmenu: [row: T, event: MouseEvent] }>()
33
+ const element = shallowRef<HTMLTableRowElement>()
34
+ const entry = computed(() => props.item.entry)
35
+ const t = useUiLocale()
36
+ watch(element, value => props.measure(value), { flush: 'post' })
37
+ </script>
38
+ <template>
39
+ <tr
40
+ ref="element"
41
+ :data-index="item.index"
42
+ :data-editing="
43
+ !item.detail && !entry.group && editing.isEditing(entry.key) ? config.editMode : undefined
44
+ "
45
+ :data-edit-error="item.error ? '' : undefined"
46
+ :data-hn-row="item.detail ? undefined : entry.id"
47
+ :aria-rowindex="config.virtualize ? item.index + layout.headerRows.value.length + 1 : undefined"
48
+ :data-state="entry.selected ? 'selected' : undefined"
49
+ :data-hn-state-group="!item.detail && config.hover ? '' : undefined"
50
+ :tabindex="
51
+ !item.detail && !entry.group && config.rowClickable && !ctl.blocked.value ? 0 : undefined
52
+ "
53
+ :class="
54
+ cn(
55
+ !item.detail && (config.hover || config.selectable) && '[&>td]:hn-state-layer',
56
+ !item.detail && !entry.group && config.rowClickable && 'hn-focus-ring cursor-pointer',
57
+ !entry.group && config.rowClass?.(entry.row),
58
+ drag.key.value === entry.id && 'opacity-50',
59
+ )
60
+ "
61
+ @contextmenu="!item.detail && !entry.group && emit('rowContextmenu', entry.row, $event)"
62
+ @click="!item.detail && !entry.group && ctl.activate(entry.row, $event)"
63
+ @keydown.enter="!item.detail && !entry.group && ctl.activate(entry.row, $event)"
64
+ @keydown.space="!item.detail && !entry.group && ctl.activate(entry.row, $event)"
65
+ >
66
+ <TableCell
67
+ :data-state="!item.detail && entry.selected ? 'selected' : undefined"
68
+ v-if="item.detail"
69
+ :colspan="colspan"
70
+ >
71
+ <p v-if="item.error" :id="editing.errorId" role="alert" class="hn-table-edit-message">
72
+ <CircleAlert aria-hidden="true" />
73
+ {{ item.error }}
74
+ </p>
75
+ <div v-else class="p-3"><Slot :render="slots.expansion" :context="entry" /></div>
76
+ </TableCell>
77
+ <template v-else-if="entry.group">
78
+ <TableCell
79
+ :data-state="!item.detail && entry.selected ? 'selected' : undefined"
80
+ v-if="slots.group"
81
+ :colspan="colspan"
82
+ >
83
+ <Slot :render="slots.group" :context="entry.group" />
84
+ </TableCell>
85
+ <template v-else>
86
+ <TableCell
87
+ :data-state="!item.detail && entry.selected ? 'selected' : undefined"
88
+ v-for="(control, index) in controls"
89
+ :key="control"
90
+ :style="layout.controlStyle('start', index)"
91
+ />
92
+ <TableCell
93
+ :data-state="!item.detail && entry.selected ? 'selected' : undefined"
94
+ v-for="(column, index) in ctl.visibleColumns.value"
95
+ :key="column.key"
96
+ :align="column.align"
97
+ :style="layout.cellStyle(column)"
98
+ >
99
+ <button
100
+ type="button"
101
+ class="hn-table-group hn-focus-ring"
102
+ v-if="index === 0"
103
+ :disabled="ctl.blocked.value"
104
+ :aria-expanded="entry.group.expanded"
105
+ :style="{ marginInlineStart: `${entry.depth * 16}px` }"
106
+ @click="entry.group.toggleExpanded()"
107
+ >
108
+ <ChevronRight
109
+ aria-hidden="true"
110
+ class="hn-transition-transform rtl:rotate-180"
111
+ :class="entry.group.expanded && 'rotate-90 rtl:rotate-90'"
112
+ />
113
+ {{ entry.group.column.label }}: {{ entry.group.value }}
114
+ <span class="text-muted">({{ entry.group.rows.length }})</span>
115
+ </button>
116
+ <template v-else>{{ entry.group.aggregate(column.key) ?? '—' }}</template>
117
+ </TableCell>
118
+ <TableCell
119
+ :data-state="!item.detail && entry.selected ? 'selected' : undefined"
120
+ v-if="config.editMode === 'row'"
121
+ :style="layout.controlStyle('end', 0)"
122
+ />
123
+ </template>
124
+ </template>
125
+ <template v-else>
126
+ <TableCell
127
+ :data-state="!item.detail && entry.selected ? 'selected' : undefined"
128
+ v-for="(control, index) in controls"
129
+ :key="control"
130
+ :style="layout.controlStyle('start', index)"
131
+ >
132
+ <DataTableAction
133
+ v-if="control === 'drag'"
134
+ data-hn-row-drag
135
+ class="touch-none cursor-grab"
136
+ :disabled="!drag.canMove(entry.row)"
137
+ :aria-label="`${t.table.moveRow}: ${entry.label}`"
138
+ @pointerdown="drag.start('row', entry.id, entry.label, $event)"
139
+ @keydown="drag.rowKeydown(entry.key, $event)"
140
+ >
141
+ <GripVertical />
142
+ </DataTableAction>
143
+ <DataTableSelection
144
+ v-else-if="control === 'select'"
145
+ :single="config.selectionMode === 'single'"
146
+ :checked="entry.indeterminate ? 'indeterminate' : entry.selected"
147
+ :disabled="ctl.blocked.value || !entry.selectable"
148
+ :label="`${t.table.selectRow}: ${entry.label}`"
149
+ :name="name"
150
+ @change="entry.toggleSelected"
151
+ />
152
+ <DataTableAction
153
+ v-else-if="control === 'expand' && entry.expandable"
154
+ :disabled="ctl.blocked.value"
155
+ :aria-label="`${entry.expanded ? t.table.collapse : t.table.expand}: ${entry.label}`"
156
+ :aria-expanded="entry.expanded"
157
+ @click="entry.toggleExpanded()"
158
+ >
159
+ <ChevronRight
160
+ aria-hidden="true"
161
+ class="hn-transition-transform rtl:rotate-180"
162
+ :class="entry.expanded && 'rotate-90 rtl:rotate-90'"
163
+ />
164
+ </DataTableAction>
165
+ </TableCell>
166
+ <TableCell
167
+ :data-state="!item.detail && entry.selected ? 'selected' : undefined"
168
+ v-for="(column, index) in ctl.visibleColumns.value"
169
+ :key="column.key"
170
+ :data-hn-cell="column.key"
171
+ :data-editing="
172
+ editing.isEditing(entry.key, column.key) && editing.canEdit(entry.row, column)
173
+ ? config.editMode
174
+ : undefined
175
+ "
176
+ :align="column.align"
177
+ :style="layout.cellStyle(column)"
178
+ :tabindex="config.editMode === 'cell' && editing.canEdit(entry.row, column) ? 0 : undefined"
179
+ :class="
180
+ cn(
181
+ typeof column.cellClass === 'function' ? column.cellClass(entry.row) : column.cellClass,
182
+ config.editMode === 'cell' && editing.canEdit(entry.row, column) && 'hn-focus-ring',
183
+ )
184
+ "
185
+ @dblclick="config.editMode === 'cell' && isRowAction($event) && entry.startEdit(column.key)"
186
+ @keydown.enter="
187
+ config.editMode === 'cell' && isRowAction($event) && entry.startEdit(column.key)
188
+ "
189
+ >
190
+ <div
191
+ :class="column.truncate && 'min-w-0 overflow-hidden'"
192
+ :style="{
193
+ maxWidth: cssSize(column.maxWidth),
194
+ paddingInlineStart:
195
+ index === 0 && config.getChildren ? `${entry.depth * 16}px` : undefined,
196
+ }"
197
+ >
198
+ <DataTableEditor
199
+ v-if="editing.isEditing(entry.key, column.key) && editing.canEdit(entry.row, column)"
200
+ :context="editing.context(entry, column)"
201
+ :slots="slots"
202
+ :cell="config.editMode === 'cell'"
203
+ />
204
+ <Slot
205
+ v-else
206
+ :render="slots[`cell-${column.key}`] ?? slots.cell"
207
+ :context="{ ...entry, column, value: ctl.valueOf(entry.row, column) }"
208
+ >
209
+ <DataTableText
210
+ :value="ctl.displayValue(entry.row, column)"
211
+ :truncate="column.truncate"
212
+ />
213
+ </Slot>
214
+ </div>
215
+ </TableCell>
216
+ <TableCell
217
+ :data-state="!item.detail && entry.selected ? 'selected' : undefined"
218
+ v-if="config.editMode === 'row'"
219
+ class="hn-table-edit-actions"
220
+ :style="layout.controlStyle('end', 0)"
221
+ >
222
+ <div v-if="editing.isEditing(entry.key)" class="hn-table-editor-control justify-center">
223
+ <DataTableAction
224
+ :loading="editing.pending.value"
225
+ :aria-describedby="editing.rowError(entry.key) ? editing.errorId : undefined"
226
+ :aria-label="t.table.save"
227
+ @click="editing.commit"
228
+ >
229
+ <Check />
230
+ </DataTableAction>
231
+ <DataTableAction
232
+ :disabled="editing.pending.value"
233
+ :aria-label="t.table.cancel"
234
+ @click="editing.cancel"
235
+ >
236
+ <X />
237
+ </DataTableAction>
238
+ </div>
239
+ <DataTableAction
240
+ v-else
241
+ :disabled="
242
+ ctl.blocked.value ||
243
+ !ctl.leaves.value.some(column => editing.canEdit(entry.row, column))
244
+ "
245
+ class="hn-table-edit-trigger"
246
+ data-hn-edit-trigger
247
+ :aria-label="`${t.table.edit}: ${entry.label}`"
248
+ @click="entry.startEdit()"
249
+ >
250
+ <Pencil />
251
+ </DataTableAction>
252
+ </TableCell>
253
+ </template>
254
+ </tr>
255
+ </template>
@@ -0,0 +1,51 @@
1
+ <script setup lang="ts">
2
+ import Checkbox from '../checkbox/Checkbox.vue'
3
+ import { checkboxBox } from '../checkbox/checkbox.variants'
4
+ import { radioDot } from '../radio-group/radio-group.variants'
5
+ defineProps<{
6
+ single?: boolean
7
+ checked: boolean | 'indeterminate'
8
+ disabled?: boolean
9
+ label: string
10
+ name: string
11
+ }>()
12
+ defineEmits<{ change: [value: boolean] }>()
13
+ </script>
14
+ <template>
15
+ <label v-if="single" class="relative inline-grid" :class="disabled && 'opacity-50'">
16
+ <input
17
+ type="radio"
18
+ :name="name"
19
+ :checked="checked === true"
20
+ :disabled="disabled"
21
+ :aria-label="label"
22
+ class="peer absolute inset-0 z-10 size-full cursor-pointer opacity-0"
23
+ @change="$emit('change', true)"
24
+ />
25
+ <span
26
+ aria-hidden="true"
27
+ :class="[
28
+ checkboxBox({ shape: 'round' }),
29
+ 'peer-focus-visible:outline-(--hn-focus-ring) peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2',
30
+ ]"
31
+ :data-state="checked === true ? 'checked' : 'unchecked'"
32
+ >
33
+ <Transition
34
+ enter-active-class="hn-transition-press"
35
+ enter-from-class="scale-50 opacity-0"
36
+ leave-active-class="hn-transition"
37
+ leave-to-class="scale-50 opacity-0"
38
+ >
39
+ <span v-if="checked === true" :class="radioDot()" />
40
+ </Transition>
41
+ </span>
42
+ </label>
43
+ <Checkbox
44
+ v-else
45
+ class="mx-auto"
46
+ :model-value="checked"
47
+ :disabled="disabled"
48
+ :aria-label="label"
49
+ @update:model-value="$emit('change', $event === true)"
50
+ />
51
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ import { shallowRef } from 'vue'
3
+ import { vTooltip } from '../tooltip/directive'
4
+ import { useTableText } from './composables/useTableText'
5
+ const props = defineProps<{ value: string | number; truncate?: boolean }>()
6
+ const element = shallowRef<HTMLElement>()
7
+ const { overflow, tooltip } = useTableText(element, () => props.value)
8
+ </script>
9
+ <template>
10
+ <span
11
+ v-if="truncate"
12
+ ref="element"
13
+ v-tooltip="tooltip"
14
+ class="block truncate"
15
+ :tabindex="overflow ? 0 : undefined"
16
+ >
17
+ {{ value }}
18
+ </span>
19
+ <template v-else>{{ value }}</template>
20
+ </template>
@@ -0,0 +1,12 @@
1
+ import { Fragment, createVNode, type SetupContext, type VNodeChild } from 'vue'
2
+
3
+ export default function DataTableSlot(
4
+ props: { render?: (context: never) => unknown; context?: unknown },
5
+ { slots }: SetupContext,
6
+ ) {
7
+ return createVNode(
8
+ Fragment,
9
+ null,
10
+ (props.render ? props.render(props.context as never) : slots.default?.()) as VNodeChild,
11
+ )
12
+ }
@@ -0,0 +1,101 @@
1
+ import type { DataTableColumn } from './types'
2
+
3
+ export function pixelWidth(value: number | string | undefined, fallback: number) {
4
+ return typeof value === 'number' && Number.isFinite(value)
5
+ ? value
6
+ : typeof value === 'string' && /^\d+(\.\d+)?px$/.test(value)
7
+ ? parseFloat(value)
8
+ : fallback
9
+ }
10
+
11
+ export function columnBounds<T extends object>(column: DataTableColumn<T>) {
12
+ const min = Math.max(0, pixelWidth(column.minWidth, 48))
13
+ return { min, max: Math.max(min, pixelWidth(column.maxWidth, 1600)) }
14
+ }
15
+
16
+ export function clampWidth<T extends object>(column: DataTableColumn<T>, width: number) {
17
+ const { min, max } = columnBounds(column)
18
+ return Math.max(min, Math.min(max, width))
19
+ }
20
+
21
+ export function allocateWidths<T extends object>(
22
+ columns: DataTableColumn<T>[],
23
+ overrides: Record<string, number>,
24
+ available: number,
25
+ ) {
26
+ const widths = Object.fromEntries(
27
+ columns.map(column => [
28
+ column.key,
29
+ clampWidth(column, pixelWidth(overrides[column.key] ?? column.width, 160)),
30
+ ]),
31
+ )
32
+ let remaining = available - Object.values(widths).reduce((sum, width) => sum + width, 0)
33
+ let flexible = columns.filter(column => !column.pin && overrides[column.key] === undefined)
34
+ while (remaining > 0.01 && flexible.length) {
35
+ const share = remaining / flexible.length
36
+ for (const column of flexible) {
37
+ const extra = Math.min(share, columnBounds(column).max - widths[column.key]!)
38
+ widths[column.key]! += extra
39
+ remaining -= extra
40
+ }
41
+ flexible = flexible.filter(column => widths[column.key]! < columnBounds(column).max - 0.01)
42
+ }
43
+ return widths
44
+ }
45
+
46
+ export function resizeBoundary<T extends object>(
47
+ columns: DataTableColumn<T>[],
48
+ column: DataTableColumn<T>,
49
+ mode: 'fit' | 'expand',
50
+ ) {
51
+ const index = columns.findIndex(item => item.key === column.key)
52
+ if (index < 0 || column.resizable === false) return
53
+ const side = column.pin === 'end' ? 'start' : 'end'
54
+ const neighbor = columns[index + (side === 'start' ? -1 : 1)]
55
+ if (mode === 'fit' && side === 'end' && neighbor?.pin === 'end') return
56
+ if (mode === 'fit' && (!neighbor || neighbor.resizable === false)) return
57
+ return { side, neighbor: mode === 'fit' ? neighbor : undefined } as const
58
+ }
59
+
60
+ export function resizeBounds<T extends object>(
61
+ column: DataTableColumn<T>,
62
+ neighbor: DataTableColumn<T> | undefined,
63
+ widths: Record<string, number>,
64
+ minimumTotalWidth = 0,
65
+ maximumWidth = Infinity,
66
+ ) {
67
+ const bounds = columnBounds(column)
68
+ bounds.max = Math.min(bounds.max, maximumWidth)
69
+ if (!neighbor) {
70
+ const otherWidth = Object.entries(widths).reduce(
71
+ (sum, [key, width]) => sum + (key === column.key ? 0 : width),
72
+ 0,
73
+ )
74
+ return {
75
+ min: Math.max(bounds.min, Math.min(widths[column.key]!, minimumTotalWidth - otherWidth)),
76
+ max: bounds.max,
77
+ }
78
+ }
79
+ const other = columnBounds(neighbor)
80
+ const total = widths[column.key]! + widths[neighbor.key]!
81
+ return {
82
+ min: Math.max(bounds.min, total - other.max),
83
+ max: Math.min(bounds.max, total - other.min),
84
+ }
85
+ }
86
+
87
+ export function resizeWidths<T extends object>(
88
+ column: DataTableColumn<T>,
89
+ neighbor: DataTableColumn<T> | undefined,
90
+ widths: Record<string, number>,
91
+ value: number,
92
+ minimumTotalWidth = 0,
93
+ maximumWidth = Infinity,
94
+ ) {
95
+ const { min, max } = resizeBounds(column, neighbor, widths, minimumTotalWidth, maximumWidth)
96
+ const width = Math.max(min, Math.min(max, value))
97
+ return {
98
+ [column.key]: width,
99
+ ...(neighbor ? { [neighbor.key]: widths[neighbor.key]! + widths[column.key]! - width } : {}),
100
+ }
101
+ }