nuxt-ui-tools 1.3.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 +10 -2
- 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/components/actions/form-actions.vue +13 -127
- package/dist/runtime/form/components/page/form-page-actions.d.vue.ts +3 -0
- package/dist/runtime/form/components/page/form-page-actions.vue +40 -0
- package/dist/runtime/form/components/page/form-page-actions.vue.d.ts +3 -0
- package/dist/runtime/form/components/page/form-page-header.d.vue.ts +24 -0
- package/dist/runtime/form/components/page/form-page-header.vue +99 -0
- package/dist/runtime/form/components/page/form-page-header.vue.d.ts +24 -0
- package/dist/runtime/form/components/page/form-page-navigation.d.vue.ts +34 -0
- package/dist/runtime/form/components/page/form-page-navigation.vue +229 -0
- package/dist/runtime/form/components/page/form-page-navigation.vue.d.ts +34 -0
- package/dist/runtime/form/components/page/form-page-section.d.vue.ts +19 -0
- package/dist/runtime/form/components/page/form-page-section.vue +114 -0
- package/dist/runtime/form/components/page/form-page-section.vue.d.ts +19 -0
- package/dist/runtime/form/components/page/form-page-sections.d.vue.ts +21 -0
- package/dist/runtime/form/components/page/form-page-sections.vue +75 -0
- package/dist/runtime/form/components/page/form-page-sections.vue.d.ts +21 -0
- package/dist/runtime/form/components/page/form-page.d.vue.ts +80 -0
- package/dist/runtime/form/components/page/form-page.vue +91 -0
- package/dist/runtime/form/components/page/form-page.vue.d.ts +80 -0
- package/dist/runtime/form/components/renderer/form-field-shell.vue +7 -11
- package/dist/runtime/form/components/root/form.vue +25 -194
- package/dist/runtime/form/composables/use-field-options.js +19 -0
- package/dist/runtime/form/composables/use-form-action-buttons.d.ts +32 -0
- package/dist/runtime/form/composables/use-form-action-buttons.js +89 -0
- package/dist/runtime/form/composables/use-form-actions.d.ts +8 -5
- package/dist/runtime/form/composables/use-form-actions.js +1 -1
- package/dist/runtime/form/composables/use-form-page-scroll.d.ts +22 -0
- package/dist/runtime/form/composables/use-form-page-scroll.js +178 -0
- package/dist/runtime/form/composables/use-form-page.d.ts +114 -0
- package/dist/runtime/form/composables/use-form-page.js +83 -0
- package/dist/runtime/form/composables/use-form-root.d.ts +34 -0
- package/dist/runtime/form/composables/use-form-root.js +171 -0
- package/dist/runtime/form/composables/use-form-runtime.js +13 -4
- package/dist/runtime/form/composables/use-form.js +2 -22
- package/dist/runtime/form/composables/use-remote-field-options.js +21 -2
- package/dist/runtime/form/fields/checkbox-card/component.vue +28 -19
- package/dist/runtime/form/fields/checkbox-card/types.d.ts +2 -5
- package/dist/runtime/form/fields/choice-card/choice-card-label.d.vue.ts +17 -0
- package/dist/runtime/form/fields/choice-card/choice-card-label.vue +50 -0
- package/dist/runtime/form/fields/choice-card/choice-card-label.vue.d.ts +17 -0
- package/dist/runtime/form/fields/choice-card/types.d.ts +31 -0
- package/dist/runtime/form/fields/choice-card/types.js +0 -0
- package/dist/runtime/form/fields/choice-card/use-choice-card.d.ts +22 -0
- package/dist/runtime/form/fields/choice-card/use-choice-card.js +52 -0
- package/dist/runtime/form/fields/radio-card/component.vue +17 -18
- package/dist/runtime/form/fields/radio-card/types.d.ts +2 -4
- package/dist/runtime/form/index.d.ts +1 -1
- package/dist/runtime/form/index.js +7 -1
- package/dist/runtime/form/schema/index.d.ts +1 -1
- package/dist/runtime/form/schema/index.js +1 -0
- package/dist/runtime/form/schema/page.d.ts +50 -0
- package/dist/runtime/form/schema/page.js +25 -0
- package/dist/runtime/form/types/index.d.ts +1 -0
- package/dist/runtime/form/types/options.d.ts +22 -2
- package/dist/runtime/form/types/page.d.ts +172 -0
- package/dist/runtime/form/types/page.js +0 -0
- package/dist/runtime/form/types/ui.d.ts +60 -0
- package/dist/runtime/form/utils/controls.d.ts +15 -0
- package/dist/runtime/form/utils/controls.js +38 -0
- package/dist/runtime/form/utils/layout.js +3 -1
- package/dist/runtime/form/utils/page.d.ts +20 -0
- package/dist/runtime/form/utils/page.js +107 -0
- package/dist/runtime/form/utils/state.d.ts +2 -0
- package/dist/runtime/form/utils/state.js +1 -1
- package/dist/runtime/form/utils/ui.js +1 -0
- package/dist/runtime/i18n/locales/en.js +18 -0
- package/dist/runtime/i18n/locales/fr.js +18 -0
- package/dist/runtime/i18n/types.d.ts +29 -0
- 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
|
@@ -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",
|