@serhiitupilow/nuxt-table 0.1.3 → 0.1.5
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 +578 -24
- package/dist/module.json +1 -1
- package/dist/runtime/assets/styles.css +1 -1
- package/dist/runtime/components/NuxtTable.d.vue.ts +6 -1
- package/dist/runtime/components/NuxtTable.vue +18 -56
- package/dist/runtime/components/NuxtTable.vue.d.ts +6 -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 +118 -39
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/types/table.d.ts +23 -2
- 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,557 @@ 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 NuxtTableManualFilterChange,
|
|
113
|
+
type NuxtTableManualSortChange,
|
|
114
|
+
type TableRow,
|
|
115
|
+
type UseNuxtTableOptions,
|
|
116
|
+
type ValueResolver,
|
|
117
|
+
} from "@serhiitupilow/nuxt-table/runtime";
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## `NuxtTable` component API
|
|
121
|
+
|
|
122
|
+
### Props
|
|
123
|
+
|
|
124
|
+
| Prop | Type | Default | Description |
|
|
125
|
+
| -------------------- | -------------------------------------------- | -------------- | ----------------------------------------------------------------------- |
|
|
126
|
+
| `columns` | `NuxtTableColumn[]` | required | Column definitions. |
|
|
127
|
+
| `rows` | `TableRow[]` | required | Data rows. |
|
|
128
|
+
| `enabledColumns` | `string[]` | `undefined` | Explicitly controls visible columns (in the current ordered sequence). |
|
|
129
|
+
| `storageKey` | `string` | `"nuxt-table"` | Prefix for persisted table UI state in `localStorage`. |
|
|
130
|
+
| `rowKey` | `string \| (row, index) => string \| number` | `"id"` | Unique key resolver for row rendering. |
|
|
131
|
+
| `title` | `string` | `"Table"` | Legacy prop kept for compatibility (not currently rendered in UI). |
|
|
132
|
+
| `showToolbar` | `boolean` | `true` | Legacy prop kept for compatibility (toolbar is not currently rendered). |
|
|
133
|
+
| `enableColumnDnd` | `boolean` | `false` | Enables drag-and-drop header reordering. |
|
|
134
|
+
| `enableColumnResize` | `boolean` | `true` | Enables resize handle on header cells. |
|
|
135
|
+
| `classNames` | `Partial<NuxtTableClassNames>` | `{}` | Class overrides for semantic class hooks. |
|
|
136
|
+
|
|
137
|
+
### Events
|
|
138
|
+
|
|
139
|
+
| Event | Payload | Description |
|
|
140
|
+
| ---------------------- | ----------------------------- | ---------------------------------------------------------------------------------- |
|
|
141
|
+
| `column-order-change` | `NuxtTableColumnOrderChange` | Emitted after successful drag-and-drop reorder. |
|
|
142
|
+
| `manual-sort-change` | `NuxtTableManualSortChange` | Emitted when sorting is changed for a column with `sortFunction`. |
|
|
143
|
+
| `manual-filter-change` | `NuxtTableManualFilterChange` | Emitted when filter value changes for a column with `filterFunction` / `filterFn`. |
|
|
144
|
+
|
|
145
|
+
### Behavior notes
|
|
146
|
+
|
|
147
|
+
- Filtering is applied before sorting.
|
|
148
|
+
- Sorting cycles by click: `asc -> desc -> off`.
|
|
149
|
+
- If a column has `filterFunction` / `filterFn`, built-in filtering is disabled for that column and table emits `manual-filter-change`.
|
|
150
|
+
- If a column has `sortFunction`, built-in sorting is disabled for that column and table emits `manual-sort-change`.
|
|
151
|
+
- Column width has a minimum of `140px`.
|
|
152
|
+
- Empty state text: `No rows match the current filters.`
|
|
153
|
+
- Rendering is table-only (no built-in toolbar/summary controls).
|
|
154
|
+
- DnD headers use cursor states: `grab` and `grabbing`.
|
|
155
|
+
|
|
156
|
+
## Column definition (`NuxtTableColumn`)
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
type ValueResolver = string | ((row: TableRow) => unknown);
|
|
160
|
+
|
|
161
|
+
interface NuxtTableColumn {
|
|
162
|
+
key: string;
|
|
163
|
+
label: string;
|
|
164
|
+
sortable?: boolean;
|
|
165
|
+
filterable?: boolean;
|
|
166
|
+
sortAscComponent?: Component;
|
|
167
|
+
sortDescComponent?: Component;
|
|
168
|
+
sortDefaultComponent?: Component;
|
|
169
|
+
sortKey?: ValueResolver;
|
|
170
|
+
sortFunction?: (
|
|
171
|
+
leftRow: TableRow,
|
|
172
|
+
rightRow: TableRow,
|
|
173
|
+
column: NuxtTableColumn,
|
|
174
|
+
tableRows: TableRow[],
|
|
175
|
+
direction: "asc" | "desc",
|
|
176
|
+
) => number;
|
|
177
|
+
filterKey?: ValueResolver;
|
|
178
|
+
formatter?: (value: unknown, row: TableRow) => string;
|
|
179
|
+
filterFunction?: (
|
|
180
|
+
row: TableRow,
|
|
181
|
+
filterValue: unknown,
|
|
182
|
+
column: NuxtTableColumn,
|
|
183
|
+
tableRows: TableRow[],
|
|
184
|
+
) => boolean;
|
|
185
|
+
filterFn?: (
|
|
186
|
+
row: TableRow,
|
|
187
|
+
filterValue: unknown,
|
|
188
|
+
column: NuxtTableColumn,
|
|
189
|
+
tableRows: TableRow[],
|
|
190
|
+
) => boolean;
|
|
191
|
+
cellComponent?: Component;
|
|
192
|
+
filterComponent?: Component;
|
|
193
|
+
headerClassName?: string;
|
|
194
|
+
cellClassName?: string;
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Field details
|
|
199
|
+
|
|
200
|
+
- `key`: primary accessor path for display value. Supports dot notation through resolvers (for example: `user.profile.name`) when used by `sortKey`/`filterKey`.
|
|
201
|
+
- `sortKey`: alternate accessor/function used for sorting.
|
|
202
|
+
- `sortFunction`: enables manual sort mode for that column. Table emits `manual-sort-change` and does not sort rows automatically.
|
|
203
|
+
- `sortAscComponent` / `sortDescComponent` / `sortDefaultComponent`: optional sort button content per state. If not provided, defaults are `Asc`, `Desc`, and `Sort`.
|
|
204
|
+
- `filterKey`: alternate accessor/function used for default text filtering.
|
|
205
|
+
- `formatter`: transforms display value for default body rendering (`<span>{{ value }}</span>`).
|
|
206
|
+
- `filterFunction`: enables manual filter mode for that column. Table emits `manual-filter-change` and does not filter rows automatically.
|
|
207
|
+
- `filterFn`: legacy alias for `filterFunction` (same manual behavior).
|
|
208
|
+
- `cellComponent`: custom body renderer receives `row`, `column`, and `value`.
|
|
209
|
+
- `filterComponent`: custom header filter renderer receives `modelValue` and `column`, and should emit `update:model-value`.
|
|
210
|
+
|
|
211
|
+
## Persistence model
|
|
212
|
+
|
|
213
|
+
State is persisted per `storageKey` in `localStorage` with keys:
|
|
214
|
+
|
|
215
|
+
- `${storageKey}:order`
|
|
216
|
+
- `${storageKey}:enabledColumns`
|
|
217
|
+
- `${storageKey}:widths`
|
|
218
|
+
|
|
219
|
+
Persisted values are validated against current `columns`; unknown keys are ignored.
|
|
220
|
+
|
|
83
221
|
## Styling
|
|
84
222
|
|
|
85
|
-
|
|
223
|
+
The component uses semantic class hooks. You can:
|
|
224
|
+
|
|
225
|
+
1. use injected default styles, and/or
|
|
226
|
+
2. override classes via `classNames`, and/or
|
|
227
|
+
3. provide your own global CSS.
|
|
86
228
|
|
|
87
|
-
|
|
229
|
+
### Default class keys (`NuxtTableClassNames`)
|
|
88
230
|
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
|
|
231
|
+
```ts
|
|
232
|
+
interface NuxtTableClassNames {
|
|
233
|
+
root: string;
|
|
234
|
+
toolbar: string;
|
|
235
|
+
toolbarTitle: string;
|
|
236
|
+
toolbarActions: string;
|
|
237
|
+
toolbarButton: string;
|
|
238
|
+
columnManager: string;
|
|
239
|
+
columnManagerTitle: string;
|
|
240
|
+
columnManagerItem: string;
|
|
241
|
+
tableWrapper: string;
|
|
242
|
+
table: string;
|
|
243
|
+
tableHead: string;
|
|
244
|
+
tableBody: string;
|
|
245
|
+
bodyRow: string;
|
|
246
|
+
emptyCell: string;
|
|
247
|
+
headerCell: string;
|
|
248
|
+
headerCellDragSource: string;
|
|
249
|
+
headerCellDragOver: string;
|
|
250
|
+
headerTop: string;
|
|
251
|
+
headerLabel: string;
|
|
252
|
+
sortButton: string;
|
|
253
|
+
filterInput: string;
|
|
254
|
+
resizeHandle: string;
|
|
255
|
+
bodyCell: string;
|
|
92
256
|
}
|
|
93
257
|
```
|
|
94
258
|
|
|
95
|
-
|
|
259
|
+
> Some toolbar-related class keys remain in the public type for compatibility, even though the current component template renders only the table.
|
|
260
|
+
|
|
261
|
+
### `classNames` example
|
|
96
262
|
|
|
97
263
|
```vue
|
|
98
264
|
<NuxtTable
|
|
99
265
|
:columns="columns"
|
|
100
266
|
:rows="rows"
|
|
101
|
-
:class-names="{
|
|
267
|
+
:class-names="{
|
|
268
|
+
table: 'my-table',
|
|
269
|
+
headerCell: 'my-header-cell',
|
|
270
|
+
bodyCell: 'my-body-cell',
|
|
271
|
+
filterInput: 'my-filter-input',
|
|
272
|
+
}"
|
|
102
273
|
/>
|
|
103
274
|
```
|
|
275
|
+
|
|
276
|
+
## Advanced examples
|
|
277
|
+
|
|
278
|
+
### Enable / disable visible columns
|
|
279
|
+
|
|
280
|
+
Use `enabledColumns` to control what is rendered.
|
|
281
|
+
|
|
282
|
+
```vue
|
|
283
|
+
<script setup lang="ts">
|
|
284
|
+
const enabledColumns = ref<string[]>(["id", "name", "status"]);
|
|
285
|
+
|
|
286
|
+
function toggleStatusColumn() {
|
|
287
|
+
if (enabledColumns.value.includes("status")) {
|
|
288
|
+
enabledColumns.value = enabledColumns.value.filter(
|
|
289
|
+
(key) => key !== "status",
|
|
290
|
+
);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
enabledColumns.value = [...enabledColumns.value, "status"];
|
|
295
|
+
}
|
|
296
|
+
</script>
|
|
297
|
+
|
|
298
|
+
<template>
|
|
299
|
+
<button type="button" @click="toggleStatusColumn">
|
|
300
|
+
Toggle status column
|
|
301
|
+
</button>
|
|
302
|
+
|
|
303
|
+
<NuxtTable
|
|
304
|
+
:columns="columns"
|
|
305
|
+
:rows="rows"
|
|
306
|
+
:enabled-columns="enabledColumns"
|
|
307
|
+
/>
|
|
308
|
+
</template>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Custom sort state components (ASC / DESC / default)
|
|
312
|
+
|
|
313
|
+
```vue
|
|
314
|
+
<!-- SortAsc.vue -->
|
|
315
|
+
<template><span>↑ ASC</span></template>
|
|
316
|
+
|
|
317
|
+
<!-- SortDesc.vue -->
|
|
318
|
+
<template><span>↓ DESC</span></template>
|
|
319
|
+
|
|
320
|
+
<!-- SortIdle.vue -->
|
|
321
|
+
<template><span>↕ SORT</span></template>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
import SortAsc from "~/components/SortAsc.vue";
|
|
326
|
+
import SortDesc from "~/components/SortDesc.vue";
|
|
327
|
+
import SortIdle from "~/components/SortIdle.vue";
|
|
328
|
+
|
|
329
|
+
const columns: NuxtTableColumn[] = [
|
|
330
|
+
{
|
|
331
|
+
key: "name",
|
|
332
|
+
label: "Name",
|
|
333
|
+
sortable: true,
|
|
334
|
+
sortAscComponent: SortAsc,
|
|
335
|
+
sortDescComponent: SortDesc,
|
|
336
|
+
sortDefaultComponent: SortIdle,
|
|
337
|
+
},
|
|
338
|
+
];
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
If these components are not provided, the table automatically uses the default labels.
|
|
342
|
+
|
|
343
|
+
### Detailed `manual-filter-change` and `manual-sort-change` flow
|
|
344
|
+
|
|
345
|
+
When `filterFunction` / `sortFunction` is set, table switches that column to manual mode and emits `manual-filter-change` / `manual-sort-change`. You control final dataset in parent component.
|
|
346
|
+
|
|
347
|
+
```vue
|
|
348
|
+
<script setup lang="ts">
|
|
349
|
+
import { computed, ref } from "vue";
|
|
350
|
+
import type {
|
|
351
|
+
NuxtTableColumn,
|
|
352
|
+
NuxtTableManualFilterChange,
|
|
353
|
+
NuxtTableManualSortChange,
|
|
354
|
+
} from "@serhiitupilow/nuxt-table/runtime";
|
|
355
|
+
|
|
356
|
+
type TicketRow = {
|
|
357
|
+
id: number;
|
|
358
|
+
title: string;
|
|
359
|
+
priority: "low" | "medium" | "high" | "critical";
|
|
360
|
+
status: "todo" | "in_progress" | "done";
|
|
361
|
+
createdAt: string;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const allRows = ref<TicketRow[]>([
|
|
365
|
+
{
|
|
366
|
+
id: 1,
|
|
367
|
+
title: "Fix auth flow",
|
|
368
|
+
priority: "high",
|
|
369
|
+
status: "in_progress",
|
|
370
|
+
createdAt: "2026-02-18",
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
id: 2,
|
|
374
|
+
title: "Write docs",
|
|
375
|
+
priority: "low",
|
|
376
|
+
status: "done",
|
|
377
|
+
createdAt: "2026-02-10",
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
id: 3,
|
|
381
|
+
title: "Release v1",
|
|
382
|
+
priority: "critical",
|
|
383
|
+
status: "todo",
|
|
384
|
+
createdAt: "2026-02-20",
|
|
385
|
+
},
|
|
386
|
+
]);
|
|
387
|
+
|
|
388
|
+
const columns: NuxtTableColumn[] = [
|
|
389
|
+
{ key: "id", label: "ID", sortable: true },
|
|
390
|
+
{ key: "title", label: "Title", sortable: true, filterable: true },
|
|
391
|
+
{
|
|
392
|
+
key: "status",
|
|
393
|
+
label: "Status",
|
|
394
|
+
filterable: true,
|
|
395
|
+
filterFunction: () => true,
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
key: "priority",
|
|
399
|
+
label: "Priority",
|
|
400
|
+
sortable: true,
|
|
401
|
+
sortFunction: () => 0,
|
|
402
|
+
},
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
const manualStatusFilter = ref<string>("");
|
|
406
|
+
const manualSort = ref<{ key: string; direction: "asc" | "desc" | null }>({
|
|
407
|
+
key: "",
|
|
408
|
+
direction: null,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const rows = computed(() => {
|
|
412
|
+
const statusFiltered = allRows.value.filter((row) => {
|
|
413
|
+
if (!manualStatusFilter.value) {
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return row.status === manualStatusFilter.value;
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
if (manualSort.value.key !== "priority" || !manualSort.value.direction) {
|
|
421
|
+
return statusFiltered;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const rank: Record<TicketRow["priority"], number> = {
|
|
425
|
+
low: 0,
|
|
426
|
+
medium: 1,
|
|
427
|
+
high: 2,
|
|
428
|
+
critical: 3,
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const directionMultiplier = manualSort.value.direction === "asc" ? 1 : -1;
|
|
432
|
+
|
|
433
|
+
return [...statusFiltered].sort((left, right) => {
|
|
434
|
+
return (rank[left.priority] - rank[right.priority]) * directionMultiplier;
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
function onManualFilterChange(payload: NuxtTableManualFilterChange) {
|
|
439
|
+
if (payload.columnKey !== "status") {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
manualStatusFilter.value = String(payload.value ?? "").trim();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function onManualSortChange(payload: NuxtTableManualSortChange) {
|
|
447
|
+
manualSort.value = {
|
|
448
|
+
key: payload.columnKey,
|
|
449
|
+
direction: payload.direction,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
</script>
|
|
453
|
+
|
|
454
|
+
<template>
|
|
455
|
+
<NuxtTable
|
|
456
|
+
:columns="columns"
|
|
457
|
+
:rows="rows"
|
|
458
|
+
@manual-filter-change="onManualFilterChange"
|
|
459
|
+
@manual-sort-change="onManualSortChange"
|
|
460
|
+
/>
|
|
461
|
+
</template>
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Notes:
|
|
465
|
+
|
|
466
|
+
- In manual mode, the table does not transform rows for that column; you pass already transformed `rows` from outside.
|
|
467
|
+
- Use payload `columnKey`, `value`, `direction`, `rows`, and `filters` from events to build server/client-side query logic.
|
|
468
|
+
- `filterFn` remains supported as a legacy alias, but `filterFunction` is preferred.
|
|
469
|
+
|
|
470
|
+
### Server-side `manual-filter-change` / `manual-sort-change` example
|
|
471
|
+
|
|
472
|
+
Use manual events to request data from backend and pass ready rows back to table.
|
|
473
|
+
|
|
474
|
+
```vue
|
|
475
|
+
<script setup lang="ts">
|
|
476
|
+
import { ref } from "vue";
|
|
477
|
+
import type {
|
|
478
|
+
NuxtTableColumn,
|
|
479
|
+
NuxtTableManualFilterChange,
|
|
480
|
+
NuxtTableManualSortChange,
|
|
481
|
+
} from "@serhiitupilow/nuxt-table/runtime";
|
|
482
|
+
|
|
483
|
+
type UserRow = {
|
|
484
|
+
id: number;
|
|
485
|
+
name: string;
|
|
486
|
+
status: "active" | "paused";
|
|
487
|
+
createdAt: string;
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const rows = ref<UserRow[]>([]);
|
|
491
|
+
const loading = ref(false);
|
|
492
|
+
|
|
493
|
+
const query = ref<{
|
|
494
|
+
page: number;
|
|
495
|
+
pageSize: number;
|
|
496
|
+
status: string;
|
|
497
|
+
sortKey: string;
|
|
498
|
+
sortDirection: "asc" | "desc" | "";
|
|
499
|
+
}>({
|
|
500
|
+
page: 1,
|
|
501
|
+
pageSize: 20,
|
|
502
|
+
status: "",
|
|
503
|
+
sortKey: "",
|
|
504
|
+
sortDirection: "",
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const columns: NuxtTableColumn[] = [
|
|
508
|
+
{ key: "id", label: "ID", sortable: true },
|
|
509
|
+
{ key: "name", label: "Name", sortable: true, filterable: true },
|
|
510
|
+
{
|
|
511
|
+
key: "status",
|
|
512
|
+
label: "Status",
|
|
513
|
+
filterable: true,
|
|
514
|
+
filterFunction: () => true,
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
key: "createdAt",
|
|
518
|
+
label: "Created",
|
|
519
|
+
sortable: true,
|
|
520
|
+
sortFunction: () => 0,
|
|
521
|
+
},
|
|
522
|
+
];
|
|
523
|
+
|
|
524
|
+
async function fetchRows() {
|
|
525
|
+
loading.value = true;
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
const data = await $fetch<UserRow[]>("/api/users", {
|
|
529
|
+
query: {
|
|
530
|
+
page: query.value.page,
|
|
531
|
+
pageSize: query.value.pageSize,
|
|
532
|
+
status: query.value.status,
|
|
533
|
+
sortKey: query.value.sortKey,
|
|
534
|
+
sortDirection: query.value.sortDirection,
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
rows.value = data;
|
|
539
|
+
} finally {
|
|
540
|
+
loading.value = false;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function onManualFilterChange(payload: NuxtTableManualFilterChange) {
|
|
545
|
+
if (payload.columnKey === "status") {
|
|
546
|
+
query.value.status = String(payload.value ?? "").trim();
|
|
547
|
+
query.value.page = 1;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
fetchRows();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function onManualSortChange(payload: NuxtTableManualSortChange) {
|
|
554
|
+
query.value.sortKey = payload.columnKey;
|
|
555
|
+
query.value.sortDirection = payload.direction ?? "";
|
|
556
|
+
query.value.page = 1;
|
|
557
|
+
|
|
558
|
+
fetchRows();
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
await fetchRows();
|
|
562
|
+
</script>
|
|
563
|
+
|
|
564
|
+
<template>
|
|
565
|
+
<NuxtTable
|
|
566
|
+
:columns="columns"
|
|
567
|
+
:rows="rows"
|
|
568
|
+
@manual-filter-change="onManualFilterChange"
|
|
569
|
+
@manual-sort-change="onManualSortChange"
|
|
570
|
+
/>
|
|
571
|
+
|
|
572
|
+
<p v-if="loading">Loading...</p>
|
|
573
|
+
</template>
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
What happens here:
|
|
577
|
+
|
|
578
|
+
- `filterFunction` and `sortFunction` switch corresponding columns to manual mode.
|
|
579
|
+
- Table emits `manual-filter-change` / `manual-sort-change` instead of transforming `rows` internally.
|
|
580
|
+
- Parent updates query params, calls API, and passes backend result as new `rows`.
|
|
581
|
+
|
|
582
|
+
### Custom filter component
|
|
583
|
+
|
|
584
|
+
```vue
|
|
585
|
+
<!-- StatusFilter.vue -->
|
|
586
|
+
<script setup lang="ts">
|
|
587
|
+
const props = defineProps<{
|
|
588
|
+
modelValue: unknown;
|
|
589
|
+
column: { key: string; label: string };
|
|
590
|
+
}>();
|
|
591
|
+
|
|
592
|
+
const emit = defineEmits<{
|
|
593
|
+
"update:model-value": [value: string];
|
|
594
|
+
}>();
|
|
595
|
+
</script>
|
|
596
|
+
|
|
597
|
+
<template>
|
|
598
|
+
<select
|
|
599
|
+
:value="String(props.modelValue ?? '')"
|
|
600
|
+
@change="
|
|
601
|
+
emit('update:model-value', ($event.target as HTMLSelectElement).value)
|
|
602
|
+
"
|
|
603
|
+
>
|
|
604
|
+
<option value="">All</option>
|
|
605
|
+
<option value="active">Active</option>
|
|
606
|
+
<option value="paused">Paused</option>
|
|
607
|
+
</select>
|
|
608
|
+
</template>
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
```ts
|
|
612
|
+
const columns: NuxtTableColumn[] = [
|
|
613
|
+
{
|
|
614
|
+
key: "status",
|
|
615
|
+
label: "Status",
|
|
616
|
+
filterable: true,
|
|
617
|
+
filterComponent: StatusFilter,
|
|
618
|
+
},
|
|
619
|
+
];
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Custom cell component
|
|
623
|
+
|
|
624
|
+
```vue
|
|
625
|
+
<!-- NameCell.vue -->
|
|
626
|
+
<script setup lang="ts">
|
|
627
|
+
const props = defineProps<{
|
|
628
|
+
row: Record<string, unknown>;
|
|
629
|
+
value: unknown;
|
|
630
|
+
}>();
|
|
631
|
+
</script>
|
|
632
|
+
|
|
633
|
+
<template>
|
|
634
|
+
<strong>{{ props.value }}</strong>
|
|
635
|
+
</template>
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
```ts
|
|
639
|
+
const columns: NuxtTableColumn[] = [
|
|
640
|
+
{
|
|
641
|
+
key: "name",
|
|
642
|
+
label: "Name",
|
|
643
|
+
cellComponent: NameCell,
|
|
644
|
+
},
|
|
645
|
+
];
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
## Troubleshooting
|
|
649
|
+
|
|
650
|
+
- DnD does nothing: ensure `enableColumnDnd` is `true`.
|
|
651
|
+
- Filters do nothing: ensure column has `filterable: true` or a `filterComponent` that emits `update:model-value`.
|
|
652
|
+
- Unexpected row keys: set a stable `rowKey` function for datasets without `id`.
|
|
653
|
+
- Style conflicts: disable `injectDefaultStyles` and provide full custom CSS.
|
|
654
|
+
|
|
655
|
+
## License
|
|
656
|
+
|
|
657
|
+
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, NuxtTableManualFilterChange, NuxtTableManualSortChange, 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;
|
|
@@ -12,8 +13,12 @@ type __VLS_Props = {
|
|
|
12
13
|
};
|
|
13
14
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
14
15
|
columnOrderChange: (payload: NuxtTableColumnOrderChange) => any;
|
|
16
|
+
manualSortChange: (payload: NuxtTableManualSortChange) => any;
|
|
17
|
+
manualFilterChange: (payload: NuxtTableManualFilterChange) => any;
|
|
15
18
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
16
19
|
onColumnOrderChange?: ((payload: NuxtTableColumnOrderChange) => any) | undefined;
|
|
20
|
+
onManualSortChange?: ((payload: NuxtTableManualSortChange) => any) | undefined;
|
|
21
|
+
onManualFilterChange?: ((payload: NuxtTableManualFilterChange) => any) | undefined;
|
|
17
22
|
}>, {
|
|
18
23
|
storageKey: string;
|
|
19
24
|
rowKey: string | ((row: TableRow, index: number) => string | number);
|
|
@@ -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" },
|
|
@@ -12,7 +13,7 @@ const props = defineProps({
|
|
|
12
13
|
enableColumnResize: { type: Boolean, required: false, default: true },
|
|
13
14
|
classNames: { type: Object, required: false }
|
|
14
15
|
});
|
|
15
|
-
const emit = defineEmits(["columnOrderChange"]);
|
|
16
|
+
const emit = defineEmits(["columnOrderChange", "manualSortChange", "manualFilterChange"]);
|
|
16
17
|
const defaultClassNames = {
|
|
17
18
|
root: "nuxt-table",
|
|
18
19
|
toolbar: "nuxt-table__toolbar",
|
|
@@ -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,
|
|
@@ -77,65 +73,31 @@ const {
|
|
|
77
73
|
enableColumnDnd: toRef(props, "enableColumnDnd"),
|
|
78
74
|
onColumnOrderChange: (payload) => {
|
|
79
75
|
emit("columnOrderChange", payload);
|
|
76
|
+
},
|
|
77
|
+
onManualSortChange: (payload) => {
|
|
78
|
+
emit("manualSortChange", payload);
|
|
79
|
+
},
|
|
80
|
+
onManualFilterChange: (payload) => {
|
|
81
|
+
emit("manualFilterChange", payload);
|
|
80
82
|
}
|
|
81
83
|
});
|
|
84
|
+
const displayedColumns = computed(() => {
|
|
85
|
+
if (!props.enabledColumns) {
|
|
86
|
+
return visibleColumns.value;
|
|
87
|
+
}
|
|
88
|
+
const enabledKeySet = new Set(props.enabledColumns);
|
|
89
|
+
return orderedColumns.value.filter((column) => enabledKeySet.has(column.key));
|
|
90
|
+
});
|
|
82
91
|
</script>
|
|
83
92
|
|
|
84
93
|
<template>
|
|
85
94
|
<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
95
|
<div :class="mergedClassNames.tableWrapper">
|
|
134
96
|
<table :class="mergedClassNames.table">
|
|
135
97
|
<thead :class="mergedClassNames.tableHead">
|
|
136
98
|
<tr>
|
|
137
99
|
<NuxtTableHeaderCell
|
|
138
|
-
v-for="column in
|
|
100
|
+
v-for="column in displayedColumns"
|
|
139
101
|
:key="column.key"
|
|
140
102
|
:column="column"
|
|
141
103
|
:filter-value="filters[column.key]"
|
|
@@ -165,7 +127,7 @@ const {
|
|
|
165
127
|
:class="mergedClassNames.bodyRow"
|
|
166
128
|
>
|
|
167
129
|
<NuxtTableBodyCell
|
|
168
|
-
v-for="column in
|
|
130
|
+
v-for="column in displayedColumns"
|
|
169
131
|
:key="`${resolveRowKey(row, rowIndex)}-${column.key}`"
|
|
170
132
|
:row="row"
|
|
171
133
|
:row-key="resolveRowKey(row, rowIndex)"
|
|
@@ -177,7 +139,7 @@ const {
|
|
|
177
139
|
</tr>
|
|
178
140
|
<tr v-if="sortedRows.length === 0">
|
|
179
141
|
<td
|
|
180
|
-
:colspan="Math.max(
|
|
142
|
+
:colspan="Math.max(displayedColumns.length, 1)"
|
|
181
143
|
:class="mergedClassNames.emptyCell"
|
|
182
144
|
>
|
|
183
145
|
No rows match the current filters.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { NuxtTableClassNames, NuxtTableColumn, NuxtTableColumnOrderChange, TableRow } from
|
|
1
|
+
import type { NuxtTableClassNames, NuxtTableColumn, NuxtTableColumnOrderChange, NuxtTableManualFilterChange, NuxtTableManualSortChange, 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;
|
|
@@ -12,8 +13,12 @@ type __VLS_Props = {
|
|
|
12
13
|
};
|
|
13
14
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
14
15
|
columnOrderChange: (payload: NuxtTableColumnOrderChange) => any;
|
|
16
|
+
manualSortChange: (payload: NuxtTableManualSortChange) => any;
|
|
17
|
+
manualFilterChange: (payload: NuxtTableManualFilterChange) => any;
|
|
15
18
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
16
19
|
onColumnOrderChange?: ((payload: NuxtTableColumnOrderChange) => any) | undefined;
|
|
20
|
+
onManualSortChange?: ((payload: NuxtTableManualSortChange) => any) | undefined;
|
|
21
|
+
onManualFilterChange?: ((payload: NuxtTableManualFilterChange) => any) | undefined;
|
|
17
22
|
}>, {
|
|
18
23
|
storageKey: string;
|
|
19
24
|
rowKey: string | ((row: TableRow, index: number) => string | number);
|
|
@@ -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) => {
|
|
@@ -29,10 +41,14 @@ export function useNuxtTable(options) {
|
|
|
29
41
|
if (!isFilterActive(filterValue)) {
|
|
30
42
|
return true;
|
|
31
43
|
}
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
const customFilterFunction = column.filterFunction ?? column.filterFn;
|
|
45
|
+
if (customFilterFunction) {
|
|
46
|
+
return true;
|
|
34
47
|
}
|
|
35
|
-
const candidate = resolveColumnValue(
|
|
48
|
+
const candidate = resolveColumnValue(
|
|
49
|
+
row,
|
|
50
|
+
column.filterKey ?? column.key
|
|
51
|
+
);
|
|
36
52
|
const candidateText = String(candidate ?? "").toLowerCase();
|
|
37
53
|
const filterText = String(filterValue ?? "").toLowerCase();
|
|
38
54
|
return candidateText.includes(filterText);
|
|
@@ -47,8 +63,13 @@ export function useNuxtTable(options) {
|
|
|
47
63
|
if (!activeColumn) {
|
|
48
64
|
return filteredRows.value;
|
|
49
65
|
}
|
|
50
|
-
const
|
|
66
|
+
const sortDirection = sortState.value.direction;
|
|
67
|
+
const directionMultiplier = sortDirection === "asc" ? 1 : -1;
|
|
51
68
|
const accessor = activeColumn.sortKey ?? activeColumn.key;
|
|
69
|
+
const customSortFunction = activeColumn.sortFunction;
|
|
70
|
+
if (customSortFunction) {
|
|
71
|
+
return filteredRows.value;
|
|
72
|
+
}
|
|
52
73
|
return [...filteredRows.value].sort((leftRow, rightRow) => {
|
|
53
74
|
const leftValue = resolveColumnValue(leftRow, accessor);
|
|
54
75
|
const rightValue = resolveColumnValue(rightRow, accessor);
|
|
@@ -73,9 +94,18 @@ export function useNuxtTable(options) {
|
|
|
73
94
|
if (!hasLoadedPersistence.value || !import.meta.client) {
|
|
74
95
|
return;
|
|
75
96
|
}
|
|
76
|
-
localStorage.setItem(
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
localStorage.setItem(
|
|
98
|
+
buildStorageKey("order"),
|
|
99
|
+
JSON.stringify(columnOrder.value)
|
|
100
|
+
);
|
|
101
|
+
localStorage.setItem(
|
|
102
|
+
buildStorageKey("enabledColumns"),
|
|
103
|
+
JSON.stringify(enabledColumnKeys.value)
|
|
104
|
+
);
|
|
105
|
+
localStorage.setItem(
|
|
106
|
+
buildStorageKey("widths"),
|
|
107
|
+
JSON.stringify(columnWidths.value)
|
|
108
|
+
);
|
|
79
109
|
},
|
|
80
110
|
{ deep: true }
|
|
81
111
|
);
|
|
@@ -88,7 +118,9 @@ export function useNuxtTable(options) {
|
|
|
88
118
|
columnOrder.value = [...currentKeys];
|
|
89
119
|
} else {
|
|
90
120
|
const currentKeySet = new Set(currentKeys);
|
|
91
|
-
const keptKeys = columnOrder.value.filter(
|
|
121
|
+
const keptKeys = columnOrder.value.filter(
|
|
122
|
+
(key) => currentKeySet.has(key)
|
|
123
|
+
);
|
|
92
124
|
const newKeys = currentKeys.filter((key) => !keptKeys.includes(key));
|
|
93
125
|
columnOrder.value = [...keptKeys, ...newKeys];
|
|
94
126
|
}
|
|
@@ -96,8 +128,12 @@ export function useNuxtTable(options) {
|
|
|
96
128
|
enabledColumnKeys.value = [...currentKeys];
|
|
97
129
|
} else {
|
|
98
130
|
const currentKeySet = new Set(currentKeys);
|
|
99
|
-
const keptEnabledKeys = enabledColumnKeys.value.filter(
|
|
100
|
-
|
|
131
|
+
const keptEnabledKeys = enabledColumnKeys.value.filter(
|
|
132
|
+
(key) => currentKeySet.has(key)
|
|
133
|
+
);
|
|
134
|
+
const missingEnabledKeys = currentKeys.filter(
|
|
135
|
+
(key) => !keptEnabledKeys.includes(key)
|
|
136
|
+
);
|
|
101
137
|
enabledColumnKeys.value = [...keptEnabledKeys, ...missingEnabledKeys];
|
|
102
138
|
}
|
|
103
139
|
const nextFilters = {};
|
|
@@ -123,20 +159,28 @@ export function useNuxtTable(options) {
|
|
|
123
159
|
if (persistedOrder) {
|
|
124
160
|
const parsedOrder = JSON.parse(persistedOrder);
|
|
125
161
|
if (Array.isArray(parsedOrder)) {
|
|
126
|
-
const validPersistedOrder = parsedOrder.filter(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
162
|
+
const validPersistedOrder = parsedOrder.filter(
|
|
163
|
+
(key) => {
|
|
164
|
+
return typeof key === "string" && availableColumnKeys.value.includes(key);
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
const missingKeys = availableColumnKeys.value.filter(
|
|
168
|
+
(key) => !validPersistedOrder.includes(key)
|
|
169
|
+
);
|
|
130
170
|
columnOrder.value = [...validPersistedOrder, ...missingKeys];
|
|
131
171
|
}
|
|
132
172
|
}
|
|
133
|
-
const persistedEnabledColumns = localStorage.getItem(
|
|
173
|
+
const persistedEnabledColumns = localStorage.getItem(
|
|
174
|
+
buildStorageKey("enabledColumns")
|
|
175
|
+
);
|
|
134
176
|
if (persistedEnabledColumns) {
|
|
135
177
|
const parsedEnabledColumns = JSON.parse(persistedEnabledColumns);
|
|
136
178
|
if (Array.isArray(parsedEnabledColumns)) {
|
|
137
|
-
enabledColumnKeys.value = parsedEnabledColumns.filter(
|
|
138
|
-
|
|
139
|
-
|
|
179
|
+
enabledColumnKeys.value = parsedEnabledColumns.filter(
|
|
180
|
+
(key) => {
|
|
181
|
+
return typeof key === "string" && availableColumnKeys.value.includes(key);
|
|
182
|
+
}
|
|
183
|
+
);
|
|
140
184
|
}
|
|
141
185
|
}
|
|
142
186
|
const persistedWidths = localStorage.getItem(buildStorageKey("widths"));
|
|
@@ -174,38 +218,71 @@ export function useNuxtTable(options) {
|
|
|
174
218
|
}
|
|
175
219
|
if (!sortState.value || sortState.value.key !== column.key) {
|
|
176
220
|
sortState.value = { key: column.key, direction: "asc" };
|
|
221
|
+
if (column.sortFunction) {
|
|
222
|
+
options.onManualSortChange?.({
|
|
223
|
+
columnKey: column.key,
|
|
224
|
+
direction: "asc",
|
|
225
|
+
column,
|
|
226
|
+
rows: [...options.rows.value],
|
|
227
|
+
filters: { ...filters.value }
|
|
228
|
+
});
|
|
229
|
+
}
|
|
177
230
|
return;
|
|
178
231
|
}
|
|
179
232
|
if (sortState.value.direction === "asc") {
|
|
180
233
|
sortState.value = { key: column.key, direction: "desc" };
|
|
234
|
+
if (column.sortFunction) {
|
|
235
|
+
options.onManualSortChange?.({
|
|
236
|
+
columnKey: column.key,
|
|
237
|
+
direction: "desc",
|
|
238
|
+
column,
|
|
239
|
+
rows: [...options.rows.value],
|
|
240
|
+
filters: { ...filters.value }
|
|
241
|
+
});
|
|
242
|
+
}
|
|
181
243
|
return;
|
|
182
244
|
}
|
|
183
245
|
sortState.value = null;
|
|
246
|
+
if (column.sortFunction) {
|
|
247
|
+
options.onManualSortChange?.({
|
|
248
|
+
columnKey: column.key,
|
|
249
|
+
direction: null,
|
|
250
|
+
column,
|
|
251
|
+
rows: [...options.rows.value],
|
|
252
|
+
filters: { ...filters.value }
|
|
253
|
+
});
|
|
254
|
+
}
|
|
184
255
|
}
|
|
185
256
|
function setFilter(columnKey, value) {
|
|
186
257
|
filters.value[columnKey] = value;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
for (const key of availableColumnKeys.value) {
|
|
191
|
-
nextFilters[key] = "";
|
|
258
|
+
const column = columnsByKey.value.get(columnKey);
|
|
259
|
+
if (!column) {
|
|
260
|
+
return;
|
|
192
261
|
}
|
|
193
|
-
|
|
262
|
+
const customFilterFunction = column.filterFunction ?? column.filterFn;
|
|
263
|
+
if (!customFilterFunction) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
options.onManualFilterChange?.({
|
|
267
|
+
columnKey,
|
|
268
|
+
value,
|
|
269
|
+
column,
|
|
270
|
+
rows: [...options.rows.value],
|
|
271
|
+
filters: { ...filters.value }
|
|
272
|
+
});
|
|
194
273
|
}
|
|
195
274
|
function toggleColumn(columnKey) {
|
|
196
275
|
if (enabledColumnKeys.value.includes(columnKey)) {
|
|
197
276
|
if (enabledColumnKeys.value.length === 1) {
|
|
198
277
|
return;
|
|
199
278
|
}
|
|
200
|
-
enabledColumnKeys.value = enabledColumnKeys.value.filter(
|
|
279
|
+
enabledColumnKeys.value = enabledColumnKeys.value.filter(
|
|
280
|
+
(key) => key !== columnKey
|
|
281
|
+
);
|
|
201
282
|
return;
|
|
202
283
|
}
|
|
203
284
|
enabledColumnKeys.value = [...enabledColumnKeys.value, columnKey];
|
|
204
285
|
}
|
|
205
|
-
function resetColumns() {
|
|
206
|
-
columnOrder.value = [...availableColumnKeys.value];
|
|
207
|
-
enabledColumnKeys.value = [...availableColumnKeys.value];
|
|
208
|
-
}
|
|
209
286
|
function onHeaderDragStart(columnKey) {
|
|
210
287
|
if (!options.enableColumnDnd.value || activeResize.value) {
|
|
211
288
|
return;
|
|
@@ -299,7 +376,10 @@ export function useNuxtTable(options) {
|
|
|
299
376
|
return;
|
|
300
377
|
}
|
|
301
378
|
const delta = event.clientX - activeResize.value.startX;
|
|
302
|
-
const nextWidth = Math.max(
|
|
379
|
+
const nextWidth = Math.max(
|
|
380
|
+
MIN_COLUMN_WIDTH,
|
|
381
|
+
Math.round(activeResize.value.startWidth + delta)
|
|
382
|
+
);
|
|
303
383
|
columnWidths.value = {
|
|
304
384
|
...columnWidths.value,
|
|
305
385
|
[activeResize.value.columnKey]: nextWidth
|
|
@@ -393,7 +473,9 @@ export function useNuxtTable(options) {
|
|
|
393
473
|
return value.length > 0;
|
|
394
474
|
}
|
|
395
475
|
if (typeof value === "object") {
|
|
396
|
-
return Object.values(value).some(
|
|
476
|
+
return Object.values(value).some(
|
|
477
|
+
(nestedValue) => isFilterActive(nestedValue)
|
|
478
|
+
);
|
|
397
479
|
}
|
|
398
480
|
return true;
|
|
399
481
|
}
|
|
@@ -430,16 +512,13 @@ export function useNuxtTable(options) {
|
|
|
430
512
|
visibleColumns,
|
|
431
513
|
sortedRows,
|
|
432
514
|
filters,
|
|
433
|
-
isColumnManagerOpen,
|
|
434
515
|
enabledColumnKeys,
|
|
435
516
|
dragSourceColumnKey,
|
|
436
517
|
dragOverColumnKey,
|
|
437
518
|
getSortDirection,
|
|
438
519
|
toggleSort,
|
|
439
520
|
setFilter,
|
|
440
|
-
clearAllFilters,
|
|
441
521
|
toggleColumn,
|
|
442
|
-
resetColumns,
|
|
443
522
|
onHeaderDragStart,
|
|
444
523
|
onHeaderDragOver,
|
|
445
524
|
onHeaderDragLeave,
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { useNuxtTable } from
|
|
2
|
-
export type { NuxtTableClassNames, NuxtTableColumn, NuxtTableColumnOrderChange, TableRow, UseNuxtTableOptions, ValueResolver, } from
|
|
1
|
+
export { useNuxtTable } from "./composables/useNuxtTable.js";
|
|
2
|
+
export type { NuxtTableClassNames, NuxtTableColumn, NuxtTableColumnOrderChange, NuxtTableManualFilterChange, NuxtTableManualSortChange, TableRow, UseNuxtTableOptions, ValueResolver, } from "./types/table.js";
|
|
@@ -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,10 +6,15 @@ 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;
|
|
13
|
+
sortFunction?: (leftRow: TableRow, rightRow: TableRow, column: NuxtTableColumn, tableRows: TableRow[], direction: "asc" | "desc") => number;
|
|
10
14
|
filterKey?: ValueResolver;
|
|
11
15
|
formatter?: (value: unknown, row: TableRow) => string;
|
|
12
|
-
|
|
16
|
+
filterFunction?: (row: TableRow, filterValue: unknown, column: NuxtTableColumn, tableRows: TableRow[]) => boolean;
|
|
17
|
+
filterFn?: (row: TableRow, filterValue: unknown, column: NuxtTableColumn, tableRows: TableRow[]) => boolean;
|
|
13
18
|
cellComponent?: Component;
|
|
14
19
|
filterComponent?: Component;
|
|
15
20
|
headerClassName?: string;
|
|
@@ -21,6 +26,20 @@ export interface NuxtTableColumnOrderChange {
|
|
|
21
26
|
fromIndex: number;
|
|
22
27
|
toIndex: number;
|
|
23
28
|
}
|
|
29
|
+
export interface NuxtTableManualSortChange {
|
|
30
|
+
columnKey: string;
|
|
31
|
+
direction: "asc" | "desc" | null;
|
|
32
|
+
column: NuxtTableColumn;
|
|
33
|
+
rows: TableRow[];
|
|
34
|
+
filters: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
export interface NuxtTableManualFilterChange {
|
|
37
|
+
columnKey: string;
|
|
38
|
+
value: unknown;
|
|
39
|
+
column: NuxtTableColumn;
|
|
40
|
+
rows: TableRow[];
|
|
41
|
+
filters: Record<string, unknown>;
|
|
42
|
+
}
|
|
24
43
|
export interface NuxtTableClassNames {
|
|
25
44
|
root: string;
|
|
26
45
|
toolbar: string;
|
|
@@ -53,4 +72,6 @@ export interface UseNuxtTableOptions {
|
|
|
53
72
|
rowKey: Ref<string | ((row: TableRow, index: number) => string | number)>;
|
|
54
73
|
enableColumnDnd: Ref<boolean>;
|
|
55
74
|
onColumnOrderChange?: (payload: NuxtTableColumnOrderChange) => void;
|
|
75
|
+
onManualSortChange?: (payload: NuxtTableManualSortChange) => void;
|
|
76
|
+
onManualFilterChange?: (payload: NuxtTableManualFilterChange) => void;
|
|
56
77
|
}
|
package/package.json
CHANGED