nuxt-ui-tools 1.1.0 → 1.3.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 +10 -1
- package/dist/runtime/dashboard/components/block/dashboard-block-state.d.vue.ts +2 -0
- package/dist/runtime/dashboard/components/block/dashboard-block-state.vue +4 -1
- package/dist/runtime/dashboard/components/block/dashboard-block-state.vue.d.ts +2 -0
- package/dist/runtime/dashboard/components/block/dashboard-chips.d.vue.ts +20 -0
- package/dist/runtime/dashboard/components/block/dashboard-chips.vue +42 -0
- package/dist/runtime/dashboard/components/block/dashboard-chips.vue.d.ts +20 -0
- package/dist/runtime/dashboard/components/block/dashboard-series-picker.d.vue.ts +8 -0
- package/dist/runtime/dashboard/components/block/dashboard-series-picker.vue +29 -0
- package/dist/runtime/dashboard/components/block/dashboard-series-picker.vue.d.ts +8 -0
- package/dist/runtime/dashboard/components/block/dashboard-skeleton.d.vue.ts +1 -1
- package/dist/runtime/dashboard/components/block/dashboard-skeleton.vue +6 -3
- package/dist/runtime/dashboard/components/block/dashboard-skeleton.vue.d.ts +1 -1
- package/dist/runtime/dashboard/components/block/dashboard-view-scope.d.vue.ts +21 -0
- package/dist/runtime/dashboard/components/block/dashboard-view-scope.vue +12 -0
- package/dist/runtime/dashboard/components/block/dashboard-view-scope.vue.d.ts +21 -0
- package/dist/runtime/dashboard/components/dashboard-alerts.vue +3 -3
- package/dist/runtime/dashboard/components/dashboard-bar-chart.d.vue.ts +14 -3
- package/dist/runtime/dashboard/components/dashboard-bar-chart.vue +30 -9
- package/dist/runtime/dashboard/components/dashboard-bar-chart.vue.d.ts +14 -3
- package/dist/runtime/dashboard/components/dashboard-bars.vue +4 -4
- package/dist/runtime/dashboard/components/dashboard-card.vue +28 -6
- package/dist/runtime/dashboard/components/dashboard-combo-chart.d.vue.ts +6 -1
- package/dist/runtime/dashboard/components/dashboard-combo-chart.vue +13 -3
- package/dist/runtime/dashboard/components/dashboard-combo-chart.vue.d.ts +6 -1
- package/dist/runtime/dashboard/components/dashboard-donut-chart.vue +5 -5
- package/dist/runtime/dashboard/components/dashboard-feed.vue +8 -7
- package/dist/runtime/dashboard/components/dashboard-filter.d.vue.ts +67 -0
- package/dist/runtime/dashboard/components/dashboard-filter.vue +134 -0
- package/dist/runtime/dashboard/components/dashboard-filter.vue.d.ts +67 -0
- package/dist/runtime/dashboard/components/dashboard-filters.d.vue.ts +49 -0
- package/dist/runtime/dashboard/components/dashboard-filters.vue +96 -0
- package/dist/runtime/dashboard/components/dashboard-filters.vue.d.ts +49 -0
- package/dist/runtime/dashboard/components/dashboard-funnel.vue +5 -5
- package/dist/runtime/dashboard/components/dashboard-gauge.vue +3 -3
- package/dist/runtime/dashboard/components/dashboard-grid.d.vue.ts +10 -3
- package/dist/runtime/dashboard/components/dashboard-grid.vue +24 -9
- package/dist/runtime/dashboard/components/dashboard-grid.vue.d.ts +10 -3
- package/dist/runtime/dashboard/components/dashboard-line-chart.d.vue.ts +14 -3
- package/dist/runtime/dashboard/components/dashboard-line-chart.vue +30 -9
- package/dist/runtime/dashboard/components/dashboard-line-chart.vue.d.ts +14 -3
- package/dist/runtime/dashboard/components/dashboard-list.d.vue.ts +3 -0
- package/dist/runtime/dashboard/components/dashboard-list.vue +18 -6
- package/dist/runtime/dashboard/components/dashboard-list.vue.d.ts +3 -0
- package/dist/runtime/dashboard/components/dashboard-page.d.vue.ts +53 -0
- package/dist/runtime/dashboard/components/dashboard-page.vue +156 -0
- package/dist/runtime/dashboard/components/dashboard-page.vue.d.ts +53 -0
- package/dist/runtime/dashboard/components/dashboard-paired-bars.vue +4 -4
- package/dist/runtime/dashboard/components/dashboard-refresh.d.vue.ts +1 -1
- package/dist/runtime/dashboard/components/dashboard-refresh.vue +1 -0
- package/dist/runtime/dashboard/components/dashboard-refresh.vue.d.ts +1 -1
- package/dist/runtime/dashboard/components/dashboard-stack-bar.vue +4 -4
- package/dist/runtime/dashboard/components/dashboard-stat.d.vue.ts +14 -2
- package/dist/runtime/dashboard/components/dashboard-stat.vue +28 -10
- package/dist/runtime/dashboard/components/dashboard-stat.vue.d.ts +14 -2
- package/dist/runtime/dashboard/components/dashboard-stats.vue +3 -3
- package/dist/runtime/dashboard/components/dashboard-table.d.vue.ts +1 -1
- package/dist/runtime/dashboard/components/dashboard-table.vue +3 -3
- package/dist/runtime/dashboard/components/dashboard-table.vue.d.ts +1 -1
- package/dist/runtime/dashboard/components/dashboard-tabs.d.vue.ts +1 -1
- package/dist/runtime/dashboard/components/dashboard-tabs.vue.d.ts +1 -1
- package/dist/runtime/dashboard/components/dashboard-total.d.vue.ts +7 -2
- package/dist/runtime/dashboard/components/dashboard-total.vue +10 -2
- package/dist/runtime/dashboard/components/dashboard-total.vue.d.ts +7 -2
- package/dist/runtime/dashboard/components/dashboard-view-tabs.d.vue.ts +33 -0
- package/dist/runtime/dashboard/components/dashboard-view-tabs.vue +66 -0
- package/dist/runtime/dashboard/components/dashboard-view-tabs.vue.d.ts +33 -0
- package/dist/runtime/dashboard/components/dashboard-widget.vue +1 -1
- package/dist/runtime/dashboard/components/filter/dashboard-filter-menu.d.vue.ts +44 -0
- package/dist/runtime/dashboard/components/filter/dashboard-filter-menu.vue +220 -0
- package/dist/runtime/dashboard/components/filter/dashboard-filter-menu.vue.d.ts +44 -0
- package/dist/runtime/dashboard/composables/use-dashboard-api.d.ts +6 -1
- package/dist/runtime/dashboard/composables/use-dashboard-api.js +29 -6
- package/dist/runtime/dashboard/composables/use-dashboard-block.d.ts +5 -4
- package/dist/runtime/dashboard/composables/use-dashboard-block.js +32 -15
- package/dist/runtime/dashboard/composables/use-dashboard-chart.d.ts +8 -1
- package/dist/runtime/dashboard/composables/use-dashboard-chart.js +16 -4
- package/dist/runtime/dashboard/composables/use-dashboard-context.d.ts +50 -0
- package/dist/runtime/dashboard/composables/use-dashboard-context.js +56 -0
- package/dist/runtime/dashboard/composables/use-dashboard-derived.js +6 -1
- package/dist/runtime/dashboard/composables/use-dashboard-filter-control.d.ts +16 -0
- package/dist/runtime/dashboard/composables/use-dashboard-filter-control.js +161 -0
- package/dist/runtime/dashboard/composables/use-dashboard-filter-scope.d.ts +37 -0
- package/dist/runtime/dashboard/composables/use-dashboard-filter-scope.js +107 -0
- package/dist/runtime/dashboard/composables/use-dashboard-format.d.ts +46 -6
- package/dist/runtime/dashboard/composables/use-dashboard-format.js +51 -8
- package/dist/runtime/dashboard/composables/use-dashboard-options.d.ts +11 -4
- package/dist/runtime/dashboard/composables/use-dashboard-options.js +33 -47
- package/dist/runtime/dashboard/composables/use-dashboard-remote-options.d.ts +8 -5
- package/dist/runtime/dashboard/composables/use-dashboard-remote-options.js +66 -77
- package/dist/runtime/dashboard/composables/use-dashboard-resource.d.ts +15 -4
- package/dist/runtime/dashboard/composables/use-dashboard-resource.js +52 -17
- package/dist/runtime/dashboard/composables/use-dashboard-scope.d.ts +10 -7
- package/dist/runtime/dashboard/composables/use-dashboard-scope.js +26 -16
- package/dist/runtime/dashboard/composables/use-dashboard-series-picker.d.ts +20 -0
- package/dist/runtime/dashboard/composables/use-dashboard-series-picker.js +30 -0
- package/dist/runtime/dashboard/composables/use-dashboard-time.d.ts +2 -0
- package/dist/runtime/dashboard/composables/use-dashboard-time.js +4 -1
- package/dist/runtime/dashboard/composables/use-dashboard-ui.d.ts +12 -2
- package/dist/runtime/dashboard/composables/use-dashboard-ui.js +3 -0
- package/dist/runtime/dashboard/composables/use-dashboard-views.d.ts +15 -3
- package/dist/runtime/dashboard/composables/use-dashboard-views.js +33 -10
- package/dist/runtime/dashboard/composables/use-dashboard.d.ts +18 -12
- package/dist/runtime/dashboard/composables/use-dashboard.js +98 -9
- package/dist/runtime/dashboard/index.d.ts +3 -1
- package/dist/runtime/dashboard/index.js +3 -1
- package/dist/runtime/dashboard/schema/index.d.ts +62 -30
- package/dist/runtime/dashboard/schema/index.js +3 -0
- package/dist/runtime/dashboard/types/blocks.d.ts +30 -4
- package/dist/runtime/dashboard/types/charts.d.ts +2 -2
- package/dist/runtime/dashboard/types/controls.d.ts +123 -0
- package/dist/runtime/dashboard/types/filters.d.ts +236 -0
- package/dist/runtime/dashboard/types/index.d.ts +3 -2
- package/dist/runtime/dashboard/types/page.d.ts +17 -0
- package/dist/runtime/dashboard/types/page.js +0 -0
- package/dist/runtime/dashboard/types/resource.d.ts +66 -28
- package/dist/runtime/dashboard/types/runtime.d.ts +39 -8
- package/dist/runtime/dashboard/types/schema.d.ts +94 -54
- package/dist/runtime/dashboard/types/ui.d.ts +78 -0
- package/dist/runtime/dashboard/utils/builders/dashboard-filters.d.ts +142 -0
- package/dist/runtime/dashboard/utils/builders/dashboard-filters.js +135 -0
- package/dist/runtime/dashboard/utils/charts.d.ts +4 -15
- package/dist/runtime/dashboard/utils/charts.js +26 -28
- package/dist/runtime/dashboard/utils/{params/codecs.d.ts → codecs.d.ts} +2 -2
- package/dist/runtime/dashboard/utils/{params/codecs.js → codecs.js} +1 -1
- package/dist/runtime/dashboard/utils/environment.d.ts +36 -0
- package/dist/runtime/dashboard/utils/environment.js +30 -0
- package/dist/runtime/dashboard/utils/filters.d.ts +44 -0
- package/dist/runtime/dashboard/utils/filters.js +62 -0
- package/dist/runtime/dashboard/utils/format.d.ts +25 -0
- package/dist/runtime/dashboard/utils/format.js +66 -0
- package/dist/runtime/dashboard/utils/grid.d.ts +17 -0
- package/dist/runtime/dashboard/utils/grid.js +14 -0
- package/dist/runtime/dashboard/utils/options.d.ts +3 -8
- package/dist/runtime/dashboard/utils/options.js +6 -5
- package/dist/runtime/dashboard/utils/remote.d.ts +7 -0
- package/dist/runtime/dashboard/utils/remote.js +14 -0
- package/dist/runtime/dashboard/utils/resource.d.ts +16 -7
- package/dist/runtime/dashboard/utils/resource.js +15 -9
- package/dist/runtime/dashboard/utils/schema.d.ts +8 -3
- package/dist/runtime/dashboard/utils/schema.js +9 -4
- package/dist/runtime/dashboard/utils/state.d.ts +11 -6
- package/dist/runtime/dashboard/utils/state.js +10 -5
- package/dist/runtime/dashboard/utils/time.d.ts +2 -0
- package/dist/runtime/dashboard/utils/time.js +11 -1
- package/dist/runtime/dashboard/utils/ui.d.ts +1 -1
- package/dist/runtime/dashboard/utils/ui.js +1 -1
- package/dist/runtime/form/components/root/form.vue +1 -1
- package/dist/runtime/form/fields/card-selection.d.ts +1 -0
- package/dist/runtime/form/fields/card-selection.js +1 -0
- package/dist/runtime/form/fields/checkbox-card/component.vue +3 -1
- package/dist/runtime/form/fields/checkbox-group/component.vue +5 -0
- package/dist/runtime/form/fields/radio/component.vue +10 -0
- package/dist/runtime/form/fields/radio-card/component.vue +3 -1
- package/dist/runtime/form/types/options.d.ts +1 -11
- package/dist/runtime/form/utils/focus.js +1 -1
- package/dist/runtime/i18n/locales/en.js +32 -1
- package/dist/runtime/i18n/locales/fr.js +32 -1
- package/dist/runtime/i18n/types.d.ts +44 -0
- package/dist/runtime/shared/styles/scrollbars.css +1 -1
- package/dist/runtime/shared/styles/tokens.css +1 -1
- package/dist/runtime/shared/types/query.d.ts +8 -0
- package/dist/runtime/shared/utils/remote-options.d.ts +10 -0
- package/dist/runtime/shared/utils/remote-options.js +6 -0
- package/dist/runtime/table/components/filters/shared/filter-option-list-end.d.vue.ts +11 -0
- package/dist/runtime/table/components/filters/shared/filter-option-list-end.vue +76 -0
- package/dist/runtime/table/components/filters/shared/filter-option-list-end.vue.d.ts +11 -0
- package/dist/runtime/table/components/filters/shared/filter-option-loading-list.d.vue.ts +3 -0
- package/dist/runtime/table/components/filters/shared/filter-option-loading-list.vue +2 -1
- package/dist/runtime/table/components/filters/shared/filter-option-loading-list.vue.d.ts +3 -0
- package/dist/runtime/table/components/filters/shared/filter-option-multiple-list.vue +1 -1
- package/dist/runtime/table/components/filters/shared/filter-option-picker-content.vue +13 -0
- package/dist/runtime/table/components/filters/shared/filter-option-single-list.d.vue.ts +2 -0
- package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue +1 -1
- package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue.d.ts +2 -0
- package/dist/runtime/table/components/filters/shared/filter-option-tree-content.d.vue.ts +3 -0
- package/dist/runtime/table/components/filters/shared/filter-option-tree-content.vue +2 -2
- package/dist/runtime/table/components/filters/shared/filter-option-tree-content.vue.d.ts +3 -0
- package/dist/runtime/table/components/filters/shared/filter-searchable-panel.vue +1 -0
- package/dist/runtime/table/composables/use-option-filter-editor-state.d.ts +13 -1
- package/dist/runtime/table/composables/use-option-filter-editor-state.js +6 -2
- package/dist/runtime/table/composables/use-table-filter-options.d.ts +11 -1
- package/dist/runtime/table/composables/use-table-filter-options.js +128 -17
- package/dist/runtime/table/composables/use-table-filter-selected-options.d.ts +20 -0
- package/dist/runtime/table/composables/use-table-filter-selected-options.js +99 -0
- package/dist/runtime/table/composables/use-table-filters.d.ts +5 -0
- package/dist/runtime/table/composables/use-table-filters.js +20 -1
- package/dist/runtime/table/composables/use-table-internals.d.ts +15 -0
- package/dist/runtime/table/composables/use-table-remote-filter-options.d.ts +42 -0
- package/dist/runtime/table/composables/use-table-remote-filter-options.js +102 -0
- package/dist/runtime/table/types/filters.d.ts +42 -0
- package/dist/runtime/table/types/index.d.ts +1 -0
- package/dist/runtime/table/types/remote-options.d.ts +64 -0
- package/dist/runtime/table/types/remote-options.js +0 -0
- package/dist/runtime/table/types/source.d.ts +15 -1
- package/dist/runtime/table/utils/builders/table-source.d.ts +2 -6
- package/dist/runtime/table/utils/filters/options.d.ts +2 -0
- package/dist/runtime/table/utils/filters/options.js +1 -1
- package/dist/runtime/table/utils/index.d.ts +1 -0
- package/dist/runtime/table/utils/index.js +1 -0
- package/dist/runtime/table/utils/prefetch.js +21 -1
- package/dist/runtime/table/utils/remote-table-options.d.ts +26 -0
- package/dist/runtime/table/utils/remote-table-options.js +88 -0
- package/package.json +4 -1
- package/dist/runtime/dashboard/composables/use-dashboard-param-scope.d.ts +0 -30
- package/dist/runtime/dashboard/composables/use-dashboard-param-scope.js +0 -71
- package/dist/runtime/dashboard/types/options.d.ts +0 -46
- package/dist/runtime/dashboard/types/params.d.ts +0 -115
- package/dist/runtime/dashboard/utils/builders/dashboard-params.d.ts +0 -85
- package/dist/runtime/dashboard/utils/builders/dashboard-params.js +0 -87
- /package/dist/runtime/dashboard/types/{options.js → controls.js} +0 -0
- /package/dist/runtime/dashboard/types/{params.js → filters.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useQuery } from "@tanstack/vue-query";
|
|
2
|
-
import { computed, unref } from "vue";
|
|
1
|
+
import { useQueries, useQuery } from "@tanstack/vue-query";
|
|
2
|
+
import { computed, unref, watch } from "vue";
|
|
3
3
|
import {
|
|
4
4
|
isBoolean,
|
|
5
5
|
isFunction,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
resolveOptionFilterUi,
|
|
18
18
|
resolveTableFacetMode
|
|
19
19
|
} from "../utils/index.js";
|
|
20
|
+
import { useTableRemoteFilterOptions } from "./use-table-remote-filter-options.js";
|
|
20
21
|
export function useTableFilterOptions(options) {
|
|
21
22
|
const isActive = computed(() => options.active?.value ?? true);
|
|
22
23
|
const isReady = computed(() => options.ready?.value ?? true);
|
|
@@ -52,29 +53,36 @@ export function useTableFilterOptions(options) {
|
|
|
52
53
|
() => shouldDeriveCounts.value && isActive.value && isReady.value
|
|
53
54
|
);
|
|
54
55
|
const isRemoteTable = computed(() => options.schema.value.source.mode === "remote");
|
|
56
|
+
const remoteConfig = computed(() => optionDefinition.value?.source?.remote);
|
|
57
|
+
const hasRemotePages = computed(() => Boolean(remoteConfig.value));
|
|
55
58
|
const hasRemoteOptionQuery = computed(
|
|
56
|
-
() => options.definition.kind === "option" && isFunction(options.definition.source?.query)
|
|
59
|
+
() => !hasRemotePages.value && options.definition.kind === "option" && isFunction(options.definition.source?.query)
|
|
57
60
|
);
|
|
61
|
+
const hasServerOptions = computed(() => hasRemotePages.value || hasRemoteOptionQuery.value);
|
|
58
62
|
const facetSpec = computed(() => options.definition.source?.facet);
|
|
59
63
|
const facetConfig = computed(
|
|
60
64
|
() => isFacetConfig(facetSpec.value) ? facetSpec.value : null
|
|
61
65
|
);
|
|
62
66
|
const hasPerFilterFacetQuery = computed(() => isFunction(facetConfig.value?.query));
|
|
63
67
|
const usesFacetCounts = computed(
|
|
64
|
-
() => Boolean(facetSpec.value) && (options.schema.value.source.mode === "client" || hasPerFilterFacetQuery.value ||
|
|
68
|
+
() => Boolean(facetSpec.value) && (options.schema.value.source.mode === "client" || hasPerFilterFacetQuery.value || // The main request cannot count options it never lists: paged options need `facet.query`.
|
|
69
|
+
isRemoteTable.value && Boolean(remoteSource.value?.facets) && !hasRemotePages.value)
|
|
70
|
+
);
|
|
71
|
+
const countsPagesRemotely = computed(
|
|
72
|
+
() => hasRemotePages.value && isRemoteTable.value && hasPerFilterFacetQuery.value
|
|
65
73
|
);
|
|
66
74
|
const resolvedTreeSearchMode = computed(() => {
|
|
67
75
|
if (optionUi.value?.presentation !== "tree") {
|
|
68
|
-
return
|
|
76
|
+
return hasServerOptions.value ? "remote" : "local";
|
|
69
77
|
}
|
|
70
78
|
const configured = optionUi.value.tree.searchMode;
|
|
71
79
|
if (configured === "local" || configured === "remote") {
|
|
72
80
|
return configured;
|
|
73
81
|
}
|
|
74
|
-
return
|
|
82
|
+
return hasServerOptions.value ? "remote" : "local";
|
|
75
83
|
});
|
|
76
84
|
const querySearch = computed(() => {
|
|
77
|
-
if (!
|
|
85
|
+
if (!hasServerOptions.value) {
|
|
78
86
|
return;
|
|
79
87
|
}
|
|
80
88
|
if (resolvedTreeSearchMode.value === "local") {
|
|
@@ -97,6 +105,30 @@ export function useTableFilterOptions(options) {
|
|
|
97
105
|
key: options.definition.key
|
|
98
106
|
})
|
|
99
107
|
);
|
|
108
|
+
const remotePages = useTableRemoteFilterOptions({
|
|
109
|
+
config: remoteConfig,
|
|
110
|
+
enabled: computed(() => isActive.value && isReady.value),
|
|
111
|
+
search: computed(() => querySearch.value ?? "")
|
|
112
|
+
});
|
|
113
|
+
watch(
|
|
114
|
+
[remotePages.entries, selectedValues],
|
|
115
|
+
([entries, values]) => {
|
|
116
|
+
if (!hasRemotePages.value || !values.length) return;
|
|
117
|
+
const picked = new Set(values.map(String));
|
|
118
|
+
options.filters.selectedOptions.remember(
|
|
119
|
+
options.definition.key,
|
|
120
|
+
flattenRawOptionEntries(entries).filter((entry) => picked.has(String(entry.value)))
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
{ immediate: true }
|
|
124
|
+
);
|
|
125
|
+
const remotePageEntries = computed(() => {
|
|
126
|
+
const listed = new Set(
|
|
127
|
+
flattenRawOptionEntries(remotePages.entries.value).map((entry) => String(entry.value))
|
|
128
|
+
);
|
|
129
|
+
const selected = options.filters.selectedOptions.getSelectedOptions(options.definition.key).filter((entry) => !listed.has(String(entry.value)));
|
|
130
|
+
return [...selected, ...remotePages.entries.value];
|
|
131
|
+
});
|
|
100
132
|
const optionQuery = useQuery(
|
|
101
133
|
computed(() => {
|
|
102
134
|
const activeDefinition = optionDefinition.value;
|
|
@@ -126,7 +158,7 @@ export function useTableFilterOptions(options) {
|
|
|
126
158
|
);
|
|
127
159
|
const perFilterFacetQuery = useQuery(
|
|
128
160
|
computed(() => {
|
|
129
|
-
if (!isRemoteTable.value || !usesFacetCounts.value || !facetConfig.value?.query) {
|
|
161
|
+
if (!isRemoteTable.value || !usesFacetCounts.value || !facetConfig.value?.query || countsPagesRemotely.value) {
|
|
130
162
|
return {
|
|
131
163
|
enabled: false,
|
|
132
164
|
queryFn: async () => ({ facets: [] }),
|
|
@@ -168,10 +200,62 @@ export function useTableFilterOptions(options) {
|
|
|
168
200
|
}
|
|
169
201
|
return result?.options ? [...result.options] : [];
|
|
170
202
|
});
|
|
203
|
+
const pageFacetRequests = computed(() => {
|
|
204
|
+
if (!countsPagesRemotely.value || !shouldResolveCounts.value) return [];
|
|
205
|
+
const paged = remotePages.pageValues.value;
|
|
206
|
+
const listed = new Set(paged.flat().map(String));
|
|
207
|
+
const outside = options.filters.selectedOptions.getSelectedOptions(options.definition.key).map((entry) => entry.value).filter((value) => !listed.has(String(value)));
|
|
208
|
+
return [outside, ...paged].filter((values) => values.length > 0);
|
|
209
|
+
});
|
|
210
|
+
const pageFacetQueries = useQueries({
|
|
211
|
+
queries: computed(() => {
|
|
212
|
+
const config = facetConfig.value;
|
|
213
|
+
const query = config?.query;
|
|
214
|
+
if (!query) return [];
|
|
215
|
+
return pageFacetRequests.value.map((values) => {
|
|
216
|
+
const definition = unref(
|
|
217
|
+
query({
|
|
218
|
+
facets: [{ key: options.definition.key, mode: resolveTableFacetMode(config), values }],
|
|
219
|
+
table: options.queryContent.facetsBaseContext.value
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
return {
|
|
223
|
+
// The table's own cache entry: it keeps the requested values next to their counts.
|
|
224
|
+
queryFn: async (context) => {
|
|
225
|
+
if (!definition.queryFn) {
|
|
226
|
+
throw new Error("facet.query must return a query definition with a queryFn.");
|
|
227
|
+
}
|
|
228
|
+
const result = await definition.queryFn({
|
|
229
|
+
...context,
|
|
230
|
+
direction: "forward",
|
|
231
|
+
pageParam: null,
|
|
232
|
+
queryKey: definition.queryKey
|
|
233
|
+
});
|
|
234
|
+
return { result, values };
|
|
235
|
+
},
|
|
236
|
+
queryKey: ["table-filter-page-facets", ...definition.queryKey],
|
|
237
|
+
refetchOnWindowFocus: QUERY_DEFAULTS.refetchOnWindowFocus,
|
|
238
|
+
staleTime: QUERY_DEFAULTS.staleTime.filterOptions
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
})
|
|
242
|
+
});
|
|
243
|
+
const pageFacetCounts = computed(
|
|
244
|
+
() => pageFacetQueries.value.flatMap((query) => {
|
|
245
|
+
if (!query.data) return [];
|
|
246
|
+
const counted = new Map(
|
|
247
|
+
query.data.result.facets.filter((facet) => facet.key === options.definition.key).flatMap((facet) => facet.options).map((option) => [String(option.value), option.count])
|
|
248
|
+
);
|
|
249
|
+
return query.data.values.map((value) => ({ count: counted.get(String(value)) ?? 0, value }));
|
|
250
|
+
})
|
|
251
|
+
);
|
|
171
252
|
const facetCounts = computed(() => {
|
|
172
253
|
if (!usesFacetCounts.value) {
|
|
173
254
|
return [];
|
|
174
255
|
}
|
|
256
|
+
if (countsPagesRemotely.value) {
|
|
257
|
+
return pageFacetCounts.value;
|
|
258
|
+
}
|
|
175
259
|
const facets = isRemoteTable.value && hasPerFilterFacetQuery.value ? unref(perFilterFacetQuery.data)?.facets ?? [] : options.queryContent.facets.value.facets;
|
|
176
260
|
return facets.find((facet) => facet.key === options.definition.key)?.options ?? [];
|
|
177
261
|
});
|
|
@@ -185,8 +269,10 @@ export function useTableFilterOptions(options) {
|
|
|
185
269
|
definition: options.definition,
|
|
186
270
|
deriveCounts: false,
|
|
187
271
|
facetCounts: resolvedFacetCounts.value,
|
|
188
|
-
|
|
189
|
-
|
|
272
|
+
// Paged options without counts yet stay unknown (a placeholder) until their page is counted.
|
|
273
|
+
missingCountFallback: shouldResolveCounts.value && usesFacetCounts.value && !countsPagesRemotely.value ? 0 : void 0,
|
|
274
|
+
options: hasRemotePages.value ? remotePageEntries.value : hasRemoteOptionQuery.value ? remoteEntries.value : staticEntries.value,
|
|
275
|
+
authoritative: hasRemotePages.value,
|
|
190
276
|
rows: [],
|
|
191
277
|
selectedValues: selectedValues.value
|
|
192
278
|
})
|
|
@@ -228,7 +314,7 @@ export function useTableFilterOptions(options) {
|
|
|
228
314
|
(entry) => !isNullish(entry.value)
|
|
229
315
|
);
|
|
230
316
|
}
|
|
231
|
-
if (!normalizedSearch.value.length ||
|
|
317
|
+
if (!normalizedSearch.value.length || hasServerOptions.value) {
|
|
232
318
|
return selectableSourceEntries.value;
|
|
233
319
|
}
|
|
234
320
|
return selectableSourceEntries.value.filter(
|
|
@@ -236,28 +322,53 @@ export function useTableFilterOptions(options) {
|
|
|
236
322
|
);
|
|
237
323
|
});
|
|
238
324
|
const isInitialLoading = computed(
|
|
239
|
-
() => optionQuery.isLoading.value && !optionQuery.isPlaceholderData.value
|
|
325
|
+
() => hasRemotePages.value ? remotePages.isLoading.value : optionQuery.isLoading.value && !optionQuery.isPlaceholderData.value
|
|
326
|
+
);
|
|
327
|
+
const isStaleLoading = computed(
|
|
328
|
+
() => hasRemotePages.value ? remotePages.isRefreshing.value : !isInitialLoading.value && optionQuery.isFetching.value
|
|
240
329
|
);
|
|
241
|
-
const isStaleLoading = computed(() => !isInitialLoading.value && optionQuery.isFetching.value);
|
|
242
330
|
const isCountLoading = computed(
|
|
243
|
-
() => isRemoteTable.value && shouldResolveCounts.value && perFilterFacetQuery.isFetching.value
|
|
331
|
+
() => isRemoteTable.value && shouldResolveCounts.value && !countsPagesRemotely.value && perFilterFacetQuery.isFetching.value
|
|
244
332
|
);
|
|
245
333
|
return {
|
|
246
|
-
error: computed(
|
|
334
|
+
error: computed(
|
|
335
|
+
() => (hasRemotePages.value ? remotePages.error.value : optionQuery.error.value) ?? perFilterFacetQuery.error.value
|
|
336
|
+
),
|
|
247
337
|
facetCounts,
|
|
248
338
|
filteredEntries,
|
|
249
339
|
filteredTreeEntries: computed(() => filteredTreeState.value.entries),
|
|
250
340
|
isCountLoading,
|
|
251
|
-
isError: computed(
|
|
341
|
+
isError: computed(
|
|
342
|
+
() => (hasRemotePages.value ? remotePages.failed.value : optionQuery.isError.value) || perFilterFacetQuery.isError.value
|
|
343
|
+
),
|
|
252
344
|
isLoading: isInitialLoading,
|
|
253
345
|
isStaleLoading,
|
|
254
|
-
refresh: () => Promise.all([
|
|
346
|
+
refresh: () => Promise.all([
|
|
347
|
+
hasRemotePages.value ? remotePages.retry() : optionQuery.refetch(),
|
|
348
|
+
perFilterFacetQuery.refetch()
|
|
349
|
+
]),
|
|
350
|
+
/** Paging of `source.remote`: the editor list loads the next page ahead of the scroll. */
|
|
351
|
+
remote: {
|
|
352
|
+
enabled: hasRemotePages,
|
|
353
|
+
failed: remotePages.failed,
|
|
354
|
+
hasMore: remotePages.hasMore,
|
|
355
|
+
loadMore: remotePages.loadMore,
|
|
356
|
+
loadingMore: remotePages.loadingMore,
|
|
357
|
+
prefetchDistance: remotePages.prefetchDistance,
|
|
358
|
+
retry: remotePages.retry
|
|
359
|
+
},
|
|
255
360
|
searchExpandedIds: computed(() => filteredTreeState.value.expandedIds),
|
|
256
361
|
showCounts: computed(() => shouldDeriveCounts.value && usesFacetCounts.value),
|
|
257
362
|
sourceEntries: selectableSourceEntries,
|
|
258
363
|
sourceTreeEntries: resolvedSourceTreeEntries
|
|
259
364
|
};
|
|
260
365
|
}
|
|
366
|
+
function flattenRawOptionEntries(entries) {
|
|
367
|
+
return entries.flatMap((entry) => [
|
|
368
|
+
...isNullish(entry.value) ? [] : [entry],
|
|
369
|
+
...flattenRawOptionEntries(entry.children ?? [])
|
|
370
|
+
]);
|
|
371
|
+
}
|
|
261
372
|
function isFacetConfig(value) {
|
|
262
373
|
return isObject(value);
|
|
263
374
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ComputedRef } from 'vue';
|
|
2
|
+
import type { TableFilterOptionEntry, TableFilterOptionValueEntry, TableQueryStateFilterRule, TableUiFilterDefinition } from '../types/index.js';
|
|
3
|
+
export interface UseTableFilterSelectedOptionsParams {
|
|
4
|
+
definitions: ComputedRef<TableUiFilterDefinition[]>;
|
|
5
|
+
/** Committed filter rules. */
|
|
6
|
+
rules: ComputedRef<TableQueryStateFilterRule[]>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Options of the committed values of remote option filters, so tags, panel chips, and the mobile
|
|
10
|
+
* sheet show labels instead of raw values. Options the editors loaded are recorded as values get
|
|
11
|
+
* picked; the rest (for example values restored from the URL) resolve through each filter's
|
|
12
|
+
* `source.remote.resolveSelected`, under the consumer's query key so a prefetched result is reused.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useTableFilterSelectedOptions(params: UseTableFilterSelectedOptionsParams): {
|
|
15
|
+
getSelectedOptions: (key: string) => TableFilterOptionValueEntry[];
|
|
16
|
+
/** Options of committed values are resolving for this filter. */
|
|
17
|
+
isResolving: (key: string) => boolean;
|
|
18
|
+
remember: (key: string, options: readonly TableFilterOptionEntry[]) => void;
|
|
19
|
+
};
|
|
20
|
+
export type TableFilterSelectedOptions = ReturnType<typeof useTableFilterSelectedOptions>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useQuery, useQueryClient } from "@tanstack/vue-query";
|
|
2
|
+
import { computed, shallowRef, watch } from "vue";
|
|
3
|
+
import { isBoolean, isNullish, isNumber, isString } from "../../shared/utils/predicate.js";
|
|
4
|
+
import { QUERY_DEFAULTS } from "../constants/query-state.js";
|
|
5
|
+
export function useTableFilterSelectedOptions(params) {
|
|
6
|
+
const queryClient = useQueryClient();
|
|
7
|
+
const known = shallowRef(/* @__PURE__ */ new Map());
|
|
8
|
+
const remoteDefinitions = computed(
|
|
9
|
+
() => params.definitions.value.flatMap((definition) => {
|
|
10
|
+
const remote = definition.kind === "option" ? definition.source?.remote : void 0;
|
|
11
|
+
return remote ? [{ key: definition.key, remote }] : [];
|
|
12
|
+
})
|
|
13
|
+
);
|
|
14
|
+
function selectedValues(key) {
|
|
15
|
+
const rule = params.rules.value.find((entry) => entry.key === key);
|
|
16
|
+
const values = Array.isArray(rule?.value) ? rule.value : isNullish(rule?.value) ? [] : [rule.value];
|
|
17
|
+
return values.filter(isPrimitiveValue);
|
|
18
|
+
}
|
|
19
|
+
const requests = computed(
|
|
20
|
+
() => remoteDefinitions.value.flatMap(({ key, remote }) => {
|
|
21
|
+
const cached = known.value.get(key);
|
|
22
|
+
const missing = selectedValues(key).filter((value) => !cached?.has(String(value)));
|
|
23
|
+
const query = missing.length ? resolveSelectedQuery(remote, missing) : void 0;
|
|
24
|
+
return query ? [{ key, query }] : [];
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
const hydration = useQuery(
|
|
28
|
+
computed(() => {
|
|
29
|
+
const snapshot = requests.value;
|
|
30
|
+
return {
|
|
31
|
+
enabled: snapshot.length > 0,
|
|
32
|
+
queryFn: () => Promise.all(
|
|
33
|
+
snapshot.map(async ({ key, query }) => ({
|
|
34
|
+
key,
|
|
35
|
+
options: await queryClient.fetchQuery({
|
|
36
|
+
queryFn: (context) => {
|
|
37
|
+
if (!query.queryFn) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"resolveSelected must return a query definition with a queryFn."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return query.queryFn({ ...context, direction: "forward", pageParam: null });
|
|
43
|
+
},
|
|
44
|
+
queryKey: query.queryKey,
|
|
45
|
+
staleTime: QUERY_DEFAULTS.staleTime.filterOptions
|
|
46
|
+
})
|
|
47
|
+
}))
|
|
48
|
+
),
|
|
49
|
+
queryKey: [
|
|
50
|
+
"table-filter-selected-options",
|
|
51
|
+
snapshot.map(({ key, query }) => [key, query.queryKey])
|
|
52
|
+
],
|
|
53
|
+
refetchOnWindowFocus: QUERY_DEFAULTS.refetchOnWindowFocus,
|
|
54
|
+
staleTime: QUERY_DEFAULTS.staleTime.filterOptions
|
|
55
|
+
};
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
watch(
|
|
59
|
+
() => hydration.data.value,
|
|
60
|
+
(results) => {
|
|
61
|
+
for (const result of results ?? []) remember(result.key, result.options);
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
function remember(key, options) {
|
|
65
|
+
const entries = flattenValueEntries(options);
|
|
66
|
+
if (!entries.length) return;
|
|
67
|
+
const current = known.value.get(key);
|
|
68
|
+
if (entries.every((entry) => current?.get(String(entry.value))?.label === entry.label)) return;
|
|
69
|
+
const next = new Map(current);
|
|
70
|
+
for (const entry of entries) next.set(String(entry.value), entry);
|
|
71
|
+
known.value = new Map(known.value).set(key, next);
|
|
72
|
+
}
|
|
73
|
+
function getSelectedOptions(key) {
|
|
74
|
+
const cached = known.value.get(key);
|
|
75
|
+
if (!cached) return [];
|
|
76
|
+
return selectedValues(key).flatMap((value) => {
|
|
77
|
+
const entry = cached.get(String(value));
|
|
78
|
+
return entry ? [entry] : [];
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
getSelectedOptions,
|
|
83
|
+
/** Options of committed values are resolving for this filter. */
|
|
84
|
+
isResolving: (key) => hydration.isFetching.value && requests.value.some((request) => request.key === key),
|
|
85
|
+
remember
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function resolveSelectedQuery(remote, values) {
|
|
89
|
+
return remote.resolveSelected?.({ values });
|
|
90
|
+
}
|
|
91
|
+
function flattenValueEntries(options) {
|
|
92
|
+
return options.flatMap((option) => [
|
|
93
|
+
...isNullish(option.value) ? [] : [{ ...option, value: option.value }],
|
|
94
|
+
...flattenValueEntries(option.children ?? [])
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
function isPrimitiveValue(value) {
|
|
98
|
+
return isString(value) || isNumber(value) || isBoolean(value);
|
|
99
|
+
}
|
|
@@ -71,6 +71,11 @@ export declare function useTableFilters(params: UseTableFiltersParams): {
|
|
|
71
71
|
}) => void;
|
|
72
72
|
searchPlaceholder: ComputedRef<string>;
|
|
73
73
|
searchQuery: import("vue").WritableComputedRef<string, string>;
|
|
74
|
+
selectedOptions: {
|
|
75
|
+
getSelectedOptions: (key: string) => import("../types/index.js").TableFilterOptionValueEntry[];
|
|
76
|
+
isResolving: (key: string) => boolean;
|
|
77
|
+
remember: (key: string, options: readonly TableFilterOptionEntry[]) => void;
|
|
78
|
+
};
|
|
74
79
|
setFilterOperator: (input: {
|
|
75
80
|
key: string;
|
|
76
81
|
operator: TableFilterOperator;
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
resolveTableFilterDefaultRules,
|
|
12
12
|
resolveFilterOptionEntries
|
|
13
13
|
} from "../utils/index.js";
|
|
14
|
+
import { useTableFilterSelectedOptions } from "./use-table-filter-selected-options.js";
|
|
14
15
|
import { useTableSearch } from "./use-table-search.js";
|
|
15
16
|
export function useTableFilters(params) {
|
|
16
17
|
const { t } = useUiToolsLocale();
|
|
@@ -34,6 +35,10 @@ export function useTableFilters(params) {
|
|
|
34
35
|
})
|
|
35
36
|
);
|
|
36
37
|
const hasActiveUiFilters = computed(() => activeUiFilters.value.length > 0);
|
|
38
|
+
const selectedOptions = useTableFilterSelectedOptions({
|
|
39
|
+
definitions,
|
|
40
|
+
rules: effectiveUiFilters
|
|
41
|
+
});
|
|
37
42
|
function getDefinition(input) {
|
|
38
43
|
return definitions.value.find(
|
|
39
44
|
(definition) => definition.key === input.key
|
|
@@ -87,7 +92,7 @@ export function useTableFilters(params) {
|
|
|
87
92
|
}
|
|
88
93
|
const preview = buildFilterPreview({
|
|
89
94
|
definition,
|
|
90
|
-
optionEntries: input.entries,
|
|
95
|
+
optionEntries: withSelectedOptions(input.key, input.entries),
|
|
91
96
|
rule: getFilterState({ key: input.key })
|
|
92
97
|
});
|
|
93
98
|
const rule = getFilterState({ key: input.key });
|
|
@@ -98,6 +103,19 @@ export function useTableFilters(params) {
|
|
|
98
103
|
dirty: !isNullish(getActiveFilterState({ key: input.key }))
|
|
99
104
|
};
|
|
100
105
|
}
|
|
106
|
+
function withSelectedOptions(key, entries = []) {
|
|
107
|
+
const listed = new Set(entries.map((entry) => String(entry.value)));
|
|
108
|
+
const missing = selectedOptions.getSelectedOptions(key).filter((entry) => !listed.has(String(entry.value)));
|
|
109
|
+
return missing.length ? [
|
|
110
|
+
...entries,
|
|
111
|
+
...missing.map((entry) => ({
|
|
112
|
+
color: entry.color,
|
|
113
|
+
icon: entry.icon,
|
|
114
|
+
label: getFilterLabelText({ label: entry.label }),
|
|
115
|
+
value: entry.value
|
|
116
|
+
}))
|
|
117
|
+
] : entries;
|
|
118
|
+
}
|
|
101
119
|
function getFilterOperator(input) {
|
|
102
120
|
const definition = getDefinition({ key: input.key });
|
|
103
121
|
const rule = getFilterState({ key: input.key });
|
|
@@ -253,6 +271,7 @@ export function useTableFilters(params) {
|
|
|
253
271
|
replaceFilters,
|
|
254
272
|
searchPlaceholder: search.searchPlaceholder,
|
|
255
273
|
searchQuery: search.searchQuery,
|
|
274
|
+
selectedOptions,
|
|
256
275
|
setFilterOperator,
|
|
257
276
|
setOptionFilterValues,
|
|
258
277
|
setScalarFilterValue,
|
|
@@ -191,6 +191,11 @@ declare function createTableInternals<TSchema>(options: {
|
|
|
191
191
|
}) => void;
|
|
192
192
|
searchPlaceholder: import("vue").ComputedRef<string>;
|
|
193
193
|
searchQuery: import("vue").WritableComputedRef<string, string>;
|
|
194
|
+
selectedOptions: {
|
|
195
|
+
getSelectedOptions: (key: string) => import("../types/index.js").TableFilterOptionValueEntry[];
|
|
196
|
+
isResolving: (key: string) => boolean;
|
|
197
|
+
remember: (key: string, options: readonly import("../types/index.js").TableFilterOptionEntry[]) => void;
|
|
198
|
+
};
|
|
194
199
|
setFilterOperator: (input: {
|
|
195
200
|
key: string;
|
|
196
201
|
operator: import("../types/index.js").TableFilterOperator;
|
|
@@ -782,6 +787,11 @@ declare function useProvideTableInternals<TSchema>(options: {
|
|
|
782
787
|
}) => void;
|
|
783
788
|
searchPlaceholder: import("vue").ComputedRef<string>;
|
|
784
789
|
searchQuery: import("vue").WritableComputedRef<string, string>;
|
|
790
|
+
selectedOptions: {
|
|
791
|
+
getSelectedOptions: (key: string) => import("../types/index.js").TableFilterOptionValueEntry[];
|
|
792
|
+
isResolving: (key: string) => boolean;
|
|
793
|
+
remember: (key: string, options: readonly import("../types/index.js").TableFilterOptionEntry[]) => void;
|
|
794
|
+
};
|
|
785
795
|
setFilterOperator: (input: {
|
|
786
796
|
key: string;
|
|
787
797
|
operator: import("../types/index.js").TableFilterOperator;
|
|
@@ -1370,6 +1380,11 @@ declare function useTableInternals(): {
|
|
|
1370
1380
|
}) => void;
|
|
1371
1381
|
searchPlaceholder: import("vue").ComputedRef<string>;
|
|
1372
1382
|
searchQuery: import("vue").WritableComputedRef<string, string>;
|
|
1383
|
+
selectedOptions: {
|
|
1384
|
+
getSelectedOptions: (key: string) => import("../types/index.js").TableFilterOptionValueEntry[];
|
|
1385
|
+
isResolving: (key: string) => boolean;
|
|
1386
|
+
remember: (key: string, options: readonly import("../types/index.js").TableFilterOptionEntry[]) => void;
|
|
1387
|
+
};
|
|
1373
1388
|
setFilterOperator: (input: {
|
|
1374
1389
|
key: string;
|
|
1375
1390
|
operator: import("../types/index.js").TableFilterOperator;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { InfiniteData } from '@tanstack/vue-query';
|
|
2
|
+
import type { ComputedRef } from 'vue';
|
|
3
|
+
import type { RemoteOptionsResult } from '../../shared/types/remote-options.js';
|
|
4
|
+
import type { RemoteOptionsPageParam } from '../../shared/utils/remote-options.js';
|
|
5
|
+
import type { TableFilterOptionEntry, TableFilterRemoteOptions } from '../types/index.js';
|
|
6
|
+
type RemotePage = RemoteOptionsResult<TableFilterOptionEntry>;
|
|
7
|
+
/** Defaults of `source.remote` on option filters, matching the form engine's remote options. */
|
|
8
|
+
export declare const TABLE_REMOTE_FILTER_OPTIONS_DEFAULTS: {
|
|
9
|
+
readonly pageSize: 25;
|
|
10
|
+
readonly searchDebounce: 250;
|
|
11
|
+
};
|
|
12
|
+
export interface UseTableRemoteFilterOptionsParams {
|
|
13
|
+
config: ComputedRef<TableFilterRemoteOptions | undefined>;
|
|
14
|
+
/** Raw search input; it is trimmed and debounced before it reaches the source. */
|
|
15
|
+
search: ComputedRef<string>;
|
|
16
|
+
/** Pages load only while an editor shows the list. */
|
|
17
|
+
enabled: ComputedRef<boolean>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Pages of a remote option filter. Nothing loads until an editor shows the list; each search term
|
|
21
|
+
* keeps its own pages in the TanStack Query cache, and the previous term's pages stay on screen
|
|
22
|
+
* while the next term loads.
|
|
23
|
+
*/
|
|
24
|
+
export declare function useTableRemoteFilterOptions(params: UseTableRemoteFilterOptionsParams): {
|
|
25
|
+
entries: ComputedRef<readonly TableFilterOptionEntry[]>;
|
|
26
|
+
error: import("vue").Ref<null, null> | import("vue").Ref<Error, Error>;
|
|
27
|
+
/** More pages exist after the loaded ones. */
|
|
28
|
+
hasMore: ComputedRef<boolean>;
|
|
29
|
+
/** The first page of the current term has not arrived yet. */
|
|
30
|
+
isLoading: ComputedRef<boolean>;
|
|
31
|
+
/** A new term, or a refetch, is loading while earlier options stay on screen. */
|
|
32
|
+
isRefreshing: ComputedRef<boolean>;
|
|
33
|
+
/** The next page is loading at the end of the list. */
|
|
34
|
+
loadingMore: ComputedRef<boolean>;
|
|
35
|
+
/** The last request failed: the next page (or the first) can be retried. */
|
|
36
|
+
failed: ComputedRef<boolean>;
|
|
37
|
+
loadMore: () => void;
|
|
38
|
+
pageValues: ComputedRef<(string | number | boolean)[][]>;
|
|
39
|
+
prefetchDistance: ComputedRef<number | "viewport">;
|
|
40
|
+
retry: () => Promise<import("@tanstack/vue-query").QueryObserverResult<InfiniteData<RemotePage, RemoteOptionsPageParam>, Error>>;
|
|
41
|
+
};
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useInfiniteQuery } from "@tanstack/vue-query";
|
|
2
|
+
import { refDebounced } from "@vueuse/core";
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
import {
|
|
5
|
+
REMOTE_OPTIONS_FIRST_PAGE,
|
|
6
|
+
resolveRemoteOptionsNextPage
|
|
7
|
+
} from "../../shared/utils/remote-options.js";
|
|
8
|
+
import { QUERY_DEFAULTS } from "../constants/query-state.js";
|
|
9
|
+
export const TABLE_REMOTE_FILTER_OPTIONS_DEFAULTS = {
|
|
10
|
+
pageSize: 25,
|
|
11
|
+
searchDebounce: 250
|
|
12
|
+
};
|
|
13
|
+
export function useTableRemoteFilterOptions(params) {
|
|
14
|
+
const pageSize = computed(
|
|
15
|
+
() => Math.max(
|
|
16
|
+
1,
|
|
17
|
+
params.config.value?.pagination?.size ?? TABLE_REMOTE_FILTER_OPTIONS_DEFAULTS.pageSize
|
|
18
|
+
)
|
|
19
|
+
);
|
|
20
|
+
const minLength = computed(() => Math.max(0, params.config.value?.search?.minLength ?? 0));
|
|
21
|
+
const search = refDebounced(
|
|
22
|
+
computed(() => params.search.value.trim()),
|
|
23
|
+
() => params.config.value?.search?.debounce ?? TABLE_REMOTE_FILTER_OPTIONS_DEFAULTS.searchDebounce
|
|
24
|
+
);
|
|
25
|
+
const canFetch = computed(
|
|
26
|
+
() => search.value.length === 0 || search.value.length >= minLength.value
|
|
27
|
+
);
|
|
28
|
+
const pages = useInfiniteQuery(
|
|
29
|
+
computed(() => {
|
|
30
|
+
const config = params.config.value;
|
|
31
|
+
const term = search.value;
|
|
32
|
+
const size = pageSize.value;
|
|
33
|
+
const firstPage = config?.load({ page: { ...REMOTE_OPTIONS_FIRST_PAGE, size }, search: term });
|
|
34
|
+
return {
|
|
35
|
+
enabled: Boolean(config) && params.enabled.value && canFetch.value,
|
|
36
|
+
getNextPageParam: (lastPage, allPages) => resolveRemoteOptionsNextPage(lastPage, allPages.length),
|
|
37
|
+
initialPageParam: REMOTE_OPTIONS_FIRST_PAGE,
|
|
38
|
+
// Keeps the previous term's options on screen while the next term loads.
|
|
39
|
+
placeholderData: (previous) => previous,
|
|
40
|
+
async queryFn(queryContext) {
|
|
41
|
+
const definition = config?.load({
|
|
42
|
+
page: { ...queryContext.pageParam, size },
|
|
43
|
+
search: term
|
|
44
|
+
});
|
|
45
|
+
if (!definition?.queryFn) {
|
|
46
|
+
throw new Error("Remote option filters must return a query definition with a queryFn.");
|
|
47
|
+
}
|
|
48
|
+
return definition.queryFn({
|
|
49
|
+
...queryContext,
|
|
50
|
+
direction: "forward",
|
|
51
|
+
pageParam: null,
|
|
52
|
+
queryKey: definition.queryKey
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
queryKey: [
|
|
56
|
+
...firstPage?.queryKey ?? ["table-filter-remote-options", "disabled"],
|
|
57
|
+
{ tableFilterOptionPages: size }
|
|
58
|
+
],
|
|
59
|
+
refetchOnWindowFocus: QUERY_DEFAULTS.refetchOnWindowFocus,
|
|
60
|
+
staleTime: QUERY_DEFAULTS.staleTime.filterOptions
|
|
61
|
+
};
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
const entries = computed(
|
|
65
|
+
() => pages.data.value?.pages.flatMap((page) => page.options) ?? []
|
|
66
|
+
);
|
|
67
|
+
const pageValues = computed(
|
|
68
|
+
() => pages.data.value?.pages.map((page) => collectOptionValues(page.options)) ?? []
|
|
69
|
+
);
|
|
70
|
+
function loadMore() {
|
|
71
|
+
if (!pages.hasNextPage.value || pages.isFetchingNextPage.value || pages.isFetching.value) return;
|
|
72
|
+
void pages.fetchNextPage();
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
entries,
|
|
76
|
+
error: pages.error,
|
|
77
|
+
/** More pages exist after the loaded ones. */
|
|
78
|
+
hasMore: computed(() => pages.hasNextPage.value),
|
|
79
|
+
/** The first page of the current term has not arrived yet. */
|
|
80
|
+
isLoading: computed(() => pages.isLoading.value && !pages.isPlaceholderData.value),
|
|
81
|
+
/** A new term, or a refetch, is loading while earlier options stay on screen. */
|
|
82
|
+
isRefreshing: computed(
|
|
83
|
+
() => pages.isFetching.value && !pages.isFetchingNextPage.value && !pages.isLoading.value
|
|
84
|
+
),
|
|
85
|
+
/** The next page is loading at the end of the list. */
|
|
86
|
+
loadingMore: computed(() => pages.isFetchingNextPage.value),
|
|
87
|
+
/** The last request failed: the next page (or the first) can be retried. */
|
|
88
|
+
failed: computed(() => pages.isError.value || pages.isFetchNextPageError.value),
|
|
89
|
+
loadMore,
|
|
90
|
+
pageValues,
|
|
91
|
+
prefetchDistance: computed(
|
|
92
|
+
() => params.config.value?.pagination?.prefetchDistance ?? "viewport"
|
|
93
|
+
),
|
|
94
|
+
retry: () => pages.isFetchNextPageError.value ? pages.fetchNextPage() : pages.refetch()
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function collectOptionValues(options) {
|
|
98
|
+
return options.flatMap((option) => [
|
|
99
|
+
...option.value === void 0 ? [] : [option.value],
|
|
100
|
+
...collectOptionValues(option.children ?? [])
|
|
101
|
+
]);
|
|
102
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RemoteOptionsPageRequest, RemoteOptionsPagination, RemoteOptionsResult, RemoteOptionsSearch } from '../../shared/types/remote-options.js';
|
|
1
2
|
import type { TableFilterOperator, TableQueryStateFilterRule, TableQueryStateFilterValue } from './query-state.js';
|
|
2
3
|
import type { TableFacetExecutionResult, TableFacetRequestDescriptor, TableFacetsContext, TableQueryDefinition } from './source.js';
|
|
3
4
|
import type { GenericObject, RenderableType, TableFieldPath, TableKnownFieldPath, TableTextValue } from './utils.js';
|
|
@@ -538,6 +539,41 @@ export interface TableFilterOptionQueryContext {
|
|
|
538
539
|
cursor?: string | null;
|
|
539
540
|
}
|
|
540
541
|
export type TableFilterOptionQueryResult<TValue = TableFilterPrimitiveValue> = TableFilterOptionQueryResultForPresentation<TValue, TableOptionFilterPresentation>;
|
|
542
|
+
/** Page requested from a remote option filter source. */
|
|
543
|
+
export interface TableFilterRemoteOptionsRequest {
|
|
544
|
+
/** Current debounced search term. Empty when the editor lists unfiltered options. */
|
|
545
|
+
search: string;
|
|
546
|
+
/** Requested page: `index` (1-based) for page pagination, `cursor` for cursor pagination. */
|
|
547
|
+
page: RemoteOptionsPageRequest;
|
|
548
|
+
}
|
|
549
|
+
/** Selected values a remote option filter needs options for. */
|
|
550
|
+
export interface TableFilterRemoteSelectedRequest<TValue = TableFilterPrimitiveValue> {
|
|
551
|
+
/** Committed filter values that the options loaded so far do not contain. */
|
|
552
|
+
values: readonly TValue[];
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Remote option source loaded page by page, following the form engine's remote options:
|
|
556
|
+
* server-side search, page or cursor pagination loaded ahead of the scroll, and options for
|
|
557
|
+
* selected values the loaded pages do not contain (for example values restored from the URL).
|
|
558
|
+
*/
|
|
559
|
+
export interface TableFilterRemoteOptions<TValue = TableFilterPrimitiveValue, TPresentation extends TableOptionFilterPresentation = TableOptionFilterPresentation> {
|
|
560
|
+
/** Query definition of one page of options for the current search term. */
|
|
561
|
+
load: (request: TableFilterRemoteOptionsRequest) => TableQueryDefinition<RemoteOptionsResult<TableOptionEntryForPresentation<TValue, TPresentation>>>;
|
|
562
|
+
/**
|
|
563
|
+
* Query definition resolving the options of selected values the loaded pages do not contain, so
|
|
564
|
+
* tags and editors show their labels. Without it, such values show as their raw value.
|
|
565
|
+
*/
|
|
566
|
+
resolveSelected?: {
|
|
567
|
+
bivarianceHack(request: TableFilterRemoteSelectedRequest<TValue>): TableQueryDefinition<readonly TableOptionEntryForPresentation<TValue, TPresentation>[]>;
|
|
568
|
+
}['bivarianceHack'];
|
|
569
|
+
/**
|
|
570
|
+
* Page size, and how far before the list end the next page loads. Defaults to
|
|
571
|
+
* `{ type: 'page', size: 25 }` with the next page requested three viewport heights ahead.
|
|
572
|
+
*/
|
|
573
|
+
pagination?: RemoteOptionsPagination;
|
|
574
|
+
/** Search debounce (default 250 ms) and minimum term length (default 0). */
|
|
575
|
+
search?: RemoteOptionsSearch;
|
|
576
|
+
}
|
|
541
577
|
export type TableFilterFacetMode = boolean | 'exclude-self' | 'include-self';
|
|
542
578
|
export interface TableFilterFacetQueryContext<TRow extends GenericObject = GenericObject, TContext extends GenericObject = GenericObject, TKey extends string = TableKnownFieldPath<TRow>> {
|
|
543
579
|
table: Omit<TableFacetsContext<TRow, TContext, TKey>, 'facets'>;
|
|
@@ -596,7 +632,13 @@ export interface TableOptionFilterDefinition<TRow extends GenericObject = Generi
|
|
|
596
632
|
kind: 'option';
|
|
597
633
|
source?: {
|
|
598
634
|
options?: readonly TableOptionEntryForPresentation<TValue, TPresentation>[];
|
|
635
|
+
/** Loads the whole option list in one query. For large lists, prefer `remote`. */
|
|
599
636
|
query?: (context: TableFilterOptionQueryContext) => TableQueryDefinition<TableOptionEntryForPresentation<TValue, TPresentation>[] | TableFilterOptionQueryResultForPresentation<TValue, TPresentation>>;
|
|
637
|
+
/**
|
|
638
|
+
* Loads options page by page with server-side search, as the editor scrolls. Takes precedence
|
|
639
|
+
* over `options` and `query`.
|
|
640
|
+
*/
|
|
641
|
+
remote?: TableFilterRemoteOptions<TValue, TPresentation>;
|
|
600
642
|
facet?: TableFilterFacetSpec<TRow, TContext, TKey>;
|
|
601
643
|
sort?: 'alpha' | 'count';
|
|
602
644
|
};
|