nuxt-ui-tools 0.1.0 → 0.1.1
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.d.mts +4 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +30 -15
- package/dist/runtime/form/components/actions/FormActions.d.vue.ts +0 -2
- package/dist/runtime/form/components/actions/FormActions.vue +5 -8
- package/dist/runtime/form/components/actions/FormActions.vue.d.ts +0 -2
- package/dist/runtime/form/components/layout/DrawerLayout.vue +5 -1
- package/dist/runtime/form/components/layout/ModalLayout.vue +5 -1
- package/dist/runtime/form/components/renderer/FormCompositeControl.d.vue.ts +15 -0
- package/dist/runtime/form/components/renderer/FormCompositeControl.vue +13 -0
- package/dist/runtime/form/components/renderer/FormCompositeControl.vue.d.ts +15 -0
- package/dist/runtime/form/components/renderer/FormFieldRenderer.d.vue.ts +1 -0
- package/dist/runtime/form/components/renderer/FormFieldRenderer.vue +19 -3
- package/dist/runtime/form/components/renderer/FormFieldRenderer.vue.d.ts +1 -0
- package/dist/runtime/form/components/renderer/FormFieldShell.d.vue.ts +4 -4
- package/dist/runtime/form/components/renderer/FormFieldShell.vue +15 -1
- package/dist/runtime/form/components/renderer/FormFieldShell.vue.d.ts +4 -4
- package/dist/runtime/form/components/root/Form.vue +5 -3
- package/dist/runtime/form/composables/use-field-control.d.ts +1 -1
- package/dist/runtime/form/composables/use-field-control.js +4 -0
- package/dist/runtime/form/composables/use-field-options.d.ts +1 -1
- package/dist/runtime/form/composables/use-field-options.js +75 -31
- package/dist/runtime/form/composables/use-form-field-chrome.d.ts +3 -0
- package/dist/runtime/form/composables/use-form-field-chrome.js +9 -0
- package/dist/runtime/form/composables/use-form-runtime.js +17 -5
- package/dist/runtime/form/fields/array-list/component.vue +4 -1
- package/dist/runtime/form/fields/auto-complete/component.vue +23 -16
- package/dist/runtime/form/fields/checkbox-card/component.vue +28 -29
- package/dist/runtime/form/fields/checkbox-group/component.vue +28 -29
- package/dist/runtime/form/fields/color-picker/component.vue +4 -2
- package/dist/runtime/form/fields/date-family/component.vue +9 -4
- package/dist/runtime/form/fields/hierarchy/SelectionCheckbox.d.vue.ts +13 -0
- package/dist/runtime/form/fields/hierarchy/SelectionCheckbox.vue +24 -0
- package/dist/runtime/form/fields/hierarchy/SelectionCheckbox.vue.d.ts +13 -0
- package/dist/runtime/form/fields/hierarchy/SelectionRadio.d.vue.ts +14 -0
- package/dist/runtime/form/fields/hierarchy/SelectionRadio.vue +27 -0
- package/dist/runtime/form/fields/hierarchy/SelectionRadio.vue.d.ts +14 -0
- package/dist/runtime/form/fields/hierarchy/component.vue +257 -142
- package/dist/runtime/form/fields/hierarchy/utils.d.ts +9 -0
- package/dist/runtime/form/fields/hierarchy/utils.js +32 -0
- package/dist/runtime/form/fields/input-group/component.vue +7 -30
- package/dist/runtime/form/fields/matrix/component.vue +9 -3
- package/dist/runtime/form/fields/phone-number/component.vue +7 -2
- package/dist/runtime/form/fields/rating/component.vue +25 -2
- package/dist/runtime/form/fields/select/component.vue +29 -21
- package/dist/runtime/form/fields/switch-group/component.vue +8 -3
- package/dist/runtime/form/fields/time/component.vue +7 -5
- package/dist/runtime/form/fields/upload/component.vue +5 -3
- package/dist/runtime/form/types/api.d.ts +9 -3
- package/dist/runtime/form/types/field-base.d.ts +3 -1
- package/dist/runtime/form/types/runtime.d.ts +2 -0
- package/dist/runtime/form/types/ui.d.ts +1 -0
- package/dist/runtime/form/utils/api.js +3 -0
- package/dist/runtime/form/utils/options.d.ts +11 -2
- package/dist/runtime/form/utils/options.js +57 -9
- package/dist/runtime/form/utils/state.js +10 -2
- package/dist/runtime/i18n/locales/en.js +38 -0
- package/dist/runtime/i18n/locales/fr.js +38 -0
- package/dist/runtime/i18n/types.d.ts +38 -0
- package/dist/runtime/i18n/use-locale.d.ts +2 -2
- package/dist/runtime/i18n/use-locale.js +3 -3
- package/dist/runtime/table/components/DataList.d.vue.ts +2 -0
- package/dist/runtime/table/components/DataList.vue.d.ts +2 -0
- package/dist/runtime/table/components/actions/RowActions.d.vue.ts +4 -2
- package/dist/runtime/table/components/actions/RowActions.vue +2 -0
- package/dist/runtime/table/components/actions/RowActions.vue.d.ts +4 -2
- package/dist/runtime/table/components/data-list/DataListAddFilter.d.vue.ts +5 -3
- package/dist/runtime/table/components/data-list/DataListAddFilter.vue +6 -1
- package/dist/runtime/table/components/data-list/DataListAddFilter.vue.d.ts +5 -3
- package/dist/runtime/table/components/data-list/DataListContent.d.vue.ts +24 -17
- package/dist/runtime/table/components/data-list/DataListContent.vue +70 -17
- package/dist/runtime/table/components/data-list/DataListContent.vue.d.ts +24 -17
- package/dist/runtime/table/components/data-list/DataListDefault.d.vue.ts +2 -0
- package/dist/runtime/table/components/data-list/DataListDefault.vue.d.ts +2 -0
- package/dist/runtime/table/components/data-list/DataListFilterTags.d.vue.ts +2 -0
- package/dist/runtime/table/components/data-list/DataListFilterTags.vue.d.ts +2 -0
- package/dist/runtime/table/components/data-list/DataListLayoutSwitch.vue +17 -11
- package/dist/runtime/table/components/data-list/DataListRoot.vue +2 -46
- package/dist/runtime/table/components/data-list/DataListSortMenu.vue +6 -0
- package/dist/runtime/table/components/filters/shared/DynamicFilterPicker.d.vue.ts +5 -0
- package/dist/runtime/table/components/filters/shared/DynamicFilterPicker.vue +182 -59
- package/dist/runtime/table/components/filters/shared/DynamicFilterPicker.vue.d.ts +5 -0
- package/dist/runtime/table/components/filters/shared/FilterMatchModeButton.d.vue.ts +3 -1
- package/dist/runtime/table/components/filters/shared/FilterMatchModeButton.vue +29 -5
- package/dist/runtime/table/components/filters/shared/FilterMatchModeButton.vue.d.ts +3 -1
- package/dist/runtime/table/components/filters/shared/FilterMatchModePanel.d.vue.ts +17 -0
- package/dist/runtime/table/components/filters/shared/FilterMatchModePanel.vue +47 -0
- package/dist/runtime/table/components/filters/shared/FilterMatchModePanel.vue.d.ts +17 -0
- package/dist/runtime/table/components/filters/shared/FilterOptionMultipleList.d.vue.ts +3 -1
- package/dist/runtime/table/components/filters/shared/FilterOptionMultipleList.vue +12 -3
- package/dist/runtime/table/components/filters/shared/FilterOptionMultipleList.vue.d.ts +3 -1
- package/dist/runtime/table/components/filters/shared/FilterOptionPickerContent.d.vue.ts +3 -0
- package/dist/runtime/table/components/filters/shared/FilterOptionPickerContent.vue +11 -1
- package/dist/runtime/table/components/filters/shared/FilterOptionPickerContent.vue.d.ts +3 -0
- package/dist/runtime/table/components/filters/shared/FilterOptionRow.d.vue.ts +3 -0
- package/dist/runtime/table/components/filters/shared/FilterOptionRow.vue +48 -9
- package/dist/runtime/table/components/filters/shared/FilterOptionRow.vue.d.ts +3 -0
- package/dist/runtime/table/components/filters/shared/FilterOptionSingleList.d.vue.ts +3 -0
- package/dist/runtime/table/components/filters/shared/FilterOptionSingleList.vue +35 -9
- package/dist/runtime/table/components/filters/shared/FilterOptionSingleList.vue.d.ts +3 -0
- package/dist/runtime/table/components/filters/shared/FilterOptionTreeContent.d.vue.ts +3 -0
- package/dist/runtime/table/components/filters/shared/FilterOptionTreeContent.vue +77 -20
- package/dist/runtime/table/components/filters/shared/FilterOptionTreeContent.vue.d.ts +3 -0
- package/dist/runtime/table/components/filters/shared/FilterPopoverShell.d.vue.ts +27 -0
- package/dist/runtime/table/components/filters/shared/FilterPopoverShell.vue +36 -0
- package/dist/runtime/table/components/filters/shared/FilterPopoverShell.vue.d.ts +27 -0
- package/dist/runtime/table/components/filters/shared/FilterSearchablePanel.d.vue.ts +3 -0
- package/dist/runtime/table/components/filters/shared/FilterSearchablePanel.vue +40 -10
- package/dist/runtime/table/components/filters/shared/FilterSearchablePanel.vue.d.ts +3 -0
- package/dist/runtime/table/components/filters/shared/FilterStageTransition.d.vue.ts +23 -0
- package/dist/runtime/table/components/filters/shared/FilterStageTransition.vue +21 -0
- package/dist/runtime/table/components/filters/shared/FilterStageTransition.vue.d.ts +23 -0
- package/dist/runtime/table/components/filters/shared/FilterTriggerTag.d.vue.ts +3 -2
- package/dist/runtime/table/components/filters/shared/FilterTriggerTag.vue +26 -25
- package/dist/runtime/table/components/filters/shared/FilterTriggerTag.vue.d.ts +3 -2
- package/dist/runtime/table/components/filters/tags/BooleanFilterTag.d.vue.ts +1 -1
- package/dist/runtime/table/components/filters/tags/BooleanFilterTag.vue +75 -22
- package/dist/runtime/table/components/filters/tags/BooleanFilterTag.vue.d.ts +1 -1
- package/dist/runtime/table/components/filters/tags/DateFilterTag.d.vue.ts +3 -1
- package/dist/runtime/table/components/filters/tags/DateFilterTag.vue +254 -160
- package/dist/runtime/table/components/filters/tags/DateFilterTag.vue.d.ts +3 -1
- package/dist/runtime/table/components/filters/tags/FilterTagsBar.d.vue.ts +7 -6
- package/dist/runtime/table/components/filters/tags/FilterTagsBar.vue +12 -27
- package/dist/runtime/table/components/filters/tags/FilterTagsBar.vue.d.ts +7 -6
- package/dist/runtime/table/components/filters/tags/NumberFilterTag.d.vue.ts +3 -2
- package/dist/runtime/table/components/filters/tags/NumberFilterTag.vue +192 -106
- package/dist/runtime/table/components/filters/tags/NumberFilterTag.vue.d.ts +3 -2
- package/dist/runtime/table/components/filters/tags/OptionFilterTag.d.vue.ts +3 -2
- package/dist/runtime/table/components/filters/tags/OptionFilterTag.vue +110 -48
- package/dist/runtime/table/components/filters/tags/OptionFilterTag.vue.d.ts +3 -2
- package/dist/runtime/table/components/filters/tags/TextFilterTag.d.vue.ts +3 -2
- package/dist/runtime/table/components/filters/tags/TextFilterTag.vue +120 -57
- package/dist/runtime/table/components/filters/tags/TextFilterTag.vue.d.ts +3 -2
- package/dist/runtime/table/components/grid/GridRenderer.vue +1 -1
- package/dist/runtime/table/components/layout/TableFooter.vue +1 -0
- package/dist/runtime/table/components/table/TableRenderer.vue +18 -9
- package/dist/runtime/table/composables/use-filter-tag-session.d.ts +3 -4
- package/dist/runtime/table/composables/use-filter-tag-session.js +12 -32
- package/dist/runtime/table/composables/use-query-state.d.ts +3 -3
- package/dist/runtime/table/composables/use-query-state.js +7 -32
- package/dist/runtime/table/composables/use-table-api.js +2 -2
- package/dist/runtime/table/composables/use-table-columns.d.ts +1 -11
- package/dist/runtime/table/composables/use-table-filter-presentation.d.ts +0 -3
- package/dist/runtime/table/composables/use-table-filter-presentation.js +0 -9
- package/dist/runtime/table/composables/use-table-filters.js +18 -7
- package/dist/runtime/table/composables/use-table-internals.d.ts +3 -42
- package/dist/runtime/table/types/actions.d.ts +1 -1
- package/dist/runtime/table/types/data-list.d.ts +41 -8
- package/dist/runtime/table/types/filters.d.ts +4 -0
- package/dist/runtime/table/utils/actions.d.ts +1 -1
- package/dist/runtime/table/utils/columns/menu.d.ts +2 -11
- package/dist/runtime/table/utils/columns/render.d.ts +2 -9
- package/dist/runtime/table/utils/columns/render.js +4 -4
- package/dist/runtime/table/utils/data-list-ui.d.ts +3 -1
- package/dist/runtime/table/utils/data-list-ui.js +45 -0
- package/dist/runtime/table/utils/filters/editor-size.d.ts +11 -0
- package/dist/runtime/table/utils/filters/editor-size.js +45 -0
- package/dist/runtime/table/utils/filters/index.d.ts +1 -1
- package/dist/runtime/table/utils/filters/index.js +1 -1
- package/dist/runtime/table/utils/query-state.d.ts +18 -1
- package/dist/runtime/table/utils/query-state.js +109 -0
- package/package.json +4 -2
- package/dist/runtime/table/utils/filters/operators.d.ts +0 -4
- package/dist/runtime/table/utils/filters/operators.js +0 -6
|
@@ -1,35 +1,15 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import UFieldGroup from "@nuxt/ui/components/FieldGroup.vue";
|
|
3
|
-
import { computed } from "vue";
|
|
4
3
|
import FormFieldRenderer from "../../components/renderer/FormFieldRenderer.vue";
|
|
5
4
|
import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
|
|
6
|
-
import { fieldPath } from "../../composables/use-form-runtime";
|
|
7
5
|
import { useFormUi } from "../../composables/use-form-ui";
|
|
8
6
|
import { mergeFormUiClass } from "../../utils/ui";
|
|
9
|
-
import NumberField from "../number/component.vue";
|
|
10
|
-
import PasswordField from "../password/component.vue";
|
|
11
|
-
import SelectField from "../select/component.vue";
|
|
12
|
-
import TextField from "../text/component.vue";
|
|
13
7
|
const props = defineProps({
|
|
14
8
|
field: { type: Object, required: true },
|
|
15
9
|
path: { type: Array, required: true },
|
|
16
10
|
parentPath: { type: Array, required: true }
|
|
17
11
|
});
|
|
18
12
|
const formUi = useFormUi();
|
|
19
|
-
const groupChildren = computed(
|
|
20
|
-
() => props.field.fields.map((child) => ({
|
|
21
|
-
field: child,
|
|
22
|
-
path: fieldPath(props.parentPath, child),
|
|
23
|
-
renderer: getGroupedRenderer(child)
|
|
24
|
-
}))
|
|
25
|
-
);
|
|
26
|
-
function getGroupedRenderer(field) {
|
|
27
|
-
if (field.type === "text") return TextField;
|
|
28
|
-
if (field.type === "password") return PasswordField;
|
|
29
|
-
if (field.type === "number") return NumberField;
|
|
30
|
-
if (field.type === "select") return SelectField;
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
13
|
</script>
|
|
34
14
|
|
|
35
15
|
<template>
|
|
@@ -40,16 +20,13 @@ function getGroupedRenderer(field) {
|
|
|
40
20
|
mergeFormUiClass('w-full', formUi.ui.value.group?.ui?.root, formUi.ui.value.group?.ui?.base)
|
|
41
21
|
"
|
|
42
22
|
>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/>
|
|
51
|
-
<FormFieldRenderer v-else :field="child.field" :parent-path="parentPath" />
|
|
52
|
-
</template>
|
|
23
|
+
<FormFieldRenderer
|
|
24
|
+
v-for="child in field.fields"
|
|
25
|
+
:key="child.key"
|
|
26
|
+
:field="child"
|
|
27
|
+
:parent-path="parentPath"
|
|
28
|
+
bare
|
|
29
|
+
/>
|
|
53
30
|
</UFieldGroup>
|
|
54
31
|
</FormFieldShell>
|
|
55
32
|
</template>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed } from "vue";
|
|
2
|
+
import { computed, useId } from "vue";
|
|
3
3
|
import FormFieldRenderer from "../../components/renderer/FormFieldRenderer.vue";
|
|
4
4
|
import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
|
|
5
5
|
import { useFormUi } from "../../composables/use-form-ui";
|
|
@@ -10,10 +10,11 @@ const props = defineProps({
|
|
|
10
10
|
path: { type: Array, required: true }
|
|
11
11
|
});
|
|
12
12
|
const formUi = useFormUi();
|
|
13
|
+
const matrixInstanceId = useId();
|
|
13
14
|
const visibleFields = computed(
|
|
14
15
|
() => props.field.fields.filter((field) => field.type !== "hidden" && field.ignore !== true)
|
|
15
16
|
);
|
|
16
|
-
const matrixId = computed(() => props.path.join("-"));
|
|
17
|
+
const matrixId = computed(() => `${matrixInstanceId}-${props.path.join("-")}`);
|
|
17
18
|
const minWidth = computed(
|
|
18
19
|
() => typeof props.field.minWidth === "number" ? `${props.field.minWidth}px` : props.field.minWidth ?? "40rem"
|
|
19
20
|
);
|
|
@@ -118,7 +119,12 @@ function controlClass(field) {
|
|
|
118
119
|
"
|
|
119
120
|
>
|
|
120
121
|
<div :class="controlClass(column)">
|
|
121
|
-
<FormFieldRenderer
|
|
122
|
+
<FormFieldRenderer
|
|
123
|
+
:field="column"
|
|
124
|
+
:parent-path="rowPath(row)"
|
|
125
|
+
:control-labelledby="`${rowLabelId(row)} ${columnLabelId(column)}`"
|
|
126
|
+
bare
|
|
127
|
+
/>
|
|
122
128
|
</div>
|
|
123
129
|
</td>
|
|
124
130
|
</tr>
|
|
@@ -4,7 +4,7 @@ import UIcon from "@nuxt/ui/components/Icon.vue";
|
|
|
4
4
|
import UInput from "@nuxt/ui/components/Input.vue";
|
|
5
5
|
import USelectMenu from "@nuxt/ui/components/SelectMenu.vue";
|
|
6
6
|
import { getCountries, getCountryCallingCode, parsePhoneNumberFromString } from "libphonenumber-js";
|
|
7
|
-
import { computed, onMounted, ref, watch } from "vue";
|
|
7
|
+
import { computed, onMounted, ref, useId, watch } from "vue";
|
|
8
8
|
import { useUiToolsLocale } from "../../../i18n/use-locale";
|
|
9
9
|
import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
|
|
10
10
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
@@ -14,7 +14,7 @@ const props = defineProps({
|
|
|
14
14
|
field: { type: Object, required: true },
|
|
15
15
|
path: { type: Array, required: true }
|
|
16
16
|
});
|
|
17
|
-
const { locale } = useUiToolsLocale();
|
|
17
|
+
const { locale, t } = useUiToolsLocale();
|
|
18
18
|
const { form, controlProps, disabled, handleBlur, placeholder } = useFieldControl(
|
|
19
19
|
() => props.field,
|
|
20
20
|
() => props.path
|
|
@@ -25,6 +25,7 @@ const phoneValue = ref("");
|
|
|
25
25
|
const syncingFromExternal = ref(false);
|
|
26
26
|
const syncingToForm = ref(false);
|
|
27
27
|
const mounted = ref(false);
|
|
28
|
+
const controlId = useId();
|
|
28
29
|
const countryOptions = computed(
|
|
29
30
|
() => getCountries().map((code) => createCountryOption(code)).filter((option) => isCountryAllowed(option)).sort((left, right) => left.label.localeCompare(right.label))
|
|
30
31
|
);
|
|
@@ -140,6 +141,8 @@ function toFlagEmoji(code) {
|
|
|
140
141
|
>
|
|
141
142
|
<USelectMenu
|
|
142
143
|
v-model="countryCode"
|
|
144
|
+
:id="`${controlId}-country`"
|
|
145
|
+
:aria-label="t('form.fields.phone.country')"
|
|
143
146
|
class="w-auto min-w-[5.75rem] shrink-0"
|
|
144
147
|
value-key="value"
|
|
145
148
|
label-key="label"
|
|
@@ -152,6 +155,8 @@ function toFlagEmoji(code) {
|
|
|
152
155
|
<UInput
|
|
153
156
|
v-model="phoneValue"
|
|
154
157
|
v-bind="controlProps"
|
|
158
|
+
:id="`${controlId}-number`"
|
|
159
|
+
:aria-label="t('form.fields.phone.number')"
|
|
155
160
|
class="min-w-0 flex-1"
|
|
156
161
|
type="tel"
|
|
157
162
|
:placeholder="placeholder"
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import UIcon from "@nuxt/ui/components/Icon.vue";
|
|
3
3
|
import { computed, ref } from "vue";
|
|
4
|
+
import { useUiToolsLocale } from "../../../i18n/use-locale";
|
|
4
5
|
import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
|
|
5
6
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
6
7
|
const props = defineProps({
|
|
7
8
|
field: { type: Object, required: true },
|
|
8
9
|
path: { type: Array, required: true }
|
|
9
10
|
});
|
|
11
|
+
const { t } = useUiToolsLocale();
|
|
10
12
|
const { form, controlProps, disabled, handleBlur } = useFieldControl(
|
|
11
13
|
() => props.field,
|
|
12
14
|
() => props.path
|
|
@@ -30,17 +32,38 @@ function setRating(value) {
|
|
|
30
32
|
}
|
|
31
33
|
model.value = value;
|
|
32
34
|
}
|
|
35
|
+
function moveRating(event) {
|
|
36
|
+
if (disabled.value) return;
|
|
37
|
+
const current = model.value ?? 0;
|
|
38
|
+
if (event.key === "Home") model.value = 1;
|
|
39
|
+
else if (event.key === "End") model.value = max.value;
|
|
40
|
+
else if (event.key === "ArrowRight" || event.key === "ArrowUp")
|
|
41
|
+
model.value = Math.min(max.value, current + 1);
|
|
42
|
+
else if (event.key === "ArrowLeft" || event.key === "ArrowDown")
|
|
43
|
+
model.value = Math.max(1, current - 1);
|
|
44
|
+
else return;
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
}
|
|
33
47
|
</script>
|
|
34
48
|
|
|
35
49
|
<template>
|
|
36
50
|
<FormFieldShell :field="field" :path="path">
|
|
37
|
-
<div
|
|
51
|
+
<div
|
|
52
|
+
v-bind="controlProps"
|
|
53
|
+
role="radiogroup"
|
|
54
|
+
class="flex w-fit items-center gap-0.5 rounded-lg"
|
|
55
|
+
@blur="handleBlur"
|
|
56
|
+
@keydown="moveRating"
|
|
57
|
+
>
|
|
38
58
|
<button
|
|
39
59
|
v-for="value in max"
|
|
40
60
|
:key="value"
|
|
41
61
|
type="button"
|
|
42
62
|
:disabled="disabled"
|
|
43
|
-
|
|
63
|
+
role="radio"
|
|
64
|
+
:aria-checked="model === value"
|
|
65
|
+
:aria-label="t('form.fields.rating.value', { value, max })"
|
|
66
|
+
:tabindex="model === value || !model && value === 1 ? 0 : -1"
|
|
44
67
|
class="grid size-8 place-items-center rounded-md text-warning transition-colors hover:bg-elevated disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
|
45
68
|
:class="value <= visualValue ? 'opacity-100' : 'opacity-40'"
|
|
46
69
|
@mouseenter="hoverValue = value"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import UButton from "@nuxt/ui/components/Button.vue";
|
|
3
3
|
import USelectMenu from "@nuxt/ui/components/SelectMenu.vue";
|
|
4
4
|
import { computed, ref } from "vue";
|
|
5
|
+
import { useUiToolsLocale } from "../../../i18n/use-locale";
|
|
5
6
|
import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
|
|
6
7
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
7
8
|
const props = defineProps({
|
|
@@ -9,6 +10,7 @@ const props = defineProps({
|
|
|
9
10
|
path: { type: Array, required: true },
|
|
10
11
|
bare: { type: Boolean, required: false }
|
|
11
12
|
});
|
|
13
|
+
const { t } = useUiToolsLocale();
|
|
12
14
|
const { form, controlProps, disabled, handleBlur, options, placeholder } = useFieldControl(
|
|
13
15
|
() => props.field,
|
|
14
16
|
() => props.path
|
|
@@ -25,27 +27,29 @@ const model = computed({
|
|
|
25
27
|
});
|
|
26
28
|
const items = computed(() => [...options.items.value]);
|
|
27
29
|
const createItem = computed(
|
|
28
|
-
() => options.creatable.value
|
|
30
|
+
() => options.creatable.value && props.field.createItem ? props.field.createItem : false
|
|
29
31
|
);
|
|
30
|
-
const
|
|
32
|
+
const showExplicitCreate = computed(
|
|
33
|
+
() => options.creatable.value && !props.field.createItem
|
|
34
|
+
);
|
|
35
|
+
const showFooterActions = computed(() => options.refreshable.value || showExplicitCreate.value);
|
|
31
36
|
const footerActionsClass = computed(
|
|
32
|
-
() => options.refreshable.value &&
|
|
37
|
+
() => options.refreshable.value && showExplicitCreate.value ? "sm:grid-cols-2" : "grid-cols-1"
|
|
38
|
+
);
|
|
39
|
+
const createActionLabel = computed(
|
|
40
|
+
() => options.createLabel.value ?? t("form.fields.options.create")
|
|
33
41
|
);
|
|
34
|
-
const createActionLabel = computed(() => options.createLabel.value ?? "Create option");
|
|
35
42
|
async function handleCreate(label) {
|
|
36
|
-
|
|
37
|
-
if (!option) return;
|
|
38
|
-
if (!options.selectCreatedOption.value) return;
|
|
39
|
-
if (props.field.multiple) {
|
|
40
|
-
const current = Array.isArray(model.value) ? model.value.filter(isOptionValue) : [];
|
|
41
|
-
model.value = [...current, option.value];
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
model.value = option.value;
|
|
43
|
+
await options.create(label);
|
|
45
44
|
}
|
|
46
45
|
async function handleCreateAction() {
|
|
47
46
|
await handleCreate(searchTerm.value);
|
|
48
47
|
}
|
|
48
|
+
async function handleNativeCreate(label) {
|
|
49
|
+
const normalizedLabel = label.trim();
|
|
50
|
+
if (!normalizedLabel) return;
|
|
51
|
+
await handleCreate(normalizedLabel);
|
|
52
|
+
}
|
|
49
53
|
async function refreshOptions() {
|
|
50
54
|
await options.refresh();
|
|
51
55
|
}
|
|
@@ -71,7 +75,7 @@ function isOptionValue(value) {
|
|
|
71
75
|
:search-input="field.searchable ?? false"
|
|
72
76
|
:clear="field.clearable === true"
|
|
73
77
|
:create-item="createItem"
|
|
74
|
-
@create="
|
|
78
|
+
@create="handleNativeCreate"
|
|
75
79
|
@blur="handleBlur"
|
|
76
80
|
>
|
|
77
81
|
<template #content-bottom>
|
|
@@ -90,10 +94,10 @@ function isOptionValue(value) {
|
|
|
90
94
|
:loading="options.pending.value || options.fetching.value"
|
|
91
95
|
@click.stop="refreshOptions"
|
|
92
96
|
>
|
|
93
|
-
|
|
97
|
+
{{ t("form.fields.options.refresh") }}
|
|
94
98
|
</UButton>
|
|
95
99
|
<UButton
|
|
96
|
-
v-if="
|
|
100
|
+
v-if="showExplicitCreate"
|
|
97
101
|
block
|
|
98
102
|
size="xs"
|
|
99
103
|
variant="ghost"
|
|
@@ -109,7 +113,9 @@ function isOptionValue(value) {
|
|
|
109
113
|
</template>
|
|
110
114
|
|
|
111
115
|
<template #create-item-label="{ item }">
|
|
112
|
-
{{
|
|
116
|
+
{{
|
|
117
|
+
options.creating.value ? t("form.fields.options.creating") : t("form.fields.options.createNamed", { label: item })
|
|
118
|
+
}}
|
|
113
119
|
</template>
|
|
114
120
|
</USelectMenu>
|
|
115
121
|
<FormFieldShell v-else :field="field" :path="path">
|
|
@@ -128,7 +134,7 @@ function isOptionValue(value) {
|
|
|
128
134
|
:search-input="field.searchable ?? false"
|
|
129
135
|
:clear="field.clearable === true"
|
|
130
136
|
:create-item="createItem"
|
|
131
|
-
@create="
|
|
137
|
+
@create="handleNativeCreate"
|
|
132
138
|
@blur="handleBlur"
|
|
133
139
|
>
|
|
134
140
|
<template #content-bottom>
|
|
@@ -147,10 +153,10 @@ function isOptionValue(value) {
|
|
|
147
153
|
:loading="options.pending.value || options.fetching.value"
|
|
148
154
|
@click.stop="refreshOptions"
|
|
149
155
|
>
|
|
150
|
-
|
|
156
|
+
{{ t("form.fields.options.refresh") }}
|
|
151
157
|
</UButton>
|
|
152
158
|
<UButton
|
|
153
|
-
v-if="
|
|
159
|
+
v-if="showExplicitCreate"
|
|
154
160
|
block
|
|
155
161
|
size="xs"
|
|
156
162
|
variant="ghost"
|
|
@@ -166,7 +172,9 @@ function isOptionValue(value) {
|
|
|
166
172
|
</template>
|
|
167
173
|
|
|
168
174
|
<template #create-item-label="{ item }">
|
|
169
|
-
{{
|
|
175
|
+
{{
|
|
176
|
+
options.creating.value ? t("form.fields.options.creating") : t("form.fields.options.createNamed", { label: item })
|
|
177
|
+
}}
|
|
170
178
|
</template>
|
|
171
179
|
</USelectMenu>
|
|
172
180
|
</FormFieldShell>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import USwitch from "@nuxt/ui/components/Switch.vue";
|
|
3
|
-
import { computed } from "vue";
|
|
3
|
+
import { computed, useId } from "vue";
|
|
4
4
|
import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
|
|
5
5
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
6
|
+
import { formOptionKey } from "../../utils/options";
|
|
6
7
|
const props = defineProps({
|
|
7
8
|
field: { type: Object, required: true },
|
|
8
9
|
path: { type: Array, required: true }
|
|
@@ -19,6 +20,7 @@ const model = computed({
|
|
|
19
20
|
set: (value) => form.setValue(props.path, value)
|
|
20
21
|
});
|
|
21
22
|
const items = computed(() => [...options.items.value]);
|
|
23
|
+
const groupId = useId();
|
|
22
24
|
function toggleOption(value, checked) {
|
|
23
25
|
const current = model.value;
|
|
24
26
|
if (checked) {
|
|
@@ -35,15 +37,18 @@ function isOptionValue(value) {
|
|
|
35
37
|
<template>
|
|
36
38
|
<FormFieldShell :field="field" :path="path">
|
|
37
39
|
<div
|
|
38
|
-
|
|
40
|
+
role="group"
|
|
39
41
|
class="grid gap-3"
|
|
40
42
|
:class="field.orientation === 'horizontal' ? 'sm:flex sm:flex-wrap' : ''"
|
|
41
43
|
>
|
|
42
44
|
<USwitch
|
|
43
45
|
v-for="item in items"
|
|
44
|
-
:
|
|
46
|
+
:id="`${groupId}:${formOptionKey(item.value)}`"
|
|
47
|
+
:key="formOptionKey(item.value)"
|
|
48
|
+
v-bind="controlProps"
|
|
45
49
|
:model-value="model.includes(item.value)"
|
|
46
50
|
:label="item.label"
|
|
51
|
+
:aria-label="item.label"
|
|
47
52
|
:description="item.description"
|
|
48
53
|
:disabled="disabled || item.disabled"
|
|
49
54
|
:checked-icon="field.checkedIcon"
|
|
@@ -4,12 +4,14 @@ import UInput from "@nuxt/ui/components/Input.vue";
|
|
|
4
4
|
import UPopover from "@nuxt/ui/components/Popover.vue";
|
|
5
5
|
import USelect from "@nuxt/ui/components/Select.vue";
|
|
6
6
|
import { computed, ref, watch } from "vue";
|
|
7
|
+
import { useUiToolsLocale } from "../../../i18n/use-locale";
|
|
7
8
|
import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
|
|
8
9
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
9
10
|
const props = defineProps({
|
|
10
11
|
field: { type: Object, required: true },
|
|
11
12
|
path: { type: Array, required: true }
|
|
12
13
|
});
|
|
14
|
+
const { t } = useUiToolsLocale();
|
|
13
15
|
const { form, controlProps, disabled, handleBlur, placeholder } = useFieldControl(
|
|
14
16
|
() => props.field,
|
|
15
17
|
() => props.path
|
|
@@ -128,7 +130,7 @@ function padTimePart(value) {
|
|
|
128
130
|
class="w-full"
|
|
129
131
|
inputmode="numeric"
|
|
130
132
|
leading-icon="i-lucide-clock"
|
|
131
|
-
:placeholder="placeholder ?? '
|
|
133
|
+
:placeholder="placeholder ?? t('form.fields.time.format')"
|
|
132
134
|
:disabled="disabled"
|
|
133
135
|
@update:model-value="handleInput"
|
|
134
136
|
@focus="handleFocus"
|
|
@@ -142,7 +144,7 @@ function padTimePart(value) {
|
|
|
142
144
|
variant="ghost"
|
|
143
145
|
size="xs"
|
|
144
146
|
:disabled="disabled"
|
|
145
|
-
aria-label="
|
|
147
|
+
:aria-label="t('form.fields.time.clear')"
|
|
146
148
|
@mousedown.prevent
|
|
147
149
|
@click="clearTime"
|
|
148
150
|
/>
|
|
@@ -158,7 +160,7 @@ function padTimePart(value) {
|
|
|
158
160
|
:items="hours"
|
|
159
161
|
class="min-w-0"
|
|
160
162
|
size="sm"
|
|
161
|
-
placeholder="
|
|
163
|
+
:placeholder="t('form.fields.time.hour')"
|
|
162
164
|
:portal="false"
|
|
163
165
|
/>
|
|
164
166
|
<USelect
|
|
@@ -166,7 +168,7 @@ function padTimePart(value) {
|
|
|
166
168
|
:items="minutes"
|
|
167
169
|
class="min-w-0"
|
|
168
170
|
size="sm"
|
|
169
|
-
placeholder="
|
|
171
|
+
:placeholder="t('form.fields.time.minute')"
|
|
170
172
|
:portal="false"
|
|
171
173
|
/>
|
|
172
174
|
</div>
|
|
@@ -209,7 +211,7 @@ function padTimePart(value) {
|
|
|
209
211
|
>
|
|
210
212
|
</div>
|
|
211
213
|
<p class="text-xs text-muted">
|
|
212
|
-
{{ model ?? "
|
|
214
|
+
{{ model ?? t("form.fields.time.format") }}
|
|
213
215
|
</p>
|
|
214
216
|
</div>
|
|
215
217
|
</template>
|
|
@@ -3,12 +3,14 @@ import UAlert from "@nuxt/ui/components/Alert.vue";
|
|
|
3
3
|
import UButton from "@nuxt/ui/components/Button.vue";
|
|
4
4
|
import UFileUpload from "@nuxt/ui/components/FileUpload.vue";
|
|
5
5
|
import { computed, onScopeDispose, ref } from "vue";
|
|
6
|
+
import { useUiToolsLocale } from "../../../i18n/use-locale";
|
|
6
7
|
import FormFieldShell from "../../components/renderer/FormFieldShell.vue";
|
|
7
8
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
8
9
|
const props = defineProps({
|
|
9
10
|
field: { type: Object, required: true },
|
|
10
11
|
path: { type: Array, required: true }
|
|
11
12
|
});
|
|
13
|
+
const { t } = useUiToolsLocale();
|
|
12
14
|
const { form, controlProps, disabled, handleBlur, params } = useFieldControl(
|
|
13
15
|
() => props.field,
|
|
14
16
|
() => props.path
|
|
@@ -44,7 +46,7 @@ async function uploadFiles() {
|
|
|
44
46
|
selectedFiles.value = null;
|
|
45
47
|
} catch (error) {
|
|
46
48
|
if (uploadRun.value !== run) return;
|
|
47
|
-
uploadError.value = error instanceof Error ? error.message : "
|
|
49
|
+
uploadError.value = error instanceof Error ? error.message : t("form.fields.upload.failed");
|
|
48
50
|
} finally {
|
|
49
51
|
if (uploadRun.value === run) uploadPending.value = false;
|
|
50
52
|
}
|
|
@@ -105,7 +107,7 @@ function isFormObject(value) {
|
|
|
105
107
|
:disabled="disabled || !files.length"
|
|
106
108
|
@click="uploadFiles"
|
|
107
109
|
>
|
|
108
|
-
|
|
110
|
+
{{ t("form.fields.upload.upload") }}
|
|
109
111
|
</UButton>
|
|
110
112
|
<UButton
|
|
111
113
|
v-if="uploadedValue"
|
|
@@ -116,7 +118,7 @@ function isFormObject(value) {
|
|
|
116
118
|
:disabled="disabled"
|
|
117
119
|
@click="removeUpload"
|
|
118
120
|
>
|
|
119
|
-
|
|
121
|
+
{{ t("form.fields.upload.remove") }}
|
|
120
122
|
</UButton>
|
|
121
123
|
</div>
|
|
122
124
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComputedRef } from 'vue';
|
|
2
|
+
import type { NestedPaths } from '../../shared/types/utils.js';
|
|
2
3
|
import type { FormContextData } from './context.js';
|
|
3
4
|
import type { FormMaybePromise, FormObject } from './utils.js';
|
|
4
5
|
import type { FormValidationOptions } from './validation.js';
|
|
@@ -111,15 +112,20 @@ export interface FormFieldApi<TValue = unknown, TOption = unknown, TContext = Fo
|
|
|
111
112
|
/**
|
|
112
113
|
* Public form API available from form-level callbacks.
|
|
113
114
|
*/
|
|
114
|
-
export
|
|
115
|
+
export type FormFieldPath<TOutput = FormObject> = TOutput extends FormObject ? FormObject extends TOutput ? string : NestedPaths<TOutput> : string;
|
|
116
|
+
export interface FormApi<TOutput = FormObject> {
|
|
115
117
|
/** Reads an internal form value by raw path. */
|
|
116
118
|
get: (path: string) => unknown;
|
|
117
119
|
/** Writes an internal form value by raw path. */
|
|
118
120
|
set: (path: string, value: unknown) => void;
|
|
119
121
|
/** Runs form validation. */
|
|
120
122
|
validate: (options?: FormValidationOptions) => Promise<boolean>;
|
|
123
|
+
/** Sets an external error on a submitted output field. */
|
|
124
|
+
setError: (path: FormFieldPath<TOutput>, message: string) => void;
|
|
125
|
+
/** Clears one external field error, or every form error when no path is provided. */
|
|
126
|
+
clearError: (path?: FormFieldPath<TOutput>) => void;
|
|
121
127
|
/** Focuses a mounted field by raw path. */
|
|
122
|
-
focus: (path:
|
|
128
|
+
focus: (path: FormFieldPath<TOutput> | readonly string[]) => Promise<boolean>;
|
|
123
129
|
/** Submits the form through the configured submit lifecycle. */
|
|
124
130
|
submit: () => Promise<void>;
|
|
125
131
|
/** Resets the form to configured defaults. */
|
|
@@ -136,7 +142,7 @@ export interface FormSubmitHandlerParams<TOutput = FormObject> {
|
|
|
136
142
|
/** Submitted output value after output transforms and omitted fields are applied. */
|
|
137
143
|
formData: TOutput;
|
|
138
144
|
/** Namespaced form API for state, validation, submit, and reset operations. */
|
|
139
|
-
api: FormApi
|
|
145
|
+
api: FormApi<TOutput>;
|
|
140
146
|
}
|
|
141
147
|
export type FormSubmitHandler<TOutput = FormObject, TSubmitData = unknown> = (params: FormSubmitHandlerParams<TOutput>) => FormMaybePromise<FormSubmitResult<TSubmitData>>;
|
|
142
148
|
export interface FormSubmitHandlerResult<TSubmitData = unknown> {
|
|
@@ -2,7 +2,7 @@ import type { FormFieldCallback } from './callbacks.js';
|
|
|
2
2
|
import type { FormField } from './field.js';
|
|
3
3
|
import type { FormContainerLayout, FormItemLayout } from './layout.js';
|
|
4
4
|
import type { FormTransformConfig } from './transform.js';
|
|
5
|
-
import type { FormDynamic, FormMaybePromise, FormObject, FormText } from './utils.js';
|
|
5
|
+
import type { FormDynamic, FormMaybePromise, FormObject, FormRenderable, FormText } from './utils.js';
|
|
6
6
|
import type { FormValidationConfig } from './validation.js';
|
|
7
7
|
/**
|
|
8
8
|
* Field kinds targeted by the current form runtime plan.
|
|
@@ -26,6 +26,8 @@ export interface FormStatefulFieldBase<TType extends FormFieldType, TValue, TCon
|
|
|
26
26
|
description?: FormText;
|
|
27
27
|
/** Optional right-side hint rendered by the field wrapper. */
|
|
28
28
|
hint?: FormText;
|
|
29
|
+
/** Optional rich content rendered beside the label. Takes precedence over `hint`. */
|
|
30
|
+
labelExtra?: FormRenderable;
|
|
29
31
|
/** Placeholder forwarded to controls that support placeholders. */
|
|
30
32
|
placeholder?: FormText;
|
|
31
33
|
/** Raw dependency paths read before evaluating callbacks. */
|
|
@@ -76,6 +76,8 @@ export interface FormRuntime {
|
|
|
76
76
|
shouldRender: (field: FormField, path: readonly string[]) => boolean;
|
|
77
77
|
validate: (options?: FormValidationOptions) => Promise<boolean>;
|
|
78
78
|
validateCurrentStep: (options?: FormValidationOptions) => Promise<boolean>;
|
|
79
|
+
setError: (path: string | readonly string[], message: string) => void;
|
|
80
|
+
clearError: (path?: string | readonly string[]) => void;
|
|
79
81
|
focusRequest: Ref<FormFocusRequest | null>;
|
|
80
82
|
registerFieldElement: (path: string | readonly string[], element: HTMLElement) => () => void;
|
|
81
83
|
focusField: (path: string | readonly string[]) => Promise<boolean>;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { pathSegments } from "./path.js";
|
|
1
2
|
export function createPublicFormApi(runtime) {
|
|
2
3
|
return {
|
|
3
4
|
get: runtime.getValue,
|
|
4
5
|
set: runtime.setValue,
|
|
5
6
|
validate: runtime.validate,
|
|
7
|
+
setError: (path, message) => runtime.setError(pathSegments(path), message),
|
|
8
|
+
clearError: (path) => runtime.clearError(path ? pathSegments(path) : void 0),
|
|
6
9
|
focus: runtime.focusField,
|
|
7
10
|
submit: async () => {
|
|
8
11
|
await runtime.submit();
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { FormFieldCallbackParams, FormOptionItem, FormOptionValue } from '../types/index.js';
|
|
2
|
+
export interface FormOptionKeys {
|
|
3
|
+
value?: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
children?: string;
|
|
6
|
+
}
|
|
2
7
|
export interface ResolvedFormOption {
|
|
3
8
|
value: FormOptionValue;
|
|
4
9
|
label: string;
|
|
@@ -6,6 +11,10 @@ export interface ResolvedFormOption {
|
|
|
6
11
|
disabled?: boolean;
|
|
7
12
|
children?: readonly ResolvedFormOption[];
|
|
8
13
|
}
|
|
9
|
-
export declare function normalizeOptionItem(option: unknown): ResolvedFormOption;
|
|
10
|
-
export declare function normalizeOptionItems(options: readonly unknown[] | undefined): ResolvedFormOption[];
|
|
14
|
+
export declare function normalizeOptionItem(option: unknown, keys?: FormOptionKeys): ResolvedFormOption;
|
|
15
|
+
export declare function normalizeOptionItems(options: readonly unknown[] | undefined, keys?: FormOptionKeys): ResolvedFormOption[];
|
|
16
|
+
export declare function formOptionKey(value: FormOptionValue): string;
|
|
17
|
+
export declare function flattenResolvedOptions(options: readonly ResolvedFormOption[]): readonly ResolvedFormOption[];
|
|
18
|
+
export declare function mergeResolvedOptions(...collections: readonly (readonly ResolvedFormOption[])[]): readonly ResolvedFormOption[];
|
|
19
|
+
export declare function normalizeOptionSelection(value: unknown, options: readonly ResolvedFormOption[]): any[] | FormOptionValue | null | undefined;
|
|
11
20
|
export declare function resolveOptionSource(source: unknown, params: FormFieldCallbackParams): readonly FormOptionItem[];
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { isRecord } from "./path.js";
|
|
2
|
-
|
|
2
|
+
import { resolveFormText } from "./text.js";
|
|
3
|
+
export function normalizeOptionItem(option, keys = {}) {
|
|
3
4
|
if (isRecord(option)) {
|
|
4
|
-
const rawValue =
|
|
5
|
-
const
|
|
5
|
+
const rawValue = readOptionProperty(option, keys.value ?? "value");
|
|
6
|
+
const fallbackValue = typeof rawValue === "undefined" ? option.key : rawValue;
|
|
7
|
+
const rawChildren = readOptionProperty(option, keys.children ?? "children");
|
|
8
|
+
const rawDescription = readOptionProperty(option, "description");
|
|
9
|
+
const children = Array.isArray(rawChildren) ? rawChildren.map((child) => normalizeOptionItem(child, keys)) : void 0;
|
|
10
|
+
const value = normalizeOptionValue(fallbackValue);
|
|
6
11
|
return {
|
|
7
|
-
value
|
|
8
|
-
label:
|
|
9
|
-
description:
|
|
12
|
+
value,
|
|
13
|
+
label: resolveFormText(readOptionProperty(option, keys.label ?? "label")) ?? String(value),
|
|
14
|
+
description: isFormText(rawDescription) ? resolveFormText(rawDescription) : void 0,
|
|
10
15
|
disabled: option.disabled === true,
|
|
11
|
-
children
|
|
16
|
+
...children ? { children } : {}
|
|
12
17
|
};
|
|
13
18
|
}
|
|
14
19
|
return {
|
|
@@ -16,8 +21,42 @@ export function normalizeOptionItem(option) {
|
|
|
16
21
|
label: String(option)
|
|
17
22
|
};
|
|
18
23
|
}
|
|
19
|
-
export function normalizeOptionItems(options) {
|
|
20
|
-
return (options ?? []).map((option) => normalizeOptionItem(option));
|
|
24
|
+
export function normalizeOptionItems(options, keys = {}) {
|
|
25
|
+
return (options ?? []).map((option) => normalizeOptionItem(option, keys));
|
|
26
|
+
}
|
|
27
|
+
export function formOptionKey(value) {
|
|
28
|
+
return `${typeof value}:${String(value)}`;
|
|
29
|
+
}
|
|
30
|
+
export function flattenResolvedOptions(options) {
|
|
31
|
+
return options.flatMap((option) => [option, ...flattenResolvedOptions(option.children ?? [])]);
|
|
32
|
+
}
|
|
33
|
+
export function mergeResolvedOptions(...collections) {
|
|
34
|
+
const seen = /* @__PURE__ */ new Set();
|
|
35
|
+
return collections.flatMap(
|
|
36
|
+
(collection) => collection.filter((option) => {
|
|
37
|
+
const key = formOptionKey(option.value);
|
|
38
|
+
if (seen.has(key)) return false;
|
|
39
|
+
seen.add(key);
|
|
40
|
+
return true;
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
export function normalizeOptionSelection(value, options) {
|
|
45
|
+
const validKeys = new Set(
|
|
46
|
+
flattenResolvedOptions(options).map((option) => formOptionKey(option.value))
|
|
47
|
+
);
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
const selected = /* @__PURE__ */ new Set();
|
|
50
|
+
return value.filter((item) => {
|
|
51
|
+
if (!isOptionValue(item)) return false;
|
|
52
|
+
const key = formOptionKey(item);
|
|
53
|
+
if (!validKeys.has(key) || selected.has(key)) return false;
|
|
54
|
+
selected.add(key);
|
|
55
|
+
return true;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (value === null || typeof value === "undefined") return value;
|
|
59
|
+
return isOptionValue(value) && validKeys.has(formOptionKey(value)) ? value : null;
|
|
21
60
|
}
|
|
22
61
|
export function resolveOptionSource(source, params) {
|
|
23
62
|
if (!source) return [];
|
|
@@ -34,3 +73,12 @@ function normalizeOptionValue(value) {
|
|
|
34
73
|
return value;
|
|
35
74
|
return String(value);
|
|
36
75
|
}
|
|
76
|
+
function readOptionProperty(option, key) {
|
|
77
|
+
return Object.getOwnPropertyDescriptor(option, key)?.value;
|
|
78
|
+
}
|
|
79
|
+
function isFormText(value) {
|
|
80
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "function";
|
|
81
|
+
}
|
|
82
|
+
function isOptionValue(value) {
|
|
83
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
84
|
+
}
|