@nuxt/ui 3.0.2 → 3.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/.nuxt/ui/accordion.ts +2 -2
- package/.nuxt/ui/alert.ts +29 -29
- package/.nuxt/ui/avatar-group.ts +1 -1
- package/.nuxt/ui/avatar.ts +3 -3
- package/.nuxt/ui/badge.ts +33 -33
- package/.nuxt/ui/breadcrumb.ts +5 -5
- package/.nuxt/ui/button.ts +43 -43
- package/.nuxt/ui/calendar.ts +17 -17
- package/.nuxt/ui/card.ts +5 -5
- package/.nuxt/ui/carousel.ts +6 -6
- package/.nuxt/ui/checkbox-group.ts +61 -0
- package/.nuxt/ui/checkbox.ts +140 -27
- package/.nuxt/ui/chip.ts +8 -8
- package/.nuxt/ui/color-picker.ts +3 -3
- package/.nuxt/ui/command-palette.ts +14 -14
- package/.nuxt/ui/context-menu.ts +33 -33
- package/.nuxt/ui/drawer.ts +10 -10
- package/.nuxt/ui/dropdown-menu.ts +34 -34
- package/.nuxt/ui/form-field.ts +6 -6
- package/.nuxt/ui/index.ts +1 -0
- package/.nuxt/ui/input-menu.ts +42 -42
- package/.nuxt/ui/input-number.ts +20 -20
- package/.nuxt/ui/input.ts +23 -23
- package/.nuxt/ui/kbd.ts +4 -4
- package/.nuxt/ui/link.ts +3 -3
- package/.nuxt/ui/modal.ts +5 -5
- package/.nuxt/ui/navigation-menu.ts +78 -80
- package/.nuxt/ui/pin-input.ts +20 -20
- package/.nuxt/ui/popover.ts +2 -2
- package/.nuxt/ui/progress.ts +17 -17
- package/.nuxt/ui/radio-group.ts +250 -35
- package/.nuxt/ui/select-menu.ts +36 -33
- package/.nuxt/ui/select.ts +32 -32
- package/.nuxt/ui/separator.ts +8 -8
- package/.nuxt/ui/skeleton.ts +1 -1
- package/.nuxt/ui/slideover.ts +4 -4
- package/.nuxt/ui/slider.ts +16 -16
- package/.nuxt/ui/stepper.ts +18 -18
- package/.nuxt/ui/switch.ts +20 -20
- package/.nuxt/ui/table.ts +15 -15
- package/.nuxt/ui/tabs.ts +32 -32
- package/.nuxt/ui/textarea.ts +40 -35
- package/.nuxt/ui/toast.ts +24 -24
- package/.nuxt/ui/tooltip.ts +2 -2
- package/.nuxt/ui/tree.ts +18 -18
- package/.nuxt/ui.css +63 -12
- package/README.md +15 -0
- package/cli/templates.mjs +18 -17
- package/dist/module.json +5 -5
- package/dist/module.mjs +12 -7
- package/dist/runtime/components/Accordion.vue +36 -93
- package/dist/runtime/components/Accordion.vue.d.ts +75 -0
- package/dist/runtime/components/Alert.vue +37 -100
- package/dist/runtime/components/Alert.vue.d.ts +81 -0
- package/dist/runtime/components/App.vue +28 -34
- package/dist/runtime/components/App.vue.d.ts +24 -0
- package/dist/runtime/components/Avatar.vue +42 -75
- package/dist/runtime/components/Avatar.vue.d.ts +39 -0
- package/dist/runtime/components/AvatarGroup.vue +40 -77
- package/dist/runtime/components/AvatarGroup.vue.d.ts +31 -0
- package/dist/runtime/components/Badge.vue +33 -62
- package/dist/runtime/components/Badge.vue.d.ts +41 -0
- package/dist/runtime/components/Breadcrumb.vue +37 -90
- package/dist/runtime/components/Breadcrumb.vue.d.ts +64 -0
- package/dist/runtime/components/Button.vue +95 -104
- package/dist/runtime/components/Button.vue.d.ts +48 -0
- package/dist/runtime/components/ButtonGroup.vue +20 -46
- package/dist/runtime/components/ButtonGroup.vue.d.ts +34 -0
- package/dist/runtime/components/Calendar.vue +67 -150
- package/dist/runtime/components/Calendar.vue.d.ts +111 -0
- package/dist/runtime/components/Card.vue +18 -40
- package/dist/runtime/components/Card.vue.d.ts +29 -0
- package/dist/runtime/components/Carousel.vue +112 -234
- package/dist/runtime/components/Carousel.vue.d.ts +122 -0
- package/dist/runtime/components/Checkbox.vue +52 -93
- package/dist/runtime/components/Checkbox.vue.d.ts +73 -0
- package/dist/runtime/components/CheckboxGroup.vue +121 -0
- package/dist/runtime/components/CheckboxGroup.vue.d.ts +78 -0
- package/dist/runtime/components/Chip.vue +26 -69
- package/dist/runtime/components/Chip.vue.d.ts +57 -0
- package/dist/runtime/components/Collapsible.vue +22 -41
- package/dist/runtime/components/Collapsible.vue.d.ts +35 -0
- package/dist/runtime/components/ColorPicker.vue +111 -197
- package/dist/runtime/components/ColorPicker.vue.d.ts +48 -0
- package/dist/runtime/components/CommandPalette.vue +96 -237
- package/dist/runtime/components/CommandPalette.vue.d.ts +161 -0
- package/dist/runtime/components/Container.vue +15 -28
- package/dist/runtime/components/Container.vue.d.ts +18 -0
- package/dist/runtime/components/ContextMenu.vue +34 -133
- package/dist/runtime/components/ContextMenu.vue.d.ts +107 -0
- package/dist/runtime/components/ContextMenuContent.vue +73 -81
- package/dist/runtime/components/ContextMenuContent.vue.d.ts +65 -0
- package/dist/runtime/components/Drawer.vue +49 -83
- package/dist/runtime/components/Drawer.vue.d.ts +78 -0
- package/dist/runtime/components/DropdownMenu.vue +39 -142
- package/dist/runtime/components/DropdownMenu.vue.d.ts +117 -0
- package/dist/runtime/components/DropdownMenuContent.vue +78 -88
- package/dist/runtime/components/DropdownMenuContent.vue.d.ts +68 -0
- package/dist/runtime/components/Form.vue +135 -220
- package/dist/runtime/components/Form.vue.d.ts +69 -0
- package/dist/runtime/components/FormField.vue +35 -77
- package/dist/runtime/components/FormField.vue.d.ts +61 -0
- package/dist/runtime/components/Icon.vue +12 -19
- package/dist/runtime/components/Icon.vue.d.ts +8 -0
- package/dist/runtime/components/Input.vue +82 -140
- package/dist/runtime/components/Input.vue.d.ts +76 -0
- package/dist/runtime/components/InputMenu.vue +185 -332
- package/dist/runtime/components/InputMenu.vue.d.ts +324 -0
- package/dist/runtime/components/InputNumber.vue +71 -133
- package/dist/runtime/components/InputNumber.vue.d.ts +182 -0
- package/dist/runtime/components/Kbd.vue +20 -46
- package/dist/runtime/components/Kbd.vue.d.ts +34 -0
- package/dist/runtime/components/Link.vue +98 -176
- package/dist/runtime/components/Link.vue.d.ts +93 -0
- package/dist/runtime/components/LinkBase.vue +33 -42
- package/dist/runtime/components/LinkBase.vue.d.ts +25 -0
- package/dist/runtime/components/Modal.vue +53 -116
- package/dist/runtime/components/Modal.vue.d.ts +93 -0
- package/dist/runtime/components/NavigationMenu.vue +77 -209
- package/dist/runtime/components/NavigationMenu.vue.d.ts +139 -0
- package/dist/runtime/components/OverlayProvider.vue +14 -18
- package/dist/runtime/components/OverlayProvider.vue.d.ts +2 -0
- package/dist/runtime/components/Pagination.vue +46 -140
- package/dist/runtime/components/Pagination.vue.d.ts +119 -0
- package/dist/runtime/components/PinInput.vue +61 -78
- package/dist/runtime/components/PinInput.vue.d.ts +57 -0
- package/dist/runtime/components/Popover.vue +47 -84
- package/dist/runtime/components/Popover.vue.d.ts +63 -0
- package/dist/runtime/components/Progress.vue +70 -131
- package/dist/runtime/components/Progress.vue.d.ts +66 -0
- package/dist/runtime/components/RadioGroup.vue +70 -132
- package/dist/runtime/components/RadioGroup.vue.d.ts +91 -0
- package/dist/runtime/components/Select.vue +110 -224
- package/dist/runtime/components/Select.vue.d.ts +145 -0
- package/dist/runtime/components/SelectMenu.vue +161 -310
- package/dist/runtime/components/SelectMenu.vue.d.ts +196 -0
- package/dist/runtime/components/Separator.vue +29 -71
- package/dist/runtime/components/Separator.vue.d.ts +54 -0
- package/dist/runtime/components/Skeleton.vue +21 -23
- package/dist/runtime/components/Skeleton.vue.d.ts +17 -0
- package/dist/runtime/components/Slideover.vue +53 -120
- package/dist/runtime/components/Slideover.vue.d.ts +94 -0
- package/dist/runtime/components/Slider.vue +46 -84
- package/dist/runtime/components/Slider.vue.d.ts +52 -0
- package/dist/runtime/components/Stepper.vue +45 -115
- package/dist/runtime/components/Stepper.vue.d.ts +83 -0
- package/dist/runtime/components/Switch.vue +45 -93
- package/dist/runtime/components/Switch.vue.d.ts +70 -0
- package/dist/runtime/components/Table.vue +163 -267
- package/dist/runtime/components/Table.vue.d.ts +183 -0
- package/dist/runtime/components/Tabs.vue +41 -107
- package/dist/runtime/components/Tabs.vue.d.ts +88 -0
- package/dist/runtime/components/Textarea.vue +113 -158
- package/dist/runtime/components/Textarea.vue.d.ts +78 -0
- package/dist/runtime/components/Toast.vue +49 -110
- package/dist/runtime/components/Toast.vue.d.ts +157 -0
- package/dist/runtime/components/Toaster.vue +70 -107
- package/dist/runtime/components/Toaster.vue.d.ts +38 -0
- package/dist/runtime/components/Tooltip.vue +38 -65
- package/dist/runtime/components/Tooltip.vue.d.ts +50 -0
- package/dist/runtime/components/Tree.vue +59 -166
- package/dist/runtime/components/Tree.vue.d.ts +111 -0
- package/dist/runtime/composables/defineShortcuts.d.ts +1 -1
- package/dist/runtime/composables/defineShortcuts.js +6 -0
- package/dist/runtime/composables/useAvatarGroup.d.ts +1 -1
- package/dist/runtime/composables/useButtonGroup.d.ts +2 -2
- package/dist/runtime/composables/useFormField.d.ts +2 -2
- package/dist/runtime/composables/useKbd.d.ts +1 -0
- package/dist/runtime/composables/useKbd.js +4 -3
- package/dist/runtime/composables/useOverlay.d.ts +14 -8
- package/dist/runtime/composables/useOverlay.js +20 -7
- package/dist/runtime/composables/usePortal.d.ts +6 -0
- package/dist/runtime/composables/usePortal.js +17 -0
- package/dist/runtime/index.css +1 -1
- package/dist/runtime/inertia/components/Link.vue +146 -0
- package/dist/runtime/inertia/components/Link.vue.d.ts +63 -0
- package/dist/runtime/inertia/stubs.d.ts +43 -0
- package/dist/runtime/inertia/stubs.js +82 -0
- package/dist/runtime/locale/bg.d.ts +3 -0
- package/dist/runtime/locale/bg.js +54 -0
- package/dist/runtime/locale/index.d.ts +5 -1
- package/dist/runtime/locale/index.js +5 -1
- package/dist/runtime/locale/kk.d.ts +3 -0
- package/dist/runtime/locale/kk.js +54 -0
- package/dist/runtime/locale/tj.d.ts +3 -0
- package/dist/runtime/locale/tj.js +54 -0
- package/dist/runtime/locale/ug_cn.d.ts +3 -0
- package/dist/runtime/locale/ug_cn.js +55 -0
- package/dist/runtime/types/form.d.ts +4 -1
- package/dist/runtime/types/index.d.ts +1 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/types/tv.d.ts +53 -0
- package/dist/runtime/types/tv.js +0 -0
- package/dist/runtime/types/utils.d.ts +7 -17
- package/dist/runtime/types/utils.js +1 -0
- package/dist/runtime/utils/link.d.ts +3 -3
- package/dist/runtime/utils/link.js +1 -0
- package/dist/runtime/utils/tv.js +2 -2
- package/dist/runtime/vue/components/Icon.vue +7 -8
- package/dist/runtime/vue/components/Icon.vue.d.ts +5 -0
- package/dist/runtime/vue/components/Link.vue +118 -202
- package/dist/runtime/vue/components/Link.vue.d.ts +93 -0
- package/dist/runtime/vue/stubs.d.ts +2 -2
- package/dist/shared/{ui.BkNUP964.mjs → ui.CbtTMMzt.mjs} +631 -366
- package/dist/types.d.mts +3 -5
- package/dist/unplugin.d.mts +6 -2
- package/dist/unplugin.mjs +25 -4
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +2 -2
- package/package.json +96 -54
- package/dist/module.cjs +0 -62
- package/dist/module.d.cts +0 -45
- package/dist/module.d.ts +0 -45
- package/dist/shared/ui.BL-XB3u1.cjs +0 -3555
- package/dist/types.d.ts +0 -7
- package/dist/unplugin.cjs +0 -232
- package/dist/unplugin.d.cts +0 -62
- package/dist/unplugin.d.ts +0 -62
- package/dist/vite.cjs +0 -23
- package/dist/vite.d.cts +0 -15
- package/dist/vite.d.ts +0 -15
|
@@ -1,293 +1,208 @@
|
|
|
1
|
-
<script
|
|
2
|
-
import
|
|
3
|
-
import _appConfig from '#build/app.config'
|
|
4
|
-
import theme from '#build/ui/form'
|
|
5
|
-
import { tv } from '../utils/tv'
|
|
6
|
-
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId } from '../types/form'
|
|
7
|
-
import type { DeepReadonly } from 'vue'
|
|
8
|
-
|
|
9
|
-
const appConfigForm = _appConfig as AppConfig & { ui: { form: Partial<typeof theme> } }
|
|
10
|
-
|
|
11
|
-
const form = tv({ extend: tv(theme), ...(appConfigForm.ui?.form || {}) })
|
|
12
|
-
|
|
13
|
-
export interface FormProps<T extends object> {
|
|
14
|
-
id?: string | number
|
|
15
|
-
/** Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. */
|
|
16
|
-
schema?: FormSchema<T>
|
|
17
|
-
/** An object representing the current state of the form. */
|
|
18
|
-
state: Partial<T>
|
|
19
|
-
/**
|
|
20
|
-
* Custom validation function to validate the form state.
|
|
21
|
-
* @param state - The current state of the form.
|
|
22
|
-
* @returns A promise that resolves to an array of FormError objects, or an array of FormError objects directly.
|
|
23
|
-
*/
|
|
24
|
-
validate?: (state: Partial<T>) => Promise<FormError[]> | FormError[]
|
|
25
|
-
/**
|
|
26
|
-
* The list of input events that trigger the form validation.
|
|
27
|
-
* @defaultValue `['blur', 'change', 'input']`
|
|
28
|
-
*/
|
|
29
|
-
validateOn?: FormInputEvents[]
|
|
30
|
-
/** Disable all inputs inside the form. */
|
|
31
|
-
disabled?: boolean
|
|
32
|
-
/**
|
|
33
|
-
* Delay in milliseconds before validating the form on input events.
|
|
34
|
-
* @defaultValue `300`
|
|
35
|
-
*/
|
|
36
|
-
validateOnInputDelay?: number
|
|
37
|
-
/**
|
|
38
|
-
* If true, schema transformations will be applied to the state on submit.
|
|
39
|
-
* @defaultValue `true`
|
|
40
|
-
*/
|
|
41
|
-
transform?: boolean
|
|
42
|
-
class?: any
|
|
43
|
-
onSubmit?: ((event: FormSubmitEvent<T>) => void | Promise<void>) | (() => void | Promise<void>)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface FormEmits<T extends object> {
|
|
47
|
-
(e: 'submit', payload: FormSubmitEvent<T>): void
|
|
48
|
-
(e: 'error', payload: FormErrorEvent): void
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface FormSlots {
|
|
52
|
-
default(props?: { errors: FormError[] }): any
|
|
53
|
-
}
|
|
1
|
+
<script>
|
|
2
|
+
import theme from "#build/ui/form";
|
|
54
3
|
</script>
|
|
55
4
|
|
|
56
|
-
<script
|
|
57
|
-
import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from
|
|
58
|
-
import { useEventBus } from
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
5
|
+
<script setup>
|
|
6
|
+
import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from "vue";
|
|
7
|
+
import { useEventBus } from "@vueuse/core";
|
|
8
|
+
import { useAppConfig } from "#imports";
|
|
9
|
+
import { formOptionsInjectionKey, formInputsInjectionKey, formBusInjectionKey, formLoadingInjectionKey } from "../composables/useFormField";
|
|
10
|
+
import { tv } from "../utils/tv";
|
|
11
|
+
import { validateSchema } from "../utils/form";
|
|
12
|
+
import { FormValidationException } from "../types/form";
|
|
13
|
+
const props = defineProps({
|
|
14
|
+
id: { type: [String, Number], required: false },
|
|
15
|
+
schema: { type: null, required: false },
|
|
16
|
+
state: { type: Object, required: true },
|
|
17
|
+
validate: { type: Function, required: false },
|
|
18
|
+
validateOn: { type: Array, required: false, default() {
|
|
19
|
+
return ["input", "blur", "change"];
|
|
20
|
+
} },
|
|
21
|
+
disabled: { type: Boolean, required: false },
|
|
22
|
+
validateOnInputDelay: { type: Number, required: false, default: 300 },
|
|
23
|
+
transform: { type: Boolean, required: false, default: true },
|
|
24
|
+
attach: { type: Boolean, required: false, default: true },
|
|
25
|
+
loadingAuto: { type: Boolean, required: false, default: true },
|
|
26
|
+
class: { type: null, required: false },
|
|
27
|
+
onSubmit: { type: Function, required: false }
|
|
28
|
+
});
|
|
29
|
+
const emits = defineEmits(["submit", "error"]);
|
|
30
|
+
defineSlots();
|
|
31
|
+
const appConfig = useAppConfig();
|
|
32
|
+
const ui = computed(() => tv({ extend: tv(theme), ...appConfig.ui?.form || {} }));
|
|
33
|
+
const formId = props.id ?? useId();
|
|
34
|
+
const bus = useEventBus(`form-${formId}`);
|
|
35
|
+
const parentBus = props.attach && inject(
|
|
78
36
|
formBusInjectionKey,
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const nestedForms = ref<Map<string | number, { validate: typeof _validate }>>(new Map())
|
|
85
|
-
|
|
37
|
+
void 0
|
|
38
|
+
);
|
|
39
|
+
provide(formBusInjectionKey, bus);
|
|
40
|
+
const nestedForms = ref(/* @__PURE__ */ new Map());
|
|
86
41
|
onMounted(async () => {
|
|
87
42
|
bus.on(async (event) => {
|
|
88
|
-
if (event.type ===
|
|
89
|
-
nestedForms.value.set(event.formId, { validate: event.validate })
|
|
90
|
-
} else if (event.type ===
|
|
91
|
-
nestedForms.value.delete(event.formId)
|
|
43
|
+
if (event.type === "attach") {
|
|
44
|
+
nestedForms.value.set(event.formId, { validate: event.validate });
|
|
45
|
+
} else if (event.type === "detach") {
|
|
46
|
+
nestedForms.value.delete(event.formId);
|
|
92
47
|
} else if (props.validateOn?.includes(event.type) && !loading.value) {
|
|
93
|
-
if (event.type !==
|
|
94
|
-
await _validate({ name: event.name, silent: true, nested: false })
|
|
48
|
+
if (event.type !== "input") {
|
|
49
|
+
await _validate({ name: event.name, silent: true, nested: false });
|
|
95
50
|
} else if (event.eager || blurredFields.has(event.name)) {
|
|
96
|
-
await _validate({ name: event.name, silent: true, nested: false })
|
|
51
|
+
await _validate({ name: event.name, silent: true, nested: false });
|
|
97
52
|
}
|
|
98
53
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
blurredFields.add(event.name)
|
|
54
|
+
if (event.type === "blur") {
|
|
55
|
+
blurredFields.add(event.name);
|
|
102
56
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
touchedFields.add(event.name)
|
|
57
|
+
if (event.type === "change" || event.type === "input" || event.type === "blur" || event.type === "focus") {
|
|
58
|
+
touchedFields.add(event.name);
|
|
106
59
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
dirtyFields.add(event.name)
|
|
60
|
+
if (event.type === "change" || event.type === "input") {
|
|
61
|
+
dirtyFields.add(event.name);
|
|
110
62
|
}
|
|
111
|
-
})
|
|
112
|
-
})
|
|
113
|
-
|
|
63
|
+
});
|
|
64
|
+
});
|
|
114
65
|
onUnmounted(() => {
|
|
115
|
-
bus.reset()
|
|
116
|
-
})
|
|
117
|
-
|
|
66
|
+
bus.reset();
|
|
67
|
+
});
|
|
118
68
|
onMounted(async () => {
|
|
119
69
|
if (parentBus) {
|
|
120
|
-
await nextTick()
|
|
121
|
-
parentBus.emit({ type:
|
|
70
|
+
await nextTick();
|
|
71
|
+
parentBus.emit({ type: "attach", validate: _validate, formId });
|
|
122
72
|
}
|
|
123
|
-
})
|
|
124
|
-
|
|
73
|
+
});
|
|
125
74
|
onUnmounted(() => {
|
|
126
75
|
if (parentBus) {
|
|
127
|
-
parentBus.emit({ type:
|
|
76
|
+
parentBus.emit({ type: "detach", formId });
|
|
128
77
|
}
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const blurredFields = new Set<keyof T>()
|
|
140
|
-
|
|
141
|
-
function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
|
|
142
|
-
return errs.map(err => ({
|
|
78
|
+
});
|
|
79
|
+
const errors = ref([]);
|
|
80
|
+
provide("form-errors", errors);
|
|
81
|
+
const inputs = ref({});
|
|
82
|
+
provide(formInputsInjectionKey, inputs);
|
|
83
|
+
const dirtyFields = /* @__PURE__ */ new Set();
|
|
84
|
+
const touchedFields = /* @__PURE__ */ new Set();
|
|
85
|
+
const blurredFields = /* @__PURE__ */ new Set();
|
|
86
|
+
function resolveErrorIds(errs) {
|
|
87
|
+
return errs.map((err) => ({
|
|
143
88
|
...err,
|
|
144
|
-
id: err?.name ? inputs.value[err.name]?.id :
|
|
145
|
-
}))
|
|
89
|
+
id: err?.name ? inputs.value[err.name]?.id : void 0
|
|
90
|
+
}));
|
|
146
91
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
async function getErrors(): Promise<FormErrorWithId[]> {
|
|
151
|
-
let errs = props.validate ? (await props.validate(props.state)) ?? [] : []
|
|
152
|
-
|
|
92
|
+
const transformedState = ref(null);
|
|
93
|
+
async function getErrors() {
|
|
94
|
+
let errs = props.validate ? await props.validate(props.state) ?? [] : [];
|
|
153
95
|
if (props.schema) {
|
|
154
|
-
const { errors, result } = await validateSchema(props.state, props.schema
|
|
155
|
-
if (
|
|
156
|
-
errs = errs.concat(
|
|
96
|
+
const { errors: errors2, result } = await validateSchema(props.state, props.schema);
|
|
97
|
+
if (errors2) {
|
|
98
|
+
errs = errs.concat(errors2);
|
|
157
99
|
} else {
|
|
158
|
-
transformedState.value = result
|
|
100
|
+
transformedState.value = result;
|
|
159
101
|
}
|
|
160
102
|
}
|
|
161
|
-
|
|
162
|
-
return resolveErrorIds(errs)
|
|
103
|
+
return resolveErrorIds(errs);
|
|
163
104
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return error
|
|
175
|
-
})
|
|
176
|
-
)
|
|
177
|
-
: []
|
|
178
|
-
|
|
105
|
+
async function _validate(opts = { silent: false, nested: true, transform: false }) {
|
|
106
|
+
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name;
|
|
107
|
+
const nestedValidatePromises = !names && opts.nested ? Array.from(nestedForms.value.values()).map(
|
|
108
|
+
({ validate }) => validate(opts).then(() => void 0).catch((error) => {
|
|
109
|
+
if (!(error instanceof FormValidationException)) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
return error;
|
|
113
|
+
})
|
|
114
|
+
) : [];
|
|
179
115
|
if (names) {
|
|
180
|
-
const otherErrors = errors.value.filter(error => !names.some((name) => {
|
|
181
|
-
const pattern = inputs.value?.[name]?.pattern
|
|
182
|
-
return name === error.name ||
|
|
183
|
-
}))
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
errors.value = otherErrors.concat(pathErrors)
|
|
116
|
+
const otherErrors = errors.value.filter((error) => !names.some((name) => {
|
|
117
|
+
const pattern = inputs.value?.[name]?.pattern;
|
|
118
|
+
return name === error.name || pattern && error.name?.match(pattern);
|
|
119
|
+
}));
|
|
120
|
+
const pathErrors = (await getErrors()).filter((error) => names.some((name) => {
|
|
121
|
+
const pattern = inputs.value?.[name]?.pattern;
|
|
122
|
+
return name === error.name || pattern && error.name?.match(pattern);
|
|
123
|
+
}));
|
|
124
|
+
errors.value = otherErrors.concat(pathErrors);
|
|
191
125
|
} else {
|
|
192
|
-
errors.value = await getErrors()
|
|
126
|
+
errors.value = await getErrors();
|
|
193
127
|
}
|
|
194
|
-
|
|
195
|
-
const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val !== undefined)
|
|
196
|
-
|
|
128
|
+
const childErrors = (await Promise.all(nestedValidatePromises)).filter((val) => val !== void 0);
|
|
197
129
|
if (errors.value.length + childErrors.length > 0) {
|
|
198
|
-
if (opts.silent) return false
|
|
199
|
-
throw new FormValidationException(formId, errors.value, childErrors)
|
|
130
|
+
if (opts.silent) return false;
|
|
131
|
+
throw new FormValidationException(formId, errors.value, childErrors);
|
|
200
132
|
}
|
|
201
|
-
|
|
202
133
|
if (opts.transform) {
|
|
203
|
-
Object.assign(props.state, transformedState.value)
|
|
134
|
+
Object.assign(props.state, transformedState.value);
|
|
204
135
|
}
|
|
205
|
-
|
|
206
|
-
return props.state as T
|
|
136
|
+
return props.state;
|
|
207
137
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
loading.value = true
|
|
214
|
-
|
|
215
|
-
const event = payload as FormSubmitEvent<any>
|
|
216
|
-
|
|
138
|
+
const loading = ref(false);
|
|
139
|
+
provide(formLoadingInjectionKey, readonly(loading));
|
|
140
|
+
async function onSubmitWrapper(payload) {
|
|
141
|
+
loading.value = props.loadingAuto && true;
|
|
142
|
+
const event = payload;
|
|
217
143
|
try {
|
|
218
|
-
event.data = await _validate({ nested: true, transform: props.transform })
|
|
219
|
-
await props.onSubmit?.(event)
|
|
220
|
-
dirtyFields.clear()
|
|
144
|
+
event.data = await _validate({ nested: true, transform: props.transform });
|
|
145
|
+
await props.onSubmit?.(event);
|
|
146
|
+
dirtyFields.clear();
|
|
221
147
|
} catch (error) {
|
|
222
148
|
if (!(error instanceof FormValidationException)) {
|
|
223
|
-
throw error
|
|
149
|
+
throw error;
|
|
224
150
|
}
|
|
225
|
-
|
|
226
|
-
const errorEvent: FormErrorEvent = {
|
|
151
|
+
const errorEvent = {
|
|
227
152
|
...event,
|
|
228
153
|
errors: error.errors,
|
|
229
154
|
children: error.children
|
|
230
|
-
}
|
|
231
|
-
emits(
|
|
155
|
+
};
|
|
156
|
+
emits("error", errorEvent);
|
|
232
157
|
} finally {
|
|
233
|
-
loading.value = false
|
|
158
|
+
loading.value = false;
|
|
234
159
|
}
|
|
235
160
|
}
|
|
236
|
-
|
|
237
|
-
const disabled = computed(() => props.disabled || loading.value)
|
|
238
|
-
|
|
161
|
+
const disabled = computed(() => props.disabled || loading.value);
|
|
239
162
|
provide(formOptionsInjectionKey, computed(() => ({
|
|
240
163
|
disabled: disabled.value,
|
|
241
164
|
validateOnInputDelay: props.validateOnInputDelay
|
|
242
|
-
})))
|
|
243
|
-
|
|
244
|
-
defineExpose<Form<T>>({
|
|
165
|
+
})));
|
|
166
|
+
defineExpose({
|
|
245
167
|
validate: _validate,
|
|
246
168
|
errors,
|
|
247
|
-
|
|
248
|
-
setErrors(errs: FormError[], name?: keyof T) {
|
|
169
|
+
setErrors(errs, name) {
|
|
249
170
|
if (name) {
|
|
250
|
-
errors.value = errors.value
|
|
251
|
-
.filter(error => error.name !== name)
|
|
252
|
-
.concat(resolveErrorIds(errs))
|
|
171
|
+
errors.value = errors.value.filter((error) => error.name !== name).concat(resolveErrorIds(errs));
|
|
253
172
|
} else {
|
|
254
|
-
errors.value = resolveErrorIds(errs)
|
|
173
|
+
errors.value = resolveErrorIds(errs);
|
|
255
174
|
}
|
|
256
175
|
},
|
|
257
|
-
|
|
258
176
|
async submit() {
|
|
259
|
-
await onSubmitWrapper(new Event(
|
|
177
|
+
await onSubmitWrapper(new Event("submit"));
|
|
260
178
|
},
|
|
261
|
-
|
|
262
|
-
getErrors(name?: keyof T) {
|
|
179
|
+
getErrors(name) {
|
|
263
180
|
if (name) {
|
|
264
|
-
return errors.value.filter(err => err.name === name)
|
|
181
|
+
return errors.value.filter((err) => err.name === name);
|
|
265
182
|
}
|
|
266
|
-
return errors.value
|
|
183
|
+
return errors.value;
|
|
267
184
|
},
|
|
268
|
-
|
|
269
|
-
clear(name?: string) {
|
|
185
|
+
clear(name) {
|
|
270
186
|
if (name) {
|
|
271
|
-
errors.value = errors.value.filter(err => err.name !== name)
|
|
187
|
+
errors.value = errors.value.filter((err) => err.name !== name);
|
|
272
188
|
} else {
|
|
273
|
-
errors.value = []
|
|
189
|
+
errors.value = [];
|
|
274
190
|
}
|
|
275
191
|
},
|
|
276
|
-
|
|
277
192
|
disabled,
|
|
193
|
+
loading,
|
|
278
194
|
dirty: computed(() => !!dirtyFields.size),
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
})
|
|
195
|
+
dirtyFields: readonly(dirtyFields),
|
|
196
|
+
blurredFields: readonly(blurredFields),
|
|
197
|
+
touchedFields: readonly(touchedFields)
|
|
198
|
+
});
|
|
284
199
|
</script>
|
|
285
200
|
|
|
286
201
|
<template>
|
|
287
202
|
<component
|
|
288
203
|
:is="parentBus ? 'div' : 'form'"
|
|
289
204
|
:id="formId"
|
|
290
|
-
:class="
|
|
205
|
+
:class="ui({ class: props.class })"
|
|
291
206
|
@submit.prevent="onSubmitWrapper"
|
|
292
207
|
>
|
|
293
208
|
<slot :errors="errors" />
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, Form, InferInput, InferOutput } from '../types/form';
|
|
2
|
+
export interface FormProps<S extends FormSchema> {
|
|
3
|
+
id?: string | number;
|
|
4
|
+
/** Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. */
|
|
5
|
+
schema?: S;
|
|
6
|
+
/** An object representing the current state of the form. */
|
|
7
|
+
state: Partial<InferInput<S>>;
|
|
8
|
+
/**
|
|
9
|
+
* Custom validation function to validate the form state.
|
|
10
|
+
* @param state - The current state of the form.
|
|
11
|
+
* @returns A promise that resolves to an array of FormError objects, or an array of FormError objects directly.
|
|
12
|
+
*/
|
|
13
|
+
validate?: (state: Partial<InferInput<S>>) => Promise<FormError[]> | FormError[];
|
|
14
|
+
/**
|
|
15
|
+
* The list of input events that trigger the form validation.
|
|
16
|
+
* @defaultValue `['blur', 'change', 'input']`
|
|
17
|
+
*/
|
|
18
|
+
validateOn?: FormInputEvents[];
|
|
19
|
+
/** Disable all inputs inside the form. */
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Delay in milliseconds before validating the form on input events.
|
|
23
|
+
* @defaultValue `300`
|
|
24
|
+
*/
|
|
25
|
+
validateOnInputDelay?: number;
|
|
26
|
+
/**
|
|
27
|
+
* If true, schema transformations will be applied to the state on submit.
|
|
28
|
+
* @defaultValue `true`
|
|
29
|
+
*/
|
|
30
|
+
transform?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* If true, this form will attach to its parent Form (if any) and validate at the same time.
|
|
33
|
+
* @defaultValue `true`
|
|
34
|
+
*/
|
|
35
|
+
attach?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* When `true`, all form elements will be disabled on `@submit` event.
|
|
38
|
+
* This will cause any focused input elements to lose their focus state.
|
|
39
|
+
* @defaultValue `true`
|
|
40
|
+
*/
|
|
41
|
+
loadingAuto?: boolean;
|
|
42
|
+
class?: any;
|
|
43
|
+
onSubmit?: ((event: FormSubmitEvent<InferOutput<S>>) => void | Promise<void>) | (() => void | Promise<void>);
|
|
44
|
+
}
|
|
45
|
+
export interface FormEmits<S extends FormSchema> {
|
|
46
|
+
(e: 'submit', payload: FormSubmitEvent<InferOutput<S>>): void;
|
|
47
|
+
(e: 'error', payload: FormErrorEvent): void;
|
|
48
|
+
}
|
|
49
|
+
export interface FormSlots {
|
|
50
|
+
default(props?: {
|
|
51
|
+
errors: FormError[];
|
|
52
|
+
}): any;
|
|
53
|
+
}
|
|
54
|
+
declare const _default: <S extends FormSchema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
55
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
56
|
+
readonly onError?: ((payload: FormErrorEvent) => any) | undefined;
|
|
57
|
+
readonly onSubmit?: ((payload: FormSubmitEvent<InferOutput<S>>) => any) | undefined;
|
|
58
|
+
} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onError" | "onSubmit"> & FormProps<S> & Partial<{}>> & import("vue").PublicProps;
|
|
59
|
+
expose(exposed: import("vue").ShallowUnwrapRef<Form<InferInput<S>>>): void;
|
|
60
|
+
attrs: any;
|
|
61
|
+
slots: FormSlots;
|
|
62
|
+
emit: FormEmits<S>;
|
|
63
|
+
}>) => import("vue").VNode & {
|
|
64
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
65
|
+
};
|
|
66
|
+
export default _default;
|
|
67
|
+
type __VLS_PrettifyLocal<T> = {
|
|
68
|
+
[K in keyof T]: T[K];
|
|
69
|
+
} & {};
|
|
@@ -1,82 +1,40 @@
|
|
|
1
|
-
<script
|
|
2
|
-
import
|
|
3
|
-
import type { AppConfig } from '@nuxt/schema'
|
|
4
|
-
import _appConfig from '#build/app.config'
|
|
5
|
-
import theme from '#build/ui/form-field'
|
|
6
|
-
import { tv } from '../utils/tv'
|
|
7
|
-
|
|
8
|
-
const appConfigFormField = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
|
|
9
|
-
|
|
10
|
-
const formField = tv({ extend: tv(theme), ...(appConfigFormField.ui?.formField || {}) })
|
|
11
|
-
|
|
12
|
-
type FormFieldVariants = VariantProps<typeof formField>
|
|
13
|
-
|
|
14
|
-
export interface FormFieldProps {
|
|
15
|
-
/**
|
|
16
|
-
* The element or component this component should render as.
|
|
17
|
-
* @defaultValue 'div'
|
|
18
|
-
*/
|
|
19
|
-
as?: any
|
|
20
|
-
/** The name of the FormField. Also used to match form errors. */
|
|
21
|
-
name?: string
|
|
22
|
-
/** A regular expression to match form error names. */
|
|
23
|
-
errorPattern?: RegExp
|
|
24
|
-
label?: string
|
|
25
|
-
description?: string
|
|
26
|
-
help?: string
|
|
27
|
-
error?: string | boolean
|
|
28
|
-
hint?: string
|
|
29
|
-
/**
|
|
30
|
-
* @defaultValue 'md'
|
|
31
|
-
*/
|
|
32
|
-
size?: FormFieldVariants['size']
|
|
33
|
-
required?: boolean
|
|
34
|
-
/** If true, validation on input will be active immediately instead of waiting for a blur event. */
|
|
35
|
-
eagerValidation?: boolean
|
|
36
|
-
/**
|
|
37
|
-
* Delay in milliseconds before validating the form on input events.
|
|
38
|
-
* @defaultValue `300`
|
|
39
|
-
*/
|
|
40
|
-
validateOnInputDelay?: number
|
|
41
|
-
class?: any
|
|
42
|
-
ui?: Partial<typeof formField.slots>
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface FormFieldSlots {
|
|
46
|
-
label(props: { label?: string }): any
|
|
47
|
-
hint(props: { hint?: string }): any
|
|
48
|
-
description(props: { description?: string }): any
|
|
49
|
-
help(props: { help?: string }): any
|
|
50
|
-
error(props: { error?: string | boolean }): any
|
|
51
|
-
default(props: { error?: string | boolean }): any
|
|
52
|
-
}
|
|
1
|
+
<script>
|
|
2
|
+
import theme from "#build/ui/form-field";
|
|
53
3
|
</script>
|
|
54
4
|
|
|
55
|
-
<script setup
|
|
56
|
-
import { computed, ref, inject, provide,
|
|
57
|
-
import { Primitive, Label } from
|
|
58
|
-
import {
|
|
59
|
-
import
|
|
60
|
-
|
|
61
|
-
const props = defineProps
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
5
|
+
<script setup>
|
|
6
|
+
import { computed, ref, inject, provide, useId } from "vue";
|
|
7
|
+
import { Primitive, Label } from "reka-ui";
|
|
8
|
+
import { useAppConfig } from "#imports";
|
|
9
|
+
import { formFieldInjectionKey, inputIdInjectionKey } from "../composables/useFormField";
|
|
10
|
+
import { tv } from "../utils/tv";
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
as: { type: null, required: false },
|
|
13
|
+
name: { type: String, required: false },
|
|
14
|
+
errorPattern: { type: null, required: false },
|
|
15
|
+
label: { type: String, required: false },
|
|
16
|
+
description: { type: String, required: false },
|
|
17
|
+
help: { type: String, required: false },
|
|
18
|
+
error: { type: [String, Boolean], required: false },
|
|
19
|
+
hint: { type: String, required: false },
|
|
20
|
+
size: { type: null, required: false },
|
|
21
|
+
required: { type: Boolean, required: false },
|
|
22
|
+
eagerValidation: { type: Boolean, required: false },
|
|
23
|
+
validateOnInputDelay: { type: Number, required: false },
|
|
24
|
+
class: { type: null, required: false },
|
|
25
|
+
ui: { type: null, required: false }
|
|
26
|
+
});
|
|
27
|
+
const slots = defineSlots();
|
|
28
|
+
const appConfig = useAppConfig();
|
|
29
|
+
const ui = computed(() => tv({ extend: tv(theme), ...appConfig.ui?.formField || {} })({
|
|
65
30
|
size: props.size,
|
|
66
31
|
required: props.required
|
|
67
|
-
}))
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const id = ref(useId())
|
|
74
|
-
// Copies id's initial value to bind aria-attributes such as aria-describedby.
|
|
75
|
-
// This is required for the RadioGroup component which unsets the id value.
|
|
76
|
-
const ariaId = id.value
|
|
77
|
-
|
|
78
|
-
provide(inputIdInjectionKey, id)
|
|
79
|
-
|
|
32
|
+
}));
|
|
33
|
+
const formErrors = inject("form-errors", null);
|
|
34
|
+
const error = computed(() => props.error || formErrors?.value?.find((error2) => error2.name && (error2.name === props.name || props.errorPattern && error2.name.match(props.errorPattern)))?.message);
|
|
35
|
+
const id = ref(useId());
|
|
36
|
+
const ariaId = id.value;
|
|
37
|
+
provide(inputIdInjectionKey, id);
|
|
80
38
|
provide(formFieldInjectionKey, computed(() => ({
|
|
81
39
|
error: error.value,
|
|
82
40
|
name: props.name,
|
|
@@ -88,7 +46,7 @@ provide(formFieldInjectionKey, computed(() => ({
|
|
|
88
46
|
description: props.description,
|
|
89
47
|
help: props.help,
|
|
90
48
|
ariaId
|
|
91
|
-
})
|
|
49
|
+
})));
|
|
92
50
|
</script>
|
|
93
51
|
|
|
94
52
|
<template>
|
|
@@ -117,7 +75,7 @@ provide(formFieldInjectionKey, computed(() => ({
|
|
|
117
75
|
<div :class="[(label || !!slots.label || description || !!slots.description) && ui.container({ class: props.ui?.container })]">
|
|
118
76
|
<slot :error="error" />
|
|
119
77
|
|
|
120
|
-
<p v-if="
|
|
78
|
+
<p v-if="typeof error === 'string' && error || !!slots.error" :id="`${ariaId}-error`" :class="ui.error({ class: props.ui?.error })">
|
|
121
79
|
<slot name="error" :error="error">
|
|
122
80
|
{{ error }}
|
|
123
81
|
</slot>
|