nuxt-ui-tools 1.16.9 → 1.17.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/runtime/i18n/locales/en.js +8 -0
- package/dist/runtime/i18n/locales/fr.js +8 -0
- package/dist/runtime/i18n/types.d.ts +8 -0
- package/dist/runtime/shared/types/query.d.ts +4 -3
- package/dist/runtime/table/components/table/table-layout.d.ts +2 -0
- package/dist/runtime/table/components/table/table-layout.js +1 -0
- package/dist/runtime/table/components/table/table-renderer.d.vue.ts +6 -6
- package/dist/runtime/table/components/table/table-renderer.vue +75 -11
- package/dist/runtime/table/components/table/table-renderer.vue.d.ts +6 -6
- package/dist/runtime/table/components/table/table-row.d.vue.ts +15 -2
- package/dist/runtime/table/components/table/table-row.vue +23 -2
- package/dist/runtime/table/components/table/table-row.vue.d.ts +15 -2
- package/dist/runtime/table/components/table/table-tree-control.d.vue.ts +19 -0
- package/dist/runtime/table/components/table/table-tree-control.vue +112 -0
- package/dist/runtime/table/components/table/table-tree-control.vue.d.ts +19 -0
- package/dist/runtime/table/components/table/table-tree-header.d.vue.ts +3 -0
- package/dist/runtime/table/components/table/table-tree-header.vue +66 -0
- package/dist/runtime/table/components/table/table-tree-header.vue.d.ts +3 -0
- package/dist/runtime/table/composables/use-table-api.d.ts +2 -0
- package/dist/runtime/table/composables/use-table-api.js +2 -0
- package/dist/runtime/table/composables/use-table-data.js +3 -2
- package/dist/runtime/table/composables/use-table-filter-options.js +1 -0
- package/dist/runtime/table/composables/use-table-internals.d.ts +48 -0
- package/dist/runtime/table/composables/use-table-internals.js +13 -4
- package/dist/runtime/table/composables/use-table-selection.d.ts +2 -0
- package/dist/runtime/table/composables/use-table-selection.js +15 -6
- package/dist/runtime/table/composables/use-table-tree.d.ts +36 -0
- package/dist/runtime/table/composables/use-table-tree.js +199 -0
- package/dist/runtime/table/types/index.d.ts +1 -0
- package/dist/runtime/table/types/layout.d.ts +3 -2
- package/dist/runtime/table/types/source.d.ts +2 -1
- package/dist/runtime/table/types/table-api.d.ts +6 -0
- package/dist/runtime/table/types/tree.d.ts +100 -0
- package/dist/runtime/table/types/tree.js +0 -0
- package/dist/runtime/table/utils/columns/defs.js +19 -2
- package/dist/runtime/table/utils/columns/types.d.ts +6 -1
- package/dist/runtime/table/utils/columns/types.js +7 -0
- package/dist/runtime/table/utils/index.d.ts +1 -0
- package/dist/runtime/table/utils/index.js +1 -0
- package/dist/runtime/table/utils/tree.d.ts +64 -0
- package/dist/runtime/table/utils/tree.js +112 -0
- package/package.json +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useUiToolsLocale } from "#ui-tools/i18n";
|
|
4
|
+
import { useTableInternals } from "../../composables/use-table-internals";
|
|
5
|
+
import TableSelectionControl from "./table-selection-control.vue";
|
|
6
|
+
const { t } = useUiToolsLocale();
|
|
7
|
+
const { selection, tree } = useTableInternals();
|
|
8
|
+
const state = computed(() => tree.expansion.state.value);
|
|
9
|
+
const label = computed(
|
|
10
|
+
() => state.value.allExpanded ? t("table.tree.collapseAll") : t("table.tree.expandAll")
|
|
11
|
+
);
|
|
12
|
+
const checkState = computed(
|
|
13
|
+
() => selection.allSelected.value ? true : selection.partiallySelected.value ? "indeterminate" : false
|
|
14
|
+
);
|
|
15
|
+
function toggle() {
|
|
16
|
+
if (state.value.allExpanded) tree.expansion.collapseAll();
|
|
17
|
+
else tree.expansion.expandAll();
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<div class="nut-dl-tree-head">
|
|
23
|
+
<span class="nut-dl-tree__node">
|
|
24
|
+
<button
|
|
25
|
+
v-if="state.branchCount > 0"
|
|
26
|
+
type="button"
|
|
27
|
+
class="nut-dl-tree__toggle"
|
|
28
|
+
:aria-label="label"
|
|
29
|
+
:title="label"
|
|
30
|
+
@click="toggle"
|
|
31
|
+
>
|
|
32
|
+
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" aria-hidden="true">
|
|
33
|
+
<path
|
|
34
|
+
v-if="state.allExpanded"
|
|
35
|
+
d="m7 20 5-5 5 5M7 4l5 5 5-5"
|
|
36
|
+
stroke="currentColor"
|
|
37
|
+
stroke-width="2.5"
|
|
38
|
+
stroke-linecap="round"
|
|
39
|
+
stroke-linejoin="round"
|
|
40
|
+
/>
|
|
41
|
+
<path
|
|
42
|
+
v-else
|
|
43
|
+
d="m7 15 5 5 5-5M7 9l5-5 5 5"
|
|
44
|
+
stroke="currentColor"
|
|
45
|
+
stroke-width="2.5"
|
|
46
|
+
stroke-linecap="round"
|
|
47
|
+
stroke-linejoin="round"
|
|
48
|
+
/>
|
|
49
|
+
</svg>
|
|
50
|
+
</button>
|
|
51
|
+
<span v-else class="nut-dl-tree__slot" aria-hidden="true" />
|
|
52
|
+
</span>
|
|
53
|
+
<span class="nut-dl-tree__check">
|
|
54
|
+
<TableSelectionControl
|
|
55
|
+
v-if="selection.selectionEnabled.value"
|
|
56
|
+
:model-value="checkState"
|
|
57
|
+
:ariaLabel="t('table.tree.selectAll')"
|
|
58
|
+
@toggle="selection.toggleAllRows({ selected: !selection.allSelected.value })"
|
|
59
|
+
/>
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.nut-dl-tree-head{align-items:center;display:flex;height:100%}
|
|
66
|
+
</style>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -8,12 +8,14 @@ import type { useTableLayout } from './use-table-layout.js';
|
|
|
8
8
|
import type { useTablePagination } from './use-table-pagination.js';
|
|
9
9
|
import type { useTableSelection } from './use-table-selection.js';
|
|
10
10
|
import type { useTableState } from './use-table-state.js';
|
|
11
|
+
import type { useTableTree } from './use-table-tree.js';
|
|
11
12
|
export interface UseTableApiParams<TSchema = TableSchemaView> {
|
|
12
13
|
runtimeSchema: ComputedRef<TableSchemaView>;
|
|
13
14
|
publicSchema: ComputedRef<TSchema>;
|
|
14
15
|
layout: ReturnType<typeof useTableLayout>;
|
|
15
16
|
state: ReturnType<typeof useTableState>;
|
|
16
17
|
selection: ReturnType<typeof useTableSelection>;
|
|
18
|
+
tree: ReturnType<typeof useTableTree>;
|
|
17
19
|
controls: ReturnType<typeof useTableControls>;
|
|
18
20
|
columns: ReturnType<typeof useTableColumns>;
|
|
19
21
|
filters: ReturnType<typeof useTableFilters>;
|
|
@@ -85,6 +85,7 @@ export function useTableApi(params) {
|
|
|
85
85
|
all() {
|
|
86
86
|
reset.query();
|
|
87
87
|
selection.clear();
|
|
88
|
+
params.tree.expansion.reset();
|
|
88
89
|
},
|
|
89
90
|
query() {
|
|
90
91
|
const { defaultLayout } = params.runtimeSchema.value;
|
|
@@ -101,6 +102,7 @@ export function useTableApi(params) {
|
|
|
101
102
|
};
|
|
102
103
|
return {
|
|
103
104
|
data,
|
|
105
|
+
expansion: params.tree.expansion,
|
|
104
106
|
filters,
|
|
105
107
|
layout: layoutApi,
|
|
106
108
|
pagination,
|
|
@@ -289,7 +289,7 @@ export function useTableData(params) {
|
|
|
289
289
|
...result
|
|
290
290
|
})),
|
|
291
291
|
queries: () => {
|
|
292
|
-
if (!
|
|
292
|
+
if (!isActiveDataSuccess.value) {
|
|
293
293
|
return [];
|
|
294
294
|
}
|
|
295
295
|
return pageContextItems.value.map(
|
|
@@ -298,7 +298,7 @@ export function useTableData(params) {
|
|
|
298
298
|
context: contextData.value,
|
|
299
299
|
rows: data.value.rows
|
|
300
300
|
}),
|
|
301
|
-
|
|
301
|
+
isPageContextEnabled.value,
|
|
302
302
|
{
|
|
303
303
|
refetchOnWindowFocus: QUERY_DEFAULTS.refetchOnWindowFocus,
|
|
304
304
|
staleTime: QUERY_DEFAULTS.staleTime.context
|
|
@@ -606,6 +606,7 @@ function createCursorQueryDefinition(options) {
|
|
|
606
606
|
}
|
|
607
607
|
return {
|
|
608
608
|
enabled: isQueryDefinitionEnabled(firstPageDefinition),
|
|
609
|
+
meta: firstPageDefinition.meta,
|
|
609
610
|
getNextPageParam(lastPage) {
|
|
610
611
|
return isTableCursorPageResult(lastPage) ? lastPage.pageInfo.nextCursor ?? void 0 : void 0;
|
|
611
612
|
},
|
|
@@ -233,6 +233,7 @@ export function useTableFilterOptions(options) {
|
|
|
233
233
|
});
|
|
234
234
|
return { result, values };
|
|
235
235
|
},
|
|
236
|
+
meta: definition.meta,
|
|
236
237
|
queryKey: ["table-filter-page-facets", ...definition.queryKey],
|
|
237
238
|
refetchOnWindowFocus: QUERY_DEFAULTS.refetchOnWindowFocus,
|
|
238
239
|
staleTime: QUERY_DEFAULTS.staleTime.filterOptions
|
|
@@ -593,6 +593,22 @@ declare function createTableInternals<TSchema>(options: {
|
|
|
593
593
|
toggleSorting: (key: string) => void;
|
|
594
594
|
visibleOrderedColumns: import("vue").ComputedRef<import("../utils/index.js").TableRuntimeColumn[]>;
|
|
595
595
|
};
|
|
596
|
+
tree: {
|
|
597
|
+
enabled: import("vue").ComputedRef<boolean>;
|
|
598
|
+
expansion: import("../types/index.js").TableExpansionApi;
|
|
599
|
+
index: import("vue").ComputedRef<import("../types/index.js").TableTreeIndex>;
|
|
600
|
+
isExpanded: (rowId: string) => boolean;
|
|
601
|
+
isOnPath: (rowId: string) => boolean;
|
|
602
|
+
isSelectable: (node: import("../types/index.js").TableTreeNode) => boolean;
|
|
603
|
+
lightLineage: (rowId?: string) => void;
|
|
604
|
+
litUntil: (groupId: string) => number;
|
|
605
|
+
motionOf: (rowId: string) => import("../types/index.js").TableTreeMotion | undefined;
|
|
606
|
+
nodeOf: (rowId: string) => import("../types/index.js").TableTreeNode | undefined;
|
|
607
|
+
selectable: import("vue").ComputedRef<"all" | "roots">;
|
|
608
|
+
toggleRow: (rowId: string) => void;
|
|
609
|
+
visibleNodes: import("vue").ComputedRef<import("../types/index.js").TableTreeNode[]>;
|
|
610
|
+
visibleRows: import("vue").ComputedRef<object[]>;
|
|
611
|
+
};
|
|
596
612
|
};
|
|
597
613
|
declare function provideTableInternals(internals: TableInternals): void;
|
|
598
614
|
declare function useProvideTableInternals<TSchema>(options: {
|
|
@@ -1189,6 +1205,22 @@ declare function useProvideTableInternals<TSchema>(options: {
|
|
|
1189
1205
|
toggleSorting: (key: string) => void;
|
|
1190
1206
|
visibleOrderedColumns: import("vue").ComputedRef<import("../utils/index.js").TableRuntimeColumn[]>;
|
|
1191
1207
|
};
|
|
1208
|
+
tree: {
|
|
1209
|
+
enabled: import("vue").ComputedRef<boolean>;
|
|
1210
|
+
expansion: import("../types/index.js").TableExpansionApi;
|
|
1211
|
+
index: import("vue").ComputedRef<import("../types/index.js").TableTreeIndex>;
|
|
1212
|
+
isExpanded: (rowId: string) => boolean;
|
|
1213
|
+
isOnPath: (rowId: string) => boolean;
|
|
1214
|
+
isSelectable: (node: import("../types/index.js").TableTreeNode) => boolean;
|
|
1215
|
+
lightLineage: (rowId?: string) => void;
|
|
1216
|
+
litUntil: (groupId: string) => number;
|
|
1217
|
+
motionOf: (rowId: string) => import("../types/index.js").TableTreeMotion | undefined;
|
|
1218
|
+
nodeOf: (rowId: string) => import("../types/index.js").TableTreeNode | undefined;
|
|
1219
|
+
selectable: import("vue").ComputedRef<"all" | "roots">;
|
|
1220
|
+
toggleRow: (rowId: string) => void;
|
|
1221
|
+
visibleNodes: import("vue").ComputedRef<import("../types/index.js").TableTreeNode[]>;
|
|
1222
|
+
visibleRows: import("vue").ComputedRef<object[]>;
|
|
1223
|
+
};
|
|
1192
1224
|
};
|
|
1193
1225
|
declare function useTableInternals(): {
|
|
1194
1226
|
actions: {
|
|
@@ -1782,6 +1814,22 @@ declare function useTableInternals(): {
|
|
|
1782
1814
|
toggleSorting: (key: string) => void;
|
|
1783
1815
|
visibleOrderedColumns: import("vue").ComputedRef<import("../utils/index.js").TableRuntimeColumn[]>;
|
|
1784
1816
|
};
|
|
1817
|
+
tree: {
|
|
1818
|
+
enabled: import("vue").ComputedRef<boolean>;
|
|
1819
|
+
expansion: import("../types/index.js").TableExpansionApi;
|
|
1820
|
+
index: import("vue").ComputedRef<import("../types/index.js").TableTreeIndex>;
|
|
1821
|
+
isExpanded: (rowId: string) => boolean;
|
|
1822
|
+
isOnPath: (rowId: string) => boolean;
|
|
1823
|
+
isSelectable: (node: import("../types/index.js").TableTreeNode) => boolean;
|
|
1824
|
+
lightLineage: (rowId?: string) => void;
|
|
1825
|
+
litUntil: (groupId: string) => number;
|
|
1826
|
+
motionOf: (rowId: string) => import("../types/index.js").TableTreeMotion | undefined;
|
|
1827
|
+
nodeOf: (rowId: string) => import("../types/index.js").TableTreeNode | undefined;
|
|
1828
|
+
selectable: import("vue").ComputedRef<"all" | "roots">;
|
|
1829
|
+
toggleRow: (rowId: string) => void;
|
|
1830
|
+
visibleNodes: import("vue").ComputedRef<import("../types/index.js").TableTreeNode[]>;
|
|
1831
|
+
visibleRows: import("vue").ComputedRef<object[]>;
|
|
1832
|
+
};
|
|
1785
1833
|
};
|
|
1786
1834
|
export type TableInternals = ReturnType<typeof createTableInternals>;
|
|
1787
1835
|
export { createTableInternals, provideTableInternals, useProvideTableInternals, useTableInternals };
|
|
@@ -14,6 +14,7 @@ import { useTableSelection } from "./use-table-selection.js";
|
|
|
14
14
|
import { useTableStartup } from "./use-table-startup.js";
|
|
15
15
|
import { useTableState } from "./use-table-state.js";
|
|
16
16
|
import { useTableSummaries } from "./use-table-summaries.js";
|
|
17
|
+
import { useTableTree } from "./use-table-tree.js";
|
|
17
18
|
function createTableInternals(options) {
|
|
18
19
|
const publicSchema = computed(() => resolveSchemaSource({ schema: options.rawSchema }));
|
|
19
20
|
const schema = computed(() => publicSchema.value);
|
|
@@ -29,10 +30,15 @@ function createTableInternals(options) {
|
|
|
29
30
|
startup,
|
|
30
31
|
state
|
|
31
32
|
});
|
|
32
|
-
const
|
|
33
|
+
const tree = useTableTree({
|
|
33
34
|
queryContent,
|
|
34
35
|
schema
|
|
35
36
|
});
|
|
37
|
+
const selection = useTableSelection({
|
|
38
|
+
queryContent,
|
|
39
|
+
schema,
|
|
40
|
+
tree
|
|
41
|
+
});
|
|
36
42
|
const controls = useTableControls({
|
|
37
43
|
layout,
|
|
38
44
|
schema
|
|
@@ -55,7 +61,8 @@ function createTableInternals(options) {
|
|
|
55
61
|
selection,
|
|
56
62
|
state,
|
|
57
63
|
tableApi,
|
|
58
|
-
tableLayout: controls.tableLayout
|
|
64
|
+
tableLayout: controls.tableLayout,
|
|
65
|
+
tree
|
|
59
66
|
});
|
|
60
67
|
const summaries = useTableSummaries({
|
|
61
68
|
queryContent,
|
|
@@ -79,7 +86,8 @@ function createTableInternals(options) {
|
|
|
79
86
|
queryContent,
|
|
80
87
|
runtimeSchema: schema,
|
|
81
88
|
selection,
|
|
82
|
-
state
|
|
89
|
+
state,
|
|
90
|
+
tree
|
|
83
91
|
});
|
|
84
92
|
if (!tableApi.value) {
|
|
85
93
|
throw new Error("Failed to initialize table API");
|
|
@@ -106,7 +114,8 @@ function createTableInternals(options) {
|
|
|
106
114
|
startup,
|
|
107
115
|
summaries,
|
|
108
116
|
tableApi: tableApi.value,
|
|
109
|
-
tableColumns
|
|
117
|
+
tableColumns,
|
|
118
|
+
tree
|
|
110
119
|
};
|
|
111
120
|
}
|
|
112
121
|
const TABLE_INTERNALS_KEY = Symbol("nuxt-ui-tools.table.internals");
|
|
@@ -1,9 +1,11 @@
|
|
|
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 { useTableTree } from './use-table-tree.js';
|
|
4
5
|
export interface UseTableSelectionParams {
|
|
5
6
|
schema: ComputedRef<TableSchemaView>;
|
|
6
7
|
queryContent: ReturnType<typeof useTableData>;
|
|
8
|
+
tree: ReturnType<typeof useTableTree>;
|
|
7
9
|
}
|
|
8
10
|
export declare function useTableSelection(options: UseTableSelectionParams): {
|
|
9
11
|
allSelected: ComputedRef<boolean>;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { computed, ref, watch } from "vue";
|
|
2
|
-
import { hasConfiguredTableActions, resolveTableRowId } from "../utils/index.js";
|
|
2
|
+
import { flattenTreeRows, hasConfiguredTableActions, resolveTableRowId } from "../utils/index.js";
|
|
3
3
|
export function useTableSelection(options) {
|
|
4
4
|
const selectedKeys = ref([]);
|
|
5
5
|
const bulkScope = ref("selection");
|
|
6
6
|
const lastTouchedRowId = ref(null);
|
|
7
|
-
|
|
7
|
+
function withTreeRows(rows) {
|
|
8
|
+
const childrenPath = options.schema.value.table?.tree?.children;
|
|
9
|
+
return childrenPath && options.tree.selectable.value === "all" ? flattenTreeRows(rows, childrenPath) : rows;
|
|
10
|
+
}
|
|
11
|
+
const pageRows = computed(
|
|
12
|
+
() => withTreeRows(options.queryContent.data.value.rows)
|
|
13
|
+
);
|
|
8
14
|
const selectionEnabled = computed(() => {
|
|
9
15
|
const mode = options.schema.value.table?.selection ?? options.schema.value.selection?.mode ?? "auto";
|
|
10
16
|
if (mode !== "auto") {
|
|
@@ -19,7 +25,7 @@ export function useTableSelection(options) {
|
|
|
19
25
|
return options.schema.value.selection?.scope ?? "all";
|
|
20
26
|
});
|
|
21
27
|
const selectionRows = computed(
|
|
22
|
-
() => selectionScope.value === "all" ? options.queryContent.selectableRows.value : pageRows.value
|
|
28
|
+
() => selectionScope.value === "all" ? withTreeRows(options.queryContent.selectableRows.value) : pageRows.value
|
|
23
29
|
);
|
|
24
30
|
const pageRowIds = computed(() => pageRows.value.map((row, index) => getRowId({ index, row })));
|
|
25
31
|
const scopeRowIds = computed(
|
|
@@ -111,15 +117,18 @@ export function useTableSelection(options) {
|
|
|
111
117
|
}
|
|
112
118
|
lastTouchedRowId.value = params.rowId;
|
|
113
119
|
}
|
|
120
|
+
const rangeRowIds = computed(
|
|
121
|
+
() => options.tree.enabled.value ? options.tree.visibleNodes.value.filter((node) => options.tree.isSelectable(node)).map((node) => node.id) : scopeRowIds.value
|
|
122
|
+
);
|
|
114
123
|
function getRangeRowIds(params) {
|
|
115
|
-
const anchorIndex =
|
|
116
|
-
const targetIndex =
|
|
124
|
+
const anchorIndex = rangeRowIds.value.indexOf(params.anchorRowId);
|
|
125
|
+
const targetIndex = rangeRowIds.value.indexOf(params.targetRowId);
|
|
117
126
|
if (anchorIndex === -1 || targetIndex === -1) {
|
|
118
127
|
return [params.targetRowId];
|
|
119
128
|
}
|
|
120
129
|
const start = Math.min(anchorIndex, targetIndex);
|
|
121
130
|
const end = Math.max(anchorIndex, targetIndex);
|
|
122
|
-
return
|
|
131
|
+
return rangeRowIds.value.slice(start, end + 1);
|
|
123
132
|
}
|
|
124
133
|
watch(
|
|
125
134
|
scopeRowIds,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ComputedRef } from 'vue';
|
|
2
|
+
import type { TableExpansionApi, TableSchemaView, TableTreeIndex, TableTreeMotion, TableTreeNode } from '../types/index.js';
|
|
3
|
+
import type { UseTableDataReturn } from './use-table-data.js';
|
|
4
|
+
export interface UseTableTreeParams {
|
|
5
|
+
schema: ComputedRef<TableSchemaView>;
|
|
6
|
+
queryContent: Pick<UseTableDataReturn, 'data'>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Owns tree rows: which rows are open, the flat list of rows that are on screen, and the motion
|
|
10
|
+
* of a branch opening or closing.
|
|
11
|
+
*
|
|
12
|
+
* Expansion is a baseline plus per-row overrides rather than a set of open ids. The baseline is
|
|
13
|
+
* the schema's `defaultExpanded` (or what `expandAll` / `collapseAll` last chose), so rows that
|
|
14
|
+
* load later, such as the next cursor page, follow it without being enumerated; an override only
|
|
15
|
+
* exists for a row someone actually toggled, and is dropped again if it matches the baseline.
|
|
16
|
+
*
|
|
17
|
+
* TanStack's expanded row model is deliberately not used: it materialises every descendant before
|
|
18
|
+
* the virtualizer sees a row. The nested source rows stay as they are, `visibleNodes` flattens
|
|
19
|
+
* only what is open, and that list is what the renderer virtualizes.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useTableTree(params: UseTableTreeParams): {
|
|
22
|
+
enabled: ComputedRef<boolean>;
|
|
23
|
+
expansion: TableExpansionApi;
|
|
24
|
+
index: ComputedRef<TableTreeIndex>;
|
|
25
|
+
isExpanded: (rowId: string) => boolean;
|
|
26
|
+
isOnPath: (rowId: string) => boolean;
|
|
27
|
+
isSelectable: (node: TableTreeNode) => boolean;
|
|
28
|
+
lightLineage: (rowId?: string) => void;
|
|
29
|
+
litUntil: (groupId: string) => number;
|
|
30
|
+
motionOf: (rowId: string) => TableTreeMotion | undefined;
|
|
31
|
+
nodeOf: (rowId: string) => TableTreeNode | undefined;
|
|
32
|
+
selectable: ComputedRef<"all" | "roots">;
|
|
33
|
+
toggleRow: (rowId: string) => void;
|
|
34
|
+
visibleNodes: ComputedRef<TableTreeNode[]>;
|
|
35
|
+
visibleRows: ComputedRef<object[]>;
|
|
36
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { usePreferredReducedMotion } from "@vueuse/core";
|
|
2
|
+
import { computed, onScopeDispose, reactive, ref, shallowRef } from "vue";
|
|
3
|
+
import { resolveTableRowId } from "../utils/rows.js";
|
|
4
|
+
import {
|
|
5
|
+
buildTableTree,
|
|
6
|
+
flattenVisibleTree,
|
|
7
|
+
getTreeLineage,
|
|
8
|
+
getVisibleDescendants,
|
|
9
|
+
isOpenByDefault
|
|
10
|
+
} 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
|
+
const EMPTY_INDEX = {
|
|
17
|
+
branches: [],
|
|
18
|
+
byId: /* @__PURE__ */ new Map(),
|
|
19
|
+
duplicateIds: [],
|
|
20
|
+
maxDepth: 0,
|
|
21
|
+
roots: []
|
|
22
|
+
};
|
|
23
|
+
export function useTableTree(params) {
|
|
24
|
+
const reducedMotion = usePreferredReducedMotion();
|
|
25
|
+
const config = computed(() => params.schema.value.table?.tree);
|
|
26
|
+
const enabled = computed(() => Boolean(config.value));
|
|
27
|
+
const defaultExpanded = computed(() => config.value?.defaultExpanded ?? false);
|
|
28
|
+
const selectable = computed(() => config.value?.selectable ?? "all");
|
|
29
|
+
const index = computed(() => {
|
|
30
|
+
const tree = config.value;
|
|
31
|
+
if (!tree) return EMPTY_INDEX;
|
|
32
|
+
const built = buildTableTree({
|
|
33
|
+
childrenPath: tree.children,
|
|
34
|
+
getRowId: (row, position) => String(resolveTableRowId({ index: position, row, rowKey: params.schema.value.rowKey })),
|
|
35
|
+
rows: params.queryContent.data.value.rows
|
|
36
|
+
});
|
|
37
|
+
if (built.duplicateIds.length && process.env.NODE_ENV !== "production")
|
|
38
|
+
console.warn(
|
|
39
|
+
`[nuxt-ui-tools] Tree rows must have unique ids across the whole tree; left out duplicates of: ${built.duplicateIds.slice(0, 5).join(", ")}`
|
|
40
|
+
);
|
|
41
|
+
return built;
|
|
42
|
+
});
|
|
43
|
+
const baseline = ref(null);
|
|
44
|
+
const overrides = reactive(/* @__PURE__ */ new Map());
|
|
45
|
+
const closing = reactive(/* @__PURE__ */ new Set());
|
|
46
|
+
function openByDefault(node) {
|
|
47
|
+
return isOpenByDefault(baseline.value ?? defaultExpanded.value, node.depth);
|
|
48
|
+
}
|
|
49
|
+
function isNodeOpen(node) {
|
|
50
|
+
return node.hasChildren && (overrides.get(node.id) ?? openByDefault(node));
|
|
51
|
+
}
|
|
52
|
+
function setNodeOpen(node, open) {
|
|
53
|
+
if (!node.hasChildren) return;
|
|
54
|
+
if (open === openByDefault(node)) overrides.delete(node.id);
|
|
55
|
+
else overrides.set(node.id, open);
|
|
56
|
+
}
|
|
57
|
+
function isExpanded(rowId) {
|
|
58
|
+
const node = index.value.byId.get(rowId);
|
|
59
|
+
return node !== void 0 && isNodeOpen(node) && !closing.has(rowId);
|
|
60
|
+
}
|
|
61
|
+
function nodeOf(rowId) {
|
|
62
|
+
return index.value.byId.get(rowId);
|
|
63
|
+
}
|
|
64
|
+
function isSelectable(node) {
|
|
65
|
+
return selectable.value === "all" || node.depth === 0;
|
|
66
|
+
}
|
|
67
|
+
function setExpanded(rowIds, open) {
|
|
68
|
+
for (const rowId of rowIds) {
|
|
69
|
+
const node = index.value.byId.get(rowId);
|
|
70
|
+
if (node) setNodeOpen(node, open);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function setAll(open) {
|
|
74
|
+
settle();
|
|
75
|
+
baseline.value = open;
|
|
76
|
+
overrides.clear();
|
|
77
|
+
}
|
|
78
|
+
const visibleNodes = computed(() => flattenVisibleTree(index.value.roots, isNodeOpen));
|
|
79
|
+
const visibleRows = computed(() => visibleNodes.value.map((node) => node.row));
|
|
80
|
+
const motion = shallowRef(null);
|
|
81
|
+
let timer;
|
|
82
|
+
let closingNode;
|
|
83
|
+
function motionOf(rowId) {
|
|
84
|
+
return motion.value?.get(rowId);
|
|
85
|
+
}
|
|
86
|
+
function playMotion(kind, branch) {
|
|
87
|
+
const rows = getVisibleDescendants(visibleNodes.value, branch, MOTION_ROW_LIMIT);
|
|
88
|
+
motion.value = new Map(
|
|
89
|
+
rows.map((node, order) => [
|
|
90
|
+
node.id,
|
|
91
|
+
{ kind, order: Math.min(order, STAGGER_ROW_LIMIT) }
|
|
92
|
+
])
|
|
93
|
+
);
|
|
94
|
+
return rows.length;
|
|
95
|
+
}
|
|
96
|
+
function settle() {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
if (closingNode) {
|
|
99
|
+
setNodeOpen(closingNode, false);
|
|
100
|
+
closing.delete(closingNode.id);
|
|
101
|
+
closingNode = void 0;
|
|
102
|
+
}
|
|
103
|
+
motion.value = null;
|
|
104
|
+
}
|
|
105
|
+
function toggleRow(rowId) {
|
|
106
|
+
const node = index.value.byId.get(rowId);
|
|
107
|
+
if (!node?.hasChildren) return;
|
|
108
|
+
if (closingNode === node) {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
closing.delete(rowId);
|
|
111
|
+
closingNode = void 0;
|
|
112
|
+
motion.value = null;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
settle();
|
|
116
|
+
const open = isNodeOpen(node);
|
|
117
|
+
if (reducedMotion.value === "reduce") {
|
|
118
|
+
setNodeOpen(node, !open);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!open) {
|
|
122
|
+
setNodeOpen(node, true);
|
|
123
|
+
const revealed = playMotion("enter", node);
|
|
124
|
+
timer = setTimeout(
|
|
125
|
+
() => motion.value = null,
|
|
126
|
+
REVEAL_MS + Math.min(revealed, STAGGER_ROW_LIMIT) * STAGGER_MS
|
|
127
|
+
);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!playMotion("leave", node)) {
|
|
131
|
+
setNodeOpen(node, false);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
closingNode = node;
|
|
135
|
+
closing.add(rowId);
|
|
136
|
+
timer = setTimeout(settle, CONCEAL_MS);
|
|
137
|
+
}
|
|
138
|
+
onScopeDispose(() => clearTimeout(timer));
|
|
139
|
+
const flatIndexById = computed(
|
|
140
|
+
() => new Map(visibleNodes.value.map((node, position) => [node.id, position]))
|
|
141
|
+
);
|
|
142
|
+
const litUntilByGroup = reactive(/* @__PURE__ */ new Map());
|
|
143
|
+
const onPath = reactive(/* @__PURE__ */ new Set());
|
|
144
|
+
function lightLineage(rowId) {
|
|
145
|
+
const node = rowId === void 0 ? void 0 : index.value.byId.get(rowId);
|
|
146
|
+
const lineage = node ? getTreeLineage(node).map((entry) => entry.id) : [];
|
|
147
|
+
const groups = lineage.slice(0, -1);
|
|
148
|
+
for (const group of litUntilByGroup.keys())
|
|
149
|
+
if (!groups.includes(group)) litUntilByGroup.delete(group);
|
|
150
|
+
for (const group of onPath) if (!groups.includes(group)) onPath.delete(group);
|
|
151
|
+
for (const [position, group] of groups.entries()) {
|
|
152
|
+
const child = lineage[position + 1];
|
|
153
|
+
litUntilByGroup.set(group, child === void 0 ? -1 : flatIndexById.value.get(child) ?? -1);
|
|
154
|
+
onPath.add(group);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function litUntil(groupId) {
|
|
158
|
+
return litUntilByGroup.get(groupId) ?? -1;
|
|
159
|
+
}
|
|
160
|
+
function isOnPath(rowId) {
|
|
161
|
+
return onPath.has(rowId);
|
|
162
|
+
}
|
|
163
|
+
const expansion = {
|
|
164
|
+
collapse: (rowIds) => setExpanded(rowIds, false),
|
|
165
|
+
collapseAll: () => setAll(false),
|
|
166
|
+
expand: (rowIds) => setExpanded(rowIds, true),
|
|
167
|
+
expandAll: () => setAll(true),
|
|
168
|
+
isExpanded,
|
|
169
|
+
reset: () => setAll(null),
|
|
170
|
+
state: computed(() => {
|
|
171
|
+
const { branches } = index.value;
|
|
172
|
+
const expandedKeys = branches.filter(isNodeOpen).map((node) => node.id);
|
|
173
|
+
return {
|
|
174
|
+
allExpanded: branches.length > 0 && expandedKeys.length === branches.length,
|
|
175
|
+
branchCount: branches.length,
|
|
176
|
+
enabled: enabled.value,
|
|
177
|
+
expandedCount: expandedKeys.length,
|
|
178
|
+
expandedKeys
|
|
179
|
+
};
|
|
180
|
+
}),
|
|
181
|
+
toggle: ({ expanded, rowId }) => setExpanded([rowId], expanded ?? !isExpanded(rowId))
|
|
182
|
+
};
|
|
183
|
+
return {
|
|
184
|
+
enabled,
|
|
185
|
+
expansion,
|
|
186
|
+
index,
|
|
187
|
+
isExpanded,
|
|
188
|
+
isOnPath,
|
|
189
|
+
isSelectable,
|
|
190
|
+
lightLineage,
|
|
191
|
+
litUntil,
|
|
192
|
+
motionOf,
|
|
193
|
+
nodeOf,
|
|
194
|
+
selectable,
|
|
195
|
+
toggleRow,
|
|
196
|
+
visibleNodes,
|
|
197
|
+
visibleRows
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TableColumnCollection, TableSummaryRequest, TableSummaryScope, TableSummaryValue } from './columns.js';
|
|
2
|
+
import type { TableTreeSchema } from './tree.js';
|
|
2
3
|
import type { GenericObject, TableDefaultSort, TableGridSortOption, TableLayout, TableRowRenderParams, RenderableType, TableSortKey, TableTextValue } from './utils.js';
|
|
3
4
|
export interface TablePersistenceOptions {
|
|
4
5
|
state?: boolean;
|
|
@@ -53,8 +54,8 @@ export interface TableGridSchema<TRow extends GenericObject = GenericObject, TCo
|
|
|
53
54
|
export interface TableTableSchema<TRow extends GenericObject = GenericObject, TContext extends GenericObject = GenericObject, TPageContext extends GenericObject = GenericObject, TSortKey extends string = TableSortKey<TRow>, TData = unknown> {
|
|
54
55
|
enabled?: boolean | string | (() => boolean | string);
|
|
55
56
|
columns?: TableColumnCollection<TRow, TContext, TPageContext, string, TSortKey, TData>;
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
/** Lets rows hold child rows that open beneath them. See `TableTreeSchema`. */
|
|
58
|
+
tree?: TableTreeSchema<TRow>;
|
|
58
59
|
defaultSorting?: TableDefaultSort<TSortKey>;
|
|
59
60
|
selection?: boolean | 'auto';
|
|
60
61
|
summaries?: TableSummariesSchema<TRow>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InfiniteData, QueryFunction, QueryKey, UseQueryOptions } from '@tanstack/vue-query';
|
|
1
|
+
import type { InfiniteData, QueryFunction, QueryKey, QueryMeta, UseQueryOptions } from '@tanstack/vue-query';
|
|
2
2
|
import type { QueryDefinition } from '../../shared/types/query.js';
|
|
3
3
|
import type { TableResolvedFilterGroup } from './filters.js';
|
|
4
4
|
import type { GenericObject, TableKnownFieldPath, TablePaginationState, TableSortKey, TableSortingRule, TableRowsFromSourceResult } from './utils.js';
|
|
@@ -19,6 +19,7 @@ export interface TableInfiniteQueryDefinition<TData = unknown> {
|
|
|
19
19
|
initialPageParam: string | null;
|
|
20
20
|
getNextPageParam: (lastPage: TData, allPages: TData[], lastPageParam: string | null, allPageParams: (string | null)[]) => string | null | undefined;
|
|
21
21
|
enabled?: boolean;
|
|
22
|
+
meta?: QueryMeta;
|
|
22
23
|
staleTime?: number;
|
|
23
24
|
gcTime?: number;
|
|
24
25
|
refetchOnWindowFocus?: boolean;
|
|
@@ -3,6 +3,7 @@ import type { useTableData } from '../composables/use-table-data.js';
|
|
|
3
3
|
import type { useTablePagination } from '../composables/use-table-pagination.js';
|
|
4
4
|
import type { TableFilterState } from './query-state.js';
|
|
5
5
|
import type { TableSchemaView } from './schema.js';
|
|
6
|
+
import type { TableExpansionApi } from './tree.js';
|
|
6
7
|
import type { ExtractTableContextData, ExtractTablePageContextData, ExtractTableRow, TableLayout, TablePaginationState, TableSortingDirection, TableSortingRule } from './utils.js';
|
|
7
8
|
export interface PublicTableQueryState {
|
|
8
9
|
layout: TableLayout;
|
|
@@ -143,6 +144,11 @@ export interface TableApi<TSchema = TableSchemaView> {
|
|
|
143
144
|
}) => void;
|
|
144
145
|
isSelected: (rowId: string) => boolean;
|
|
145
146
|
};
|
|
147
|
+
/**
|
|
148
|
+
* Open and close tree rows. With no `table.tree` in the schema, `state.enabled` is `false` and
|
|
149
|
+
* nothing here has an effect.
|
|
150
|
+
*/
|
|
151
|
+
expansion: TableExpansionApi;
|
|
146
152
|
reset: {
|
|
147
153
|
query: () => void;
|
|
148
154
|
all: () => void;
|