nuxt-ui-tools 1.28.0 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/form/components/page/form-page-section.vue +4 -1
- package/dist/runtime/form/types/page.d.ts +6 -0
- package/dist/runtime/i18n/locales/en.js +5 -0
- package/dist/runtime/i18n/locales/fr.js +5 -0
- package/dist/runtime/i18n/types.d.ts +7 -0
- package/dist/runtime/shared/composables/use-sheet-navigation.d.ts +25 -0
- package/dist/runtime/shared/composables/use-sheet-navigation.js +41 -0
- package/dist/runtime/shared/index.d.ts +1 -0
- package/dist/runtime/shared/index.js +1 -0
- package/dist/runtime/table/components/grid/grid-renderer.vue +33 -8
- package/dist/runtime/table/components/group-header.d.vue.ts +30 -0
- package/dist/runtime/table/components/group-header.vue +44 -0
- package/dist/runtime/table/components/group-header.vue.d.ts +30 -0
- package/dist/runtime/table/components/table/table-group-header.d.vue.ts +9 -0
- package/dist/runtime/table/components/table/table-group-header.vue +56 -0
- package/dist/runtime/table/components/table/table-group-header.vue.d.ts +9 -0
- package/dist/runtime/table/components/table/table-renderer.vue +12 -7
- package/dist/runtime/table/components/table/table-row.d.vue.ts +4 -1
- package/dist/runtime/table/components/table/table-row.vue +16 -1
- package/dist/runtime/table/components/table/table-row.vue.d.ts +4 -1
- package/dist/runtime/table/components/table/table-selection-control.d.vue.ts +2 -0
- package/dist/runtime/table/components/table/table-selection-control.vue +3 -2
- package/dist/runtime/table/components/table/table-selection-control.vue.d.ts +2 -0
- package/dist/runtime/table/composables/use-table-grid.d.ts +7 -3
- package/dist/runtime/table/composables/use-table-grid.js +27 -2
- package/dist/runtime/table/composables/use-table-groups.d.ts +28 -0
- package/dist/runtime/table/composables/use-table-groups.js +133 -0
- package/dist/runtime/table/composables/use-table-internals.d.ts +42 -0
- package/dist/runtime/table/composables/use-table-internals.js +9 -0
- package/dist/runtime/table/composables/use-table-selection.d.ts +5 -0
- package/dist/runtime/table/composables/use-table-selection.js +13 -3
- package/dist/runtime/table/composables/use-table-tree.js +7 -5
- package/dist/runtime/table/constants/row-motion.d.ts +8 -0
- package/dist/runtime/table/constants/row-motion.js +5 -0
- package/dist/runtime/table/types/groups.d.ts +44 -0
- package/dist/runtime/table/types/groups.js +0 -0
- package/dist/runtime/table/types/index.d.ts +1 -0
- package/dist/runtime/table/types/layout.d.ts +5 -0
- package/dist/runtime/table/utils/groups.d.ts +14 -0
- package/dist/runtime/table/utils/groups.js +55 -0
- package/dist/runtime/table/utils/index.d.ts +1 -0
- package/dist/runtime/table/utils/index.js +1 -0
- package/package.json +1 -1
|
@@ -19,9 +19,15 @@ export function useTableGrid(options) {
|
|
|
19
19
|
const cardsPerRow = computed(
|
|
20
20
|
() => Math.max(1, Math.floor(columnCount.value / itemColumnSpan.value))
|
|
21
21
|
);
|
|
22
|
-
const rows = computed(
|
|
22
|
+
const rows = computed(
|
|
23
|
+
() => options.groups.enabled.value ? options.groups.orderedRows.value : options.data.value.rows
|
|
24
|
+
);
|
|
23
25
|
const rowChunks = computed(
|
|
24
|
-
() =>
|
|
26
|
+
() => options.groups.enabled.value ? chunkGroups({
|
|
27
|
+
cardsPerRow: cardsPerRow.value,
|
|
28
|
+
groups: options.groups.groups.value,
|
|
29
|
+
isCollapsed: options.groups.isCollapsed
|
|
30
|
+
}) : chunkRows({
|
|
25
31
|
cardsPerRow: cardsPerRow.value,
|
|
26
32
|
rows: rows.value
|
|
27
33
|
})
|
|
@@ -48,6 +54,25 @@ function resolveResponsiveGridNumber(value) {
|
|
|
48
54
|
function isResponsiveGridValueResolver(value) {
|
|
49
55
|
return isFunction(value);
|
|
50
56
|
}
|
|
57
|
+
function chunkGroups(options) {
|
|
58
|
+
const chunks = [];
|
|
59
|
+
let offset = 0;
|
|
60
|
+
for (const group of options.groups) {
|
|
61
|
+
chunks.push({ end: offset, group, index: chunks.length, rows: [], start: offset });
|
|
62
|
+
if (!options.isCollapsed(group)) {
|
|
63
|
+
for (const chunk of chunkRows({ cardsPerRow: options.cardsPerRow, rows: group.rows })) {
|
|
64
|
+
chunks.push({
|
|
65
|
+
...chunk,
|
|
66
|
+
end: offset + chunk.end,
|
|
67
|
+
index: chunks.length,
|
|
68
|
+
start: offset + chunk.start
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
offset += group.rows.length;
|
|
73
|
+
}
|
|
74
|
+
return chunks;
|
|
75
|
+
}
|
|
51
76
|
function chunkRows(options) {
|
|
52
77
|
return Array.from(
|
|
53
78
|
{ length: Math.ceil(options.rows.length / options.cardsPerRow) },
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ComputedRef } from 'vue';
|
|
2
|
+
import type { TableGroup, TableSchemaView, TableTreeMotion } from '../types/index.js';
|
|
3
|
+
import type { UseTableDataReturn } from './use-table-data.js';
|
|
4
|
+
import type { useTableLayout } from './use-table-layout.js';
|
|
5
|
+
export interface UseTableGroupsParams {
|
|
6
|
+
schema: ComputedRef<TableSchemaView>;
|
|
7
|
+
layout: ReturnType<typeof useTableLayout>;
|
|
8
|
+
queryContent: Pick<UseTableDataReturn, 'data'>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Owns row groups: which groups the loaded rows form, which are folded, and the motion of folding
|
|
12
|
+
* one. The data stays flat; `displayRows` adds the header rows to what the table renders.
|
|
13
|
+
*
|
|
14
|
+
* Fold state is the schema's `collapsed` plus the groups the reader toggled, kept per table so a
|
|
15
|
+
* trip to a record and back finds the list as it was left. A table with `table.tree` has its own
|
|
16
|
+
* hierarchy and is not grouped.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useTableGroups(params: UseTableGroupsParams): {
|
|
19
|
+
config: ComputedRef<import("../types/index.js").TableGroupBySchema<object> | undefined>;
|
|
20
|
+
displayRows: ComputedRef<object[]>;
|
|
21
|
+
enabled: ComputedRef<boolean>;
|
|
22
|
+
groups: ComputedRef<TableGroup<object>[]>;
|
|
23
|
+
isCollapsed: (group: TableGroup) => boolean;
|
|
24
|
+
motionOf: (rowId: string) => TableTreeMotion | undefined;
|
|
25
|
+
orderedRows: ComputedRef<object[]>;
|
|
26
|
+
toggle: (group: TableGroup) => void;
|
|
27
|
+
visibleRowIds: ComputedRef<string[]>;
|
|
28
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { usePreferredReducedMotion } from "@vueuse/core";
|
|
2
|
+
import { computed, getCurrentInstance, onMounted, onScopeDispose, ref, shallowRef } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
ROW_CONCEAL_MS,
|
|
5
|
+
ROW_MOTION_LIMIT,
|
|
6
|
+
ROW_REVEAL_MS,
|
|
7
|
+
ROW_STAGGER_LIMIT,
|
|
8
|
+
ROW_STAGGER_MS
|
|
9
|
+
} from "../constants/row-motion.js";
|
|
10
|
+
import {
|
|
11
|
+
buildTableGroups,
|
|
12
|
+
parseStoredGroupKeys,
|
|
13
|
+
tableGroupRow,
|
|
14
|
+
tableGroupsStorageKey
|
|
15
|
+
} from "../utils/groups.js";
|
|
16
|
+
import { resolveTableRowId } from "../utils/rows.js";
|
|
17
|
+
export function useTableGroups(params) {
|
|
18
|
+
const reducedMotion = usePreferredReducedMotion();
|
|
19
|
+
const config = computed(() => {
|
|
20
|
+
const { grid, table } = params.schema.value;
|
|
21
|
+
if (params.layout.activeLayout.value === "grid") return grid?.groupBy;
|
|
22
|
+
return table?.tree ? void 0 : table?.groupBy;
|
|
23
|
+
});
|
|
24
|
+
const enabled = computed(() => Boolean(config.value));
|
|
25
|
+
const groups = computed(() => {
|
|
26
|
+
const groupBy = config.value;
|
|
27
|
+
if (!groupBy) return [];
|
|
28
|
+
return buildTableGroups({
|
|
29
|
+
getRowId: (row, index) => String(resolveTableRowId({ index, row, rowKey: params.schema.value.rowKey })),
|
|
30
|
+
groupBy,
|
|
31
|
+
rows: params.queryContent.data.value.rows
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
const toggled = ref(/* @__PURE__ */ new Set());
|
|
35
|
+
const persists = computed(() => params.schema.value.persistence?.preferences !== false);
|
|
36
|
+
function storage() {
|
|
37
|
+
try {
|
|
38
|
+
return typeof window === "undefined" || !persists.value ? null : window.localStorage;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function restore() {
|
|
44
|
+
try {
|
|
45
|
+
const raw = storage()?.getItem(tableGroupsStorageKey(params.schema.value.tableKey)) ?? null;
|
|
46
|
+
toggled.value = new Set(parseStoredGroupKeys(raw));
|
|
47
|
+
} catch {
|
|
48
|
+
toggled.value = /* @__PURE__ */ new Set();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function remember() {
|
|
52
|
+
const key = tableGroupsStorageKey(params.schema.value.tableKey);
|
|
53
|
+
try {
|
|
54
|
+
if (toggled.value.size) storage()?.setItem(key, JSON.stringify([...toggled.value]));
|
|
55
|
+
else storage()?.removeItem(key);
|
|
56
|
+
} catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (getCurrentInstance()) onMounted(restore);
|
|
61
|
+
else restore();
|
|
62
|
+
function startsCollapsed(group) {
|
|
63
|
+
const collapsed = config.value?.collapsed;
|
|
64
|
+
return typeof collapsed === "function" ? collapsed({ rows: group.rows, value: group.value }) : Boolean(collapsed);
|
|
65
|
+
}
|
|
66
|
+
function isCollapsed(group) {
|
|
67
|
+
return startsCollapsed(group) !== toggled.value.has(group.key);
|
|
68
|
+
}
|
|
69
|
+
function flip(group) {
|
|
70
|
+
const next = new Set(toggled.value);
|
|
71
|
+
if (!next.delete(group.key)) next.add(group.key);
|
|
72
|
+
toggled.value = next;
|
|
73
|
+
remember();
|
|
74
|
+
}
|
|
75
|
+
const motion = shallowRef(null);
|
|
76
|
+
const closing = ref(null);
|
|
77
|
+
let timer;
|
|
78
|
+
function settle() {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
closing.value = null;
|
|
81
|
+
motion.value = null;
|
|
82
|
+
}
|
|
83
|
+
function toggle(group) {
|
|
84
|
+
settle();
|
|
85
|
+
const opening = isCollapsed(group);
|
|
86
|
+
flip(group);
|
|
87
|
+
if (reducedMotion.value === "reduce" || params.layout.activeLayout.value === "grid") return;
|
|
88
|
+
const moving = group.rowIds.slice(0, ROW_MOTION_LIMIT);
|
|
89
|
+
motion.value = new Map(
|
|
90
|
+
moving.map((rowId, order) => [
|
|
91
|
+
rowId,
|
|
92
|
+
{ kind: opening ? "enter" : "leave", order: Math.min(order, ROW_STAGGER_LIMIT) }
|
|
93
|
+
])
|
|
94
|
+
);
|
|
95
|
+
if (opening) {
|
|
96
|
+
timer = setTimeout(
|
|
97
|
+
settle,
|
|
98
|
+
ROW_REVEAL_MS + Math.min(moving.length, ROW_STAGGER_LIMIT) * ROW_STAGGER_MS
|
|
99
|
+
);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
closing.value = group.key;
|
|
103
|
+
timer = setTimeout(settle, ROW_CONCEAL_MS);
|
|
104
|
+
}
|
|
105
|
+
onScopeDispose(() => clearTimeout(timer));
|
|
106
|
+
function motionOf(rowId) {
|
|
107
|
+
return motion.value?.get(rowId);
|
|
108
|
+
}
|
|
109
|
+
function rowsListed(group) {
|
|
110
|
+
return !isCollapsed(group) || closing.value === group.key;
|
|
111
|
+
}
|
|
112
|
+
const displayRows = computed(
|
|
113
|
+
() => groups.value.flatMap((group) => [
|
|
114
|
+
tableGroupRow(group),
|
|
115
|
+
...rowsListed(group) ? group.rows : []
|
|
116
|
+
])
|
|
117
|
+
);
|
|
118
|
+
const orderedRows = computed(() => groups.value.flatMap((group) => group.rows));
|
|
119
|
+
const visibleRowIds = computed(
|
|
120
|
+
() => groups.value.flatMap((group) => isCollapsed(group) ? [] : group.rowIds)
|
|
121
|
+
);
|
|
122
|
+
return {
|
|
123
|
+
config,
|
|
124
|
+
displayRows,
|
|
125
|
+
enabled,
|
|
126
|
+
groups,
|
|
127
|
+
isCollapsed,
|
|
128
|
+
motionOf,
|
|
129
|
+
orderedRows,
|
|
130
|
+
toggle,
|
|
131
|
+
visibleRowIds
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -228,6 +228,17 @@ declare function createTableInternals<TSchema>(options: {
|
|
|
228
228
|
rowChunks: import("vue").ComputedRef<import("./use-table-grid.js").GridRowChunk<object>[]>;
|
|
229
229
|
rows: import("vue").ComputedRef<object[]>;
|
|
230
230
|
};
|
|
231
|
+
groups: {
|
|
232
|
+
config: import("vue").ComputedRef<import("../types/index.js").TableGroupBySchema<object> | undefined>;
|
|
233
|
+
displayRows: import("vue").ComputedRef<object[]>;
|
|
234
|
+
enabled: import("vue").ComputedRef<boolean>;
|
|
235
|
+
groups: import("vue").ComputedRef<import("../types/index.js").TableGroup<object>[]>;
|
|
236
|
+
isCollapsed: (group: import("../types/index.js").TableGroup) => boolean;
|
|
237
|
+
motionOf: (rowId: string) => import("../types/index.js").TableTreeMotion | undefined;
|
|
238
|
+
orderedRows: import("vue").ComputedRef<object[]>;
|
|
239
|
+
toggle: (group: import("../types/index.js").TableGroup) => void;
|
|
240
|
+
visibleRowIds: import("vue").ComputedRef<string[]>;
|
|
241
|
+
};
|
|
231
242
|
layout: {
|
|
232
243
|
activeLayout: import("vue").WritableComputedRef<import("../types/index.js").TableLayout, import("../types/index.js").TableLayout>;
|
|
233
244
|
gridEnabled: import("vue").ComputedRef<boolean>;
|
|
@@ -345,6 +356,9 @@ declare function createTableInternals<TSchema>(options: {
|
|
|
345
356
|
row: import("../types/index.js").GenericObject;
|
|
346
357
|
index?: number;
|
|
347
358
|
}) => string;
|
|
359
|
+
getRowsSelectionState: (params: {
|
|
360
|
+
rowIds: string[];
|
|
361
|
+
}) => boolean | "indeterminate";
|
|
348
362
|
isRowSelected: (params: {
|
|
349
363
|
rowId: string;
|
|
350
364
|
}) => boolean;
|
|
@@ -851,6 +865,17 @@ declare function useProvideTableInternals<TSchema>(options: {
|
|
|
851
865
|
rowChunks: import("vue").ComputedRef<import("./use-table-grid.js").GridRowChunk<object>[]>;
|
|
852
866
|
rows: import("vue").ComputedRef<object[]>;
|
|
853
867
|
};
|
|
868
|
+
groups: {
|
|
869
|
+
config: import("vue").ComputedRef<import("../types/index.js").TableGroupBySchema<object> | undefined>;
|
|
870
|
+
displayRows: import("vue").ComputedRef<object[]>;
|
|
871
|
+
enabled: import("vue").ComputedRef<boolean>;
|
|
872
|
+
groups: import("vue").ComputedRef<import("../types/index.js").TableGroup<object>[]>;
|
|
873
|
+
isCollapsed: (group: import("../types/index.js").TableGroup) => boolean;
|
|
874
|
+
motionOf: (rowId: string) => import("../types/index.js").TableTreeMotion | undefined;
|
|
875
|
+
orderedRows: import("vue").ComputedRef<object[]>;
|
|
876
|
+
toggle: (group: import("../types/index.js").TableGroup) => void;
|
|
877
|
+
visibleRowIds: import("vue").ComputedRef<string[]>;
|
|
878
|
+
};
|
|
854
879
|
layout: {
|
|
855
880
|
activeLayout: import("vue").WritableComputedRef<import("../types/index.js").TableLayout, import("../types/index.js").TableLayout>;
|
|
856
881
|
gridEnabled: import("vue").ComputedRef<boolean>;
|
|
@@ -968,6 +993,9 @@ declare function useProvideTableInternals<TSchema>(options: {
|
|
|
968
993
|
row: import("../types/index.js").GenericObject;
|
|
969
994
|
index?: number;
|
|
970
995
|
}) => string;
|
|
996
|
+
getRowsSelectionState: (params: {
|
|
997
|
+
rowIds: string[];
|
|
998
|
+
}) => boolean | "indeterminate";
|
|
971
999
|
isRowSelected: (params: {
|
|
972
1000
|
rowId: string;
|
|
973
1001
|
}) => boolean;
|
|
@@ -1471,6 +1499,17 @@ declare function useTableInternals(): {
|
|
|
1471
1499
|
rowChunks: import("vue").ComputedRef<import("./use-table-grid.js").GridRowChunk<object>[]>;
|
|
1472
1500
|
rows: import("vue").ComputedRef<object[]>;
|
|
1473
1501
|
};
|
|
1502
|
+
groups: {
|
|
1503
|
+
config: import("vue").ComputedRef<import("../types/index.js").TableGroupBySchema<object> | undefined>;
|
|
1504
|
+
displayRows: import("vue").ComputedRef<object[]>;
|
|
1505
|
+
enabled: import("vue").ComputedRef<boolean>;
|
|
1506
|
+
groups: import("vue").ComputedRef<import("../types/index.js").TableGroup<object>[]>;
|
|
1507
|
+
isCollapsed: (group: import("../types/index.js").TableGroup) => boolean;
|
|
1508
|
+
motionOf: (rowId: string) => import("../types/index.js").TableTreeMotion | undefined;
|
|
1509
|
+
orderedRows: import("vue").ComputedRef<object[]>;
|
|
1510
|
+
toggle: (group: import("../types/index.js").TableGroup) => void;
|
|
1511
|
+
visibleRowIds: import("vue").ComputedRef<string[]>;
|
|
1512
|
+
};
|
|
1474
1513
|
layout: {
|
|
1475
1514
|
activeLayout: import("vue").WritableComputedRef<import("../types/index.js").TableLayout, import("../types/index.js").TableLayout>;
|
|
1476
1515
|
gridEnabled: import("vue").ComputedRef<boolean>;
|
|
@@ -1588,6 +1627,9 @@ declare function useTableInternals(): {
|
|
|
1588
1627
|
row: import("../types/index.js").GenericObject;
|
|
1589
1628
|
index?: number;
|
|
1590
1629
|
}) => string;
|
|
1630
|
+
getRowsSelectionState: (params: {
|
|
1631
|
+
rowIds: string[];
|
|
1632
|
+
}) => boolean | "indeterminate";
|
|
1591
1633
|
isRowSelected: (params: {
|
|
1592
1634
|
rowId: string;
|
|
1593
1635
|
}) => boolean;
|
|
@@ -8,6 +8,7 @@ import { useTableData } from "./use-table-data.js";
|
|
|
8
8
|
import { useTableFilterPresentation } from "./use-table-filter-presentation.js";
|
|
9
9
|
import { useTableFilters } from "./use-table-filters.js";
|
|
10
10
|
import { useTableGrid } from "./use-table-grid.js";
|
|
11
|
+
import { useTableGroups } from "./use-table-groups.js";
|
|
11
12
|
import { useTableLayout } from "./use-table-layout.js";
|
|
12
13
|
import { useTablePagination } from "./use-table-pagination.js";
|
|
13
14
|
import { useTableRowSummary } from "./use-table-row-summary.js";
|
|
@@ -35,7 +36,13 @@ function createTableInternals(options) {
|
|
|
35
36
|
queryContent,
|
|
36
37
|
schema
|
|
37
38
|
});
|
|
39
|
+
const groups = useTableGroups({
|
|
40
|
+
layout,
|
|
41
|
+
queryContent,
|
|
42
|
+
schema
|
|
43
|
+
});
|
|
38
44
|
const selection = useTableSelection({
|
|
45
|
+
groups,
|
|
39
46
|
queryContent,
|
|
40
47
|
schema,
|
|
41
48
|
tree
|
|
@@ -60,6 +67,7 @@ function createTableInternals(options) {
|
|
|
60
67
|
});
|
|
61
68
|
const grid = useTableGrid({
|
|
62
69
|
data: queryContent.data,
|
|
70
|
+
groups,
|
|
63
71
|
schema
|
|
64
72
|
});
|
|
65
73
|
const tableColumns = useTableColumns({
|
|
@@ -112,6 +120,7 @@ function createTableInternals(options) {
|
|
|
112
120
|
filterPresentation,
|
|
113
121
|
filters,
|
|
114
122
|
grid,
|
|
123
|
+
groups,
|
|
115
124
|
layout,
|
|
116
125
|
pagination,
|
|
117
126
|
queryContent,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { ComputedRef } from 'vue';
|
|
2
2
|
import type { GenericObject, TableSchemaView } from '../types/index.js';
|
|
3
3
|
import type { useTableData } from './use-table-data.js';
|
|
4
|
+
import type { useTableGroups } from './use-table-groups.js';
|
|
4
5
|
import type { useTableTree } from './use-table-tree.js';
|
|
5
6
|
export interface UseTableSelectionParams {
|
|
6
7
|
schema: ComputedRef<TableSchemaView>;
|
|
7
8
|
queryContent: ReturnType<typeof useTableData>;
|
|
8
9
|
tree: ReturnType<typeof useTableTree>;
|
|
10
|
+
groups: ReturnType<typeof useTableGroups>;
|
|
9
11
|
}
|
|
10
12
|
export declare function useTableSelection(options: UseTableSelectionParams): {
|
|
11
13
|
allSelected: ComputedRef<boolean>;
|
|
@@ -15,6 +17,9 @@ export declare function useTableSelection(options: UseTableSelectionParams): {
|
|
|
15
17
|
row: GenericObject;
|
|
16
18
|
index?: number;
|
|
17
19
|
}) => string;
|
|
20
|
+
getRowsSelectionState: (params: {
|
|
21
|
+
rowIds: string[];
|
|
22
|
+
}) => boolean | "indeterminate";
|
|
18
23
|
isRowSelected: (params: {
|
|
19
24
|
rowId: string;
|
|
20
25
|
}) => boolean;
|
|
@@ -61,6 +61,12 @@ export function useTableSelection(options) {
|
|
|
61
61
|
function isRowSelected(params) {
|
|
62
62
|
return selectedKeys.value.includes(params.rowId);
|
|
63
63
|
}
|
|
64
|
+
function getRowsSelectionState(params) {
|
|
65
|
+
const selected = new Set(selectedKeys.value);
|
|
66
|
+
const count = params.rowIds.filter((rowId) => selected.has(rowId)).length;
|
|
67
|
+
if (count === 0) return false;
|
|
68
|
+
return count === params.rowIds.length ? true : "indeterminate";
|
|
69
|
+
}
|
|
64
70
|
function selectRows(params) {
|
|
65
71
|
if (!selectionEnabled.value) {
|
|
66
72
|
return;
|
|
@@ -117,9 +123,12 @@ export function useTableSelection(options) {
|
|
|
117
123
|
}
|
|
118
124
|
lastTouchedRowId.value = params.rowId;
|
|
119
125
|
}
|
|
120
|
-
const rangeRowIds = computed(
|
|
121
|
-
(
|
|
122
|
-
|
|
126
|
+
const rangeRowIds = computed(() => {
|
|
127
|
+
if (options.tree.enabled.value) {
|
|
128
|
+
return options.tree.visibleNodes.value.filter((node) => options.tree.isSelectable(node)).map((node) => node.id);
|
|
129
|
+
}
|
|
130
|
+
return options.groups.enabled.value ? options.groups.visibleRowIds.value : scopeRowIds.value;
|
|
131
|
+
});
|
|
123
132
|
function getRangeRowIds(params) {
|
|
124
133
|
const anchorIndex = rangeRowIds.value.indexOf(params.anchorRowId);
|
|
125
134
|
const targetIndex = rangeRowIds.value.indexOf(params.targetRowId);
|
|
@@ -157,6 +166,7 @@ export function useTableSelection(options) {
|
|
|
157
166
|
bulkScope,
|
|
158
167
|
clearSelection,
|
|
159
168
|
getRowId,
|
|
169
|
+
getRowsSelectionState,
|
|
160
170
|
isRowSelected,
|
|
161
171
|
lastTouchedRowId,
|
|
162
172
|
partiallySelected,
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { usePreferredReducedMotion } from "@vueuse/core";
|
|
2
2
|
import { computed, onScopeDispose, reactive, ref, shallowRef } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
ROW_CONCEAL_MS as CONCEAL_MS,
|
|
5
|
+
ROW_MOTION_LIMIT as MOTION_ROW_LIMIT,
|
|
6
|
+
ROW_REVEAL_MS as REVEAL_MS,
|
|
7
|
+
ROW_STAGGER_LIMIT as STAGGER_ROW_LIMIT,
|
|
8
|
+
ROW_STAGGER_MS as STAGGER_MS
|
|
9
|
+
} from "../constants/row-motion.js";
|
|
3
10
|
import { resolveTableRowId } from "../utils/rows.js";
|
|
4
11
|
import {
|
|
5
12
|
buildTableTree,
|
|
@@ -8,11 +15,6 @@ import {
|
|
|
8
15
|
getVisibleDescendants,
|
|
9
16
|
isOpenByDefault
|
|
10
17
|
} from "../utils/tree.js";
|
|
11
|
-
const CONCEAL_MS = 120;
|
|
12
|
-
const REVEAL_MS = 190;
|
|
13
|
-
const STAGGER_MS = 24;
|
|
14
|
-
const MOTION_ROW_LIMIT = 24;
|
|
15
|
-
const STAGGER_ROW_LIMIT = 8;
|
|
16
18
|
const EMPTY_INDEX = {
|
|
17
19
|
branches: [],
|
|
18
20
|
byId: /* @__PURE__ */ new Map(),
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** How long a closing branch or group plays out before its rows leave the list. */
|
|
2
|
+
export declare const ROW_CONCEAL_MS = 120;
|
|
3
|
+
/** How long the last revealed row takes to settle; the row's CSS owns the actual timing. */
|
|
4
|
+
export declare const ROW_REVEAL_MS = 190;
|
|
5
|
+
export declare const ROW_STAGGER_MS = 24;
|
|
6
|
+
/** Rows beyond these start or stop without a transition: they are mostly off screen anyway. */
|
|
7
|
+
export declare const ROW_MOTION_LIMIT = 24;
|
|
8
|
+
export declare const ROW_STAGGER_LIMIT = 8;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { GenericObject, RenderableType, TableKnownFieldPath } from './utils.js';
|
|
2
|
+
/** What a group's `label`, `summary` and `collapsed` receive. */
|
|
3
|
+
export interface TableGroupParams<TRow extends GenericObject = GenericObject> {
|
|
4
|
+
/** The value its rows share; `undefined` for rows that have none. */
|
|
5
|
+
value: unknown;
|
|
6
|
+
/** Its rows among those loaded, in display order. */
|
|
7
|
+
rows: TRow[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Gathers rows that share a value under a header row that folds them.
|
|
11
|
+
*
|
|
12
|
+
* Groups exist only on screen: the data stays flat, so filters, pagination and bulk actions keep
|
|
13
|
+
* working on rows. They cover the rows that are loaded, so a remote source needs a page large
|
|
14
|
+
* enough to hold a whole group. A column sort applies inside each group.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* table: {
|
|
19
|
+
* groupBy: {
|
|
20
|
+
* key: 'country',
|
|
21
|
+
* label: ({ value }) => countryName(value),
|
|
22
|
+
* },
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export interface TableGroupBySchema<TRow extends GenericObject = GenericObject> {
|
|
27
|
+
/** The row field to group on, or a function for a value the row does not hold as a field. */
|
|
28
|
+
key: TableKnownFieldPath<TRow> | ((row: TRow) => unknown);
|
|
29
|
+
/** The header's text. Defaults to the value, or the locale's "Other" for rows with none. */
|
|
30
|
+
label?: (params: TableGroupParams<TRow>) => RenderableType;
|
|
31
|
+
/** Shown next to the label. Defaults to the number of rows. */
|
|
32
|
+
summary?: (params: TableGroupParams<TRow>) => RenderableType;
|
|
33
|
+
/** Values listed here come first, in this order. The rest follow by value, empty ones last. */
|
|
34
|
+
order?: readonly unknown[];
|
|
35
|
+
/** Whether a group starts folded. A group the reader has toggled keeps that state. */
|
|
36
|
+
collapsed?: boolean | ((params: TableGroupParams<TRow>) => boolean);
|
|
37
|
+
}
|
|
38
|
+
/** One group of the loaded rows, resolved once per data change. */
|
|
39
|
+
export interface TableGroup<TRow extends GenericObject = GenericObject> {
|
|
40
|
+
readonly key: string;
|
|
41
|
+
readonly value: unknown;
|
|
42
|
+
readonly rows: TRow[];
|
|
43
|
+
readonly rowIds: string[];
|
|
44
|
+
}
|
|
File without changes
|
|
@@ -3,6 +3,7 @@ export type * from './columns';
|
|
|
3
3
|
export type * from './context';
|
|
4
4
|
export type * from './data-list';
|
|
5
5
|
export type * from './filters';
|
|
6
|
+
export type * from './groups';
|
|
6
7
|
export type * from './layout';
|
|
7
8
|
export type * from './query-state';
|
|
8
9
|
export type * from './remote-options';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ButtonProps } from '@nuxt/ui/components/Button.vue';
|
|
2
2
|
import type { LinkProps } from '@nuxt/ui/components/Link.vue';
|
|
3
3
|
import type { TableColumnCollection, TableSummaryRequest, TableSummaryScope, TableSummaryValue } from './columns.js';
|
|
4
|
+
import type { TableGroupBySchema } from './groups.js';
|
|
4
5
|
import type { TableTreeSchema } from './tree.js';
|
|
5
6
|
import type { GenericObject, TableDefaultSort, TableGridSortOption, TableLayout, TableRowRenderParams, RenderableType, TableSortKey, TableTextValue } from './utils.js';
|
|
6
7
|
export interface TablePersistenceOptions {
|
|
@@ -75,12 +76,16 @@ export interface TableGridSchema<TRow extends GenericObject = GenericObject, TCo
|
|
|
75
76
|
itemSize?: number | string | (() => number | string);
|
|
76
77
|
sortOptions?: TableGridSortOption<TSortKey>[];
|
|
77
78
|
defaultSorting?: TableDefaultSort<TSortKey>;
|
|
79
|
+
/** Gathers the cards that share a value under a header that folds them. See `TableGroupBySchema`. */
|
|
80
|
+
groupBy?: TableGroupBySchema<TRow>;
|
|
78
81
|
}
|
|
79
82
|
export interface TableTableSchema<TRow extends GenericObject = GenericObject, TContext extends GenericObject = GenericObject, TPageContext extends GenericObject = GenericObject, TSortKey extends string = TableSortKey<TRow>, TData = unknown> {
|
|
80
83
|
enabled?: boolean | string | (() => boolean | string);
|
|
81
84
|
columns?: TableColumnCollection<TRow, TContext, TPageContext, string, TSortKey, TData>;
|
|
82
85
|
/** Lets rows hold child rows that open beneath them. See `TableTreeSchema`. */
|
|
83
86
|
tree?: TableTreeSchema<TRow>;
|
|
87
|
+
/** Gathers rows that share a value under a header row that folds them. Ignored with `tree`. See `TableGroupBySchema`. */
|
|
88
|
+
groupBy?: TableGroupBySchema<TRow>;
|
|
84
89
|
defaultSorting?: TableDefaultSort<TSortKey>;
|
|
85
90
|
selection?: boolean | 'auto';
|
|
86
91
|
summaries?: TableSummariesSchema<TRow>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { GenericObject, TableGroup, TableGroupBySchema } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Brings the rows sharing a value together. Rows keep their order inside a group, so the sort the
|
|
4
|
+
* source applied survives in each one.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildTableGroups<TRow extends GenericObject>(options: {
|
|
7
|
+
rows: TRow[];
|
|
8
|
+
groupBy: TableGroupBySchema<TRow>;
|
|
9
|
+
getRowId: (row: TRow, index: number) => string;
|
|
10
|
+
}): TableGroup<TRow>[];
|
|
11
|
+
export declare function tableGroupRow(group: TableGroup): GenericObject;
|
|
12
|
+
export declare function tableGroupOf(row: GenericObject): TableGroup | undefined;
|
|
13
|
+
export declare function tableGroupsStorageKey(tableKey: string): string;
|
|
14
|
+
export declare function parseStoredGroupKeys(raw: string | null): string[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getTableRowValue } from "./rows.js";
|
|
2
|
+
const GROUP_ROW_KEY = "__nutTableGroup";
|
|
3
|
+
function groupValueOf(row, groupBy) {
|
|
4
|
+
const { key } = groupBy;
|
|
5
|
+
const value = typeof key === "function" ? key(row) : getTableRowValue({ path: String(key), row });
|
|
6
|
+
return value === null || value === void 0 || value === "" ? void 0 : value;
|
|
7
|
+
}
|
|
8
|
+
function compareGroupValues(order, left, right) {
|
|
9
|
+
const leftRank = order.indexOf(left);
|
|
10
|
+
const rightRank = order.indexOf(right);
|
|
11
|
+
if (leftRank !== -1 && rightRank !== -1) return leftRank - rightRank;
|
|
12
|
+
if (leftRank !== -1) return -1;
|
|
13
|
+
if (rightRank !== -1) return 1;
|
|
14
|
+
if (left === void 0) return 1;
|
|
15
|
+
if (right === void 0) return -1;
|
|
16
|
+
return String(left).localeCompare(String(right), void 0, { numeric: true });
|
|
17
|
+
}
|
|
18
|
+
export function buildTableGroups(options) {
|
|
19
|
+
const groups = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const [index, row] of options.rows.entries()) {
|
|
21
|
+
const value = groupValueOf(row, options.groupBy);
|
|
22
|
+
const group = groups.get(value) ?? { rowIds: [], rows: [] };
|
|
23
|
+
group.rows.push(row);
|
|
24
|
+
group.rowIds.push(options.getRowId(row, index));
|
|
25
|
+
groups.set(value, group);
|
|
26
|
+
}
|
|
27
|
+
const order = options.groupBy.order ?? [];
|
|
28
|
+
return [...groups.entries()].sort(([left], [right]) => compareGroupValues(order, left, right)).map(([value, group]) => ({
|
|
29
|
+
key: `group:${JSON.stringify(value ?? null)}`,
|
|
30
|
+
rowIds: group.rowIds,
|
|
31
|
+
rows: group.rows,
|
|
32
|
+
value
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
export function tableGroupRow(group) {
|
|
36
|
+
return { [GROUP_ROW_KEY]: group };
|
|
37
|
+
}
|
|
38
|
+
function isTableGroupRow(row) {
|
|
39
|
+
return GROUP_ROW_KEY in row;
|
|
40
|
+
}
|
|
41
|
+
export function tableGroupOf(row) {
|
|
42
|
+
return isTableGroupRow(row) ? row[GROUP_ROW_KEY] : void 0;
|
|
43
|
+
}
|
|
44
|
+
export function tableGroupsStorageKey(tableKey) {
|
|
45
|
+
return `nuxt-ui-tools:table-groups:${tableKey}`;
|
|
46
|
+
}
|
|
47
|
+
export function parseStoredGroupKeys(raw) {
|
|
48
|
+
if (!raw) return [];
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(raw);
|
|
51
|
+
return Array.isArray(parsed) ? parsed.filter((key) => typeof key === "string") : [];
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -6,6 +6,7 @@ export * from './data-list-ui.js';
|
|
|
6
6
|
export * from './facets.js';
|
|
7
7
|
export * from './filter-persistence.js';
|
|
8
8
|
export * from './filters/index.js';
|
|
9
|
+
export * from './groups.js';
|
|
9
10
|
export * from './pagination.js';
|
|
10
11
|
export * from './prefetch.js';
|
|
11
12
|
export * from './query-state.js';
|
|
@@ -6,6 +6,7 @@ export * from "./data-list-ui.js";
|
|
|
6
6
|
export * from "./facets.js";
|
|
7
7
|
export * from "./filter-persistence.js";
|
|
8
8
|
export * from "./filters/index.js";
|
|
9
|
+
export * from "./groups.js";
|
|
9
10
|
export * from "./pagination.js";
|
|
10
11
|
export * from "./prefetch.js";
|
|
11
12
|
export * from "./query-state.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ui-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.0",
|
|
4
4
|
"description": "Nuxt module and runtime toolkit for UI-heavy Nuxt applications",
|
|
5
5
|
"homepage": "https://github.com/ChronicStone/nuxt-ui-tools#readme",
|
|
6
6
|
"bugs": "https://github.com/ChronicStone/nuxt-ui-tools/issues",
|