@xen-orchestra/web-core 0.31.1 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/assets/css/_colors.pcss +8 -0
- package/lib/components/button-group/VtsButtonGroup.vue +5 -1
- package/lib/components/menu/MenuList.vue +1 -0
- package/lib/components/modal/VtsModal.vue +82 -0
- package/lib/components/modal/VtsModalButton.vue +36 -0
- package/lib/components/modal/VtsModalCancelButton.vue +37 -0
- package/lib/components/modal/VtsModalConfirmButton.vue +21 -0
- package/lib/components/modal/VtsModalList.vue +34 -0
- package/lib/components/object-icon/VtsObjectIcon.vue +3 -8
- package/lib/components/status/VtsStatus.vue +66 -0
- package/lib/components/task/VtsQuickTaskList.vue +17 -5
- package/lib/components/tree/VtsTreeItem.vue +2 -2
- package/lib/components/ui/button/UiButton.vue +13 -67
- package/lib/components/ui/input/UiInput.vue +4 -1
- package/lib/components/ui/modal/UiModal.vue +164 -0
- package/lib/components/ui/quick-task-item/UiQuickTaskItem.vue +2 -2
- package/lib/composables/context.composable.ts +3 -5
- package/lib/composables/link-component.composable.ts +3 -2
- package/lib/composables/pagination.composable.ts +3 -2
- package/lib/composables/tree-filter.composable.ts +5 -3
- package/lib/icons/fa-icons.ts +4 -0
- package/lib/icons/index.ts +17 -0
- package/lib/locales/en.json +14 -1
- package/lib/locales/fr.json +14 -1
- package/lib/packages/collection/use-collection.ts +3 -2
- package/lib/packages/form-select/use-form-option-controller.ts +3 -2
- package/lib/packages/form-select/use-form-select.ts +8 -7
- package/lib/packages/menu/action.ts +4 -3
- package/lib/packages/menu/link.ts +5 -4
- package/lib/packages/menu/router-link.ts +3 -2
- package/lib/packages/menu/toggle-target.ts +3 -2
- package/lib/packages/modal/ModalProvider.vue +17 -0
- package/lib/packages/modal/README.md +253 -0
- package/lib/packages/modal/create-modal-opener.ts +103 -0
- package/lib/packages/modal/modal.store.ts +22 -0
- package/lib/packages/modal/types.ts +92 -0
- package/lib/packages/modal/use-modal.ts +53 -0
- package/lib/packages/progress/use-progress.ts +4 -3
- package/lib/packages/table/README.md +336 -0
- package/lib/packages/table/apply-extensions.ts +26 -0
- package/lib/packages/table/define-columns.ts +62 -0
- package/lib/packages/table/define-renderer/define-table-cell-renderer.ts +27 -0
- package/lib/packages/table/define-renderer/define-table-renderer.ts +47 -0
- package/lib/packages/table/define-renderer/define-table-row-renderer.ts +29 -0
- package/lib/packages/table/define-renderer/define-table-section-renderer.ts +29 -0
- package/lib/packages/table/define-table/define-multi-source-table.ts +39 -0
- package/lib/packages/table/define-table/define-table.ts +13 -0
- package/lib/packages/table/define-table/define-typed-table.ts +18 -0
- package/lib/packages/table/index.ts +11 -0
- package/lib/packages/table/transform-sources.ts +13 -0
- package/lib/packages/table/types/extensions.ts +16 -0
- package/lib/packages/table/types/index.ts +47 -0
- package/lib/packages/table/types/table-cell.ts +18 -0
- package/lib/packages/table/types/table-row.ts +20 -0
- package/lib/packages/table/types/table-section.ts +19 -0
- package/lib/packages/table/types/table.ts +28 -0
- package/lib/packages/threshold/use-threshold.ts +4 -3
- package/lib/types/vue-virtual-scroller.d.ts +101 -0
- package/lib/utils/injection-keys.util.ts +3 -0
- package/lib/utils/progress.util.ts +2 -1
- package/lib/utils/to-computed.util.ts +15 -0
- package/package.json +3 -2
- package/lib/components/backup-state/VtsBackupState.vue +0 -37
- package/lib/components/connection-status/VtsConnectionStatus.vue +0 -36
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# Table System
|
|
2
|
+
|
|
3
|
+
Table system that separates data logic from presentation through reusable renderers.
|
|
4
|
+
|
|
5
|
+
## Understanding Renderers
|
|
6
|
+
|
|
7
|
+
A **renderer** is a function that creates a VNode for a specific part of the table (cell, row, or table). When you define a renderer, you specify:
|
|
8
|
+
|
|
9
|
+
1. **Component**: The Vue component to render (loaded asynchronously)
|
|
10
|
+
2. **Props function** (optional): Default props based on configuration
|
|
11
|
+
3. **Extensions** (optional): Named categories of additional functionality
|
|
12
|
+
|
|
13
|
+
### Props System
|
|
14
|
+
|
|
15
|
+
The `props` parameter is a function that receives an optional typed config and returns default props:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
const TextBody = defineTableCellRenderer({
|
|
19
|
+
component: () => import('./TextCell.vue'),
|
|
20
|
+
props: (config: { text: string }) => ({ data: config.text }),
|
|
21
|
+
// ^ This config type will be enforced when using the renderer
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Usage - TypeScript knows you need to provide `text`
|
|
25
|
+
TextBody({ text: 'Hello' })
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
When you use the renderer, you can:
|
|
29
|
+
|
|
30
|
+
- Provide the expected config to satisfy the `props` function
|
|
31
|
+
- Add additional props that will be merged
|
|
32
|
+
- Override default props
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// The renderer merges:
|
|
36
|
+
// 1. Props from the props function: { text: 'Hello' }
|
|
37
|
+
// 2. Additional/override props: { class: 'custom' }
|
|
38
|
+
TextBody({
|
|
39
|
+
text: 'Hello', // Used by props function
|
|
40
|
+
props: {
|
|
41
|
+
// Additional or override props
|
|
42
|
+
class: 'custom',
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Extensions System
|
|
48
|
+
|
|
49
|
+
Extensions work like the `props` function but are optional and named. Each extension:
|
|
50
|
+
|
|
51
|
+
- Has a unique name (like `selectable`, `highlightable`)
|
|
52
|
+
- Receives typed configuration
|
|
53
|
+
- Returns the extension arguments (only `props` for now) to merge into the component
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const MyRow = defineTableRowRenderer({
|
|
57
|
+
component: () => import('./Row.vue'),
|
|
58
|
+
extensions: {
|
|
59
|
+
selectable: (config: { id: string; selectedId: Ref<string | null> }) => ({
|
|
60
|
+
props: {
|
|
61
|
+
selected: config.id === config.selectedId.value,
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
highlightable: (config: { isHighlighted: boolean }) => ({
|
|
65
|
+
props: {
|
|
66
|
+
highlighted: config.isHighlighted,
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Usage - provide config for the extensions you want to use
|
|
73
|
+
MyRow({
|
|
74
|
+
cells: () => [...],
|
|
75
|
+
extensions: {
|
|
76
|
+
selectable: { id: user.id, selectedId },
|
|
77
|
+
highlightable: { isHighlighted: true },
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Extensions are optional when using the renderer - you only provide the ones you need.
|
|
83
|
+
|
|
84
|
+
## Defining Renderers
|
|
85
|
+
|
|
86
|
+
### Cell Renderers
|
|
87
|
+
|
|
88
|
+
Create header and body cell renderers:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { defineTableCellRenderer } from '@core/packages/table'
|
|
92
|
+
|
|
93
|
+
const TextHeader = defineTableCellRenderer({
|
|
94
|
+
component: () => import('./VtsHeaderCell.vue'),
|
|
95
|
+
props: (config: { label: string }) => ({
|
|
96
|
+
label: config.label,
|
|
97
|
+
icon: icon('fa:align-left'),
|
|
98
|
+
}),
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const TextBody = defineTableCellRenderer({
|
|
102
|
+
component: () => import('./body-cells/VtsTextCell.vue'),
|
|
103
|
+
props: (config: { text: string | number }) => ({ text: config.text }),
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Usage
|
|
107
|
+
TextHeader({ label: 'Name' })
|
|
108
|
+
TextBody({ text: user.name })
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Row Renderers
|
|
112
|
+
|
|
113
|
+
Create row renderer:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { defineTableRowRenderer } from '@core/packages/table'
|
|
117
|
+
|
|
118
|
+
const DefaultRow = defineTableRowRenderer({
|
|
119
|
+
component: () => import('./VtsRow.vue'),
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
DefaultRow({
|
|
123
|
+
cells: () => [...]
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Table Renderers
|
|
128
|
+
|
|
129
|
+
Create table renderer:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { defineTableRenderer } from '@core/packages/table'
|
|
133
|
+
|
|
134
|
+
const DefaultTable = defineTableRenderer({
|
|
135
|
+
component: () => import('./VtsTableNew.vue'),
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
// Usage
|
|
139
|
+
DefaultTable({
|
|
140
|
+
thead: MyThead(...),
|
|
141
|
+
// thead: { rows: () => [...] }, // to use native "thead"
|
|
142
|
+
// thead: { cells: () => [...] }, // to use native "thead" + "tr",
|
|
143
|
+
tbody: MyTBody(...),
|
|
144
|
+
// tbody: { rows: () => [...] }, // to use native "tbody"
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Building Tables
|
|
149
|
+
|
|
150
|
+
### Column Definition
|
|
151
|
+
|
|
152
|
+
Use `defineColumns` to create columns configuration.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const columns = defineColumns({
|
|
156
|
+
name: {
|
|
157
|
+
header: () => TextHeader({ label: 'Name' }),
|
|
158
|
+
body: user => TextBody({ text: user.name }),
|
|
159
|
+
},
|
|
160
|
+
email: {
|
|
161
|
+
header: () => TextHeader({ label: 'Email' }),
|
|
162
|
+
body: user => TextBody({ text: user.email }),
|
|
163
|
+
},
|
|
164
|
+
// Conditional column
|
|
165
|
+
role: isAdmin
|
|
166
|
+
? {
|
|
167
|
+
header: () => TextHeader({ label: 'Role' }),
|
|
168
|
+
body: user => TextBody({ text: user.role }),
|
|
169
|
+
}
|
|
170
|
+
: undefined,
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`header` and `body` can also take a config parameter if needed:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const columns = defineColumns({
|
|
178
|
+
name: {
|
|
179
|
+
header: (config) => TextHeader(...),
|
|
180
|
+
body: (user, config) => TextBody(...),
|
|
181
|
+
},
|
|
182
|
+
})
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// API
|
|
187
|
+
columns.getHeaderCells(config?) // Array of header cell VNodes
|
|
188
|
+
columns.getBodyCells(user, config?) // Array of body cell VNodes for a row
|
|
189
|
+
columns.toggleColumn('name') // Toggle column visibility
|
|
190
|
+
columns.toggleColumn('name', true) // Force column visibility
|
|
191
|
+
columns.visibleColumnsCount // ComputedRef<number>, useful for colspan
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Table Definition
|
|
195
|
+
|
|
196
|
+
Use one of three table definition functions:
|
|
197
|
+
|
|
198
|
+
#### Define basic single-source table: `defineTable`
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const { getHeaderCells, getBodyCells } = defineColumns(...)
|
|
202
|
+
|
|
203
|
+
const useUserTable = defineTable((sources: ComputedRef<User[]>) =>
|
|
204
|
+
() => DefaultTable({
|
|
205
|
+
thead: {
|
|
206
|
+
cells: () => getHeaderCells()
|
|
207
|
+
},
|
|
208
|
+
tbody: {
|
|
209
|
+
rows: () => sources.value.map(user =>
|
|
210
|
+
DefaultRow({
|
|
211
|
+
cells: () => getBodyCells(user)
|
|
212
|
+
})
|
|
213
|
+
)
|
|
214
|
+
},
|
|
215
|
+
})
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// Usage
|
|
221
|
+
const users = ref<User[]>([...])
|
|
222
|
+
|
|
223
|
+
const table = useUserTable(users, {})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`defineTable` setup function can also define a config parameter as second argument:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const useUserTable = defineTable((sources: ComputedRef<User[]>, config: { needThis: string }) => ...)
|
|
230
|
+
|
|
231
|
+
const table = useUserTable(users, { needThis: 'value' })
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### Define type-discriminated table: `defineTypedTable`
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
type Source = { type: 'user'; sources: ComputedRef<User[]> } | { type: 'admin'; sources: ComputedRef<Admin[]> }
|
|
238
|
+
|
|
239
|
+
const useItemTable = defineTypedTable(({ type, sources }: Source) => {
|
|
240
|
+
// If type === 'user', sources is ComputedRef<User[]>
|
|
241
|
+
// If type === 'admin', sources is ComputedRef<Admin[]>
|
|
242
|
+
|
|
243
|
+
return () => DefaultTable({...})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
// Usage
|
|
247
|
+
useItemTable('admin', admins, {})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### Define multiple sources table: `defineMultiSourceTable`
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
type Sources = {
|
|
254
|
+
users: ComputedRef<User[]>
|
|
255
|
+
admins: ComputedRef<Admin[]>
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const useDashboard = defineMultiSourceTable((sources: Sources) => {
|
|
259
|
+
// sources.users: ComputedRef<User[]>
|
|
260
|
+
// sources.admins: ComputedRef<Admin[]>
|
|
261
|
+
|
|
262
|
+
return () => DefaultTable({...})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// Usage
|
|
266
|
+
useDashboard({ users, admins }, {})
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Source Transformation
|
|
270
|
+
|
|
271
|
+
When using a defined table, if passed sources doesn't match expected sources, then a `transform` config will be required to add missing or incorrectly typed properties:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
type User = {
|
|
275
|
+
id: string
|
|
276
|
+
fullName: string
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const useUserTable = defineTable((sources: ComputedRef<User[]>) => {})
|
|
280
|
+
|
|
281
|
+
// Raw data has different shape
|
|
282
|
+
interface RawUser {
|
|
283
|
+
uuid: string
|
|
284
|
+
firstName: string
|
|
285
|
+
lastName: string
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Transform is required when types don't match
|
|
289
|
+
useUserTable(rawUsers, {
|
|
290
|
+
transform: user => ({
|
|
291
|
+
id: user.uuid,
|
|
292
|
+
fullName: `${user.firstName} ${user.lastName}`,
|
|
293
|
+
}),
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
// Transform is optional when types already match
|
|
297
|
+
useUserTable(users, {})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Rendering the table
|
|
301
|
+
|
|
302
|
+
```vue
|
|
303
|
+
<template>
|
|
304
|
+
<MyUsersTable />
|
|
305
|
+
</template>
|
|
306
|
+
|
|
307
|
+
<script setup lang="ts">
|
|
308
|
+
const MyUsersTable = useUsersTable(users, {})
|
|
309
|
+
</script>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Props
|
|
313
|
+
|
|
314
|
+
When a table is rendered, each element's props will be merged together in the following order:
|
|
315
|
+
|
|
316
|
+
1. Props from the renderer `props` function
|
|
317
|
+
2. Props from extensions `props` functions
|
|
318
|
+
3. Props provided when using the renderer
|
|
319
|
+
|
|
320
|
+
They will be merged with Vue's default merging strategy (for example, `class` and `style` will be concatenated).
|
|
321
|
+
|
|
322
|
+
## API Reference
|
|
323
|
+
|
|
324
|
+
### Renderer Functions
|
|
325
|
+
|
|
326
|
+
- `defineTableRenderer` - Define table wrapper (`table`)
|
|
327
|
+
- `defineTableSectionRenderer` - Define table sections (`thead` / `tbody`)
|
|
328
|
+
- `defineTableRowRenderer` - Define table rows (`tr`)
|
|
329
|
+
- `defineTableCellRenderer` - Define table cells (`th` / `td`)
|
|
330
|
+
|
|
331
|
+
### Table Functions
|
|
332
|
+
|
|
333
|
+
- `defineTable` - Single source table
|
|
334
|
+
- `defineTypedTable` - Type-discriminated table
|
|
335
|
+
- `defineMultiSourceTable` - Multiple sources table
|
|
336
|
+
- `defineColumns` - Column definitions
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Extensions } from '.'
|
|
2
|
+
import { mergeProps } from 'vue'
|
|
3
|
+
|
|
4
|
+
export function applyExtensions(
|
|
5
|
+
config: {
|
|
6
|
+
props?: (config: any) => Record<string, any>
|
|
7
|
+
extensions?: Extensions<any>
|
|
8
|
+
},
|
|
9
|
+
renderConfig: {
|
|
10
|
+
props?: Record<string, any>
|
|
11
|
+
extensions?: Record<string, any>
|
|
12
|
+
}
|
|
13
|
+
): { props: Record<string, any> } {
|
|
14
|
+
const baseProps = mergeProps(config.props?.(renderConfig) ?? {}, renderConfig.props ?? {})
|
|
15
|
+
|
|
16
|
+
if (!renderConfig.extensions) {
|
|
17
|
+
return { props: baseProps }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const props = Object.entries(renderConfig.extensions).reduce(
|
|
21
|
+
(props, [extName, extData]) => mergeProps(props, config.extensions?.[extName!](extData).props ?? {}),
|
|
22
|
+
baseProps
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return { props }
|
|
26
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { TableCellVNode } from '.'
|
|
2
|
+
import { reactive, computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
export function defineColumns<
|
|
5
|
+
TSource,
|
|
6
|
+
TColumns extends Record<
|
|
7
|
+
string,
|
|
8
|
+
| {
|
|
9
|
+
header: (arg: THeaderArg) => TableCellVNode
|
|
10
|
+
body: (source: TSource, arg: TBodyArg) => TableCellVNode
|
|
11
|
+
}
|
|
12
|
+
| undefined
|
|
13
|
+
>,
|
|
14
|
+
TColumnName extends Extract<keyof TColumns, string>,
|
|
15
|
+
THeaderArg = undefined,
|
|
16
|
+
TBodyArg = undefined,
|
|
17
|
+
>(
|
|
18
|
+
config: TColumns &
|
|
19
|
+
Record<
|
|
20
|
+
string,
|
|
21
|
+
| {
|
|
22
|
+
header: (arg: THeaderArg) => TableCellVNode
|
|
23
|
+
body: (source: TSource, arg: TBodyArg) => TableCellVNode
|
|
24
|
+
}
|
|
25
|
+
| undefined
|
|
26
|
+
>
|
|
27
|
+
) {
|
|
28
|
+
const columnNames = Object.keys(config).filter(key => config[key] !== undefined) as TColumnName[]
|
|
29
|
+
|
|
30
|
+
const hiddenColumnNames = reactive(new Set()) as Set<TColumnName>
|
|
31
|
+
|
|
32
|
+
const visibleColumnNames = computed(() => columnNames.filter(name => !hiddenColumnNames.has(name)))
|
|
33
|
+
|
|
34
|
+
const visibleColumns = computed(() => visibleColumnNames.value.map(name => config[name]!))
|
|
35
|
+
|
|
36
|
+
const visibleColumnsCount = computed(() => visibleColumnNames.value.length)
|
|
37
|
+
|
|
38
|
+
type GetHeaderCells = THeaderArg extends undefined
|
|
39
|
+
? (arg?: THeaderArg) => TableCellVNode[]
|
|
40
|
+
: (arg: THeaderArg) => TableCellVNode[]
|
|
41
|
+
|
|
42
|
+
type GetBodyCells = TBodyArg extends undefined
|
|
43
|
+
? (source: TSource, arg?: TBodyArg) => TableCellVNode[]
|
|
44
|
+
: (source: TSource, arg: TBodyArg) => TableCellVNode[]
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
getHeaderCells: ((arg?: THeaderArg) =>
|
|
48
|
+
visibleColumns.value.map(column => column.header(arg as THeaderArg))) as GetHeaderCells,
|
|
49
|
+
|
|
50
|
+
getBodyCells: ((source: TSource, arg?: TBodyArg) =>
|
|
51
|
+
visibleColumns.value.map(column => column.body(source, arg as TBodyArg))) as GetBodyCells,
|
|
52
|
+
|
|
53
|
+
toggleColumn: (name: TColumnName, forcedValue = !hiddenColumnNames.has(name)) => {
|
|
54
|
+
if (forcedValue) {
|
|
55
|
+
hiddenColumnNames.add(name)
|
|
56
|
+
} else {
|
|
57
|
+
hiddenColumnNames.delete(name)
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
visibleColumnsCount,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type VNode, defineAsyncComponent, h } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
type TableCellRenderer,
|
|
4
|
+
type TableCellVNode,
|
|
5
|
+
type Extensions,
|
|
6
|
+
applyExtensions,
|
|
7
|
+
type ComponentLoader,
|
|
8
|
+
type PropsOverride,
|
|
9
|
+
} from '..'
|
|
10
|
+
|
|
11
|
+
export function defineTableCellRenderer<
|
|
12
|
+
TComponentProps extends Record<string, any>,
|
|
13
|
+
TExtensions extends Extensions<TComponentProps>,
|
|
14
|
+
TPropsConfig extends Record<string, any>,
|
|
15
|
+
>(config: {
|
|
16
|
+
component: ComponentLoader<TComponentProps>
|
|
17
|
+
props?: (config: TPropsConfig) => PropsOverride<TComponentProps>
|
|
18
|
+
extensions?: TExtensions
|
|
19
|
+
}): TableCellRenderer<TComponentProps, TExtensions, TPropsConfig> {
|
|
20
|
+
const component = defineAsyncComponent(config.component)
|
|
21
|
+
|
|
22
|
+
return function RenderCell(renderConfig) {
|
|
23
|
+
const extension = applyExtensions(config, renderConfig)
|
|
24
|
+
|
|
25
|
+
return h(component, extension.props) satisfies VNode as TableCellVNode
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type VNode, defineAsyncComponent, h } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
applyExtensions,
|
|
4
|
+
type ComponentLoader,
|
|
5
|
+
type PropsOverride,
|
|
6
|
+
type Extensions,
|
|
7
|
+
type TableRenderer,
|
|
8
|
+
type TableVNode,
|
|
9
|
+
} from '..'
|
|
10
|
+
|
|
11
|
+
export function defineTableRenderer<
|
|
12
|
+
TComponentProps extends Record<string, any>,
|
|
13
|
+
TExtensions extends Extensions<TComponentProps>,
|
|
14
|
+
TPropsConfig extends Record<string, any>,
|
|
15
|
+
>(config: {
|
|
16
|
+
component: ComponentLoader<TComponentProps>
|
|
17
|
+
props?: (config: TPropsConfig) => PropsOverride<TComponentProps>
|
|
18
|
+
extensions?: TExtensions
|
|
19
|
+
}): TableRenderer<TComponentProps, TExtensions, TPropsConfig> {
|
|
20
|
+
const component = defineAsyncComponent(config.component)
|
|
21
|
+
|
|
22
|
+
return function RenderTable(renderConfig) {
|
|
23
|
+
const { thead, tbody } = renderConfig
|
|
24
|
+
|
|
25
|
+
const renderThead =
|
|
26
|
+
typeof thead === 'function'
|
|
27
|
+
? thead
|
|
28
|
+
: 'cells' in thead && thead.cells
|
|
29
|
+
? () => h('thead', {}, h('tr', {}, { default: () => thead.cells() }))
|
|
30
|
+
: 'rows' in thead && thead.rows
|
|
31
|
+
? () => h('thead', {}, { default: () => thead.rows() })
|
|
32
|
+
: () => undefined
|
|
33
|
+
|
|
34
|
+
const renderTbody =
|
|
35
|
+
typeof tbody === 'function'
|
|
36
|
+
? tbody
|
|
37
|
+
: 'rows' in tbody && tbody.rows
|
|
38
|
+
? () => h('tbody', {}, { default: () => tbody.rows() })
|
|
39
|
+
: () => undefined
|
|
40
|
+
|
|
41
|
+
const extension = applyExtensions(config, renderConfig)
|
|
42
|
+
|
|
43
|
+
return h(component, extension.props, () => {
|
|
44
|
+
return [renderThead(), renderTbody()]
|
|
45
|
+
}) satisfies VNode as TableVNode
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type VNode, defineAsyncComponent, h } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
applyExtensions,
|
|
4
|
+
type TableRowRenderer,
|
|
5
|
+
type TableRowVNode,
|
|
6
|
+
type Extensions,
|
|
7
|
+
type ComponentLoader,
|
|
8
|
+
type PropsOverride,
|
|
9
|
+
} from '..'
|
|
10
|
+
|
|
11
|
+
export function defineRowRenderer<
|
|
12
|
+
TComponentProps extends Record<string, any>,
|
|
13
|
+
TExtensions extends Extensions<TComponentProps>,
|
|
14
|
+
TPropsConfig extends Record<string, any>,
|
|
15
|
+
>(config: {
|
|
16
|
+
component: ComponentLoader<TComponentProps>
|
|
17
|
+
props?: (config: TPropsConfig) => PropsOverride<TComponentProps>
|
|
18
|
+
extensions?: TExtensions
|
|
19
|
+
}): TableRowRenderer<TComponentProps, TExtensions, TPropsConfig> {
|
|
20
|
+
const component = defineAsyncComponent(config.component)
|
|
21
|
+
|
|
22
|
+
return function RenderRow(renderConfig): TableRowVNode {
|
|
23
|
+
const extension = applyExtensions(config, renderConfig)
|
|
24
|
+
|
|
25
|
+
return h(component, extension.props, {
|
|
26
|
+
default: () => renderConfig.cells(),
|
|
27
|
+
}) satisfies VNode as TableRowVNode
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type VNode, defineAsyncComponent, h } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
applyExtensions,
|
|
4
|
+
type TableSectionRenderer,
|
|
5
|
+
type TableSectionVNode,
|
|
6
|
+
type Extensions,
|
|
7
|
+
type ComponentLoader,
|
|
8
|
+
type PropsOverride,
|
|
9
|
+
} from '..'
|
|
10
|
+
|
|
11
|
+
export function defineSectionRenderer<
|
|
12
|
+
TComponentProps extends Record<string, any>,
|
|
13
|
+
TExtensions extends Extensions<TComponentProps>,
|
|
14
|
+
TPropsConfig extends Record<string, any>,
|
|
15
|
+
>(config: {
|
|
16
|
+
component: ComponentLoader<TComponentProps>
|
|
17
|
+
props?: (config: TPropsConfig) => PropsOverride<TComponentProps>
|
|
18
|
+
extensions?: TExtensions
|
|
19
|
+
}): TableSectionRenderer<TComponentProps, TExtensions, TPropsConfig> {
|
|
20
|
+
const component = defineAsyncComponent(config.component)
|
|
21
|
+
|
|
22
|
+
return function RenderSection(renderConfig): TableSectionVNode {
|
|
23
|
+
const extension = applyExtensions(config, renderConfig)
|
|
24
|
+
|
|
25
|
+
return h(component, extension.props, {
|
|
26
|
+
default: () => renderConfig.rows(),
|
|
27
|
+
}) satisfies VNode as TableSectionVNode
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter } from 'vue'
|
|
2
|
+
import { type Sources, type TransformProperty, transformSources } from '..'
|
|
3
|
+
|
|
4
|
+
export function defineMultiSourceTable<
|
|
5
|
+
TSources extends Record<string, Sources>,
|
|
6
|
+
TConfig extends Record<string, unknown> = Record<string, unknown>,
|
|
7
|
+
>(setup: (sources: TSources, config: TConfig) => any) {
|
|
8
|
+
return function useTable<
|
|
9
|
+
TUseSources extends {
|
|
10
|
+
[K in keyof TSources]: any
|
|
11
|
+
},
|
|
12
|
+
TTransforms extends {
|
|
13
|
+
[K in keyof TSources]: TSources[K] extends Sources<infer T>
|
|
14
|
+
? TransformProperty<T, TUseSources[K], K & string>[K & string]
|
|
15
|
+
: never
|
|
16
|
+
},
|
|
17
|
+
>(
|
|
18
|
+
sources: {
|
|
19
|
+
[K in keyof TUseSources]: MaybeRefOrGetter<TUseSources[K][]>
|
|
20
|
+
},
|
|
21
|
+
config: TConfig &
|
|
22
|
+
(Record<keyof TTransforms, undefined> extends TTransforms
|
|
23
|
+
? { transform?: Partial<TTransforms> }
|
|
24
|
+
: {
|
|
25
|
+
transform: { [K in keyof TTransforms as undefined extends TTransforms[K] ? K : never]?: TTransforms[K] } & {
|
|
26
|
+
[K in keyof TTransforms as undefined extends TTransforms[K] ? never : K]: TTransforms[K]
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
) {
|
|
30
|
+
const transformedSources = Object.fromEntries(
|
|
31
|
+
Object.entries(sources).map(([key, value]) => [
|
|
32
|
+
key,
|
|
33
|
+
transformSources(value, (config.transform as any)?.[key as any]),
|
|
34
|
+
])
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return setup(transformedSources as TSources, config)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter } from 'vue'
|
|
2
|
+
import { type Sources, type TableVNode, type TransformProperty, transformSources } from '..'
|
|
3
|
+
|
|
4
|
+
export function defineTable<TSource, TConfig extends Record<string, unknown> = Record<string, unknown>>(
|
|
5
|
+
setup: (sources: Sources<TSource>, config: TConfig) => () => TableVNode
|
|
6
|
+
) {
|
|
7
|
+
return function useTable<TUseSource>(
|
|
8
|
+
sources: MaybeRefOrGetter<TUseSource[]>,
|
|
9
|
+
config: TConfig & TransformProperty<TSource, TUseSource>
|
|
10
|
+
) {
|
|
11
|
+
return setup(transformSources<TSource>(sources, config?.transform), config)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter } from 'vue'
|
|
2
|
+
import { type Sources, type TransformProperty, transformSources } from '..'
|
|
3
|
+
|
|
4
|
+
export function defineTypedTable<
|
|
5
|
+
TTypedSource extends { type: string; data: Sources },
|
|
6
|
+
TConfig extends Record<string, unknown> = Record<string, unknown>,
|
|
7
|
+
>(setup: (typedSource: TTypedSource, config: TConfig) => any) {
|
|
8
|
+
return function useTable<
|
|
9
|
+
TUseSource,
|
|
10
|
+
const TType extends TTypedSource['type'],
|
|
11
|
+
TSource extends TTypedSource extends { type: TType; data: Sources<infer TSource> } ? TSource : never,
|
|
12
|
+
>(type: TType, sources: MaybeRefOrGetter<TUseSource[]>, config: TConfig & TransformProperty<TSource, TUseSource>) {
|
|
13
|
+
return setup(
|
|
14
|
+
{ type, data: transformSources<TSource>(sources, config.transform) } as unknown as TTypedSource,
|
|
15
|
+
config
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './apply-extensions'
|
|
2
|
+
export * from './define-renderer/define-table-cell-renderer'
|
|
3
|
+
export * from './define-renderer/define-table-row-renderer'
|
|
4
|
+
export * from './define-columns'
|
|
5
|
+
export * from './define-table/define-multi-source-table'
|
|
6
|
+
export * from './define-table/define-table'
|
|
7
|
+
export * from './define-renderer/define-table-renderer'
|
|
8
|
+
export * from './define-renderer/define-table-section-renderer'
|
|
9
|
+
export * from './define-table/define-typed-table'
|
|
10
|
+
export * from './transform-sources'
|
|
11
|
+
export * from './types'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useArrayMap } from '@vueuse/shared'
|
|
2
|
+
import { type MaybeRefOrGetter, type ComputedRef, computed, toValue } from 'vue'
|
|
3
|
+
|
|
4
|
+
export function transformSources<TSource>(
|
|
5
|
+
sources: MaybeRefOrGetter<any[]>,
|
|
6
|
+
transformer?: (source: any, index: number) => any
|
|
7
|
+
) {
|
|
8
|
+
return transformer
|
|
9
|
+
? (useArrayMap(sources, (source, index) => ({ ...source, ...transformer!(source, index) })) as ComputedRef<
|
|
10
|
+
TSource[]
|
|
11
|
+
>)
|
|
12
|
+
: (computed(() => toValue(sources)) as unknown as ComputedRef<TSource[]>)
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PropsOverride, Renderer } from '.'
|
|
2
|
+
|
|
3
|
+
export type Extension<
|
|
4
|
+
TConfig extends Record<string, any> = Record<string, any>,
|
|
5
|
+
TComponentProps extends Record<string, any> = Record<string, any>,
|
|
6
|
+
> = (config: TConfig) => { props: PropsOverride<TComponentProps> }
|
|
7
|
+
|
|
8
|
+
export type Extensions<TComponentProps extends Record<string, any>> = Record<string, Extension<any, TComponentProps>>
|
|
9
|
+
|
|
10
|
+
export type ExtensionConfig<
|
|
11
|
+
TRenderer extends Renderer<any, any, any>,
|
|
12
|
+
TExtensionName extends keyof $Extensions,
|
|
13
|
+
$Extensions extends Record<string, any> = TRenderer extends Renderer<any, infer TExtensions, any>
|
|
14
|
+
? { [K in keyof TExtensions]: Parameters<TExtensions[K]>[0] }
|
|
15
|
+
: never,
|
|
16
|
+
> = $Extensions[TExtensionName]
|