nuxt-ui-tools 1.4.0 → 1.5.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 +2 -1
- package/dist/runtime/dashboard/composables/use-dashboard-remote-options.js +8 -3
- package/dist/runtime/dashboard/types/filters.d.ts +7 -1
- package/dist/runtime/dashboard/utils/builders/dashboard-filters.js +19 -2
- package/dist/runtime/form/composables/use-field-options.js +19 -0
- package/dist/runtime/form/composables/use-remote-field-options.js +21 -2
- package/dist/runtime/form/types/options.d.ts +22 -2
- package/dist/runtime/shared/index.d.ts +1 -0
- package/dist/runtime/shared/index.js +1 -0
- package/dist/runtime/shared/types/query.d.ts +5 -1
- package/dist/runtime/shared/types/remote-options.d.ts +56 -0
- package/dist/runtime/shared/utils/define-remote-options.d.ts +23 -0
- package/dist/runtime/shared/utils/define-remote-options.js +24 -0
- package/dist/runtime/shared/utils/remote-options-query.d.ts +6 -0
- package/dist/runtime/shared/utils/remote-options-query.js +18 -0
- package/dist/runtime/table/components/data-list/data-list-filter-tags.d.vue.ts +1 -1
- package/dist/runtime/table/components/data-list/data-list-filter-tags.vue +1 -1
- package/dist/runtime/table/components/data-list/data-list-filter-tags.vue.d.ts +1 -1
- package/dist/runtime/table/components/filters/panel/filter-panel.vue +14 -2
- package/dist/runtime/table/components/filters/shared/filter-option-multiple-list.vue +2 -2
- package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue +2 -2
- package/dist/runtime/table/components/filters/shared/filter-option-tree-content.vue +5 -5
- package/dist/runtime/table/components/filters/shared/filter-trigger-tag.vue +7 -1
- package/dist/runtime/table/composables/use-table-filter-selected-options.js +16 -1
- package/dist/runtime/table/types/filters.d.ts +7 -0
- package/dist/runtime/table/types/remote-options.d.ts +3 -16
- package/dist/runtime/table/utils/remote-table-options.js +37 -37
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -143,8 +143,9 @@ function setupImports(runtimeDir) {
|
|
|
143
143
|
{ from: "i18n", name: "provideUiToolsLocale" },
|
|
144
144
|
{ from: "i18n", name: "useUiToolsLocale" }
|
|
145
145
|
]),
|
|
146
|
-
// Shared
|
|
146
|
+
// Shared runtime
|
|
147
147
|
...withRuntime(runtimeDir, [
|
|
148
|
+
{ from: "shared", name: "defineRemoteOptions" },
|
|
148
149
|
{ from: "shared", name: "getResponsiveValue" },
|
|
149
150
|
{ from: "shared", name: "parseResponsiveValue" },
|
|
150
151
|
{ from: "shared", name: "resolveResponsiveValueAtBreakpoint" },
|
|
@@ -10,7 +10,6 @@ import { mergeDashboardOptions, resolveDashboardOptionValues } from "../utils/op
|
|
|
10
10
|
import { runDashboardRemoteResult } from "../utils/remote.js";
|
|
11
11
|
export function useDashboardRemoteOptions(params) {
|
|
12
12
|
const { config } = params;
|
|
13
|
-
const queryKey = config.queryKey ?? params.queryKey;
|
|
14
13
|
const pageSize = Math.max(
|
|
15
14
|
1,
|
|
16
15
|
config.pagination?.size ?? DASHBOARD_REMOTE_OPTIONS_DEFAULTS.pageSize
|
|
@@ -22,6 +21,12 @@ export function useDashboardRemoteOptions(params) {
|
|
|
22
21
|
computed(() => search.value.trim()),
|
|
23
22
|
config.search?.debounce ?? DASHBOARD_REMOTE_OPTIONS_DEFAULTS.searchDebounce
|
|
24
23
|
);
|
|
24
|
+
function queryKeyFor(term2) {
|
|
25
|
+
return config.queryKeyFor?.({
|
|
26
|
+
page: { ...REMOTE_OPTIONS_FIRST_PAGE, size: pageSize },
|
|
27
|
+
search: term2
|
|
28
|
+
}) ?? config.queryKey ?? params.queryKey;
|
|
29
|
+
}
|
|
25
30
|
const pages = useInfiniteQuery(
|
|
26
31
|
computed(() => ({
|
|
27
32
|
enabled: (open.value || search.value !== "") && (term.value.length === 0 || term.value.length >= minLength),
|
|
@@ -33,7 +38,7 @@ export function useDashboardRemoteOptions(params) {
|
|
|
33
38
|
config.load({ page: { ...context.pageParam, size: pageSize }, search: term.value }),
|
|
34
39
|
context
|
|
35
40
|
),
|
|
36
|
-
queryKey: [...
|
|
41
|
+
queryKey: config.queryKeyFor ? [...queryKeyFor(term.value), { remoteOptionPages: pageSize }] : [...queryKeyFor(term.value), "options", term.value],
|
|
37
42
|
staleTime: DASHBOARD_REMOTE_OPTIONS_DEFAULTS.staleTime
|
|
38
43
|
}))
|
|
39
44
|
);
|
|
@@ -50,7 +55,7 @@ export function useDashboardRemoteOptions(params) {
|
|
|
50
55
|
computed(() => ({
|
|
51
56
|
enabled: Boolean(config.resolveSelected) && missing.value.length > 0,
|
|
52
57
|
queryFn: (context) => config.resolveSelected ? runDashboardRemoteResult(config.resolveSelected({ values: missing.value }), context) : Promise.resolve([]),
|
|
53
|
-
queryKey: [...
|
|
58
|
+
queryKey: config.selectedQueryKeyFor ? config.selectedQueryKeyFor({ values: missing.value }) : [...queryKeyFor(""), "selected", missing.value],
|
|
54
59
|
// Hydrated labels stay while the next set of missing values resolves.
|
|
55
60
|
placeholderData: (previous) => previous,
|
|
56
61
|
staleTime: DASHBOARD_REMOTE_OPTIONS_DEFAULTS.selectedStaleTime
|
|
@@ -150,8 +150,14 @@ export interface DashboardRemoteOptionsConfig {
|
|
|
150
150
|
/** Defaults to `{ type: 'page', size: 25 }`. */
|
|
151
151
|
pagination?: RemoteOptionsPagination;
|
|
152
152
|
search?: RemoteOptionsSearch;
|
|
153
|
-
/** Cache identity of
|
|
153
|
+
/** Cache identity of an inline source. Defaults to a key derived from the filter location. */
|
|
154
154
|
queryKey?: QueryKey;
|
|
155
|
+
/** Cache identity of a reusable loader, derived from its endpoint request and current search. */
|
|
156
|
+
queryKeyFor?: (request: DashboardRemoteOptionsRequest) => QueryKey;
|
|
157
|
+
/** Cache identity of a selected lookup, including scope absent from the page endpoint. */
|
|
158
|
+
selectedQueryKeyFor?: (request: {
|
|
159
|
+
values: readonly string[];
|
|
160
|
+
}) => QueryKey;
|
|
155
161
|
}
|
|
156
162
|
/** Option items of a filter: fixed, or read reactively (e.g. from another query's data). */
|
|
157
163
|
export type DashboardFilterItems = readonly DashboardOption[] | (() => readonly DashboardOption[]);
|
|
@@ -79,8 +79,25 @@ function optionsFilter(items, options) {
|
|
|
79
79
|
return defineFilter("options", codec, { ...options, items });
|
|
80
80
|
}
|
|
81
81
|
function remoteFilter(config, extra) {
|
|
82
|
-
const {
|
|
83
|
-
|
|
82
|
+
const {
|
|
83
|
+
load,
|
|
84
|
+
pagination,
|
|
85
|
+
queryKey,
|
|
86
|
+
queryKeyFor,
|
|
87
|
+
resolveSelected,
|
|
88
|
+
search,
|
|
89
|
+
selectedQueryKeyFor,
|
|
90
|
+
...options
|
|
91
|
+
} = config;
|
|
92
|
+
const remote = {
|
|
93
|
+
load,
|
|
94
|
+
pagination,
|
|
95
|
+
queryKey,
|
|
96
|
+
queryKeyFor,
|
|
97
|
+
resolveSelected,
|
|
98
|
+
search,
|
|
99
|
+
selectedQueryKeyFor
|
|
100
|
+
};
|
|
84
101
|
return defineFilter("remote", optionalStringCodec, {
|
|
85
102
|
searchable: true,
|
|
86
103
|
...options,
|
|
@@ -350,8 +350,27 @@ function resolveRemoteOptionConfig(field) {
|
|
|
350
350
|
return null;
|
|
351
351
|
}
|
|
352
352
|
const options = Object.getOwnPropertyDescriptor(field, "options")?.value;
|
|
353
|
+
if (isRemoteLoaderOptionConfig(options)) {
|
|
354
|
+
const { loader, ...fieldOptions } = options;
|
|
355
|
+
return {
|
|
356
|
+
...fieldOptions,
|
|
357
|
+
pagination: loader.pagination,
|
|
358
|
+
queryKeyFor: loader.queryKeyFor,
|
|
359
|
+
resolveSelected: loader.resolveSelected,
|
|
360
|
+
search: loader.search,
|
|
361
|
+
selectedQueryKeyFor: loader.selectedQueryKeyFor,
|
|
362
|
+
source: loader.load
|
|
363
|
+
};
|
|
364
|
+
}
|
|
353
365
|
return isRemoteOptionConfig(options) ? options : null;
|
|
354
366
|
}
|
|
367
|
+
function isRemoteLoaderOptionConfig(value) {
|
|
368
|
+
if (!isRecord(value) || value.mode !== "remote" || !isRecord(value.loader)) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
const { loader } = value;
|
|
372
|
+
return isFunction(loader.load) && isFunction(loader.resolveSelected) && isRecord(loader.pagination);
|
|
373
|
+
}
|
|
355
374
|
function isRemoteOptionConfig(value) {
|
|
356
375
|
return isRecord(value) && value.mode === "remote" && isFunction(Object.getOwnPropertyDescriptor(value, "source")?.value) && isRecord(value.pagination);
|
|
357
376
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useQueryClient } from "@tanstack/vue-query";
|
|
1
|
+
import { hashKey, useQueryClient } from "@tanstack/vue-query";
|
|
2
2
|
import { debounceFilter, watchWithFilter } from "@vueuse/core";
|
|
3
3
|
import { computed, ref, shallowRef, watch } from "vue";
|
|
4
4
|
import { formOptionKey, mergeResolvedOptions, normalizeOptionItems } from "../utils/options.js";
|
|
@@ -384,7 +384,7 @@ export function useRemoteFieldOptions(params) {
|
|
|
384
384
|
selectedError.value = null;
|
|
385
385
|
return;
|
|
386
386
|
}
|
|
387
|
-
retainSelected(
|
|
387
|
+
retainSelected(loaded.value);
|
|
388
388
|
if (missingSelected.value.length) {
|
|
389
389
|
void hydrateSelected({ reconcile: false });
|
|
390
390
|
}
|
|
@@ -410,6 +410,25 @@ export function useRemoteFieldOptions(params) {
|
|
|
410
410
|
},
|
|
411
411
|
{ deep: true }
|
|
412
412
|
);
|
|
413
|
+
const pageIdentity = computed(() => {
|
|
414
|
+
const config = params.config.value;
|
|
415
|
+
return config?.queryKeyFor ? hashKey(
|
|
416
|
+
config.queryKeyFor({
|
|
417
|
+
page: { cursor: null, index: 1, size: pageSize.value },
|
|
418
|
+
search: ""
|
|
419
|
+
})
|
|
420
|
+
) : null;
|
|
421
|
+
});
|
|
422
|
+
const selectedIdentity = computed(() => {
|
|
423
|
+
const config = params.config.value;
|
|
424
|
+
return config?.selectedQueryKeyFor ? hashKey(config.selectedQueryKeyFor({ values: [] })) : null;
|
|
425
|
+
});
|
|
426
|
+
watch([pageIdentity, selectedIdentity], ([page, selected], [previousPage, previousSelected]) => {
|
|
427
|
+
if (page === previousPage && selected === previousSelected) return;
|
|
428
|
+
selectionGeneration.value += 1;
|
|
429
|
+
if (page !== previousPage) void reload({ clearSearch: false });
|
|
430
|
+
if (selectedValues.value.length) void hydrateSelected({ reconcile: true });
|
|
431
|
+
});
|
|
413
432
|
return {
|
|
414
433
|
activate,
|
|
415
434
|
error: errorValue,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { QueryKey } from '@tanstack/vue-query';
|
|
2
|
+
import type { RemoteOptionsLoader, RemoteOptionsPageRequest, RemoteOptionsPagination, RemoteOptionsResult, RemoteOptionsSearch } from '../../shared/types/remote-options.js';
|
|
2
3
|
import type { FormValue } from './/index.js';
|
|
3
4
|
import type { FormFieldCallback, FormFieldCallbackParams } from './callbacks.js';
|
|
4
5
|
import type { FormQueryOptions } from './context.js';
|
|
@@ -92,6 +93,15 @@ export interface FormRemoteOptionConfig<TOption, TContext = NonNullable<unknown>
|
|
|
92
93
|
resolveSelected?: (request: FormRemoteSelectedRequest<TContext, TDeps, TValue, TOption>) => FormRemoteSource<readonly TOption[]>;
|
|
93
94
|
pagination: RemoteOptionsPagination;
|
|
94
95
|
search?: RemoteOptionsSearch;
|
|
96
|
+
/** Identity of the first page, so reusable loaders follow external scope changes. */
|
|
97
|
+
queryKeyFor?: (request: {
|
|
98
|
+
search: string;
|
|
99
|
+
page: RemoteOptionsPageRequest;
|
|
100
|
+
}) => QueryKey;
|
|
101
|
+
/** Identity of selected labels, including scope absent from the page endpoint. */
|
|
102
|
+
selectedQueryKeyFor?: (request: {
|
|
103
|
+
values: readonly FormOptionValue[];
|
|
104
|
+
}) => QueryKey;
|
|
95
105
|
/** Dependency aliases whose changes reset the loaded pages and re-run selected hydration. */
|
|
96
106
|
refreshOn?: readonly string[];
|
|
97
107
|
/** Clears selected values the latest successful selected hydration did not return. Defaults to `false`. */
|
|
@@ -103,4 +113,14 @@ export interface FormRemoteOptionConfig<TOption, TContext = NonNullable<unknown>
|
|
|
103
113
|
/** Disable the field while its option source has no usable data yet. */
|
|
104
114
|
disableOnLoading?: boolean;
|
|
105
115
|
}
|
|
106
|
-
|
|
116
|
+
/** Reuses one remote loader while keeping field-specific refresh and creation behavior local. */
|
|
117
|
+
export interface FormRemoteLoaderOptionConfig<TOption, TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue = FormValue> extends Omit<FormRemoteOptionConfig<TOption, TContext, TDeps, TValue>, 'source' | 'resolveSelected' | 'pagination' | 'search' | 'queryKeyFor' | 'selectedQueryKeyFor'> {
|
|
118
|
+
loader: RemoteOptionsLoader<TOption>;
|
|
119
|
+
source?: never;
|
|
120
|
+
resolveSelected?: never;
|
|
121
|
+
pagination?: never;
|
|
122
|
+
search?: never;
|
|
123
|
+
queryKeyFor?: never;
|
|
124
|
+
selectedQueryKeyFor?: never;
|
|
125
|
+
}
|
|
126
|
+
export type FormAnyOptionConfig<TOption, TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue = FormValue> = FormOptionConfig<TOption, TContext, TDeps, TValue> | FormRemoteOptionConfig<TOption, TContext, TDeps, TValue> | FormRemoteLoaderOptionConfig<TOption, TContext, TDeps, TValue> | FormOptionsSource<TOption, TContext, TDeps, TValue>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './composables/use-responsive-value.js';
|
|
2
2
|
export * from './composables/use-range-select.js';
|
|
3
|
+
export { defineRemoteOptions } from './utils/define-remote-options.js';
|
|
3
4
|
export * from './types/responsive.js';
|
|
4
5
|
export * from './types/utils.js';
|
|
5
6
|
export * from './utils/object.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./composables/use-responsive-value.js";
|
|
2
2
|
export * from "./composables/use-range-select.js";
|
|
3
|
+
export { defineRemoteOptions } from "./utils/define-remote-options.js";
|
|
3
4
|
export * from "./types/responsive.js";
|
|
4
5
|
export * from "./types/utils.js";
|
|
5
6
|
export * from "./utils/object.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { QueryFunction, QueryKey, UseQueryOptions } from '@tanstack/vue-query';
|
|
1
|
+
import type { QueryFunction, QueryKey, QueryOptions, UseQueryOptions } from '@tanstack/vue-query';
|
|
2
2
|
/**
|
|
3
3
|
* TanStack query definition accepted by runtime domains that own the `useQuery` call themselves
|
|
4
4
|
* (table sources, dashboard queries): an explicit `queryKey`, an optional `queryFn`, and any other
|
|
@@ -16,3 +16,7 @@ export interface QueryFnDefinition<TData = unknown> {
|
|
|
16
16
|
queryKey: QueryKey;
|
|
17
17
|
queryFn: QueryFunction<TData, QueryKey, string | null>;
|
|
18
18
|
}
|
|
19
|
+
/** Data returned by a query function, including generated options whose function may be a ref. */
|
|
20
|
+
export type QueryFunctionResult<TQuery> = TQuery extends {
|
|
21
|
+
queryFn: (...args: never[]) => infer TResult;
|
|
22
|
+
} ? Awaited<TResult> : TQuery extends QueryOptions<infer TQueryFnData, infer _TError, infer _TData, infer _TQueryData, infer _TQueryKey> ? Awaited<TQueryFnData> : TQuery extends UseQueryOptions<infer _TQueryFnData, infer _TError, infer TData, infer _TQueryData, infer _TQueryKey> ? Awaited<TData> : never;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import type { QueryKey } from '@tanstack/vue-query';
|
|
2
|
+
import type { QueryFnDefinition, QueryFunctionResult } from './query.js';
|
|
3
|
+
export type RemoteOptionValue = string | number | boolean;
|
|
4
|
+
/** Option shape shared by dashboard, table, and form remote pickers. */
|
|
5
|
+
export interface RemoteOption<TValue extends RemoteOptionValue = RemoteOptionValue> {
|
|
6
|
+
value: TValue;
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
1
9
|
/**
|
|
2
10
|
* Page envelope returned by an index-paginated remote option source.
|
|
3
11
|
*/
|
|
@@ -35,3 +43,51 @@ export interface RemoteOptionsSearch {
|
|
|
35
43
|
/** Minimum term length before a search request runs. Defaults to 0. */
|
|
36
44
|
minLength?: number;
|
|
37
45
|
}
|
|
46
|
+
/** One reusable, query-backed option source. Inline remote definitions remain supported. */
|
|
47
|
+
export interface RemoteOptionsLoader<TOption> {
|
|
48
|
+
load: (request: {
|
|
49
|
+
search: string;
|
|
50
|
+
page: RemoteOptionsPageRequest;
|
|
51
|
+
}) => QueryFnDefinition<RemoteOptionsResult<TOption>>;
|
|
52
|
+
resolveSelected: (request: {
|
|
53
|
+
values: readonly RemoteOptionValue[];
|
|
54
|
+
}) => QueryFnDefinition<readonly TOption[]>;
|
|
55
|
+
pagination: RemoteOptionsPagination;
|
|
56
|
+
search?: RemoteOptionsSearch;
|
|
57
|
+
/** Identity of a page query, used by pickers that cache an infinite list as one query. */
|
|
58
|
+
queryKeyFor?: (request: {
|
|
59
|
+
search: string;
|
|
60
|
+
page: RemoteOptionsPageRequest;
|
|
61
|
+
}) => QueryKey;
|
|
62
|
+
/** Identity of a selected-value lookup, including any scope used only by that endpoint. */
|
|
63
|
+
selectedQueryKeyFor?: (request: {
|
|
64
|
+
values: readonly RemoteOptionValue[];
|
|
65
|
+
}) => QueryKey;
|
|
66
|
+
}
|
|
67
|
+
/** Query options returned by an endpoint or generated client. */
|
|
68
|
+
export interface RemoteOptionsQueryDefinition {
|
|
69
|
+
queryKey: QueryKey;
|
|
70
|
+
}
|
|
71
|
+
/** Endpoint query definitions; supplied before mapping so their result types are inferred first. */
|
|
72
|
+
export interface RemoteOptionsQueries<TPageQuery extends RemoteOptionsQueryDefinition, TSelectedQuery extends RemoteOptionsQueryDefinition> {
|
|
73
|
+
load: (request: {
|
|
74
|
+
search: string;
|
|
75
|
+
page: RemoteOptionsPageRequest;
|
|
76
|
+
}) => TPageQuery;
|
|
77
|
+
resolveSelected: (request: {
|
|
78
|
+
values: readonly RemoteOptionValue[];
|
|
79
|
+
}) => TSelectedQuery;
|
|
80
|
+
}
|
|
81
|
+
/** Maps endpoint responses to the option result shared by all three pickers. */
|
|
82
|
+
export interface DefineRemoteOptionsConfig<TPageQuery extends RemoteOptionsQueryDefinition, TSelectedQuery extends RemoteOptionsQueryDefinition, TOption extends RemoteOption> {
|
|
83
|
+
/** Stable namespace for mapped query results; requests still use their endpoint query keys. */
|
|
84
|
+
key: string;
|
|
85
|
+
/** Maps the endpoint page to options and the next-page marker. */
|
|
86
|
+
mapPage: (result: QueryFunctionResult<TPageQuery>) => RemoteOptionsResult<TOption>;
|
|
87
|
+
/** Maps selected lookup results to option items. */
|
|
88
|
+
mapSelected: (result: QueryFunctionResult<TSelectedQuery>) => readonly NoInfer<TOption>[];
|
|
89
|
+
/** Page type and size expected by the endpoint. */
|
|
90
|
+
pagination: RemoteOptionsPagination;
|
|
91
|
+
/** Debounce and minimum term length applied by the consuming picker. */
|
|
92
|
+
search?: RemoteOptionsSearch;
|
|
93
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DefineRemoteOptionsConfig, RemoteOption, RemoteOptionsLoader, RemoteOptionsQueries, RemoteOptionsQueryDefinition } from '../types/remote-options.js';
|
|
2
|
+
/**
|
|
3
|
+
* Defines a query-backed option loader once for dashboard filters, table filters, and form fields.
|
|
4
|
+
* The endpoint's query key separates requests; `key` separates mapped option data from other
|
|
5
|
+
* consumers of that endpoint. Inline remote definitions continue to work in each domain.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const users = defineRemoteOptions({
|
|
10
|
+
* load: ({ search, page }) => api.users.list.queryOptions({ search, cursor: page.cursor }),
|
|
11
|
+
* resolveSelected: ({ values }) => api.users.byIds.queryOptions({ ids: values }),
|
|
12
|
+
* }, {
|
|
13
|
+
* key: 'users',
|
|
14
|
+
* mapPage: ({ rows, nextCursor }) => ({
|
|
15
|
+
* options: rows.map((user) => ({ value: user.id, label: user.name })),
|
|
16
|
+
* nextCursor,
|
|
17
|
+
* }),
|
|
18
|
+
* mapSelected: ({ rows }) => rows.map((user) => ({ value: user.id, label: user.name })),
|
|
19
|
+
* pagination: { type: 'cursor', size: 25 },
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function defineRemoteOptions<TPageQuery extends RemoteOptionsQueryDefinition, TSelectedQuery extends RemoteOptionsQueryDefinition, const TOption extends RemoteOption>(queries: RemoteOptionsQueries<TPageQuery, TSelectedQuery>, config: DefineRemoteOptionsConfig<TPageQuery, TSelectedQuery, TOption>): RemoteOptionsLoader<TOption>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mapRemoteOptionsQuery } from "./remote-options-query.js";
|
|
2
|
+
export function defineRemoteOptions(queries, config) {
|
|
3
|
+
const suffix = ["remote-options", config.key];
|
|
4
|
+
return {
|
|
5
|
+
load: (request) => mapRemoteOptionsQuery(
|
|
6
|
+
queries.load(request),
|
|
7
|
+
config.mapPage,
|
|
8
|
+
[...suffix, "page"]
|
|
9
|
+
),
|
|
10
|
+
pagination: config.pagination,
|
|
11
|
+
queryKeyFor: (request) => [...queries.load(request).queryKey, ...suffix, "page"],
|
|
12
|
+
resolveSelected: (request) => mapRemoteOptionsQuery(
|
|
13
|
+
queries.resolveSelected(request),
|
|
14
|
+
config.mapSelected,
|
|
15
|
+
[...suffix, "selected"]
|
|
16
|
+
),
|
|
17
|
+
selectedQueryKeyFor: (request) => [
|
|
18
|
+
...queries.resolveSelected(request).queryKey,
|
|
19
|
+
...suffix,
|
|
20
|
+
"selected"
|
|
21
|
+
],
|
|
22
|
+
search: config.search
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { QueryKey } from '@tanstack/vue-query';
|
|
2
|
+
import type { QueryFnDefinition } from '../types/query.js';
|
|
3
|
+
/** Maps an endpoint query while keeping its own key and cancellation context. */
|
|
4
|
+
export declare function mapRemoteOptionsQuery<TData, TResult>(definition: {
|
|
5
|
+
queryKey: QueryKey;
|
|
6
|
+
}, map: (result: TData) => TResult, suffix: readonly unknown[]): QueryFnDefinition<TResult>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isFunction } from "./predicate.js";
|
|
2
|
+
export function mapRemoteOptionsQuery(definition, map, suffix) {
|
|
3
|
+
return {
|
|
4
|
+
async queryFn(context) {
|
|
5
|
+
if (!hasQueryFunction(definition)) {
|
|
6
|
+
throw new Error(
|
|
7
|
+
"[remote options] `load` and `resolveSelected` must return a query with a queryFn."
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
const result = await definition.queryFn({ ...context, queryKey: definition.queryKey });
|
|
11
|
+
return map(result);
|
|
12
|
+
},
|
|
13
|
+
queryKey: [...definition.queryKey, ...suffix]
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function hasQueryFunction(definition) {
|
|
17
|
+
return "queryFn" in definition && isFunction(definition.queryFn);
|
|
18
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { DataListControlSize, DataListFilterTagsUi } from '../../types/inde
|
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
showAdd?: boolean;
|
|
4
4
|
showClear?: boolean;
|
|
5
|
-
/** `
|
|
5
|
+
/** `tags` keeps inline tags on mobile (default); `sheet` moves all filters into a bottom sheet. */
|
|
6
6
|
mobile?: 'sheet' | 'tags';
|
|
7
7
|
size?: DataListControlSize;
|
|
8
8
|
ui?: DataListFilterTagsUi;
|
|
@@ -9,7 +9,7 @@ import FilterTagsBar from "../filters/tags/filter-tags-bar.vue";
|
|
|
9
9
|
const props = defineProps({
|
|
10
10
|
showAdd: { type: Boolean, required: false, default: false },
|
|
11
11
|
showClear: { type: Boolean, required: false, default: false },
|
|
12
|
-
mobile: { type: String, required: false, default: "
|
|
12
|
+
mobile: { type: String, required: false, default: "tags" },
|
|
13
13
|
size: { type: String, required: false },
|
|
14
14
|
ui: { type: Object, required: false }
|
|
15
15
|
});
|
|
@@ -2,7 +2,7 @@ import type { DataListControlSize, DataListFilterTagsUi } from '../../types/inde
|
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
showAdd?: boolean;
|
|
4
4
|
showClear?: boolean;
|
|
5
|
-
/** `
|
|
5
|
+
/** `tags` keeps inline tags on mobile (default); `sheet` moves all filters into a bottom sheet. */
|
|
6
6
|
mobile?: 'sheet' | 'tags';
|
|
7
7
|
size?: DataListControlSize;
|
|
8
8
|
ui?: DataListFilterTagsUi;
|
|
@@ -5,6 +5,7 @@ import USlideover from "@nuxt/ui/components/Slideover.vue";
|
|
|
5
5
|
import { computed, onMounted, watch } from "vue";
|
|
6
6
|
import { useUiToolsLocale } from "#ui-tools/i18n";
|
|
7
7
|
import { isNullish } from "../../../../shared/utils/predicate";
|
|
8
|
+
import { useDataListBreakpoint } from "../../../composables/use-data-list-breakpoint";
|
|
8
9
|
import { useDataListUi } from "../../../composables/use-data-list-ui";
|
|
9
10
|
import { useTableInternals } from "../../../composables/use-table-internals";
|
|
10
11
|
import { mergeDataListProps, mergeDataListUiClass } from "../../../utils";
|
|
@@ -19,6 +20,7 @@ const props = defineProps({
|
|
|
19
20
|
});
|
|
20
21
|
const internals = useTableInternals();
|
|
21
22
|
const dataListUi = useDataListUi();
|
|
23
|
+
const { isMobile } = useDataListBreakpoint();
|
|
22
24
|
const { locale, t } = useUiToolsLocale();
|
|
23
25
|
const config = computed(() => dataListUi.ui.value.filterPanel);
|
|
24
26
|
const resolvedUi = computed(() => ({ ...config.value?.ui, ...props.ui }));
|
|
@@ -39,6 +41,7 @@ const triggerBind = computed(() => {
|
|
|
39
41
|
return rest;
|
|
40
42
|
});
|
|
41
43
|
const triggerLabel = computed(() => triggerProps.value.label ?? t("table.filters.panel.trigger"));
|
|
44
|
+
const hasTriggerIcon = computed(() => Boolean(triggerProps.value.icon));
|
|
42
45
|
const countProps = computed(
|
|
43
46
|
() => controlProps.value.count === false ? null : mergeDataListProps(
|
|
44
47
|
{ color: "neutral", size: "xs", variant: "solid" },
|
|
@@ -212,6 +215,10 @@ onMounted(() => {
|
|
|
212
215
|
<UButton
|
|
213
216
|
v-bind="triggerBind"
|
|
214
217
|
:aria-expanded="open"
|
|
218
|
+
:aria-label="triggerLabel"
|
|
219
|
+
:square="
|
|
220
|
+
triggerProps.square ?? (isMobile && hasTriggerIcon && (!countProps || activeCount === 0))
|
|
221
|
+
"
|
|
215
222
|
:ui="{
|
|
216
223
|
base: mergeDataListUiClass(
|
|
217
224
|
'nut-dl-fpanel-trigger shrink-0',
|
|
@@ -221,11 +228,16 @@ onMounted(() => {
|
|
|
221
228
|
}"
|
|
222
229
|
>
|
|
223
230
|
<span
|
|
231
|
+
v-if="!isMobile || !hasTriggerIcon || countProps && activeCount > 0"
|
|
224
232
|
:class="
|
|
225
|
-
mergeDataListUiClass(
|
|
233
|
+
mergeDataListUiClass(
|
|
234
|
+
'nut-dl-fpanel-trigger__content flex items-center gap-2',
|
|
235
|
+
void 0,
|
|
236
|
+
resolvedUi.triggerContent
|
|
237
|
+
)
|
|
226
238
|
"
|
|
227
239
|
>
|
|
228
|
-
<span>{{ triggerLabel }}</span>
|
|
240
|
+
<span v-if="!isMobile || !hasTriggerIcon">{{ triggerLabel }}</span>
|
|
229
241
|
<UBadge
|
|
230
242
|
v-if="countProps && activeCount > 0"
|
|
231
243
|
v-bind="countProps"
|
|
@@ -48,11 +48,12 @@ const geometry = computed(() => resolveDataListControlGeometry(size.value));
|
|
|
48
48
|
:key="entry.value == null ? entry.label : String(entry.value)"
|
|
49
49
|
:class="
|
|
50
50
|
mergeDataListUiClass(
|
|
51
|
-
`nut-dl-option flex items-center rounded-md text-left outline-none transition-colors hover:bg-elevated ${sizeClasses.option} ${entry.selected ? 'text-highlighted' : 'text-default'}`,
|
|
51
|
+
`nut-dl-option flex cursor-pointer items-center rounded-md text-left outline-none transition-colors hover:bg-elevated ${sizeClasses.option} ${entry.selected ? 'text-highlighted' : 'text-default'}`,
|
|
52
52
|
void 0,
|
|
53
53
|
ui?.option
|
|
54
54
|
)
|
|
55
55
|
"
|
|
56
|
+
@click="emit('select', { event: $event, entry, index, sectionKey: section.key })"
|
|
56
57
|
>
|
|
57
58
|
<UCheckbox
|
|
58
59
|
:model-value="entry.selected ?? false"
|
|
@@ -67,7 +68,6 @@ const geometry = computed(() => resolveDataListControlGeometry(size.value));
|
|
|
67
68
|
<button
|
|
68
69
|
type="button"
|
|
69
70
|
:class="['flex min-w-0 flex-1 items-center text-left', geometry.toolbarGap]"
|
|
70
|
-
@click="emit('select', { event: $event, entry, index, sectionKey: section.key })"
|
|
71
71
|
>
|
|
72
72
|
<span
|
|
73
73
|
v-if="entry.color"
|
|
@@ -39,7 +39,7 @@ const modelValue = defineModel({ type: null, ...{
|
|
|
39
39
|
root: 'w-full',
|
|
40
40
|
fieldset: mergeDataListUiClass('grid gap-0.5', void 0, ui?.list),
|
|
41
41
|
item: mergeDataListUiClass(
|
|
42
|
-
`flex items-center rounded-md transition-colors hover:bg-elevated data-[state=checked]:bg-elevated ${sizeClasses.option}`,
|
|
42
|
+
`relative flex cursor-pointer items-center rounded-md transition-colors hover:bg-elevated data-[state=checked]:bg-elevated ${sizeClasses.option}`,
|
|
43
43
|
void 0,
|
|
44
44
|
ui?.option
|
|
45
45
|
),
|
|
@@ -47,7 +47,7 @@ const modelValue = defineModel({ type: null, ...{
|
|
|
47
47
|
base: 'cursor-pointer',
|
|
48
48
|
wrapper: 'min-w-0 flex-1',
|
|
49
49
|
label: mergeDataListUiClass(
|
|
50
|
-
`w-full cursor-pointer text-default ${sizeClasses.optionLabel}`,
|
|
50
|
+
`w-full cursor-pointer text-default before:absolute before:inset-0 ${sizeClasses.optionLabel}`,
|
|
51
51
|
void 0,
|
|
52
52
|
ui?.optionLabel
|
|
53
53
|
)
|
|
@@ -61,12 +61,13 @@ function activateTreeEntry(entry) {
|
|
|
61
61
|
:key="entry.id"
|
|
62
62
|
:class="
|
|
63
63
|
mergeDataListUiClass(
|
|
64
|
-
`flex items-center rounded-md text-left transition-colors hover:bg-elevated ${sizeClasses.option} ${entry.selected ? 'bg-elevated text-highlighted' : 'text-default'}`,
|
|
64
|
+
`flex cursor-pointer items-center rounded-md text-left transition-colors hover:bg-elevated ${sizeClasses.option} ${entry.selected ? 'bg-elevated text-highlighted' : 'text-default'}`,
|
|
65
65
|
void 0,
|
|
66
66
|
ui?.option
|
|
67
67
|
)
|
|
68
68
|
"
|
|
69
69
|
:style="getTreeIndentStyle(entry.depth)"
|
|
70
|
+
@click="activateTreeEntry(entry)"
|
|
70
71
|
>
|
|
71
72
|
<button
|
|
72
73
|
v-if="entry.expandable"
|
|
@@ -111,7 +112,6 @@ function activateTreeEntry(entry) {
|
|
|
111
112
|
type="button"
|
|
112
113
|
class="flex min-w-0 flex-1 items-center text-left"
|
|
113
114
|
:class="geometry.toolbarGap"
|
|
114
|
-
@click="activateTreeEntry(entry)"
|
|
115
115
|
>
|
|
116
116
|
<UIcon
|
|
117
117
|
v-if="entry.icon"
|
|
@@ -161,7 +161,7 @@ function activateTreeEntry(entry) {
|
|
|
161
161
|
root: 'w-full',
|
|
162
162
|
fieldset: mergeDataListUiClass('grid gap-0.5', void 0, ui?.list),
|
|
163
163
|
item: mergeDataListUiClass(
|
|
164
|
-
`flex items-center rounded-md transition-colors hover:bg-elevated data-[state=checked]:bg-elevated ${sizeClasses.option}`,
|
|
164
|
+
`relative flex cursor-pointer items-center rounded-md transition-colors hover:bg-elevated data-[state=checked]:bg-elevated ${sizeClasses.option}`,
|
|
165
165
|
void 0,
|
|
166
166
|
ui?.option
|
|
167
167
|
),
|
|
@@ -169,7 +169,7 @@ function activateTreeEntry(entry) {
|
|
|
169
169
|
base: 'cursor-pointer',
|
|
170
170
|
wrapper: 'min-w-0 flex-1',
|
|
171
171
|
label: mergeDataListUiClass(
|
|
172
|
-
`w-full cursor-pointer text-default ${sizeClasses.optionLabel}`,
|
|
172
|
+
`w-full cursor-pointer text-default before:absolute before:inset-0 ${sizeClasses.optionLabel}`,
|
|
173
173
|
void 0,
|
|
174
174
|
ui?.optionLabel
|
|
175
175
|
)
|
|
@@ -187,7 +187,7 @@ function activateTreeEntry(entry) {
|
|
|
187
187
|
:aria-label="item.expanded ? `Collapse ${item.label}` : `Expand ${item.label}`"
|
|
188
188
|
:aria-expanded="item.expanded"
|
|
189
189
|
:class="[
|
|
190
|
-
'flex shrink-0 items-center justify-center rounded-sm text-muted transition-transform hover:bg-accented',
|
|
190
|
+
'relative z-10 flex shrink-0 items-center justify-center rounded-sm text-muted transition-transform hover:bg-accented',
|
|
191
191
|
sizeClasses.optionIcon,
|
|
192
192
|
item.expanded ? 'rotate-90' : ''
|
|
193
193
|
]"
|
|
@@ -68,7 +68,13 @@ const labelClass = computed(() => `flex min-w-0 items-center ${geometry.value.to
|
|
|
68
68
|
|
|
69
69
|
<template>
|
|
70
70
|
<div
|
|
71
|
-
:class="
|
|
71
|
+
:class="
|
|
72
|
+
mergeDataListUiClass(
|
|
73
|
+
'nut-dl-tag-wrap inline-flex min-w-0 max-w-full align-top',
|
|
74
|
+
void 0,
|
|
75
|
+
ui?.root
|
|
76
|
+
)
|
|
77
|
+
"
|
|
72
78
|
>
|
|
73
79
|
<template v-if="showOperatorPickerFirst">
|
|
74
80
|
<UButton
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useQuery, useQueryClient } from "@tanstack/vue-query";
|
|
1
|
+
import { hashKey, useQuery, useQueryClient } from "@tanstack/vue-query";
|
|
2
2
|
import { computed, shallowRef, watch } from "vue";
|
|
3
3
|
import { isBoolean, isNullish, isNumber, isString } from "../../shared/utils/predicate.js";
|
|
4
4
|
import { QUERY_DEFAULTS } from "../constants/query-state.js";
|
|
@@ -11,6 +11,21 @@ export function useTableFilterSelectedOptions(params) {
|
|
|
11
11
|
return remote ? [{ key: definition.key, remote }] : [];
|
|
12
12
|
})
|
|
13
13
|
);
|
|
14
|
+
const selectedScopes = computed(
|
|
15
|
+
() => remoteDefinitions.value.map(({ key, remote }) => ({
|
|
16
|
+
identity: remote.selectedQueryKeyFor ? hashKey(remote.selectedQueryKeyFor({ values: [] })) : null,
|
|
17
|
+
key
|
|
18
|
+
}))
|
|
19
|
+
);
|
|
20
|
+
watch(selectedScopes, (current, previous) => {
|
|
21
|
+
const earlier = new Map(previous.map((entry) => [entry.key, entry.identity]));
|
|
22
|
+
const next = new Map(known.value);
|
|
23
|
+
for (const entry of current) {
|
|
24
|
+
if (earlier.has(entry.key) && earlier.get(entry.key) !== entry.identity)
|
|
25
|
+
next.delete(entry.key);
|
|
26
|
+
}
|
|
27
|
+
if (next.size !== known.value.size) known.value = next;
|
|
28
|
+
});
|
|
14
29
|
function selectedValues(key) {
|
|
15
30
|
const rule = params.rules.value.find((entry) => entry.key === key);
|
|
16
31
|
const values = Array.isArray(rule?.value) ? rule.value : isNullish(rule?.value) ? [] : [rule.value];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { QueryKey } from '@tanstack/vue-query';
|
|
1
2
|
import type { RemoteOptionsPageRequest, RemoteOptionsPagination, RemoteOptionsResult, RemoteOptionsSearch } from '../../shared/types/remote-options.js';
|
|
2
3
|
import type { TableFilterOperator, TableQueryStateFilterRule, TableQueryStateFilterValue } from './query-state.js';
|
|
3
4
|
import type { TableFacetExecutionResult, TableFacetRequestDescriptor, TableFacetsContext, TableQueryDefinition } from './source.js';
|
|
@@ -566,6 +567,12 @@ export interface TableFilterRemoteOptions<TValue = TableFilterPrimitiveValue, TP
|
|
|
566
567
|
resolveSelected?: {
|
|
567
568
|
bivarianceHack(request: TableFilterRemoteSelectedRequest<TValue>): TableQueryDefinition<readonly TableOptionEntryForPresentation<TValue, TPresentation>[]>;
|
|
568
569
|
}['bivarianceHack'];
|
|
570
|
+
/** Identity of a selected lookup for reusable loaders with changing external scope. */
|
|
571
|
+
selectedQueryKeyFor?: {
|
|
572
|
+
bivarianceHack(request: {
|
|
573
|
+
values: readonly TValue[];
|
|
574
|
+
}): QueryKey;
|
|
575
|
+
}['bivarianceHack'];
|
|
569
576
|
/**
|
|
570
577
|
* Page size, and how far before the list end the next page loads. Defaults to
|
|
571
578
|
* `{ type: 'page', size: 25 }` with the next page requested three viewport heights ahead.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { QueryKey } from '@tanstack/vue-query';
|
|
2
|
-
import type {
|
|
3
|
-
import type { RemoteOptionsPageRequest, RemoteOptionsPagination, RemoteOptionsResult, RemoteOptionsSearch } from '../../shared/types/remote-options.js';
|
|
2
|
+
import type { RemoteOptionsLoader, RemoteOptionsPagination, RemoteOptionsSearch } from '../../shared/types/remote-options.js';
|
|
4
3
|
import type { TableResolvedFilterGroup } from './filters.js';
|
|
5
4
|
import type { TableCursorPageResult, TableOffsetPageResult, TableRemoteSourceRequest, TableSourceExecutionResult, TableSourceQueryResult, TableSourceRow } from './source.js';
|
|
6
5
|
import type { GenericObject, TableKnownFieldPath, TableSortingRule, TableSortKey } from './utils.js';
|
|
@@ -47,18 +46,6 @@ export interface RemoteTableOptionsConfig<TRow extends GenericObject, TOption ex
|
|
|
47
46
|
}
|
|
48
47
|
/**
|
|
49
48
|
* Remote option source built by `remoteTableOptions()`. It fits dashboard `p.remote()`, table
|
|
50
|
-
* option filters (`source.remote`), and form remote options (`
|
|
49
|
+
* option filters (`source.remote`), and form remote options (`loader: options`).
|
|
51
50
|
*/
|
|
52
|
-
export
|
|
53
|
-
/** Query definition of one page of options for a search term. */
|
|
54
|
-
load: (request: {
|
|
55
|
-
search: string;
|
|
56
|
-
page: RemoteOptionsPageRequest;
|
|
57
|
-
}) => QueryFnDefinition<RemoteOptionsResult<TOption>>;
|
|
58
|
-
/** Query definition of the options of selected values. */
|
|
59
|
-
resolveSelected: (request: {
|
|
60
|
-
values: readonly (string | number | boolean)[];
|
|
61
|
-
}) => QueryFnDefinition<readonly TOption[]>;
|
|
62
|
-
pagination: RemoteOptionsPagination;
|
|
63
|
-
search?: RemoteOptionsSearch;
|
|
64
|
-
}
|
|
51
|
+
export type RemoteTableOptions<TOption> = RemoteOptionsLoader<TOption>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { isArray,
|
|
1
|
+
import { isArray, isObject } from "../../shared/utils/predicate.js";
|
|
2
|
+
import { mapRemoteOptionsQuery } from "../../shared/utils/remote-options-query.js";
|
|
2
3
|
const DEFAULT_PAGINATION = { size: 25, type: "cursor" };
|
|
3
4
|
export function remoteTableOptions(query, config) {
|
|
4
5
|
const pagination = config.pagination ?? DEFAULT_PAGINATION;
|
|
@@ -13,55 +14,54 @@ export function remoteTableOptions(query, config) {
|
|
|
13
14
|
sorting: params.filters ? [] : [...sorting]
|
|
14
15
|
};
|
|
15
16
|
}
|
|
17
|
+
function selectedRequest(values) {
|
|
18
|
+
return request({
|
|
19
|
+
filters: [
|
|
20
|
+
{
|
|
21
|
+
children: [
|
|
22
|
+
{
|
|
23
|
+
key: config.valueKey ?? "id",
|
|
24
|
+
operator: "isAnyOf",
|
|
25
|
+
type: "condition",
|
|
26
|
+
value: [...values]
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
combinator: "and",
|
|
30
|
+
type: "group"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
page: { cursor: null, index: 1, size: Math.max(1, values.length) },
|
|
34
|
+
search: ""
|
|
35
|
+
});
|
|
36
|
+
}
|
|
16
37
|
return {
|
|
17
38
|
load: ({ page, search: term }) => mapQuery(query(request({ page, search: term })), (result) => ({
|
|
18
39
|
...resolvePage(result),
|
|
19
40
|
options: result.rows.map(config.option)
|
|
20
41
|
})),
|
|
21
42
|
pagination,
|
|
22
|
-
|
|
23
|
-
query(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
value: [...values]
|
|
33
|
-
}
|
|
34
|
-
],
|
|
35
|
-
combinator: "and",
|
|
36
|
-
type: "group"
|
|
37
|
-
}
|
|
38
|
-
],
|
|
39
|
-
page: { cursor: null, index: 1, size: Math.max(1, values.length) },
|
|
40
|
-
search: ""
|
|
41
|
-
})
|
|
42
|
-
),
|
|
43
|
-
(result) => result.rows.map(config.option)
|
|
44
|
-
),
|
|
45
|
-
search
|
|
43
|
+
queryKeyFor: ({ page, search: term }) => [
|
|
44
|
+
...query(request({ page, search: term })).queryKey,
|
|
45
|
+
"remote-options"
|
|
46
|
+
],
|
|
47
|
+
resolveSelected: ({ values }) => mapQuery(query(selectedRequest(values)), (result) => result.rows.map(config.option)),
|
|
48
|
+
search,
|
|
49
|
+
selectedQueryKeyFor: ({ values }) => [
|
|
50
|
+
...query(selectedRequest(values)).queryKey,
|
|
51
|
+
"remote-options"
|
|
52
|
+
]
|
|
46
53
|
};
|
|
47
54
|
}
|
|
48
55
|
function mapQuery(definition, map) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
async queryFn(context) {
|
|
53
|
-
if (!isQueryFn(queryFn))
|
|
54
|
-
throw new Error("[remoteTableOptions] `query` must return a query with a queryFn.");
|
|
55
|
-
const result = await queryFn({ ...context, queryKey });
|
|
56
|
+
return mapRemoteOptionsQuery(
|
|
57
|
+
definition,
|
|
58
|
+
(result) => {
|
|
56
59
|
if (!isTableResult(result))
|
|
57
60
|
throw new Error("[remoteTableOptions] the query must resolve to a table response (`rows`).");
|
|
58
61
|
return map(result);
|
|
59
62
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
function isQueryFn(value) {
|
|
64
|
-
return isFunction(value);
|
|
63
|
+
["remote-options"]
|
|
64
|
+
);
|
|
65
65
|
}
|
|
66
66
|
function isTableResult(value) {
|
|
67
67
|
return isObject(value) && isArray(value.rows);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ui-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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",
|