@veristone/nuxt-v-app 0.2.6 → 0.2.8
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/app/components/V/A/Card.vue +115 -91
- package/app/components/V/A/Table/README.md +31 -13
- package/app/components/V/A/Table/index.vue +75 -48
- package/app/components/Va/Dashboard/VaDashboardPricePlan.vue +72 -87
- package/app/pages/playground/index.vue +123 -106
- package/app/pages/playground/tables.vue +427 -215
- package/package.json +2 -2
|
@@ -3,106 +3,130 @@
|
|
|
3
3
|
* VACard - Veristone Card
|
|
4
4
|
* Matches XACard API exactly.
|
|
5
5
|
*/
|
|
6
|
-
const props = withDefaults(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
6
|
+
const props = withDefaults(
|
|
7
|
+
defineProps<{
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
icon?: string;
|
|
11
|
+
collapsible?: boolean;
|
|
12
|
+
collapsed?: boolean;
|
|
13
|
+
padding?: boolean;
|
|
14
|
+
divider?: boolean;
|
|
15
|
+
variant?: "default" | "error" | "warning" | "success";
|
|
16
|
+
}>(),
|
|
17
|
+
{
|
|
18
|
+
title: "",
|
|
19
|
+
description: "",
|
|
20
|
+
icon: "",
|
|
21
|
+
collapsible: false,
|
|
22
|
+
collapsed: false,
|
|
23
|
+
padding: true,
|
|
24
|
+
divider: true,
|
|
25
|
+
variant: "default",
|
|
26
|
+
}
|
|
27
|
+
);
|
|
25
28
|
|
|
26
|
-
const isCollapsed = ref(props.collapsed)
|
|
27
|
-
watch(
|
|
29
|
+
const isCollapsed = ref(props.collapsed);
|
|
30
|
+
watch(
|
|
31
|
+
() => props.collapsed,
|
|
32
|
+
(val) => (isCollapsed.value = val)
|
|
33
|
+
);
|
|
28
34
|
|
|
29
35
|
const toggleCollapse = () => {
|
|
30
|
-
|
|
31
|
-
}
|
|
36
|
+
if (props.collapsible) isCollapsed.value = !isCollapsed.value;
|
|
37
|
+
};
|
|
32
38
|
|
|
33
39
|
const variantClass = computed(() => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
switch (props.variant) {
|
|
41
|
+
case "error":
|
|
42
|
+
return "border-red-200 dark:border-red-900 bg-red-50/50 dark:bg-red-900/10";
|
|
43
|
+
case "warning":
|
|
44
|
+
return "border-amber-200 dark:border-amber-900 bg-amber-50/50 dark:bg-amber-900/10";
|
|
45
|
+
case "success":
|
|
46
|
+
return "border-green-200 dark:border-green-900 bg-green-50/50 dark:bg-green-900/10";
|
|
47
|
+
default:
|
|
48
|
+
return "border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900";
|
|
49
|
+
}
|
|
50
|
+
});
|
|
41
51
|
</script>
|
|
42
52
|
|
|
43
53
|
<template>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
:class="{ '-rotate-180': !isCollapsed }"
|
|
75
|
-
/>
|
|
76
|
-
</button>
|
|
77
|
-
</div>
|
|
78
|
-
<p v-if="description" class="text-xs text-gray-500 dark:text-gray-400 truncate mt-0.5 font-medium">{{ description }}</p>
|
|
79
|
-
</div>
|
|
80
|
-
</slot>
|
|
81
|
-
</div>
|
|
54
|
+
<div
|
|
55
|
+
class="va-card rounded-xl border transition-all duration-200 shadow-[0_2px_9px_rgba(0,0,0,0.08)] hover:shadow-[0_4px_12px_rgba(61,113,136,0.12)]"
|
|
56
|
+
:class="variantClass"
|
|
57
|
+
>
|
|
58
|
+
<!-- Header -->
|
|
59
|
+
<div
|
|
60
|
+
v-if="title || $slots['header-actions'] || icon"
|
|
61
|
+
class="flex items-center justify-between"
|
|
62
|
+
:class="[
|
|
63
|
+
padding ? 'px-5 py-4' : 'px-4 py-3',
|
|
64
|
+
divider && !isCollapsed
|
|
65
|
+
? 'border-b border-gray-100 dark:border-gray-800'
|
|
66
|
+
: '',
|
|
67
|
+
]"
|
|
68
|
+
>
|
|
69
|
+
<div class="flex items-center gap-3 overflow-hidden">
|
|
70
|
+
<slot name="header">
|
|
71
|
+
<div
|
|
72
|
+
v-if="icon"
|
|
73
|
+
class="flex-shrink-0 p-1.5 rounded-md bg-gray-100 dark:bg-gray-800 text-gray-500"
|
|
74
|
+
>
|
|
75
|
+
<UIcon :name="icon" class="w-5 h-5" />
|
|
76
|
+
</div>
|
|
77
|
+
<div class="min-w-0">
|
|
78
|
+
<div class="flex items-center gap-2">
|
|
79
|
+
<h3
|
|
80
|
+
class="font-bold text-gray-900 dark:text-white truncate text-base"
|
|
81
|
+
>
|
|
82
|
+
{{ title }}
|
|
83
|
+
</h3>
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
<button
|
|
86
|
+
v-if="collapsible"
|
|
87
|
+
@click="toggleCollapse"
|
|
88
|
+
class="ml-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors focus:outline-none"
|
|
89
|
+
>
|
|
90
|
+
<UIcon
|
|
91
|
+
name="i-lucide-chevron-down"
|
|
92
|
+
class="w-4 h-4 transition-transform duration-300"
|
|
93
|
+
:class="{ '-rotate-180': !isCollapsed }"
|
|
94
|
+
/>
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
<p
|
|
98
|
+
v-if="description"
|
|
99
|
+
class="text-xs text-gray-500 dark:text-gray-400 truncate mt-0.5 font-medium"
|
|
100
|
+
>
|
|
101
|
+
{{ description }}
|
|
102
|
+
</p>
|
|
103
|
+
</div>
|
|
104
|
+
</slot>
|
|
105
|
+
</div>
|
|
88
106
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
<!-- XACard uses 'header-actions' slot -->
|
|
108
|
+
<div
|
|
109
|
+
v-if="$slots['header-actions']"
|
|
110
|
+
class="flex-shrink-0 flex items-center gap-2 ml-4"
|
|
111
|
+
>
|
|
112
|
+
<slot name="header-actions" />
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
97
115
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
<!-- Body -->
|
|
117
|
+
<div v-show="!isCollapsed" class="transition-all duration-300 ease-in-out">
|
|
118
|
+
<div :class="{ 'p-5': padding, 'p-0': !padding }">
|
|
119
|
+
<slot />
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<!-- Footer -->
|
|
123
|
+
<div
|
|
124
|
+
v-if="$slots.footer"
|
|
125
|
+
class="bg-gray-50 dark:bg-gray-800/50 border-t border-gray-100 dark:border-gray-800"
|
|
126
|
+
:class="padding ? 'px-5 py-3' : 'px-4 py-2'"
|
|
127
|
+
>
|
|
128
|
+
<slot name="footer" />
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
108
132
|
</template>
|
|
@@ -34,12 +34,13 @@ The primary table component. Wraps Nuxt UI's `<UTable>` with a toolbar, filter c
|
|
|
34
34
|
| `loading` | `boolean` | `false` | Shows loading skeleton on initial load |
|
|
35
35
|
| `initialPageLimit` | `number` | `10` | Rows per page (client-side mode) |
|
|
36
36
|
| `selectable` | `boolean` | `false` | Enable row selection checkboxes |
|
|
37
|
+
| `showSearch` | `boolean` | `false` | Show the built-in search input in the toolbar |
|
|
37
38
|
| `filters` | `FilterDefinition[]` | — | Filter dropdown definitions |
|
|
38
39
|
| `onRefresh` | `() => void \| Promise<void>` | — | Shows refresh button; called on click |
|
|
39
40
|
| `onRowClick` | `(row: unknown) => void` | — | Called when a row is clicked |
|
|
40
41
|
| `defaultSort` | `string` | — | Column accessorKey to sort by on mount |
|
|
41
42
|
| `defaultSortDesc` | `boolean` | `false` | Sort descending by default |
|
|
42
|
-
| `manualPagination` | `boolean` | `false` | Enable server-side pagination mode |
|
|
43
|
+
| `manualPagination` | `boolean` | `false` | Enable server-side pagination mode (disables client-side sorting and filtering) |
|
|
43
44
|
| `total` | `number` | — | Total row count from server (required for manualPagination) |
|
|
44
45
|
|
|
45
46
|
#### v-model Bindings
|
|
@@ -47,8 +48,8 @@ The primary table component. Wraps Nuxt UI's `<UTable>` with a toolbar, filter c
|
|
|
47
48
|
| Model | Type | Description |
|
|
48
49
|
|-------|------|-------------|
|
|
49
50
|
| `sorting` | `SortingState` | TanStack sorting state |
|
|
50
|
-
| `filterValues` | `Record<string, unknown>` | Active filter values |
|
|
51
|
-
| `globalFilter` | `string` | Search/query filter |
|
|
51
|
+
| `filterValues` | `Record<string, unknown>` | Active filter values (for built-in filters) |
|
|
52
|
+
| `globalFilter` | `string` | Search/query filter (for built-in search) |
|
|
52
53
|
| `page` | `number` | Current page (1-based, server-side mode) |
|
|
53
54
|
| `itemsPerPage` | `number` | Page size (server-side mode) |
|
|
54
55
|
|
|
@@ -378,6 +379,8 @@ const columns = [
|
|
|
378
379
|
|
|
379
380
|
### Server-side pagination
|
|
380
381
|
|
|
382
|
+
When `manualPagination` is enabled, the table disables client-side sorting and filtering. The parent component owns all query logic — it watches the table's `sorting`, `page`, and `itemsPerPage` models, builds API params, and fetches data.
|
|
383
|
+
|
|
381
384
|
```vue
|
|
382
385
|
<script setup>
|
|
383
386
|
const columns = [
|
|
@@ -386,23 +389,34 @@ const columns = [
|
|
|
386
389
|
{ accessorKey: 'status', header: 'Status' },
|
|
387
390
|
]
|
|
388
391
|
|
|
389
|
-
|
|
392
|
+
const sorting = ref([])
|
|
390
393
|
const page = ref(1)
|
|
391
394
|
const itemsPerPage = ref(20)
|
|
392
395
|
const total = ref(0)
|
|
393
396
|
const users = ref([])
|
|
394
397
|
const loading = ref(false)
|
|
395
398
|
|
|
396
|
-
//
|
|
399
|
+
// Parent-owned search (not built into the table)
|
|
400
|
+
const search = ref('')
|
|
401
|
+
|
|
402
|
+
// Build API params from table state + parent state
|
|
403
|
+
const apiParams = computed(() => {
|
|
404
|
+
const params = { page: page.value, limit: itemsPerPage.value }
|
|
405
|
+
const sort = sorting.value[0]
|
|
406
|
+
if (sort?.id) {
|
|
407
|
+
params.sort_by = sort.id
|
|
408
|
+
params.sort_order = sort.desc ? 'desc' : 'asc'
|
|
409
|
+
}
|
|
410
|
+
if (search.value.trim()) {
|
|
411
|
+
params.search = search.value.trim()
|
|
412
|
+
}
|
|
413
|
+
return params
|
|
414
|
+
})
|
|
415
|
+
|
|
397
416
|
async function fetchUsers() {
|
|
398
417
|
loading.value = true
|
|
399
418
|
try {
|
|
400
|
-
const response = await $fetch('/api/users', {
|
|
401
|
-
query: {
|
|
402
|
-
page: page.value,
|
|
403
|
-
limit: itemsPerPage.value,
|
|
404
|
-
}
|
|
405
|
-
})
|
|
419
|
+
const response = await $fetch('/api/users', { query: apiParams.value })
|
|
406
420
|
users.value = response.data
|
|
407
421
|
total.value = response.total
|
|
408
422
|
} finally {
|
|
@@ -410,11 +424,13 @@ async function fetchUsers() {
|
|
|
410
424
|
}
|
|
411
425
|
}
|
|
412
426
|
|
|
413
|
-
|
|
414
|
-
watch([page, itemsPerPage], fetchUsers, { immediate: true })
|
|
427
|
+
watch(apiParams, fetchUsers, { immediate: true, deep: true })
|
|
415
428
|
</script>
|
|
416
429
|
|
|
417
430
|
<template>
|
|
431
|
+
<!-- Parent-owned search input -->
|
|
432
|
+
<UInput v-model="search" placeholder="Search..." icon="i-lucide-search" class="mb-4 max-w-xs" />
|
|
433
|
+
|
|
418
434
|
<VATable
|
|
419
435
|
name="Users"
|
|
420
436
|
:data="users"
|
|
@@ -422,8 +438,10 @@ watch([page, itemsPerPage], fetchUsers, { immediate: true })
|
|
|
422
438
|
:loading="loading"
|
|
423
439
|
:manual-pagination="true"
|
|
424
440
|
:total="total"
|
|
441
|
+
v-model:sorting="sorting"
|
|
425
442
|
v-model:page="page"
|
|
426
443
|
v-model:items-per-page="itemsPerPage"
|
|
444
|
+
:on-refresh="fetchUsers"
|
|
427
445
|
/>
|
|
428
446
|
</template>
|
|
429
447
|
```
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
<!-- Normal Toolbar (shown when no items selected) -->
|
|
41
41
|
<div v-else class="flex items-center gap-2">
|
|
42
42
|
<UInput
|
|
43
|
+
v-if="showSearch"
|
|
43
44
|
v-model="globalFilter"
|
|
44
45
|
placeholder="Search..."
|
|
45
46
|
icon="i-lucide-search"
|
|
@@ -58,6 +59,8 @@
|
|
|
58
59
|
class="w-40"
|
|
59
60
|
/>
|
|
60
61
|
</template>
|
|
62
|
+
|
|
63
|
+
<slot name="toolbar-left" />
|
|
61
64
|
</div>
|
|
62
65
|
|
|
63
66
|
<div class="flex items-center gap-2">
|
|
@@ -97,7 +100,7 @@
|
|
|
97
100
|
<UTable
|
|
98
101
|
ref="table"
|
|
99
102
|
v-model:sorting="sorting"
|
|
100
|
-
v-model:global-filter="
|
|
103
|
+
v-model:global-filter="tableGlobalFilter"
|
|
101
104
|
v-model:pagination="pagination"
|
|
102
105
|
v-model:column-visibility="columnVisibility"
|
|
103
106
|
v-model:row-selection="rowSelection"
|
|
@@ -105,7 +108,7 @@
|
|
|
105
108
|
:columns="tableColumns"
|
|
106
109
|
:loading="loading"
|
|
107
110
|
:row-selection-options="{ enableRowSelection: props.selectable }"
|
|
108
|
-
:sorting-options="
|
|
111
|
+
:sorting-options="sortingOptions"
|
|
109
112
|
:pagination-options="{
|
|
110
113
|
getPaginationRowModel: getPaginationRowModel(),
|
|
111
114
|
manualPagination: props.manualPagination,
|
|
@@ -119,7 +122,7 @@
|
|
|
119
122
|
}"
|
|
120
123
|
empty="Nothing to show."
|
|
121
124
|
v-bind="$attrs"
|
|
122
|
-
|
|
125
|
+
:on-select="props.onRowClick ? (_e: any, row: any) => handleRowClick(row) : undefined"
|
|
123
126
|
>
|
|
124
127
|
<template v-for="slotEntry in forwardedSlots" :key="slotEntry.target" #[slotEntry.target]="slotProps">
|
|
125
128
|
<slot :name="slotEntry.source" v-bind="slotProps" />
|
|
@@ -162,42 +165,50 @@
|
|
|
162
165
|
</UCard>
|
|
163
166
|
</template>
|
|
164
167
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
168
|
+
/**
|
|
169
|
+
* VATable - Veristone data table component built on Nuxt UI's UTable + TanStack Table.
|
|
170
|
+
*
|
|
171
|
+
* @props
|
|
172
|
+
* name - Optional card header title
|
|
173
|
+
* data - Row data array
|
|
174
|
+
* columns - TanStack TableColumn definitions; supports `meta.preset` for cell rendering
|
|
175
|
+
* loading - Shows skeleton on initial load; shows inline spinner on subsequent refreshes
|
|
176
|
+
* initialPageLimit - Default page size (default: 10)
|
|
177
|
+
* selectable - Enables row checkboxes; exposes `selectedRows` via template ref
|
|
178
|
+
* showSearch - Renders a global search input in the toolbar
|
|
179
|
+
* filters - Array of { key, label, options } for toolbar USelect dropdowns
|
|
180
|
+
* onRefresh - Async callback; shows a refresh spinner button in the toolbar
|
|
181
|
+
* onRowClick - Called with the row's original data when a row is clicked
|
|
182
|
+
* defaultSort - Column key to sort by on mount
|
|
183
|
+
* defaultSortDesc - Sort descending when defaultSort is set (default: false)
|
|
184
|
+
* manualPagination - Enables server-side pagination mode
|
|
185
|
+
* total - Total row count for server-side pagination
|
|
186
|
+
*
|
|
187
|
+
* @models (v-model)
|
|
188
|
+
* sorting - SortingState — sync sort state with parent
|
|
189
|
+
* filterValues - Record<string, unknown> — sync active filter values with parent
|
|
190
|
+
* globalFilter - string — sync search input value with parent
|
|
191
|
+
* page - number (1-based) — current page in manual pagination mode
|
|
192
|
+
* itemsPerPage - number — page size in manual pagination mode
|
|
193
|
+
*
|
|
194
|
+
* @slots
|
|
195
|
+
* header-right - Content rendered on the right side of the card header
|
|
196
|
+
* toolbar-left - Custom controls (e.g. filter selects) injected into the toolbar
|
|
197
|
+
* next to the search input and built-in filter dropdowns
|
|
198
|
+
* Example:
|
|
199
|
+
* <template #toolbar-left>
|
|
200
|
+
* <USelect v-model="status" :items="opts" placeholder="Status" size="sm" />
|
|
201
|
+
* </template>
|
|
202
|
+
* bulk-actions - Shown in the toolbar when rows are selected; receives
|
|
203
|
+
* { selected, count, clear }
|
|
204
|
+
* actions-cell - Renders an "Actions" column; receives { row }
|
|
205
|
+
* [column]-cell - Override cell rendering for a specific column by accessor key
|
|
206
|
+
*
|
|
207
|
+
* @exposes
|
|
208
|
+
* selectedRows - Array of selected row originals
|
|
209
|
+
* rowSelection - Raw TanStack row selection state
|
|
210
|
+
* clearSelection - Clears the current row selection
|
|
211
|
+
*/
|
|
201
212
|
|
|
202
213
|
// Sorting state - v-model support
|
|
203
214
|
const sorting = defineModel<SortingState>("sorting", {
|
|
@@ -285,14 +296,6 @@ function clearSelection() {
|
|
|
285
296
|
rowSelection.value = {};
|
|
286
297
|
}
|
|
287
298
|
|
|
288
|
-
watch(
|
|
289
|
-
() => filterValues.value,
|
|
290
|
-
() => {
|
|
291
|
-
clearSelection();
|
|
292
|
-
},
|
|
293
|
-
{ deep: true }
|
|
294
|
-
);
|
|
295
|
-
|
|
296
299
|
// Table ref for accessing TanStack API
|
|
297
300
|
const table = ref<{ tableApi?: any } | null>(null);
|
|
298
301
|
|
|
@@ -328,6 +331,30 @@ watch(
|
|
|
328
331
|
// Global filter - v-model support
|
|
329
332
|
const globalFilter = defineModel<string>("globalFilter", { default: "" });
|
|
330
333
|
|
|
334
|
+
// In manual mode, prevent TanStack from filtering client-side
|
|
335
|
+
const tableGlobalFilter = computed({
|
|
336
|
+
get: () => props.manualPagination ? undefined : globalFilter.value,
|
|
337
|
+
set: (v: string) => { globalFilter.value = v ?? '' },
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Reset page when sorting changes
|
|
341
|
+
watch(
|
|
342
|
+
sorting,
|
|
343
|
+
() => {
|
|
344
|
+
clearSelection();
|
|
345
|
+
if (props.manualPagination) {
|
|
346
|
+
page.value = 1;
|
|
347
|
+
} else {
|
|
348
|
+
pagination.value = { ...pagination.value, pageIndex: 0 };
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
{ deep: true, flush: 'sync' }
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const sortingOptions = computed(() =>
|
|
355
|
+
props.manualPagination ? { manualSorting: true } : undefined
|
|
356
|
+
);
|
|
357
|
+
|
|
331
358
|
// Initialize default sorting if provided
|
|
332
359
|
onMounted(() => {
|
|
333
360
|
if (props.defaultSort && sorting.value.length === 0) {
|
|
@@ -457,7 +484,7 @@ const tableColumns = computed(() => {
|
|
|
457
484
|
|
|
458
485
|
const forwardedSlots = computed(() => {
|
|
459
486
|
const passthrough = Object.keys(slots).filter((name) => {
|
|
460
|
-
return !['default', 'header-right', 'bulk-actions'].includes(name);
|
|
487
|
+
return !['default', 'header-right', 'bulk-actions', 'toolbar-left'].includes(name);
|
|
461
488
|
});
|
|
462
489
|
|
|
463
490
|
return passthrough.map((source) => {
|