@serhiitupilow/nuxt-table 0.1.3 → 0.1.4
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/README.md +317 -24
- package/dist/module.json +1 -1
- package/dist/runtime/assets/styles.css +1 -1
- package/dist/runtime/components/NuxtTable.d.vue.ts +2 -1
- package/dist/runtime/components/NuxtTable.vue +11 -55
- package/dist/runtime/components/NuxtTable.vue.d.ts +2 -1
- package/dist/runtime/components/NuxtTableHeaderCell.d.vue.ts +3 -3
- package/dist/runtime/components/NuxtTableHeaderCell.vue +46 -8
- package/dist/runtime/components/NuxtTableHeaderCell.vue.d.ts +3 -3
- package/dist/runtime/composables/useNuxtTable.d.ts +2 -5
- package/dist/runtime/composables/useNuxtTable.js +68 -37
- package/dist/runtime/types/table.d.ts +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,37 +1,41 @@
|
|
|
1
1
|
# @serhiitupilow/nuxt-table
|
|
2
2
|
|
|
3
|
-
Nuxt module that
|
|
3
|
+
A Nuxt module that registers a global `NuxtTable` component for data tables with:
|
|
4
4
|
|
|
5
|
-
- sorting
|
|
6
|
-
- filtering
|
|
7
|
-
- column visibility manager
|
|
8
|
-
- column resize
|
|
5
|
+
- client-side sorting
|
|
6
|
+
- client-side filtering
|
|
9
7
|
- optional drag-and-drop column reordering
|
|
10
|
-
-
|
|
8
|
+
- optional column resize
|
|
9
|
+
- persisted column order/visibility/widths in `localStorage`
|
|
10
|
+
- configurable cell/header/filter rendering
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Requirements
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
- `nuxt >= 3.11.0`
|
|
15
|
+
- `vue >= 3.4.0`
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
15
18
|
|
|
16
19
|
```bash
|
|
17
20
|
npm i @serhiitupilow/nuxt-table
|
|
18
21
|
# or
|
|
22
|
+
pnpm add @serhiitupilow/nuxt-table
|
|
23
|
+
# or
|
|
19
24
|
yarn add @serhiitupilow/nuxt-table
|
|
20
25
|
# or
|
|
21
26
|
bun add @serhiitupilow/nuxt-table
|
|
22
|
-
# or
|
|
23
|
-
pnpm add @serhiitupilow/nuxt-table
|
|
24
27
|
```
|
|
25
28
|
|
|
26
|
-
## Nuxt
|
|
29
|
+
## Nuxt setup
|
|
27
30
|
|
|
28
31
|
```ts
|
|
32
|
+
// nuxt.config.ts
|
|
29
33
|
export default defineNuxtConfig({
|
|
30
34
|
modules: ["@serhiitupilow/nuxt-table"],
|
|
31
35
|
});
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
### Module options
|
|
35
39
|
|
|
36
40
|
```ts
|
|
37
41
|
export default defineNuxtConfig({
|
|
@@ -42,21 +46,38 @@ export default defineNuxtConfig({
|
|
|
42
46
|
});
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
| Option | Type | Default | Description |
|
|
50
|
+
| --------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------ |
|
|
51
|
+
| `injectDefaultStyles` | `boolean` | `true` | Injects bundled CSS from the module runtime. Set to `false` if you fully style classes yourself. |
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
46
54
|
|
|
47
55
|
```vue
|
|
48
56
|
<script setup lang="ts">
|
|
49
|
-
import type { NuxtTableColumn } from "@serhiitupilow/nuxt-table/
|
|
57
|
+
import type { NuxtTableColumn } from "@serhiitupilow/nuxt-table/runtime";
|
|
58
|
+
|
|
59
|
+
type UserRow = {
|
|
60
|
+
id: number;
|
|
61
|
+
name: string;
|
|
62
|
+
status: "active" | "paused";
|
|
63
|
+
createdAt: string;
|
|
64
|
+
};
|
|
50
65
|
|
|
51
66
|
const columns: NuxtTableColumn[] = [
|
|
52
67
|
{ key: "id", label: "ID", sortable: true, filterable: true },
|
|
53
68
|
{ key: "name", label: "Name", sortable: true, filterable: true },
|
|
54
69
|
{ key: "status", label: "Status", sortable: true, filterable: true },
|
|
70
|
+
{
|
|
71
|
+
key: "createdAt",
|
|
72
|
+
label: "Created",
|
|
73
|
+
sortable: true,
|
|
74
|
+
formatter: (value) => new Date(String(value)).toLocaleDateString(),
|
|
75
|
+
},
|
|
55
76
|
];
|
|
56
77
|
|
|
57
|
-
const rows = [
|
|
58
|
-
{ id: 1, name: "Alice", status: "active" },
|
|
59
|
-
{ id: 2, name: "Bob", status: "paused" },
|
|
78
|
+
const rows: UserRow[] = [
|
|
79
|
+
{ id: 1, name: "Alice", status: "active", createdAt: "2026-02-01" },
|
|
80
|
+
{ id: 2, name: "Bob", status: "paused", createdAt: "2026-02-14" },
|
|
60
81
|
];
|
|
61
82
|
|
|
62
83
|
function onColumnOrderChange(payload: {
|
|
@@ -80,24 +101,296 @@ function onColumnOrderChange(payload: {
|
|
|
80
101
|
</template>
|
|
81
102
|
```
|
|
82
103
|
|
|
104
|
+
## Public runtime exports
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import {
|
|
108
|
+
useNuxtTable,
|
|
109
|
+
type NuxtTableClassNames,
|
|
110
|
+
type NuxtTableColumn,
|
|
111
|
+
type NuxtTableColumnOrderChange,
|
|
112
|
+
type TableRow,
|
|
113
|
+
type UseNuxtTableOptions,
|
|
114
|
+
type ValueResolver,
|
|
115
|
+
} from "@serhiitupilow/nuxt-table/runtime";
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## `NuxtTable` component API
|
|
119
|
+
|
|
120
|
+
### Props
|
|
121
|
+
|
|
122
|
+
| Prop | Type | Default | Description |
|
|
123
|
+
| -------------------- | -------------------------------------------- | -------------- | ----------------------------------------------------------------------- |
|
|
124
|
+
| `columns` | `NuxtTableColumn[]` | required | Column definitions. |
|
|
125
|
+
| `rows` | `TableRow[]` | required | Data rows. |
|
|
126
|
+
| `enabledColumns` | `string[]` | `undefined` | Explicitly controls visible columns (in the current ordered sequence). |
|
|
127
|
+
| `storageKey` | `string` | `"nuxt-table"` | Prefix for persisted table UI state in `localStorage`. |
|
|
128
|
+
| `rowKey` | `string \| (row, index) => string \| number` | `"id"` | Unique key resolver for row rendering. |
|
|
129
|
+
| `title` | `string` | `"Table"` | Legacy prop kept for compatibility (not currently rendered in UI). |
|
|
130
|
+
| `showToolbar` | `boolean` | `true` | Legacy prop kept for compatibility (toolbar is not currently rendered). |
|
|
131
|
+
| `enableColumnDnd` | `boolean` | `false` | Enables drag-and-drop header reordering. |
|
|
132
|
+
| `enableColumnResize` | `boolean` | `true` | Enables resize handle on header cells. |
|
|
133
|
+
| `classNames` | `Partial<NuxtTableClassNames>` | `{}` | Class overrides for semantic class hooks. |
|
|
134
|
+
|
|
135
|
+
### Events
|
|
136
|
+
|
|
137
|
+
| Event | Payload | Description |
|
|
138
|
+
| --------------------- | ---------------------------- | ----------------------------------------------- |
|
|
139
|
+
| `column-order-change` | `NuxtTableColumnOrderChange` | Emitted after successful drag-and-drop reorder. |
|
|
140
|
+
|
|
141
|
+
### Behavior notes
|
|
142
|
+
|
|
143
|
+
- Filtering is applied before sorting.
|
|
144
|
+
- Sorting cycles by click: `asc -> desc -> off`.
|
|
145
|
+
- Column width has a minimum of `140px`.
|
|
146
|
+
- Empty state text: `No rows match the current filters.`
|
|
147
|
+
- Rendering is table-only (no built-in toolbar/summary controls).
|
|
148
|
+
- DnD headers use cursor states: `grab` and `grabbing`.
|
|
149
|
+
|
|
150
|
+
## Column definition (`NuxtTableColumn`)
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
type ValueResolver = string | ((row: TableRow) => unknown);
|
|
154
|
+
|
|
155
|
+
interface NuxtTableColumn {
|
|
156
|
+
key: string;
|
|
157
|
+
label: string;
|
|
158
|
+
sortable?: boolean;
|
|
159
|
+
filterable?: boolean;
|
|
160
|
+
sortAscComponent?: Component;
|
|
161
|
+
sortDescComponent?: Component;
|
|
162
|
+
sortDefaultComponent?: Component;
|
|
163
|
+
sortKey?: ValueResolver;
|
|
164
|
+
filterKey?: ValueResolver;
|
|
165
|
+
formatter?: (value: unknown, row: TableRow) => string;
|
|
166
|
+
filterFn?: (
|
|
167
|
+
row: TableRow,
|
|
168
|
+
filterValue: unknown,
|
|
169
|
+
column: NuxtTableColumn,
|
|
170
|
+
) => boolean;
|
|
171
|
+
cellComponent?: Component;
|
|
172
|
+
filterComponent?: Component;
|
|
173
|
+
headerClassName?: string;
|
|
174
|
+
cellClassName?: string;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Field details
|
|
179
|
+
|
|
180
|
+
- `key`: primary accessor path for display value. Supports dot notation through resolvers (for example: `user.profile.name`) when used by `sortKey`/`filterKey`.
|
|
181
|
+
- `sortKey`: alternate accessor/function used for sorting.
|
|
182
|
+
- `sortAscComponent` / `sortDescComponent` / `sortDefaultComponent`: optional sort button content per state. If not provided, defaults are `Asc`, `Desc`, and `Sort`.
|
|
183
|
+
- `filterKey`: alternate accessor/function used for default text filtering.
|
|
184
|
+
- `formatter`: transforms display value for default body rendering (`<span>{{ value }}</span>`).
|
|
185
|
+
- `filterFn`: custom row-level filter logic. If set, it overrides default string `includes` filtering for that column.
|
|
186
|
+
- `cellComponent`: custom body renderer receives `row`, `column`, and `value`.
|
|
187
|
+
- `filterComponent`: custom header filter renderer receives `modelValue` and `column`, and should emit `update:model-value`.
|
|
188
|
+
|
|
189
|
+
## Persistence model
|
|
190
|
+
|
|
191
|
+
State is persisted per `storageKey` in `localStorage` with keys:
|
|
192
|
+
|
|
193
|
+
- `${storageKey}:order`
|
|
194
|
+
- `${storageKey}:enabledColumns`
|
|
195
|
+
- `${storageKey}:widths`
|
|
196
|
+
|
|
197
|
+
Persisted values are validated against current `columns`; unknown keys are ignored.
|
|
198
|
+
|
|
83
199
|
## Styling
|
|
84
200
|
|
|
85
|
-
|
|
201
|
+
The component uses semantic class hooks. You can:
|
|
202
|
+
|
|
203
|
+
1. use injected default styles, and/or
|
|
204
|
+
2. override classes via `classNames`, and/or
|
|
205
|
+
3. provide your own global CSS.
|
|
86
206
|
|
|
87
|
-
|
|
207
|
+
### Default class keys (`NuxtTableClassNames`)
|
|
88
208
|
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
|
|
209
|
+
```ts
|
|
210
|
+
interface NuxtTableClassNames {
|
|
211
|
+
root: string;
|
|
212
|
+
toolbar: string;
|
|
213
|
+
toolbarTitle: string;
|
|
214
|
+
toolbarActions: string;
|
|
215
|
+
toolbarButton: string;
|
|
216
|
+
columnManager: string;
|
|
217
|
+
columnManagerTitle: string;
|
|
218
|
+
columnManagerItem: string;
|
|
219
|
+
tableWrapper: string;
|
|
220
|
+
table: string;
|
|
221
|
+
tableHead: string;
|
|
222
|
+
tableBody: string;
|
|
223
|
+
bodyRow: string;
|
|
224
|
+
emptyCell: string;
|
|
225
|
+
headerCell: string;
|
|
226
|
+
headerCellDragSource: string;
|
|
227
|
+
headerCellDragOver: string;
|
|
228
|
+
headerTop: string;
|
|
229
|
+
headerLabel: string;
|
|
230
|
+
sortButton: string;
|
|
231
|
+
filterInput: string;
|
|
232
|
+
resizeHandle: string;
|
|
233
|
+
bodyCell: string;
|
|
92
234
|
}
|
|
93
235
|
```
|
|
94
236
|
|
|
95
|
-
|
|
237
|
+
> Some toolbar-related class keys remain in the public type for compatibility, even though the current component template renders only the table.
|
|
238
|
+
|
|
239
|
+
### `classNames` example
|
|
96
240
|
|
|
97
241
|
```vue
|
|
98
242
|
<NuxtTable
|
|
99
243
|
:columns="columns"
|
|
100
244
|
:rows="rows"
|
|
101
|
-
:class-names="{
|
|
245
|
+
:class-names="{
|
|
246
|
+
table: 'my-table',
|
|
247
|
+
headerCell: 'my-header-cell',
|
|
248
|
+
bodyCell: 'my-body-cell',
|
|
249
|
+
filterInput: 'my-filter-input',
|
|
250
|
+
}"
|
|
102
251
|
/>
|
|
103
252
|
```
|
|
253
|
+
|
|
254
|
+
## Advanced examples
|
|
255
|
+
|
|
256
|
+
### Enable / disable visible columns
|
|
257
|
+
|
|
258
|
+
Use `enabledColumns` to control what is rendered.
|
|
259
|
+
|
|
260
|
+
```vue
|
|
261
|
+
<script setup lang="ts">
|
|
262
|
+
const enabledColumns = ref<string[]>(["id", "name", "status"]);
|
|
263
|
+
|
|
264
|
+
function toggleStatusColumn() {
|
|
265
|
+
if (enabledColumns.value.includes("status")) {
|
|
266
|
+
enabledColumns.value = enabledColumns.value.filter(
|
|
267
|
+
(key) => key !== "status",
|
|
268
|
+
);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
enabledColumns.value = [...enabledColumns.value, "status"];
|
|
273
|
+
}
|
|
274
|
+
</script>
|
|
275
|
+
|
|
276
|
+
<template>
|
|
277
|
+
<button type="button" @click="toggleStatusColumn">
|
|
278
|
+
Toggle status column
|
|
279
|
+
</button>
|
|
280
|
+
|
|
281
|
+
<NuxtTable
|
|
282
|
+
:columns="columns"
|
|
283
|
+
:rows="rows"
|
|
284
|
+
:enabled-columns="enabledColumns"
|
|
285
|
+
/>
|
|
286
|
+
</template>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Custom sort state components (ASC / DESC / default)
|
|
290
|
+
|
|
291
|
+
```vue
|
|
292
|
+
<!-- SortAsc.vue -->
|
|
293
|
+
<template><span>↑ ASC</span></template>
|
|
294
|
+
|
|
295
|
+
<!-- SortDesc.vue -->
|
|
296
|
+
<template><span>↓ DESC</span></template>
|
|
297
|
+
|
|
298
|
+
<!-- SortIdle.vue -->
|
|
299
|
+
<template><span>↕ SORT</span></template>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import SortAsc from "~/components/SortAsc.vue";
|
|
304
|
+
import SortDesc from "~/components/SortDesc.vue";
|
|
305
|
+
import SortIdle from "~/components/SortIdle.vue";
|
|
306
|
+
|
|
307
|
+
const columns: NuxtTableColumn[] = [
|
|
308
|
+
{
|
|
309
|
+
key: "name",
|
|
310
|
+
label: "Name",
|
|
311
|
+
sortable: true,
|
|
312
|
+
sortAscComponent: SortAsc,
|
|
313
|
+
sortDescComponent: SortDesc,
|
|
314
|
+
sortDefaultComponent: SortIdle,
|
|
315
|
+
},
|
|
316
|
+
];
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
If these components are not provided, the table automatically uses the default labels.
|
|
320
|
+
|
|
321
|
+
### Custom filter component
|
|
322
|
+
|
|
323
|
+
```vue
|
|
324
|
+
<!-- StatusFilter.vue -->
|
|
325
|
+
<script setup lang="ts">
|
|
326
|
+
const props = defineProps<{
|
|
327
|
+
modelValue: unknown;
|
|
328
|
+
column: { key: string; label: string };
|
|
329
|
+
}>();
|
|
330
|
+
|
|
331
|
+
const emit = defineEmits<{
|
|
332
|
+
"update:model-value": [value: string];
|
|
333
|
+
}>();
|
|
334
|
+
</script>
|
|
335
|
+
|
|
336
|
+
<template>
|
|
337
|
+
<select
|
|
338
|
+
:value="String(props.modelValue ?? '')"
|
|
339
|
+
@change="
|
|
340
|
+
emit('update:model-value', ($event.target as HTMLSelectElement).value)
|
|
341
|
+
"
|
|
342
|
+
>
|
|
343
|
+
<option value="">All</option>
|
|
344
|
+
<option value="active">Active</option>
|
|
345
|
+
<option value="paused">Paused</option>
|
|
346
|
+
</select>
|
|
347
|
+
</template>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
const columns: NuxtTableColumn[] = [
|
|
352
|
+
{
|
|
353
|
+
key: "status",
|
|
354
|
+
label: "Status",
|
|
355
|
+
filterable: true,
|
|
356
|
+
filterComponent: StatusFilter,
|
|
357
|
+
},
|
|
358
|
+
];
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Custom cell component
|
|
362
|
+
|
|
363
|
+
```vue
|
|
364
|
+
<!-- NameCell.vue -->
|
|
365
|
+
<script setup lang="ts">
|
|
366
|
+
const props = defineProps<{
|
|
367
|
+
row: Record<string, unknown>;
|
|
368
|
+
value: unknown;
|
|
369
|
+
}>();
|
|
370
|
+
</script>
|
|
371
|
+
|
|
372
|
+
<template>
|
|
373
|
+
<strong>{{ props.value }}</strong>
|
|
374
|
+
</template>
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
const columns: NuxtTableColumn[] = [
|
|
379
|
+
{
|
|
380
|
+
key: "name",
|
|
381
|
+
label: "Name",
|
|
382
|
+
cellComponent: NameCell,
|
|
383
|
+
},
|
|
384
|
+
];
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Troubleshooting
|
|
388
|
+
|
|
389
|
+
- DnD does nothing: ensure `enableColumnDnd` is `true`.
|
|
390
|
+
- Filters do nothing: ensure column has `filterable: true` or a `filterComponent` that emits `update:model-value`.
|
|
391
|
+
- Unexpected row keys: set a stable `rowKey` function for datasets without `id`.
|
|
392
|
+
- Style conflicts: disable `injectDefaultStyles` and provide full custom CSS.
|
|
393
|
+
|
|
394
|
+
## License
|
|
395
|
+
|
|
396
|
+
MIT
|
package/dist/module.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.nuxt-table{display:grid;gap:1rem}.nuxt-table__toolbar{align-items:center;display:flex;gap:.75rem;justify-content:space-between}.nuxt-table__toolbar-title{font-size:1.125rem;font-weight:600;margin:0}.nuxt-table__toolbar-actions{align-items:center;display:flex;gap:.5rem;position:relative}.nuxt-table__toolbar-button{background:#fff;border:1px solid #d1d5db;border-radius:.375rem;cursor:pointer;font-size:.875rem;line-height:1;padding:.5rem .75rem}.nuxt-table__column-manager{background:#fff;border:1px solid #d1d5db;border-radius:.5rem;box-shadow:0 10px 20px rgba(15,23,42,.14);min-width:14rem;padding:.75rem;position:absolute;right:0;top:
|
|
1
|
+
.nuxt-table{display:grid;gap:1rem}.nuxt-table__toolbar{align-items:center;display:flex;gap:.75rem;justify-content:space-between}.nuxt-table__toolbar-title{font-size:1.125rem;font-weight:600;margin:0}.nuxt-table__toolbar-actions{align-items:center;display:flex;gap:.5rem;position:relative}.nuxt-table__toolbar-button{background:#fff;border:1px solid #d1d5db;border-radius:.375rem;cursor:pointer;font-size:.875rem;line-height:1;padding:.5rem .75rem}.nuxt-table__column-manager{position:relative}.nuxt-table__column-manager>summary{list-style:none}.nuxt-table__column-manager>summary::-webkit-details-marker{display:none}.nuxt-table__column-manager[open]{background:#fff;border:1px solid #d1d5db;border-radius:.5rem;box-shadow:0 10px 20px rgba(15,23,42,.14);min-width:14rem;padding:.75rem;position:absolute;right:0;top:0;z-index:30}.nuxt-table__column-manager[open]>summary{margin-bottom:.5rem}.nuxt-table__column-manager-title{font-size:.875rem;font-weight:600;margin:0 0 .5rem}.nuxt-table__column-manager-item{align-items:center;cursor:pointer;display:flex;font-size:.875rem;gap:.5rem;margin-bottom:.375rem}.nuxt-table__column-manager-item:last-child{margin-bottom:0}.nuxt-table__wrapper{border:1px solid #d1d5db;border-radius:.5rem;overflow-x:auto}.nuxt-table__table{border-collapse:collapse;min-width:100%;table-layout:fixed;width:100%}.nuxt-table__head{background:#f8fafc}.nuxt-table__header-cell{border-bottom:1px solid #e5e7eb;border-right:1px solid #e5e7eb;padding:.75rem;position:relative;text-align:left;transition:background-color .2s ease,box-shadow .2s ease,transform .2s ease,opacity .2s ease;vertical-align:top}.nuxt-table__header-cell:last-child{border-right:0}.nuxt-table__header-cell--drag-source{box-shadow:inset 0 0 0 1px #9ca3af;opacity:.82}.nuxt-table__header-cell--drag-over{background:#f1f5f9;box-shadow:inset 0 0 0 2px #cbd5e1}.nuxt-table__header-cell--dnd{cursor:grab}.nuxt-table__header-cell--dragging{cursor:grabbing}.nuxt-table__header-top{align-items:center;display:flex;gap:.5rem;justify-content:space-between;margin-bottom:.5rem}.nuxt-table__header-label{font-weight:600}.nuxt-table__sort-button{background:#fff;border:1px solid #d1d5db;border-radius:.375rem;cursor:pointer;font-size:.75rem;line-height:1;padding:.25rem .5rem}.nuxt-table__filter-input{border:1px solid #d1d5db;border-radius:.375rem;font-size:.875rem;padding:.375rem .5rem;width:100%}.nuxt-table__resize-handle{cursor:col-resize;height:100%;position:absolute;right:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:.5rem}.nuxt-table__body-row{border-bottom:1px solid #e5e7eb}.nuxt-table__body-row:last-child{border-bottom:0}.nuxt-table__body-cell{border-right:1px solid #e5e7eb;padding:.75rem;vertical-align:top}.nuxt-table__body-cell:last-child{border-right:0}.nuxt-table__empty-cell{color:#6b7280;padding:1rem;text-align:center}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { NuxtTableClassNames, NuxtTableColumn, NuxtTableColumnOrderChange, TableRow } from
|
|
1
|
+
import type { NuxtTableClassNames, NuxtTableColumn, NuxtTableColumnOrderChange, TableRow } from "../types/table.js";
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
columns: NuxtTableColumn[];
|
|
4
4
|
rows: TableRow[];
|
|
5
|
+
enabledColumns?: string[];
|
|
5
6
|
storageKey?: string;
|
|
6
7
|
rowKey?: string | ((row: TableRow, index: number) => string | number);
|
|
7
8
|
title?: string;
|
|
@@ -4,6 +4,7 @@ import { useNuxtTable } from "../composables/useNuxtTable";
|
|
|
4
4
|
const props = defineProps({
|
|
5
5
|
columns: { type: Array, required: true },
|
|
6
6
|
rows: { type: Array, required: true },
|
|
7
|
+
enabledColumns: { type: Array, required: false },
|
|
7
8
|
storageKey: { type: String, required: false, default: "nuxt-table" },
|
|
8
9
|
rowKey: { type: [String, Function], required: false, default: "id" },
|
|
9
10
|
title: { type: String, required: false, default: "Table" },
|
|
@@ -49,16 +50,11 @@ const {
|
|
|
49
50
|
visibleColumns,
|
|
50
51
|
sortedRows,
|
|
51
52
|
filters,
|
|
52
|
-
isColumnManagerOpen,
|
|
53
|
-
enabledColumnKeys,
|
|
54
53
|
dragSourceColumnKey,
|
|
55
54
|
dragOverColumnKey,
|
|
56
55
|
getSortDirection,
|
|
57
56
|
toggleSort,
|
|
58
57
|
setFilter,
|
|
59
|
-
clearAllFilters,
|
|
60
|
-
toggleColumn,
|
|
61
|
-
resetColumns,
|
|
62
58
|
onHeaderDragStart,
|
|
63
59
|
onHeaderDragOver,
|
|
64
60
|
onHeaderDragLeave,
|
|
@@ -79,63 +75,23 @@ const {
|
|
|
79
75
|
emit("columnOrderChange", payload);
|
|
80
76
|
}
|
|
81
77
|
});
|
|
78
|
+
const displayedColumns = computed(() => {
|
|
79
|
+
if (!props.enabledColumns) {
|
|
80
|
+
return visibleColumns.value;
|
|
81
|
+
}
|
|
82
|
+
const enabledKeySet = new Set(props.enabledColumns);
|
|
83
|
+
return orderedColumns.value.filter((column) => enabledKeySet.has(column.key));
|
|
84
|
+
});
|
|
82
85
|
</script>
|
|
83
86
|
|
|
84
87
|
<template>
|
|
85
88
|
<div :class="mergedClassNames.root">
|
|
86
|
-
<div v-if="props.showToolbar" :class="mergedClassNames.toolbar">
|
|
87
|
-
<h2 :class="mergedClassNames.toolbarTitle">{{ props.title }}</h2>
|
|
88
|
-
|
|
89
|
-
<div :class="mergedClassNames.toolbarActions">
|
|
90
|
-
<button
|
|
91
|
-
type="button"
|
|
92
|
-
:class="mergedClassNames.toolbarButton"
|
|
93
|
-
@click="isColumnManagerOpen = !isColumnManagerOpen"
|
|
94
|
-
>
|
|
95
|
-
Columns
|
|
96
|
-
</button>
|
|
97
|
-
<button
|
|
98
|
-
type="button"
|
|
99
|
-
:class="mergedClassNames.toolbarButton"
|
|
100
|
-
@click="clearAllFilters"
|
|
101
|
-
>
|
|
102
|
-
Clear Filters
|
|
103
|
-
</button>
|
|
104
|
-
<button
|
|
105
|
-
type="button"
|
|
106
|
-
:class="mergedClassNames.toolbarButton"
|
|
107
|
-
@click="resetColumns"
|
|
108
|
-
>
|
|
109
|
-
Reset Columns
|
|
110
|
-
</button>
|
|
111
|
-
|
|
112
|
-
<div
|
|
113
|
-
v-if="isColumnManagerOpen"
|
|
114
|
-
:class="mergedClassNames.columnManager"
|
|
115
|
-
>
|
|
116
|
-
<p :class="mergedClassNames.columnManagerTitle">Enable Columns</p>
|
|
117
|
-
<label
|
|
118
|
-
v-for="column in orderedColumns"
|
|
119
|
-
:key="`manager-${column.key}`"
|
|
120
|
-
:class="mergedClassNames.columnManagerItem"
|
|
121
|
-
>
|
|
122
|
-
<input
|
|
123
|
-
type="checkbox"
|
|
124
|
-
:checked="enabledColumnKeys.includes(column.key)"
|
|
125
|
-
@change="toggleColumn(column.key)"
|
|
126
|
-
>
|
|
127
|
-
<span>{{ column.label }}</span>
|
|
128
|
-
</label>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
89
|
<div :class="mergedClassNames.tableWrapper">
|
|
134
90
|
<table :class="mergedClassNames.table">
|
|
135
91
|
<thead :class="mergedClassNames.tableHead">
|
|
136
92
|
<tr>
|
|
137
93
|
<NuxtTableHeaderCell
|
|
138
|
-
v-for="column in
|
|
94
|
+
v-for="column in displayedColumns"
|
|
139
95
|
:key="column.key"
|
|
140
96
|
:column="column"
|
|
141
97
|
:filter-value="filters[column.key]"
|
|
@@ -165,7 +121,7 @@ const {
|
|
|
165
121
|
:class="mergedClassNames.bodyRow"
|
|
166
122
|
>
|
|
167
123
|
<NuxtTableBodyCell
|
|
168
|
-
v-for="column in
|
|
124
|
+
v-for="column in displayedColumns"
|
|
169
125
|
:key="`${resolveRowKey(row, rowIndex)}-${column.key}`"
|
|
170
126
|
:row="row"
|
|
171
127
|
:row-key="resolveRowKey(row, rowIndex)"
|
|
@@ -177,7 +133,7 @@ const {
|
|
|
177
133
|
</tr>
|
|
178
134
|
<tr v-if="sortedRows.length === 0">
|
|
179
135
|
<td
|
|
180
|
-
:colspan="Math.max(
|
|
136
|
+
:colspan="Math.max(displayedColumns.length, 1)"
|
|
181
137
|
:class="mergedClassNames.emptyCell"
|
|
182
138
|
>
|
|
183
139
|
No rows match the current filters.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { NuxtTableClassNames, NuxtTableColumn, NuxtTableColumnOrderChange, TableRow } from
|
|
1
|
+
import type { NuxtTableClassNames, NuxtTableColumn, NuxtTableColumnOrderChange, TableRow } from "../types/table.js";
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
columns: NuxtTableColumn[];
|
|
4
4
|
rows: TableRow[];
|
|
5
|
+
enabledColumns?: string[];
|
|
5
6
|
storageKey?: string;
|
|
6
7
|
rowKey?: string | ((row: TableRow, index: number) => string | number);
|
|
7
8
|
title?: string;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { ComponentPublicInstance } from
|
|
2
|
-
import type { NuxtTableClassNames, NuxtTableColumn } from
|
|
1
|
+
import type { ComponentPublicInstance } from "vue";
|
|
2
|
+
import type { NuxtTableClassNames, NuxtTableColumn } from "../types/table.js";
|
|
3
3
|
type __VLS_Props = {
|
|
4
4
|
column: NuxtTableColumn;
|
|
5
5
|
filterValue: unknown;
|
|
6
6
|
columnStyle: Record<string, string | undefined>;
|
|
7
|
-
sortDirection:
|
|
7
|
+
sortDirection: "asc" | "desc" | null;
|
|
8
8
|
isDragSource: boolean;
|
|
9
9
|
isDragOver: boolean;
|
|
10
10
|
isDndEnabled: boolean;
|
|
@@ -21,26 +21,58 @@ const emit = defineEmits(["dragStart", "dragOver", "dragLeave", "drop", "dragEnd
|
|
|
21
21
|
:class="[
|
|
22
22
|
props.classNames.headerCell,
|
|
23
23
|
props.column.headerClassName,
|
|
24
|
+
props.isDndEnabled ? 'nuxt-table__header-cell--dnd' : '',
|
|
25
|
+
props.isDragSource ? 'nuxt-table__header-cell--dragging' : '',
|
|
24
26
|
props.isDragSource ? props.classNames.headerCellDragSource : '',
|
|
25
27
|
props.isDragOver && !props.isDragSource ? props.classNames.headerCellDragOver : ''
|
|
26
28
|
]"
|
|
27
29
|
:draggable="props.isDndEnabled"
|
|
28
|
-
@dragstart="
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@
|
|
30
|
+
@dragstart="
|
|
31
|
+
props.isDndEnabled ? emit('dragStart', props.column.key) : void 0
|
|
32
|
+
"
|
|
33
|
+
@dragover.prevent="
|
|
34
|
+
props.isDndEnabled ? emit('dragOver', props.column.key) : void 0
|
|
35
|
+
"
|
|
36
|
+
@dragenter.prevent="
|
|
37
|
+
props.isDndEnabled ? emit('dragOver', props.column.key) : void 0
|
|
38
|
+
"
|
|
39
|
+
@dragleave="
|
|
40
|
+
props.isDndEnabled ? emit('dragLeave', props.column.key) : void 0
|
|
41
|
+
"
|
|
32
42
|
@drop="props.isDndEnabled ? emit('drop', props.column.key) : void 0"
|
|
33
43
|
@dragend="props.isDndEnabled ? emit('dragEnd') : void 0"
|
|
34
44
|
>
|
|
35
45
|
<div :class="props.classNames.headerTop">
|
|
36
|
-
<span :class="props.classNames.headerLabel">{{
|
|
46
|
+
<span :class="props.classNames.headerLabel">{{
|
|
47
|
+
props.column.label
|
|
48
|
+
}}</span>
|
|
37
49
|
<button
|
|
38
50
|
v-if="props.column.sortable"
|
|
39
51
|
type="button"
|
|
40
52
|
:class="props.classNames.sortButton"
|
|
41
53
|
@click="emit('toggleSort', props.column)"
|
|
42
54
|
>
|
|
43
|
-
<
|
|
55
|
+
<component
|
|
56
|
+
:is="props.column.sortAscComponent"
|
|
57
|
+
v-if="props.sortDirection === 'asc' && props.column.sortAscComponent"
|
|
58
|
+
:column="props.column"
|
|
59
|
+
:sort-direction="props.sortDirection"
|
|
60
|
+
/>
|
|
61
|
+
<component
|
|
62
|
+
:is="props.column.sortDescComponent"
|
|
63
|
+
v-else-if="
|
|
64
|
+
props.sortDirection === 'desc' && props.column.sortDescComponent
|
|
65
|
+
"
|
|
66
|
+
:column="props.column"
|
|
67
|
+
:sort-direction="props.sortDirection"
|
|
68
|
+
/>
|
|
69
|
+
<component
|
|
70
|
+
:is="props.column.sortDefaultComponent"
|
|
71
|
+
v-else-if="!props.sortDirection && props.column.sortDefaultComponent"
|
|
72
|
+
:column="props.column"
|
|
73
|
+
:sort-direction="props.sortDirection"
|
|
74
|
+
/>
|
|
75
|
+
<span v-else-if="props.sortDirection === 'asc'">Asc</span>
|
|
44
76
|
<span v-else-if="props.sortDirection === 'desc'">Desc</span>
|
|
45
77
|
<span v-else>Sort</span>
|
|
46
78
|
</button>
|
|
@@ -59,8 +91,14 @@ const emit = defineEmits(["dragStart", "dragOver", "dragLeave", "drop", "dragEnd
|
|
|
59
91
|
type="text"
|
|
60
92
|
:class="props.classNames.filterInput"
|
|
61
93
|
:placeholder="`Filter ${props.column.label}`"
|
|
62
|
-
@input="
|
|
63
|
-
|
|
94
|
+
@input="
|
|
95
|
+
emit(
|
|
96
|
+
'setFilter',
|
|
97
|
+
props.column.key,
|
|
98
|
+
$event.target.value
|
|
99
|
+
)
|
|
100
|
+
"
|
|
101
|
+
/>
|
|
64
102
|
|
|
65
103
|
<div
|
|
66
104
|
v-if="props.isResizeEnabled"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { ComponentPublicInstance } from
|
|
2
|
-
import type { NuxtTableClassNames, NuxtTableColumn } from
|
|
1
|
+
import type { ComponentPublicInstance } from "vue";
|
|
2
|
+
import type { NuxtTableClassNames, NuxtTableColumn } from "../types/table.js";
|
|
3
3
|
type __VLS_Props = {
|
|
4
4
|
column: NuxtTableColumn;
|
|
5
5
|
filterValue: unknown;
|
|
6
6
|
columnStyle: Record<string, string | undefined>;
|
|
7
|
-
sortDirection:
|
|
7
|
+
sortDirection: "asc" | "desc" | null;
|
|
8
8
|
isDragSource: boolean;
|
|
9
9
|
isDragOver: boolean;
|
|
10
10
|
isDndEnabled: boolean;
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import type { ComponentPublicInstance } from
|
|
2
|
-
import type { NuxtTableColumn, TableRow, UseNuxtTableOptions } from
|
|
1
|
+
import type { ComponentPublicInstance } from "vue";
|
|
2
|
+
import type { NuxtTableColumn, TableRow, UseNuxtTableOptions } from "../types/table.js";
|
|
3
3
|
export declare function useNuxtTable(options: UseNuxtTableOptions): {
|
|
4
4
|
orderedColumns: import("vue").ComputedRef<NuxtTableColumn[]>;
|
|
5
5
|
visibleColumns: import("vue").ComputedRef<NuxtTableColumn[]>;
|
|
6
6
|
sortedRows: import("vue").ComputedRef<TableRow[]>;
|
|
7
7
|
filters: import("vue").Ref<Record<string, unknown>, Record<string, unknown>>;
|
|
8
|
-
isColumnManagerOpen: import("vue").Ref<boolean, boolean>;
|
|
9
8
|
enabledColumnKeys: import("vue").Ref<string[], string[]>;
|
|
10
9
|
dragSourceColumnKey: import("vue").Ref<string | null, string | null>;
|
|
11
10
|
dragOverColumnKey: import("vue").Ref<string | null, string | null>;
|
|
12
11
|
getSortDirection: (columnKey: string) => "asc" | "desc" | null;
|
|
13
12
|
toggleSort: (column: NuxtTableColumn) => void;
|
|
14
13
|
setFilter: (columnKey: string, value: unknown) => void;
|
|
15
|
-
clearAllFilters: () => void;
|
|
16
14
|
toggleColumn: (columnKey: string) => void;
|
|
17
|
-
resetColumns: () => void;
|
|
18
15
|
onHeaderDragStart: (columnKey: string) => void;
|
|
19
16
|
onHeaderDragOver: (columnKey: string) => void;
|
|
20
17
|
onHeaderDragLeave: (columnKey: string) => void;
|
|
@@ -1,18 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
nextTick,
|
|
4
|
+
onBeforeUnmount,
|
|
5
|
+
onMounted,
|
|
6
|
+
ref,
|
|
7
|
+
watch
|
|
8
|
+
} from "vue";
|
|
2
9
|
const MIN_COLUMN_WIDTH = 140;
|
|
3
10
|
export function useNuxtTable(options) {
|
|
4
11
|
const columnOrder = ref([]);
|
|
5
12
|
const enabledColumnKeys = ref([]);
|
|
6
|
-
const sortState = ref(
|
|
13
|
+
const sortState = ref(
|
|
14
|
+
null
|
|
15
|
+
);
|
|
7
16
|
const filters = ref({});
|
|
8
|
-
const isColumnManagerOpen = ref(false);
|
|
9
17
|
const dragSourceColumnKey = ref(null);
|
|
10
18
|
const dragOverColumnKey = ref(null);
|
|
11
19
|
const hasLoadedPersistence = ref(false);
|
|
12
20
|
const headerElements = ref({});
|
|
13
21
|
const columnWidths = ref({});
|
|
14
22
|
const activeResize = ref(null);
|
|
15
|
-
const availableColumnKeys = computed(
|
|
23
|
+
const availableColumnKeys = computed(
|
|
24
|
+
() => options.columns.value.map((column) => column.key)
|
|
25
|
+
);
|
|
16
26
|
const columnsByKey = computed(() => {
|
|
17
27
|
return new Map(options.columns.value.map((column) => [column.key, column]));
|
|
18
28
|
});
|
|
@@ -20,7 +30,9 @@ export function useNuxtTable(options) {
|
|
|
20
30
|
return columnOrder.value.map((columnKey) => columnsByKey.value.get(columnKey)).filter((column) => Boolean(column));
|
|
21
31
|
});
|
|
22
32
|
const visibleColumns = computed(() => {
|
|
23
|
-
return orderedColumns.value.filter(
|
|
33
|
+
return orderedColumns.value.filter(
|
|
34
|
+
(column) => enabledColumnKeys.value.includes(column.key)
|
|
35
|
+
);
|
|
24
36
|
});
|
|
25
37
|
const filteredRows = computed(() => {
|
|
26
38
|
return options.rows.value.filter((row) => {
|
|
@@ -32,7 +44,10 @@ export function useNuxtTable(options) {
|
|
|
32
44
|
if (column.filterFn) {
|
|
33
45
|
return column.filterFn(row, filterValue, column);
|
|
34
46
|
}
|
|
35
|
-
const candidate = resolveColumnValue(
|
|
47
|
+
const candidate = resolveColumnValue(
|
|
48
|
+
row,
|
|
49
|
+
column.filterKey ?? column.key
|
|
50
|
+
);
|
|
36
51
|
const candidateText = String(candidate ?? "").toLowerCase();
|
|
37
52
|
const filterText = String(filterValue ?? "").toLowerCase();
|
|
38
53
|
return candidateText.includes(filterText);
|
|
@@ -73,9 +88,18 @@ export function useNuxtTable(options) {
|
|
|
73
88
|
if (!hasLoadedPersistence.value || !import.meta.client) {
|
|
74
89
|
return;
|
|
75
90
|
}
|
|
76
|
-
localStorage.setItem(
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
localStorage.setItem(
|
|
92
|
+
buildStorageKey("order"),
|
|
93
|
+
JSON.stringify(columnOrder.value)
|
|
94
|
+
);
|
|
95
|
+
localStorage.setItem(
|
|
96
|
+
buildStorageKey("enabledColumns"),
|
|
97
|
+
JSON.stringify(enabledColumnKeys.value)
|
|
98
|
+
);
|
|
99
|
+
localStorage.setItem(
|
|
100
|
+
buildStorageKey("widths"),
|
|
101
|
+
JSON.stringify(columnWidths.value)
|
|
102
|
+
);
|
|
79
103
|
},
|
|
80
104
|
{ deep: true }
|
|
81
105
|
);
|
|
@@ -88,7 +112,9 @@ export function useNuxtTable(options) {
|
|
|
88
112
|
columnOrder.value = [...currentKeys];
|
|
89
113
|
} else {
|
|
90
114
|
const currentKeySet = new Set(currentKeys);
|
|
91
|
-
const keptKeys = columnOrder.value.filter(
|
|
115
|
+
const keptKeys = columnOrder.value.filter(
|
|
116
|
+
(key) => currentKeySet.has(key)
|
|
117
|
+
);
|
|
92
118
|
const newKeys = currentKeys.filter((key) => !keptKeys.includes(key));
|
|
93
119
|
columnOrder.value = [...keptKeys, ...newKeys];
|
|
94
120
|
}
|
|
@@ -96,8 +122,12 @@ export function useNuxtTable(options) {
|
|
|
96
122
|
enabledColumnKeys.value = [...currentKeys];
|
|
97
123
|
} else {
|
|
98
124
|
const currentKeySet = new Set(currentKeys);
|
|
99
|
-
const keptEnabledKeys = enabledColumnKeys.value.filter(
|
|
100
|
-
|
|
125
|
+
const keptEnabledKeys = enabledColumnKeys.value.filter(
|
|
126
|
+
(key) => currentKeySet.has(key)
|
|
127
|
+
);
|
|
128
|
+
const missingEnabledKeys = currentKeys.filter(
|
|
129
|
+
(key) => !keptEnabledKeys.includes(key)
|
|
130
|
+
);
|
|
101
131
|
enabledColumnKeys.value = [...keptEnabledKeys, ...missingEnabledKeys];
|
|
102
132
|
}
|
|
103
133
|
const nextFilters = {};
|
|
@@ -123,20 +153,28 @@ export function useNuxtTable(options) {
|
|
|
123
153
|
if (persistedOrder) {
|
|
124
154
|
const parsedOrder = JSON.parse(persistedOrder);
|
|
125
155
|
if (Array.isArray(parsedOrder)) {
|
|
126
|
-
const validPersistedOrder = parsedOrder.filter(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
156
|
+
const validPersistedOrder = parsedOrder.filter(
|
|
157
|
+
(key) => {
|
|
158
|
+
return typeof key === "string" && availableColumnKeys.value.includes(key);
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
const missingKeys = availableColumnKeys.value.filter(
|
|
162
|
+
(key) => !validPersistedOrder.includes(key)
|
|
163
|
+
);
|
|
130
164
|
columnOrder.value = [...validPersistedOrder, ...missingKeys];
|
|
131
165
|
}
|
|
132
166
|
}
|
|
133
|
-
const persistedEnabledColumns = localStorage.getItem(
|
|
167
|
+
const persistedEnabledColumns = localStorage.getItem(
|
|
168
|
+
buildStorageKey("enabledColumns")
|
|
169
|
+
);
|
|
134
170
|
if (persistedEnabledColumns) {
|
|
135
171
|
const parsedEnabledColumns = JSON.parse(persistedEnabledColumns);
|
|
136
172
|
if (Array.isArray(parsedEnabledColumns)) {
|
|
137
|
-
enabledColumnKeys.value = parsedEnabledColumns.filter(
|
|
138
|
-
|
|
139
|
-
|
|
173
|
+
enabledColumnKeys.value = parsedEnabledColumns.filter(
|
|
174
|
+
(key) => {
|
|
175
|
+
return typeof key === "string" && availableColumnKeys.value.includes(key);
|
|
176
|
+
}
|
|
177
|
+
);
|
|
140
178
|
}
|
|
141
179
|
}
|
|
142
180
|
const persistedWidths = localStorage.getItem(buildStorageKey("widths"));
|
|
@@ -185,27 +223,18 @@ export function useNuxtTable(options) {
|
|
|
185
223
|
function setFilter(columnKey, value) {
|
|
186
224
|
filters.value[columnKey] = value;
|
|
187
225
|
}
|
|
188
|
-
function clearAllFilters() {
|
|
189
|
-
const nextFilters = {};
|
|
190
|
-
for (const key of availableColumnKeys.value) {
|
|
191
|
-
nextFilters[key] = "";
|
|
192
|
-
}
|
|
193
|
-
filters.value = nextFilters;
|
|
194
|
-
}
|
|
195
226
|
function toggleColumn(columnKey) {
|
|
196
227
|
if (enabledColumnKeys.value.includes(columnKey)) {
|
|
197
228
|
if (enabledColumnKeys.value.length === 1) {
|
|
198
229
|
return;
|
|
199
230
|
}
|
|
200
|
-
enabledColumnKeys.value = enabledColumnKeys.value.filter(
|
|
231
|
+
enabledColumnKeys.value = enabledColumnKeys.value.filter(
|
|
232
|
+
(key) => key !== columnKey
|
|
233
|
+
);
|
|
201
234
|
return;
|
|
202
235
|
}
|
|
203
236
|
enabledColumnKeys.value = [...enabledColumnKeys.value, columnKey];
|
|
204
237
|
}
|
|
205
|
-
function resetColumns() {
|
|
206
|
-
columnOrder.value = [...availableColumnKeys.value];
|
|
207
|
-
enabledColumnKeys.value = [...availableColumnKeys.value];
|
|
208
|
-
}
|
|
209
238
|
function onHeaderDragStart(columnKey) {
|
|
210
239
|
if (!options.enableColumnDnd.value || activeResize.value) {
|
|
211
240
|
return;
|
|
@@ -299,7 +328,10 @@ export function useNuxtTable(options) {
|
|
|
299
328
|
return;
|
|
300
329
|
}
|
|
301
330
|
const delta = event.clientX - activeResize.value.startX;
|
|
302
|
-
const nextWidth = Math.max(
|
|
331
|
+
const nextWidth = Math.max(
|
|
332
|
+
MIN_COLUMN_WIDTH,
|
|
333
|
+
Math.round(activeResize.value.startWidth + delta)
|
|
334
|
+
);
|
|
303
335
|
columnWidths.value = {
|
|
304
336
|
...columnWidths.value,
|
|
305
337
|
[activeResize.value.columnKey]: nextWidth
|
|
@@ -393,7 +425,9 @@ export function useNuxtTable(options) {
|
|
|
393
425
|
return value.length > 0;
|
|
394
426
|
}
|
|
395
427
|
if (typeof value === "object") {
|
|
396
|
-
return Object.values(value).some(
|
|
428
|
+
return Object.values(value).some(
|
|
429
|
+
(nestedValue) => isFilterActive(nestedValue)
|
|
430
|
+
);
|
|
397
431
|
}
|
|
398
432
|
return true;
|
|
399
433
|
}
|
|
@@ -430,16 +464,13 @@ export function useNuxtTable(options) {
|
|
|
430
464
|
visibleColumns,
|
|
431
465
|
sortedRows,
|
|
432
466
|
filters,
|
|
433
|
-
isColumnManagerOpen,
|
|
434
467
|
enabledColumnKeys,
|
|
435
468
|
dragSourceColumnKey,
|
|
436
469
|
dragOverColumnKey,
|
|
437
470
|
getSortDirection,
|
|
438
471
|
toggleSort,
|
|
439
472
|
setFilter,
|
|
440
|
-
clearAllFilters,
|
|
441
473
|
toggleColumn,
|
|
442
|
-
resetColumns,
|
|
443
474
|
onHeaderDragStart,
|
|
444
475
|
onHeaderDragOver,
|
|
445
476
|
onHeaderDragLeave,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Component, Ref } from
|
|
1
|
+
import type { Component, Ref } from "vue";
|
|
2
2
|
export type TableRow = Record<string, any>;
|
|
3
3
|
export type ValueResolver = string | ((row: TableRow) => unknown);
|
|
4
4
|
export interface NuxtTableColumn {
|
|
@@ -6,6 +6,9 @@ export interface NuxtTableColumn {
|
|
|
6
6
|
label: string;
|
|
7
7
|
sortable?: boolean;
|
|
8
8
|
filterable?: boolean;
|
|
9
|
+
sortAscComponent?: Component;
|
|
10
|
+
sortDescComponent?: Component;
|
|
11
|
+
sortDefaultComponent?: Component;
|
|
9
12
|
sortKey?: ValueResolver;
|
|
10
13
|
filterKey?: ValueResolver;
|
|
11
14
|
formatter?: (value: unknown, row: TableRow) => string;
|
package/package.json
CHANGED