nuxt-ui-tools 1.18.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/runtime/form/components/page/form-page-header.d.vue.ts +3 -1
- package/dist/runtime/form/components/page/form-page-header.vue +44 -34
- package/dist/runtime/form/components/page/form-page-header.vue.d.ts +3 -1
- package/dist/runtime/form/components/page/form-page-section.vue +14 -0
- package/dist/runtime/form/components/page/form-page.vue +2 -1
- package/dist/runtime/form/components/utils/form-option-choice-label.d.vue.ts +16 -0
- package/dist/runtime/form/components/utils/form-option-choice-label.vue +37 -0
- package/dist/runtime/form/components/utils/form-option-choice-label.vue.d.ts +16 -0
- package/dist/runtime/form/components/utils/form-option-display-value.d.vue.ts +14 -0
- package/dist/runtime/form/components/utils/form-option-display-value.vue +20 -0
- package/dist/runtime/form/components/utils/form-option-display-value.vue.d.ts +14 -0
- package/dist/runtime/form/components/utils/form-option-item-label.d.vue.ts +9 -2
- package/dist/runtime/form/components/utils/form-option-item-label.vue +20 -13
- package/dist/runtime/form/components/utils/form-option-item-label.vue.d.ts +9 -2
- package/dist/runtime/form/components/utils/form-option-leading.d.vue.ts +23 -0
- package/dist/runtime/form/components/utils/form-option-leading.vue +41 -0
- package/dist/runtime/form/components/utils/form-option-leading.vue.d.ts +23 -0
- package/dist/runtime/form/components/utils/form-option-render.d.ts +11 -0
- package/dist/runtime/form/components/utils/form-option-render.js +5 -0
- package/dist/runtime/form/components/utils/form-option-suffix.d.vue.ts +8 -0
- package/dist/runtime/form/components/utils/form-option-suffix.vue +15 -0
- package/dist/runtime/form/components/utils/form-option-suffix.vue.d.ts +8 -0
- package/dist/runtime/form/components/utils/form-option-value-overlay.d.vue.ts +30 -0
- package/dist/runtime/form/components/utils/form-option-value-overlay.vue +44 -0
- package/dist/runtime/form/components/utils/form-option-value-overlay.vue.d.ts +30 -0
- package/dist/runtime/form/composables/use-choice-group-items.d.ts +27 -0
- package/dist/runtime/form/composables/use-choice-group-items.js +24 -0
- package/dist/runtime/form/composables/use-field-control.d.ts +5 -5
- package/dist/runtime/form/composables/use-form-layout.js +4 -3
- package/dist/runtime/form/composables/use-option-menu.d.ts +20 -0
- package/dist/runtime/form/composables/use-option-menu.js +41 -0
- package/dist/runtime/form/fields/auto-complete/component.vue +92 -51
- package/dist/runtime/form/fields/auto-complete/config.d.ts +1 -0
- package/dist/runtime/form/fields/auto-complete/config.js +1 -1
- package/dist/runtime/form/fields/auto-complete/types.d.ts +2 -2
- package/dist/runtime/form/fields/checkbox-card/component.vue +3 -3
- package/dist/runtime/form/fields/checkbox-card/types.d.ts +3 -3
- package/dist/runtime/form/fields/checkbox-group/component.vue +18 -4
- package/dist/runtime/form/fields/checkbox-group/types.d.ts +2 -2
- package/dist/runtime/form/fields/choice-card/types.d.ts +3 -22
- package/dist/runtime/form/fields/choice-card/utils.d.ts +0 -17
- package/dist/runtime/form/fields/choice-card/utils.js +0 -11
- package/dist/runtime/form/fields/index.d.ts +2 -0
- package/dist/runtime/form/fields/radio/component.vue +19 -2
- package/dist/runtime/form/fields/radio/types.d.ts +2 -2
- package/dist/runtime/form/fields/radio-card/component.vue +3 -3
- package/dist/runtime/form/fields/radio-card/types.d.ts +3 -3
- package/dist/runtime/form/fields/select/component.vue +56 -58
- package/dist/runtime/form/fields/select/config.d.ts +1 -0
- package/dist/runtime/form/fields/select/config.js +1 -1
- package/dist/runtime/form/fields/select/types.d.ts +2 -2
- package/dist/runtime/form/types/layout.d.ts +6 -0
- package/dist/runtime/form/types/options.d.ts +137 -0
- package/dist/runtime/form/types/page.d.ts +7 -0
- package/dist/runtime/form/types/ui.d.ts +7 -0
- package/dist/runtime/form/utils/options.d.ts +37 -13
- package/dist/runtime/form/utils/options.js +71 -0
- package/dist/runtime/form/utils/page-surface.d.ts +5 -0
- package/dist/runtime/form/utils/page-surface.js +1 -0
- package/dist/runtime/form/utils/page.js +1 -0
- package/dist/runtime/table/types/remote-options.d.ts +4 -1
- package/dist/runtime/table/utils/remote-table-options.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
import type { FormOptionValueRenderContext, ResolvedFormOption } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Draws the chosen option of an auto-complete over the text of its input, while the field rests.
|
|
5
|
+
* The input holds plain text, so a `renderValue` result or a suffix after the label can only sit
|
|
6
|
+
* on top of it: the input keeps its own text (made transparent by the field while `active`), its
|
|
7
|
+
* selection and its focus, and the overlay copies the input's padding and type so the value starts
|
|
8
|
+
* where the text would. It ignores the pointer, and is hidden from assistive technology because
|
|
9
|
+
* the input already announces the label.
|
|
10
|
+
*/
|
|
11
|
+
type __VLS_Props = {
|
|
12
|
+
/** The chosen option, or none. */
|
|
13
|
+
option?: ResolvedFormOption;
|
|
14
|
+
render?: (context: FormOptionValueRenderContext) => VNodeChild;
|
|
15
|
+
/** True while the overlay stands in for the text of the input. */
|
|
16
|
+
active: boolean;
|
|
17
|
+
};
|
|
18
|
+
declare var __VLS_1: {};
|
|
19
|
+
type __VLS_Slots = {} & {
|
|
20
|
+
default?: (props: typeof __VLS_1) => any;
|
|
21
|
+
};
|
|
22
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
23
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
24
|
+
declare const _default: typeof __VLS_export;
|
|
25
|
+
export default _default;
|
|
26
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
27
|
+
new (): {
|
|
28
|
+
$slots: S;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FormControlUi, FormOptionRuntimeState, ResolvedFormOption } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Items for the Nuxt UI radio and checkbox groups of a field whose options carry more than a
|
|
4
|
+
* label. The group gets the plain parts of each option only: the leading visual, the suffix and a
|
|
5
|
+
* custom render are drawn in its label slot (`optionOf` finds the resolved option behind a slot
|
|
6
|
+
* item), and the description sits under the label, lined up with it. Nothing else of the option
|
|
7
|
+
* reaches the group, whose own `icon` is the check mark of a checkbox.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useChoiceGroupItems<TValue extends string | ResolvedFormOption['value']>(params: {
|
|
10
|
+
/** True when a `renderOption` function owns the content of the options. */
|
|
11
|
+
custom: () => boolean;
|
|
12
|
+
options: FormOptionRuntimeState;
|
|
13
|
+
ui: () => FormControlUi | undefined;
|
|
14
|
+
/** The value the group knows an option by. */
|
|
15
|
+
valueOf: (option: ResolvedFormOption) => TValue;
|
|
16
|
+
}): {
|
|
17
|
+
items: import("vue").ComputedRef<{
|
|
18
|
+
description: string | undefined;
|
|
19
|
+
disabled: boolean | undefined;
|
|
20
|
+
label: string;
|
|
21
|
+
ui: {
|
|
22
|
+
description: string;
|
|
23
|
+
} | undefined;
|
|
24
|
+
value: TValue;
|
|
25
|
+
}[]>;
|
|
26
|
+
optionOf: (key: string) => ResolvedFormOption;
|
|
27
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { formOptionKey, resolveOptionDescriptionIndent } from "../utils/options.js";
|
|
3
|
+
import { mergeFormUiClass } from "../utils/ui.js";
|
|
4
|
+
export function useChoiceGroupItems(params) {
|
|
5
|
+
const optionByKey = computed(
|
|
6
|
+
() => new Map(params.options.items.value.map((option) => [formOptionKey(option.value), option]))
|
|
7
|
+
);
|
|
8
|
+
const items = computed(
|
|
9
|
+
() => params.options.items.value.map((option) => {
|
|
10
|
+
const indent = params.custom() ? void 0 : resolveOptionDescriptionIndent(option);
|
|
11
|
+
return {
|
|
12
|
+
description: params.custom() ? void 0 : option.description,
|
|
13
|
+
disabled: option.disabled,
|
|
14
|
+
label: option.label,
|
|
15
|
+
ui: indent ? { description: mergeFormUiClass(params.ui()?.description, indent) } : void 0,
|
|
16
|
+
value: params.valueOf(option)
|
|
17
|
+
};
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
function optionOf(key) {
|
|
21
|
+
return optionByKey.value.get(key) ?? { label: key, value: key };
|
|
22
|
+
}
|
|
23
|
+
return { items, optionOf };
|
|
24
|
+
}
|
|
@@ -28,17 +28,17 @@ export declare function useFieldControl<TField extends FormField>(field: () => T
|
|
|
28
28
|
interactionOwnerClass: import("vue").ComputedRef<string>;
|
|
29
29
|
options: {
|
|
30
30
|
activate: () => void;
|
|
31
|
-
add: (option: FormValue) => import("../
|
|
31
|
+
add: (option: FormValue) => import("../types/index.js").ResolvedFormOption;
|
|
32
32
|
creatable: import("vue").ComputedRef<boolean>;
|
|
33
|
-
create: (label?: string) => Promise<import("../
|
|
33
|
+
create: (label?: string) => Promise<import("../types/index.js").ResolvedFormOption | null>;
|
|
34
34
|
createLabel: import("vue").ComputedRef<string | undefined>;
|
|
35
35
|
creating: import("vue").ComputedRef<boolean>;
|
|
36
36
|
disableOnLoading: import("vue").ComputedRef<boolean>;
|
|
37
37
|
error: import("vue").ComputedRef<unknown>;
|
|
38
38
|
fetching: import("vue").ComputedRef<boolean>;
|
|
39
39
|
hasMore: import("vue").ComputedRef<boolean>;
|
|
40
|
-
items: import("vue").ComputedRef<readonly import("../
|
|
41
|
-
loadChildren: (option: import("../
|
|
40
|
+
items: import("vue").ComputedRef<readonly import("../types/index.js").ResolvedFormOption[]>;
|
|
41
|
+
loadChildren: (option: import("../types/index.js").ResolvedFormOption) => Promise<void>;
|
|
42
42
|
loadMore: () => Promise<void>;
|
|
43
43
|
loading: import("vue").ComputedRef<boolean>;
|
|
44
44
|
loadingMore: import("vue").ComputedRef<boolean>;
|
|
@@ -51,7 +51,7 @@ export declare function useFieldControl<TField extends FormField>(field: () => T
|
|
|
51
51
|
retryable: import("vue").ComputedRef<boolean>;
|
|
52
52
|
search: import("vue").ComputedRef<string>;
|
|
53
53
|
selectCreatedOption: import("vue").ComputedRef<boolean>;
|
|
54
|
-
selectedItems: import("vue").ComputedRef<readonly import("../
|
|
54
|
+
selectedItems: import("vue").ComputedRef<readonly import("../types/index.js").ResolvedFormOption[]>;
|
|
55
55
|
setSearch: (term: string) => void;
|
|
56
56
|
};
|
|
57
57
|
params: import("vue").ComputedRef<import("../types/index.js").FormFieldCallbackParams<{}, import("../types/index.js").FormDependencies, unknown, unknown>>;
|
|
@@ -30,9 +30,10 @@ export function useFormItemLayout(params) {
|
|
|
30
30
|
),
|
|
31
31
|
normalizeFormGridColumnSpanStyle
|
|
32
32
|
);
|
|
33
|
-
const style = computed(
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const style = computed(() => {
|
|
34
|
+
const spanStyle = span.value ?? normalizeFormGridColumnSpanStyle(String(FORM_LAYOUT_DEFAULTS.fieldSpan));
|
|
35
|
+
return params.layout()?.newRow ? `${spanStyle}; grid-column-start: 1` : spanStyle;
|
|
36
|
+
});
|
|
36
37
|
return {
|
|
37
38
|
span,
|
|
38
39
|
style
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FormAutoCompleteField, FormOptionRuntimeState, FormOptionValue, FormSelectField, ResolvedFormOption } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* What the select and the auto-complete share around the options they list in a Nuxt UI menu: the
|
|
4
|
+
* menu items (grouped when the options carry headings), which option is selected, and the option
|
|
5
|
+
* the closed control shows with its leading visual and its custom value.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useOptionMenu(params: {
|
|
8
|
+
field: () => FormSelectField | FormAutoCompleteField;
|
|
9
|
+
model: () => FormOptionValue | readonly FormOptionValue[] | null | undefined;
|
|
10
|
+
multiple: () => boolean;
|
|
11
|
+
options: FormOptionRuntimeState;
|
|
12
|
+
loadMoreLabel: () => string;
|
|
13
|
+
}): {
|
|
14
|
+
customValueOption: import("vue").ComputedRef<ResolvedFormOption | undefined>;
|
|
15
|
+
isSelected: (option: ResolvedFormOption) => boolean;
|
|
16
|
+
items: import("vue").ComputedRef<import("../utils/options.js").OptionMenuItem[] | import("../utils/options.js").OptionMenuItem[][]>;
|
|
17
|
+
leadingOption: import("vue").ComputedRef<ResolvedFormOption | undefined>;
|
|
18
|
+
renderOption: import("vue").ComputedRef<((context: import("../types/index.js").FormOptionRenderContext<import("../types/index.js").FormOptionItem<FormOptionValue>>) => import("vue").VNodeChild) | undefined>;
|
|
19
|
+
renderValue: import("vue").ComputedRef<((context: import("../types/index.js").FormOptionValueRenderContext<import("../types/index.js").FormOptionItem<FormOptionValue>>) => import("vue").VNodeChild) | undefined>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { formOptionKey, resolveOptionLeading, toMenuItems } from "../utils/options.js";
|
|
3
|
+
export function useOptionMenu(params) {
|
|
4
|
+
const renderOption = computed(() => params.field().renderOption);
|
|
5
|
+
const renderValue = computed(() => params.field().renderValue);
|
|
6
|
+
const items = computed(
|
|
7
|
+
() => toMenuItems(params.options.items.value, {
|
|
8
|
+
label: params.loadMoreLabel(),
|
|
9
|
+
show: params.options.remote.value && (params.options.hasMore.value || params.options.loadingMore.value)
|
|
10
|
+
})
|
|
11
|
+
);
|
|
12
|
+
const selectedKeys = computed(() => {
|
|
13
|
+
const model = params.model();
|
|
14
|
+
const values = Array.isArray(model) ? model : [model];
|
|
15
|
+
return new Set(
|
|
16
|
+
values.filter((value) => value !== null && value !== void 0).map(formOptionKey)
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
const valueOption = computed(
|
|
20
|
+
() => params.multiple() ? void 0 : params.options.selectedItems.value[0]
|
|
21
|
+
);
|
|
22
|
+
const leadingOption = computed(() => {
|
|
23
|
+
const option = valueOption.value;
|
|
24
|
+
return option && !renderValue.value && resolveOptionLeading(option) ? option : void 0;
|
|
25
|
+
});
|
|
26
|
+
const customValueOption = computed(() => {
|
|
27
|
+
const option = valueOption.value;
|
|
28
|
+
return option && (renderValue.value || option.suffix) ? option : void 0;
|
|
29
|
+
});
|
|
30
|
+
function isSelected(option) {
|
|
31
|
+
return selectedKeys.value.has(formOptionKey(option.value));
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
customValueOption,
|
|
35
|
+
isSelected,
|
|
36
|
+
items,
|
|
37
|
+
leadingOption,
|
|
38
|
+
renderOption,
|
|
39
|
+
renderValue
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -4,9 +4,12 @@ import { computed, ref, watch } from "vue";
|
|
|
4
4
|
import { useUiToolsLocale } from "../../../i18n/use-locale";
|
|
5
5
|
import FormFieldShell from "../../components/renderer/form-field-shell.vue";
|
|
6
6
|
import FormOptionItemLabel from "../../components/utils/form-option-item-label.vue";
|
|
7
|
+
import FormOptionLeading from "../../components/utils/form-option-leading.vue";
|
|
7
8
|
import FormOptionMenuFooter from "../../components/utils/form-option-menu-footer.vue";
|
|
9
|
+
import FormOptionSuffix from "../../components/utils/form-option-suffix.vue";
|
|
10
|
+
import FormOptionValueOverlay from "../../components/utils/form-option-value-overlay.vue";
|
|
8
11
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
9
|
-
import {
|
|
12
|
+
import { useOptionMenu } from "../../composables/use-option-menu";
|
|
10
13
|
import { isBoolean, isNumber, isString } from "../../utils/predicate";
|
|
11
14
|
import { mergeFormUiClass } from "../../utils/ui";
|
|
12
15
|
const props = defineProps({
|
|
@@ -30,6 +33,7 @@ const {
|
|
|
30
33
|
{ omit: ["createItem", "clearable"] }
|
|
31
34
|
);
|
|
32
35
|
const searchTerm = ref("");
|
|
36
|
+
const open = ref(false);
|
|
33
37
|
const model = computed({
|
|
34
38
|
get: () => {
|
|
35
39
|
const value = form.getValue(props.path);
|
|
@@ -43,16 +47,27 @@ const model = computed({
|
|
|
43
47
|
},
|
|
44
48
|
set: (value) => form.setValue(props.path, value)
|
|
45
49
|
});
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const menu = useOptionMenu({
|
|
51
|
+
field: () => props.field,
|
|
52
|
+
loadMoreLabel: () => t("form.fields.options.loadingMore"),
|
|
53
|
+
model: () => model.value,
|
|
54
|
+
multiple: () => fieldProps.value.multiple === true,
|
|
55
|
+
options
|
|
56
|
+
});
|
|
57
|
+
const overlayActive = computed(
|
|
58
|
+
() => menu.customValueOption.value !== void 0 && !open.value && searchTerm.value === ""
|
|
59
|
+
);
|
|
53
60
|
const controlUi = computed(() => ({
|
|
54
61
|
...controlProps.value.ui,
|
|
55
|
-
|
|
62
|
+
base: mergeFormUiClass(
|
|
63
|
+
controlProps.value.ui?.base,
|
|
64
|
+
overlayActive.value ? "text-transparent" : void 0
|
|
65
|
+
),
|
|
66
|
+
content: mergeFormUiClass(controlProps.value.ui?.content, interactionOwnerClass.value),
|
|
67
|
+
itemDescription: mergeFormUiClass(
|
|
68
|
+
menu.renderOption.value ? "hidden" : "text-[12.5px]",
|
|
69
|
+
controlProps.value.ui?.itemDescription
|
|
70
|
+
)
|
|
56
71
|
}));
|
|
57
72
|
const createItem = computed(
|
|
58
73
|
() => options.creatable.value && fieldProps.value.createItem ? fieldProps.value.createItem : false
|
|
@@ -68,10 +83,9 @@ watch(searchTerm, (term) => {
|
|
|
68
83
|
options.setSearch(term);
|
|
69
84
|
}
|
|
70
85
|
});
|
|
71
|
-
function handleOpen(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
86
|
+
function handleOpen(next) {
|
|
87
|
+
open.value = next;
|
|
88
|
+
if (next) options.activate();
|
|
75
89
|
}
|
|
76
90
|
async function handleCreate(label) {
|
|
77
91
|
await options.create(label);
|
|
@@ -93,47 +107,74 @@ function isOptionValue(value) {
|
|
|
93
107
|
|
|
94
108
|
<template>
|
|
95
109
|
<FormFieldShell :field="field" :path="path">
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
<FormOptionValueOverlay
|
|
111
|
+
:option="menu.customValueOption.value"
|
|
112
|
+
:render="menu.renderValue.value"
|
|
113
|
+
:active="overlayActive"
|
|
114
|
+
>
|
|
115
|
+
<UInputMenu
|
|
116
|
+
v-model="model"
|
|
117
|
+
v-model:search-term="searchTerm"
|
|
118
|
+
v-bind="controlProps"
|
|
119
|
+
class="w-full"
|
|
120
|
+
value-key="value"
|
|
121
|
+
label-key="label"
|
|
122
|
+
:items="menu.items.value"
|
|
123
|
+
:multiple="fieldProps.multiple"
|
|
124
|
+
:placeholder="placeholder"
|
|
125
|
+
:disabled="disabled"
|
|
126
|
+
:loading="
|
|
108
127
|
validationPending || options.loading.value || options.fetching.value || options.creating.value
|
|
109
128
|
"
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
:clear="fieldProps.clearable === true"
|
|
130
|
+
:ignore-filter="options.remote.value"
|
|
131
|
+
:create-item="createItem"
|
|
132
|
+
:ui="controlUi"
|
|
133
|
+
@update:open="handleOpen"
|
|
134
|
+
@create="handleNativeCreate"
|
|
135
|
+
@blur="handleBlur"
|
|
136
|
+
>
|
|
137
|
+
<template #create-item-label="{ item }">
|
|
138
|
+
{{
|
|
139
|
+
options.creating.value ? t("form.fields.options.creating") : t("form.fields.options.createNamed", { label: item })
|
|
140
|
+
}}
|
|
141
|
+
</template>
|
|
142
|
+
|
|
143
|
+
<template v-if="menu.leadingOption.value" #leading="{ ui }">
|
|
144
|
+
<FormOptionLeading
|
|
145
|
+
:option="menu.leadingOption.value"
|
|
146
|
+
:icon-class="ui.leadingIcon()"
|
|
147
|
+
:avatar-size="ui.itemLeadingAvatarSize()"
|
|
148
|
+
chip-class="size-5"
|
|
149
|
+
/>
|
|
150
|
+
</template>
|
|
151
|
+
|
|
152
|
+
<template #item-leading="{ item }">
|
|
153
|
+
<FormOptionLeading :option="menu.renderOption.value ? void 0 : item" />
|
|
154
|
+
</template>
|
|
155
|
+
|
|
156
|
+
<template #item-label="{ item }">
|
|
157
|
+
<FormOptionItemLabel
|
|
158
|
+
:option="item"
|
|
159
|
+
:selected="menu.isSelected(item)"
|
|
160
|
+
:render="menu.renderOption.value"
|
|
161
|
+
/>
|
|
162
|
+
</template>
|
|
123
163
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
164
|
+
<template #item-trailing="{ item }">
|
|
165
|
+
<FormOptionSuffix v-if="!menu.renderOption.value" :option="item" />
|
|
166
|
+
</template>
|
|
127
167
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
168
|
+
<template #content-bottom>
|
|
169
|
+
<FormOptionMenuFooter
|
|
170
|
+
:options="options"
|
|
171
|
+
:disabled="disabled"
|
|
172
|
+
:show-create="showExplicitCreate"
|
|
173
|
+
:create-label="createActionLabel"
|
|
174
|
+
@create="handleCreateAction"
|
|
175
|
+
/>
|
|
176
|
+
</template>
|
|
177
|
+
</UInputMenu>
|
|
178
|
+
</FormOptionValueOverlay>
|
|
138
179
|
</FormFieldShell>
|
|
139
180
|
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineFormFieldKind } from "../../utils/field-kind.js";
|
|
2
2
|
export const autoCompleteFieldKind = defineFormFieldKind({
|
|
3
3
|
layout: { item: true },
|
|
4
|
-
options: { enabled: true },
|
|
4
|
+
options: { enabled: true, groups: true },
|
|
5
5
|
state: "stateful",
|
|
6
6
|
transform: true,
|
|
7
7
|
type: "auto-complete",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { FormStatefulFieldBase } from '../../types/field-base.js';
|
|
2
2
|
import type { FieldOptionValue, FieldProps, FallbackNever, NullableValue } from '../../types/field-output-utils.js';
|
|
3
|
-
import type { FormAnyOptionConfig, FormOptionItem, FormOptionValue } from '../../types/options.js';
|
|
3
|
+
import type { FormAnyOptionConfig, FormOptionGrouping, FormOptionItem, FormOptionValue, FormOptionValueRenderers } from '../../types/options.js';
|
|
4
4
|
import type { FormSelectCreateItem } from '../select/types.js';
|
|
5
5
|
export interface FormAutoCompleteProps {
|
|
6
6
|
multiple?: boolean;
|
|
7
7
|
clearable?: boolean;
|
|
8
8
|
createItem?: FormSelectCreateItem;
|
|
9
9
|
}
|
|
10
|
-
export interface FormAutoCompleteField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormOptionItem<TValue> = FormOptionItem<TValue>> extends FormStatefulFieldBase<'auto-complete', TValue | readonly TValue[] | null, TContext, TDeps, FormAutoCompleteProps> {
|
|
10
|
+
export interface FormAutoCompleteField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormOptionItem<TValue> = FormOptionItem<TValue>> extends FormStatefulFieldBase<'auto-complete', TValue | readonly TValue[] | null, TContext, TDeps, FormAutoCompleteProps>, FormOptionGrouping, FormOptionValueRenderers<TOption> {
|
|
11
11
|
options: FormAnyOptionConfig<TOption, TContext, TDeps, TValue | readonly TValue[] | null>;
|
|
12
12
|
}
|
|
13
13
|
type AutoCompleteFieldValue<TField> = FieldProps<TField> extends {
|
|
@@ -3,11 +3,11 @@ import UCheckboxGroup from "@nuxt/ui/components/CheckboxGroup.vue";
|
|
|
3
3
|
import { computed } from "vue";
|
|
4
4
|
import FormFieldShell from "../../components/renderer/form-field-shell.vue";
|
|
5
5
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
6
|
-
import { formOptionKey } from "../../utils/options";
|
|
6
|
+
import { formOptionKey, groupOptions } from "../../utils/options";
|
|
7
7
|
import { isBoolean, isNumber, isString, isUndefined } from "../../utils/predicate";
|
|
8
8
|
import ChoiceCardLabel from "../choice-card/choice-card-label.vue";
|
|
9
9
|
import { CHOICE_CARD_PROPS, useChoiceCard } from "../choice-card/use-choice-card";
|
|
10
|
-
import { choiceCardGroupAttrs
|
|
10
|
+
import { choiceCardGroupAttrs } from "../choice-card/utils";
|
|
11
11
|
const props = defineProps({
|
|
12
12
|
field: { type: Object, required: true },
|
|
13
13
|
path: { type: Array, required: true }
|
|
@@ -45,7 +45,7 @@ const items = computed(
|
|
|
45
45
|
value: formOptionKey(item.value)
|
|
46
46
|
}))
|
|
47
47
|
);
|
|
48
|
-
const groups = computed(() =>
|
|
48
|
+
const groups = computed(() => groupOptions(items.value));
|
|
49
49
|
function selectionOf(group) {
|
|
50
50
|
return model.value.filter((key) => group.items.some((item) => item.value === key));
|
|
51
51
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { FormStatefulFieldBase } from '../../types/field-base.js';
|
|
2
2
|
import type { FieldOptionValue, NullableValue } from '../../types/field-output-utils.js';
|
|
3
|
-
import type { FormOptionConfig, FormOptionValue, FormOptionsSource } from '../../types/options.js';
|
|
4
|
-
import type {
|
|
5
|
-
export interface FormCheckboxCardField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormChoiceCardOptionItem<TValue> = FormChoiceCardOptionItem<TValue>> extends FormStatefulFieldBase<'checkbox-card', readonly TValue[] | null, TContext, TDeps, FormChoiceCardProps>,
|
|
3
|
+
import type { FormOptionConfig, FormOptionGrouping, FormOptionValue, FormOptionsSource } from '../../types/options.js';
|
|
4
|
+
import type { FormChoiceCardOptionItem, FormChoiceCardProps } from '../choice-card/types.js';
|
|
5
|
+
export interface FormCheckboxCardField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormChoiceCardOptionItem<TValue> = FormChoiceCardOptionItem<TValue>> extends FormStatefulFieldBase<'checkbox-card', readonly TValue[] | null, TContext, TDeps, FormChoiceCardProps>, FormOptionGrouping {
|
|
6
6
|
options: FormOptionConfig<TOption, TContext, TDeps, readonly TValue[] | null> | FormOptionsSource<TOption, TContext, TDeps, readonly TValue[] | null>;
|
|
7
7
|
}
|
|
8
8
|
export type CheckboxCardFieldOutput<TField> = readonly FieldOptionValue<TField>[] | NullableValue;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import UCheckboxGroup from "@nuxt/ui/components/CheckboxGroup.vue";
|
|
3
3
|
import { computed } from "vue";
|
|
4
4
|
import FormFieldShell from "../../components/renderer/form-field-shell.vue";
|
|
5
|
+
import FormOptionChoiceLabel from "../../components/utils/form-option-choice-label.vue";
|
|
6
|
+
import { useChoiceGroupItems } from "../../composables/use-choice-group-items";
|
|
5
7
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
6
8
|
import { formOptionKey } from "../../utils/options";
|
|
7
9
|
import { isBoolean, isNumber, isString, isUndefined } from "../../utils/predicate";
|
|
@@ -31,9 +33,13 @@ const model = computed({
|
|
|
31
33
|
})
|
|
32
34
|
)
|
|
33
35
|
});
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
)
|
|
36
|
+
const renderOption = computed(() => props.field.renderOption);
|
|
37
|
+
const { items, optionOf } = useChoiceGroupItems({
|
|
38
|
+
custom: () => renderOption.value !== void 0,
|
|
39
|
+
options,
|
|
40
|
+
ui: () => controlProps.value.ui,
|
|
41
|
+
valueOf: (option) => formOptionKey(option.value)
|
|
42
|
+
});
|
|
37
43
|
const groupUi = computed(() => ({
|
|
38
44
|
...controlProps.value.ui,
|
|
39
45
|
fieldset: mergeFormUiClass(
|
|
@@ -65,6 +71,14 @@ function isOptionValue(value) {
|
|
|
65
71
|
:indicator="fieldProps.indicator"
|
|
66
72
|
:disabled="disabled"
|
|
67
73
|
@blur="handleBlur"
|
|
68
|
-
|
|
74
|
+
>
|
|
75
|
+
<template #label="{ item }">
|
|
76
|
+
<FormOptionChoiceLabel
|
|
77
|
+
:option="optionOf(item.value)"
|
|
78
|
+
:selected="model.includes(item.value)"
|
|
79
|
+
:render="renderOption"
|
|
80
|
+
/>
|
|
81
|
+
</template>
|
|
82
|
+
</UCheckboxGroup>
|
|
69
83
|
</FormFieldShell>
|
|
70
84
|
</template>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FormStatefulFieldBase } from '../../types/field-base.js';
|
|
2
2
|
import type { FieldOptionValue, NullableValue } from '../../types/field-output-utils.js';
|
|
3
|
-
import type { FormOptionConfig, FormOptionItem, FormOptionValue, FormOptionsSource } from '../../types/options.js';
|
|
3
|
+
import type { FormOptionConfig, FormOptionItem, FormOptionRenderers, FormOptionValue, FormOptionsSource } from '../../types/options.js';
|
|
4
4
|
export interface FormCheckboxGroupProps {
|
|
5
5
|
/**
|
|
6
6
|
* `list` (the default) lines up bare checkboxes, `card` frames each option on its own, and
|
|
@@ -10,7 +10,7 @@ export interface FormCheckboxGroupProps {
|
|
|
10
10
|
orientation?: 'horizontal' | 'vertical';
|
|
11
11
|
indicator?: 'start' | 'end' | 'hidden';
|
|
12
12
|
}
|
|
13
|
-
export interface FormCheckboxGroupField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormOptionItem<TValue> = FormOptionItem<TValue>> extends FormStatefulFieldBase<'checkbox-group', readonly TValue[] | null, TContext, TDeps, FormCheckboxGroupProps> {
|
|
13
|
+
export interface FormCheckboxGroupField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue extends FormOptionValue = FormOptionValue, TOption extends FormOptionItem<TValue> = FormOptionItem<TValue>> extends FormStatefulFieldBase<'checkbox-group', readonly TValue[] | null, TContext, TDeps, FormCheckboxGroupProps>, FormOptionRenderers<TOption> {
|
|
14
14
|
options: FormOptionConfig<TOption, TContext, TDeps, readonly TValue[] | null> | FormOptionsSource<TOption, TContext, TDeps, readonly TValue[] | null>;
|
|
15
15
|
}
|
|
16
16
|
export type CheckboxGroupFieldOutput<TField> = readonly FieldOptionValue<TField>[] | NullableValue;
|
|
@@ -46,29 +46,10 @@ export interface FormChoiceCardProps {
|
|
|
46
46
|
density?: FormChoiceCardDensity;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* Option
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* ```ts
|
|
53
|
-
* // Rows from the API, each with a `section` to list the cards under.
|
|
54
|
-
* { type: 'radio-card', options: () => queryOptions({ queryKey: ['plans'], queryFn: fetchPlans }), groupKey: 'section' }
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export interface FormChoiceCardGrouping {
|
|
58
|
-
/**
|
|
59
|
-
* Property of the options that holds the heading they are listed under. Options written inline
|
|
60
|
-
* in the schema group by their `group` without it. The records a function, a promise or a query
|
|
61
|
-
* returns group only when this names the property, so records that already carry a `group` for
|
|
62
|
-
* another purpose are never regrouped by surprise. `false` lists every option together, even
|
|
63
|
-
* inline options that carry a `group`.
|
|
64
|
-
*/
|
|
65
|
-
groupKey?: string | false;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Option of a radio or checkbox card: the shared option shape plus the icon and the heading the
|
|
69
|
-
* cards can show.
|
|
49
|
+
* Option of a radio or checkbox card: the shared option shape plus the heading the cards can show.
|
|
50
|
+
* A card draws its `icon` only, so the avatar, chip and suffix of other option fields are left out.
|
|
70
51
|
*/
|
|
71
|
-
export interface FormChoiceCardOption<TValue extends FormOptionValue = FormOptionValue> extends FormOption<TValue> {
|
|
52
|
+
export interface FormChoiceCardOption<TValue extends FormOptionValue = FormOptionValue> extends Omit<FormOption<TValue>, 'avatar' | 'chip' | 'suffix'> {
|
|
72
53
|
/** Icon shown with the label, inline or in a tile (see `props.icon`). */
|
|
73
54
|
icon?: string;
|
|
74
55
|
/**
|
|
@@ -1,21 +1,4 @@
|
|
|
1
1
|
import type { FormObject } from '../../types/index.js';
|
|
2
|
-
export interface ChoiceCardGroup<TItem> {
|
|
3
|
-
/** Stable key of the group: its heading, or an empty string for the options without one. */
|
|
4
|
-
key: string;
|
|
5
|
-
/** Heading shown above the cards of the group; none for options that declare no group. */
|
|
6
|
-
heading?: string;
|
|
7
|
-
/** The group's options, as the mutable list the Nuxt UI groups take. */
|
|
8
|
-
items: TItem[];
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Lists options under their group headings. Headings follow the order in which they first appear
|
|
12
|
-
* in `items`, and each keeps its options in their authored order. The options without a group form
|
|
13
|
-
* one more group with no heading, placed where its first option appears. Without any group the
|
|
14
|
-
* result is a single headingless group, so an ungrouped list renders as it always did.
|
|
15
|
-
*/
|
|
16
|
-
export declare function groupChoiceCardItems<TItem extends {
|
|
17
|
-
group?: string;
|
|
18
|
-
}>(items: readonly TItem[]): readonly ChoiceCardGroup<TItem>[];
|
|
19
2
|
/**
|
|
20
3
|
* Attributes for the Nuxt UI group at `index` among the groups of one field. Every group carries
|
|
21
4
|
* the control attributes of the field, but an `id` names a single element: the first group keeps
|
|
@@ -1,15 +1,4 @@
|
|
|
1
1
|
import { isString } from "../../utils/predicate.js";
|
|
2
|
-
export function groupChoiceCardItems(items) {
|
|
3
|
-
const groups = /* @__PURE__ */ new Map();
|
|
4
|
-
for (const item of items) {
|
|
5
|
-
const key = item.group?.trim() ?? "";
|
|
6
|
-
const members = groups.get(key);
|
|
7
|
-
if (members) members.push(item);
|
|
8
|
-
else groups.set(key, [item]);
|
|
9
|
-
}
|
|
10
|
-
if (groups.size === 0) return [{ items: [], key: "" }];
|
|
11
|
-
return [...groups].map(([key, members]) => ({ heading: key || void 0, items: members, key }));
|
|
12
|
-
}
|
|
13
2
|
export function choiceCardGroupAttrs(attrs, index) {
|
|
14
3
|
const id = attrs.id;
|
|
15
4
|
if (index === 0 || !isString(id)) return attrs;
|
|
@@ -86,6 +86,7 @@ export declare const formFieldKinds: readonly [{
|
|
|
86
86
|
};
|
|
87
87
|
readonly options: {
|
|
88
88
|
readonly enabled: true;
|
|
89
|
+
readonly groups: true;
|
|
89
90
|
};
|
|
90
91
|
readonly state: "stateful";
|
|
91
92
|
readonly transform: true;
|
|
@@ -529,6 +530,7 @@ export declare const formFieldKinds: readonly [{
|
|
|
529
530
|
};
|
|
530
531
|
readonly options: {
|
|
531
532
|
readonly enabled: true;
|
|
533
|
+
readonly groups: true;
|
|
532
534
|
};
|
|
533
535
|
readonly state: "stateful";
|
|
534
536
|
readonly transform: true;
|