@bitrix24/b24ui-nuxt 0.5.10 → 0.5.11
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/meta.cjs +72112 -0
- package/dist/meta.d.cts +72110 -0
- package/dist/meta.d.mts +5060 -5060
- package/dist/meta.d.ts +72110 -0
- package/dist/meta.mjs +5060 -5060
- package/dist/module.cjs +63 -0
- package/dist/module.d.cts +15 -0
- package/dist/module.d.ts +15 -0
- package/dist/module.json +3 -3
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/Advice.vue +54 -47
- package/dist/runtime/components/Alert.vue +96 -71
- package/dist/runtime/components/App.vue +34 -37
- package/dist/runtime/components/Avatar.vue +81 -69
- package/dist/runtime/components/AvatarGroup.vue +76 -53
- package/dist/runtime/components/Badge.vue +83 -83
- package/dist/runtime/components/Button.vue +157 -220
- package/dist/runtime/components/ButtonGroup.vue +51 -35
- package/dist/runtime/components/Calendar.vue +152 -186
- package/dist/runtime/components/Checkbox.vue +73 -84
- package/dist/runtime/components/Chip.vue +74 -59
- package/dist/runtime/components/Collapsible.vue +41 -44
- package/dist/runtime/components/Container.vue +27 -18
- package/dist/runtime/components/Countdown.vue +378 -198
- package/dist/runtime/components/DescriptionList.vue +149 -102
- package/dist/runtime/components/DropdownMenu.vue +139 -83
- package/dist/runtime/components/DropdownMenuContent.vue +84 -137
- package/dist/runtime/components/Form.vue +216 -162
- package/dist/runtime/components/FormField.vue +80 -76
- package/dist/runtime/components/Input.vue +179 -160
- package/dist/runtime/components/InputMenu.vue +380 -300
- package/dist/runtime/components/InputNumber.vue +175 -178
- package/dist/runtime/components/Kbd.vue +45 -33
- package/dist/runtime/components/Link.vue +173 -179
- package/dist/runtime/components/LinkBase.vue +42 -64
- package/dist/runtime/components/Modal.vue +127 -105
- package/dist/runtime/components/ModalDialogClose.vue +8 -4
- package/dist/runtime/components/Navbar.vue +33 -24
- package/dist/runtime/components/NavbarDivider.vue +33 -24
- package/dist/runtime/components/NavbarSection.vue +33 -24
- package/dist/runtime/components/NavbarSpacer.vue +33 -24
- package/dist/runtime/components/NavigationMenu.vue +210 -144
- package/dist/runtime/components/OverlayProvider.vue +17 -13
- package/dist/runtime/components/Popover.vue +81 -81
- package/dist/runtime/components/Progress.vue +136 -109
- package/dist/runtime/components/RadioGroup.vue +134 -120
- package/dist/runtime/components/Range.vue +85 -94
- package/dist/runtime/components/Select.vue +260 -212
- package/dist/runtime/components/SelectMenu.vue +365 -272
- package/dist/runtime/components/Separator.vue +71 -61
- package/dist/runtime/components/Sidebar.vue +33 -24
- package/dist/runtime/components/SidebarBody.vue +38 -30
- package/dist/runtime/components/SidebarFooter.vue +33 -24
- package/dist/runtime/components/SidebarHeader.vue +33 -24
- package/dist/runtime/components/SidebarHeading.vue +33 -24
- package/dist/runtime/components/SidebarLayout.vue +70 -40
- package/dist/runtime/components/SidebarSection.vue +33 -24
- package/dist/runtime/components/SidebarSpacer.vue +33 -24
- package/dist/runtime/components/Skeleton.vue +22 -17
- package/dist/runtime/components/Slideover.vue +131 -108
- package/dist/runtime/components/StackedLayout.vue +73 -40
- package/dist/runtime/components/Switch.vue +95 -100
- package/dist/runtime/components/Tabs.vue +107 -81
- package/dist/runtime/components/Textarea.vue +201 -177
- package/dist/runtime/components/Toast.vue +105 -94
- package/dist/runtime/components/Toaster.vue +116 -94
- package/dist/runtime/components/Tooltip.vue +64 -78
- package/dist/runtime/components/content/TableWrapper.vue +70 -58
- package/dist/runtime/composables/useAvatarGroup.d.ts +1 -1
- package/dist/runtime/composables/useButtonGroup.d.ts +2 -2
- package/dist/runtime/composables/useComponentIcons.d.ts +3 -3
- package/dist/runtime/composables/useFormField.d.ts +1 -1
- package/dist/runtime/prose/A.vue +23 -18
- package/dist/runtime/prose/Blockquote.vue +23 -18
- package/dist/runtime/prose/Code.vue +31 -23
- package/dist/runtime/prose/Em.vue +23 -18
- package/dist/runtime/prose/H1.vue +23 -18
- package/dist/runtime/prose/H2.vue +23 -18
- package/dist/runtime/prose/H3.vue +23 -18
- package/dist/runtime/prose/H4.vue +23 -18
- package/dist/runtime/prose/H5.vue +23 -18
- package/dist/runtime/prose/H6.vue +23 -18
- package/dist/runtime/prose/Hr.vue +19 -18
- package/dist/runtime/prose/Img.vue +23 -18
- package/dist/runtime/prose/Li.vue +23 -18
- package/dist/runtime/prose/Ol.vue +23 -18
- package/dist/runtime/prose/P.vue +23 -18
- package/dist/runtime/prose/Pre.vue +33 -28
- package/dist/runtime/prose/Strong.vue +23 -18
- package/dist/runtime/prose/Table.vue +54 -44
- package/dist/runtime/prose/Tbody.vue +23 -18
- package/dist/runtime/prose/Td.vue +23 -18
- package/dist/runtime/prose/Th.vue +23 -18
- package/dist/runtime/prose/Thead.vue +23 -18
- package/dist/runtime/prose/Tr.vue +23 -18
- package/dist/runtime/prose/Ul.vue +23 -18
- package/dist/runtime/vue/components/Link.vue +202 -201
- package/dist/shared/b24ui-nuxt.DrKwIWoc.cjs +7721 -0
- package/dist/types.d.mts +5 -3
- package/dist/types.d.ts +7 -0
- package/dist/unplugin.cjs +236 -0
- package/dist/unplugin.d.cts +33 -0
- package/dist/unplugin.d.ts +33 -0
- package/dist/vite.cjs +21 -0
- package/dist/vite.d.cts +14 -0
- package/dist/vite.d.ts +14 -0
- package/package.json +25 -13
- package/dist/runtime/components/Advice.vue.d.ts +0 -170
- package/dist/runtime/components/Alert.vue.d.ts +0 -464
- package/dist/runtime/components/App.vue.d.ts +0 -23
- package/dist/runtime/components/Avatar.vue.d.ts +0 -281
- package/dist/runtime/components/AvatarGroup.vue.d.ts +0 -204
- package/dist/runtime/components/Badge.vue.d.ts +0 -517
- package/dist/runtime/components/Button.vue.d.ts +0 -640
- package/dist/runtime/components/ButtonGroup.vue.d.ts +0 -116
- package/dist/runtime/components/Calendar.vue.d.ts +0 -437
- package/dist/runtime/components/Checkbox.vue.d.ts +0 -354
- package/dist/runtime/components/Chip.vue.d.ts +0 -271
- package/dist/runtime/components/Collapsible.vue.d.ts +0 -118
- package/dist/runtime/components/Container.vue.d.ts +0 -27
- package/dist/runtime/components/Countdown.vue.d.ts +0 -356
- package/dist/runtime/components/DescriptionList.vue.d.ts +0 -379
- package/dist/runtime/components/DropdownMenu.vue.d.ts +0 -533
- package/dist/runtime/components/DropdownMenuContent.vue.d.ts +0 -228
- package/dist/runtime/components/Form.vue.d.ts +0 -55
- package/dist/runtime/components/FormField.vue.d.ts +0 -282
- package/dist/runtime/components/Input.vue.d.ts +0 -755
- package/dist/runtime/components/InputMenu.vue.d.ts +0 -1504
- package/dist/runtime/components/InputNumber.vue.d.ts +0 -658
- package/dist/runtime/components/Kbd.vue.d.ts +0 -109
- package/dist/runtime/components/Link.vue.d.ts +0 -129
- package/dist/runtime/components/LinkBase.vue.d.ts +0 -48
- package/dist/runtime/components/Modal.vue.d.ts +0 -327
- package/dist/runtime/components/ModalDialogClose.vue.d.ts +0 -10
- package/dist/runtime/components/Navbar.vue.d.ts +0 -101
- package/dist/runtime/components/NavbarDivider.vue.d.ts +0 -101
- package/dist/runtime/components/NavbarSection.vue.d.ts +0 -101
- package/dist/runtime/components/NavbarSpacer.vue.d.ts +0 -101
- package/dist/runtime/components/NavigationMenu.vue.d.ts +0 -824
- package/dist/runtime/components/OverlayProvider.vue.d.ts +0 -2
- package/dist/runtime/components/Popover.vue.d.ts +0 -147
- package/dist/runtime/components/Progress.vue.d.ts +0 -592
- package/dist/runtime/components/RadioGroup.vue.d.ts +0 -723
- package/dist/runtime/components/Range.vue.d.ts +0 -417
- package/dist/runtime/components/Select.vue.d.ts +0 -1200
- package/dist/runtime/components/SelectMenu.vue.d.ts +0 -1298
- package/dist/runtime/components/Separator.vue.d.ts +0 -400
- package/dist/runtime/components/Sidebar.vue.d.ts +0 -101
- package/dist/runtime/components/SidebarBody.vue.d.ts +0 -90
- package/dist/runtime/components/SidebarFooter.vue.d.ts +0 -101
- package/dist/runtime/components/SidebarHeader.vue.d.ts +0 -101
- package/dist/runtime/components/SidebarHeading.vue.d.ts +0 -101
- package/dist/runtime/components/SidebarLayout.vue.d.ts +0 -222
- package/dist/runtime/components/SidebarSection.vue.d.ts +0 -101
- package/dist/runtime/components/SidebarSpacer.vue.d.ts +0 -101
- package/dist/runtime/components/Skeleton.vue.d.ts +0 -26
- package/dist/runtime/components/Slideover.vue.d.ts +0 -360
- package/dist/runtime/components/StackedLayout.vue.d.ts +0 -192
- package/dist/runtime/components/Switch.vue.d.ts +0 -587
- package/dist/runtime/components/Tabs.vue.d.ts +0 -453
- package/dist/runtime/components/Textarea.vue.d.ts +0 -601
- package/dist/runtime/components/Toast.vue.d.ts +0 -438
- package/dist/runtime/components/Toaster.vue.d.ts +0 -219
- package/dist/runtime/components/Tooltip.vue.d.ts +0 -186
- package/dist/runtime/components/content/TableWrapper.vue.d.ts +0 -237
- package/dist/runtime/prose/A.vue.d.ts +0 -84
- package/dist/runtime/prose/Blockquote.vue.d.ts +0 -84
- package/dist/runtime/prose/Code.vue.d.ts +0 -97
- package/dist/runtime/prose/Em.vue.d.ts +0 -84
- package/dist/runtime/prose/H1.vue.d.ts +0 -97
- package/dist/runtime/prose/H2.vue.d.ts +0 -123
- package/dist/runtime/prose/H3.vue.d.ts +0 -123
- package/dist/runtime/prose/H4.vue.d.ts +0 -123
- package/dist/runtime/prose/H5.vue.d.ts +0 -123
- package/dist/runtime/prose/H6.vue.d.ts +0 -123
- package/dist/runtime/prose/Hr.vue.d.ts +0 -74
- package/dist/runtime/prose/Img.vue.d.ts +0 -77
- package/dist/runtime/prose/Li.vue.d.ts +0 -84
- package/dist/runtime/prose/Ol.vue.d.ts +0 -84
- package/dist/runtime/prose/P.vue.d.ts +0 -84
- package/dist/runtime/prose/Pre.vue.d.ts +0 -117
- package/dist/runtime/prose/Strong.vue.d.ts +0 -84
- package/dist/runtime/prose/Table.vue.d.ts +0 -144
- package/dist/runtime/prose/Tbody.vue.d.ts +0 -84
- package/dist/runtime/prose/Td.vue.d.ts +0 -84
- package/dist/runtime/prose/Th.vue.d.ts +0 -84
- package/dist/runtime/prose/Thead.vue.d.ts +0 -84
- package/dist/runtime/prose/Tr.vue.d.ts +0 -84
- package/dist/runtime/prose/Ul.vue.d.ts +0 -84
- package/dist/runtime/vue/components/Link.vue.d.ts +0 -129
|
@@ -1,231 +1,285 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { AppConfig } from '@nuxt/schema'
|
|
3
|
+
import _appConfig from '#build/app.config'
|
|
4
|
+
import theme from '#build/b24ui/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 & { b24ui: { form: Partial<typeof theme> } }
|
|
10
|
+
|
|
11
|
+
const form = tv({ extend: tv(theme), ...(appConfigForm.b24ui?.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
|
+
}
|
|
7
54
|
</script>
|
|
8
55
|
|
|
9
|
-
<script setup>
|
|
10
|
-
import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from
|
|
11
|
-
import { useEventBus } from
|
|
12
|
-
import { formOptionsInjectionKey, formInputsInjectionKey, formBusInjectionKey, formLoadingInjectionKey } from
|
|
13
|
-
import { validateSchema } from
|
|
14
|
-
import { FormValidationException } from
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
20
|
-
schema: {
|
|
21
|
-
type: null,
|
|
22
|
-
required: false
|
|
23
|
-
},
|
|
24
|
-
state: {
|
|
25
|
-
type: Object,
|
|
26
|
-
required: true
|
|
27
|
-
},
|
|
28
|
-
validate: {
|
|
29
|
-
type: Function,
|
|
30
|
-
required: false
|
|
31
|
-
},
|
|
32
|
-
validateOn: {
|
|
33
|
-
type: Array,
|
|
34
|
-
required: false,
|
|
35
|
-
default() {
|
|
36
|
-
return ["input", "blur", "change"];
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
disabled: {
|
|
40
|
-
type: Boolean,
|
|
41
|
-
required: false
|
|
42
|
-
},
|
|
43
|
-
validateOnInputDelay: {
|
|
44
|
-
type: Number,
|
|
45
|
-
required: false,
|
|
46
|
-
default: 300
|
|
47
|
-
},
|
|
48
|
-
transform: {
|
|
49
|
-
type: Boolean,
|
|
50
|
-
required: false,
|
|
51
|
-
default: true
|
|
52
|
-
},
|
|
53
|
-
class: {
|
|
54
|
-
type: null,
|
|
55
|
-
required: false
|
|
56
|
+
<script lang="ts" setup generic="T extends object">
|
|
57
|
+
import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from 'vue'
|
|
58
|
+
import { useEventBus } from '@vueuse/core'
|
|
59
|
+
import { formOptionsInjectionKey, formInputsInjectionKey, formBusInjectionKey, formLoadingInjectionKey } from '../composables/useFormField'
|
|
60
|
+
import { validateSchema } from '../utils/form'
|
|
61
|
+
import { FormValidationException } from '../types/form'
|
|
62
|
+
|
|
63
|
+
const props = withDefaults(defineProps<FormProps<T>>(), {
|
|
64
|
+
validateOn() {
|
|
65
|
+
return ['input', 'blur', 'change'] as FormInputEvents[]
|
|
56
66
|
},
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const bus = useEventBus(`form-${formId}`)
|
|
67
|
+
validateOnInputDelay: 300,
|
|
68
|
+
transform: true
|
|
69
|
+
})
|
|
70
|
+
const emits = defineEmits<FormEmits<T>>()
|
|
71
|
+
defineSlots<FormSlots>()
|
|
72
|
+
|
|
73
|
+
const formId = props.id ?? useId() as string
|
|
74
|
+
|
|
75
|
+
const bus = useEventBus<FormEvent<T>>(`form-${formId}`)
|
|
66
76
|
const parentBus = inject(
|
|
67
77
|
formBusInjectionKey,
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
undefined
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
provide(formBusInjectionKey, bus)
|
|
82
|
+
|
|
83
|
+
const nestedForms = ref<Map<string | number, { validate: typeof _validate }>>(new Map())
|
|
84
|
+
|
|
72
85
|
onMounted(async () => {
|
|
73
86
|
bus.on(async (event) => {
|
|
74
|
-
if (event.type ===
|
|
75
|
-
nestedForms.value.set(event.formId, { validate: event.validate })
|
|
76
|
-
} else if (event.type ===
|
|
77
|
-
nestedForms.value.delete(event.formId)
|
|
87
|
+
if (event.type === 'attach') {
|
|
88
|
+
nestedForms.value.set(event.formId, { validate: event.validate })
|
|
89
|
+
} else if (event.type === 'detach') {
|
|
90
|
+
nestedForms.value.delete(event.formId)
|
|
78
91
|
} else if (props.validateOn?.includes(event.type) && !loading.value) {
|
|
79
|
-
if (event.type !==
|
|
80
|
-
await _validate({ name: event.name, silent: true, nested: false })
|
|
92
|
+
if (event.type !== 'input') {
|
|
93
|
+
await _validate({ name: event.name, silent: true, nested: false })
|
|
81
94
|
} else if (event.eager || blurredFields.has(event.name)) {
|
|
82
|
-
await _validate({ name: event.name, silent: true, nested: false })
|
|
95
|
+
await _validate({ name: event.name, silent: true, nested: false })
|
|
83
96
|
}
|
|
84
97
|
}
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
|
|
99
|
+
if (event.type === 'blur') {
|
|
100
|
+
blurredFields.add(event.name)
|
|
87
101
|
}
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
|
|
103
|
+
if (event.type === 'change' || event.type === 'input' || event.type === 'blur' || event.type === 'focus') {
|
|
104
|
+
touchedFields.add(event.name)
|
|
90
105
|
}
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
|
|
107
|
+
if (event.type === 'change' || event.type === 'input') {
|
|
108
|
+
dirtyFields.add(event.name)
|
|
93
109
|
}
|
|
94
|
-
})
|
|
95
|
-
})
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
96
113
|
onUnmounted(() => {
|
|
97
|
-
bus.reset()
|
|
98
|
-
})
|
|
114
|
+
bus.reset()
|
|
115
|
+
})
|
|
116
|
+
|
|
99
117
|
onMounted(async () => {
|
|
100
118
|
if (parentBus) {
|
|
101
|
-
await nextTick()
|
|
102
|
-
parentBus.emit({ type:
|
|
119
|
+
await nextTick()
|
|
120
|
+
parentBus.emit({ type: 'attach', validate: _validate, formId })
|
|
103
121
|
}
|
|
104
|
-
})
|
|
122
|
+
})
|
|
123
|
+
|
|
105
124
|
onUnmounted(() => {
|
|
106
125
|
if (parentBus) {
|
|
107
|
-
parentBus.emit({ type:
|
|
126
|
+
parentBus.emit({ type: 'detach', formId })
|
|
108
127
|
}
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const errors = ref<FormErrorWithId[]>([])
|
|
131
|
+
provide('form-errors', errors)
|
|
132
|
+
|
|
133
|
+
const inputs = ref<{ [P in keyof T]?: { id?: string, pattern?: RegExp } }>({})
|
|
134
|
+
provide(formInputsInjectionKey, inputs as any)
|
|
135
|
+
|
|
136
|
+
const dirtyFields = new Set<keyof T>()
|
|
137
|
+
const touchedFields = new Set<keyof T>()
|
|
138
|
+
const blurredFields = new Set<keyof T>()
|
|
139
|
+
|
|
140
|
+
function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
|
|
141
|
+
return errs.map(err => ({
|
|
119
142
|
...err,
|
|
120
|
-
id: err?.name ? inputs.value[err.name]?.id :
|
|
121
|
-
}))
|
|
143
|
+
id: err?.name ? inputs.value[err.name]?.id : undefined
|
|
144
|
+
}))
|
|
122
145
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
146
|
+
|
|
147
|
+
const transformedState = ref<T | null>(null)
|
|
148
|
+
|
|
149
|
+
async function getErrors(): Promise<FormErrorWithId[]> {
|
|
150
|
+
let errs = props.validate ? (await props.validate(props.state)) ?? [] : []
|
|
151
|
+
|
|
126
152
|
if (props.schema) {
|
|
127
|
-
const { errors
|
|
128
|
-
if (
|
|
129
|
-
errs = errs.concat(
|
|
153
|
+
const { errors, result } = await validateSchema(props.state, props.schema as FormSchema<typeof props.state>)
|
|
154
|
+
if (errors) {
|
|
155
|
+
errs = errs.concat(errors)
|
|
130
156
|
} else {
|
|
131
|
-
transformedState.value = result
|
|
157
|
+
transformedState.value = result
|
|
132
158
|
}
|
|
133
159
|
}
|
|
134
|
-
|
|
160
|
+
|
|
161
|
+
return resolveErrorIds(errs)
|
|
135
162
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
163
|
+
|
|
164
|
+
async function _validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<T | false> {
|
|
165
|
+
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof T)[]
|
|
166
|
+
|
|
167
|
+
const nestedValidatePromises = !names && opts.nested
|
|
168
|
+
? Array.from(nestedForms.value.values()).map(
|
|
169
|
+
({ validate }) => validate(opts).then(() => undefined).catch((error: Error) => {
|
|
170
|
+
if (!(error instanceof FormValidationException)) {
|
|
171
|
+
throw error
|
|
172
|
+
}
|
|
173
|
+
return error
|
|
174
|
+
})
|
|
175
|
+
)
|
|
176
|
+
: []
|
|
177
|
+
|
|
146
178
|
if (names) {
|
|
147
|
-
const otherErrors = errors.value.filter(
|
|
148
|
-
const pattern = inputs.value?.[name]?.pattern
|
|
149
|
-
return name === error.name || pattern && error.name?.match(pattern)
|
|
150
|
-
}))
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
179
|
+
const otherErrors = errors.value.filter(error => !names.some((name) => {
|
|
180
|
+
const pattern = inputs.value?.[name]?.pattern
|
|
181
|
+
return name === error.name || (pattern && error.name?.match(pattern))
|
|
182
|
+
}))
|
|
183
|
+
|
|
184
|
+
const pathErrors = (await getErrors()).filter(error => names.some((name) => {
|
|
185
|
+
const pattern = inputs.value?.[name]?.pattern
|
|
186
|
+
return name === error.name || (pattern && error.name?.match(pattern))
|
|
187
|
+
}))
|
|
188
|
+
|
|
189
|
+
errors.value = otherErrors.concat(pathErrors)
|
|
156
190
|
} else {
|
|
157
|
-
errors.value = await getErrors()
|
|
191
|
+
errors.value = await getErrors()
|
|
158
192
|
}
|
|
159
|
-
|
|
193
|
+
|
|
194
|
+
const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val !== undefined)
|
|
195
|
+
|
|
160
196
|
if (errors.value.length + childErrors.length > 0) {
|
|
161
|
-
if (opts.silent) return false
|
|
162
|
-
throw new FormValidationException(formId, errors.value, childErrors)
|
|
197
|
+
if (opts.silent) return false
|
|
198
|
+
throw new FormValidationException(formId, errors.value, childErrors)
|
|
163
199
|
}
|
|
200
|
+
|
|
164
201
|
if (opts.transform) {
|
|
165
|
-
Object.assign(props.state, transformedState.value)
|
|
202
|
+
Object.assign(props.state, transformedState.value)
|
|
166
203
|
}
|
|
167
|
-
|
|
204
|
+
|
|
205
|
+
return props.state as T
|
|
168
206
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
207
|
+
|
|
208
|
+
const loading = ref(false)
|
|
209
|
+
provide(formLoadingInjectionKey, readonly(loading))
|
|
210
|
+
|
|
211
|
+
async function onSubmitWrapper(payload: Event) {
|
|
212
|
+
loading.value = true
|
|
213
|
+
|
|
214
|
+
const event = payload as FormSubmitEvent<any>
|
|
215
|
+
|
|
174
216
|
try {
|
|
175
|
-
event.data = await _validate({ nested: true, transform: props.transform })
|
|
176
|
-
await props.onSubmit?.(event)
|
|
177
|
-
dirtyFields.clear()
|
|
217
|
+
event.data = await _validate({ nested: true, transform: props.transform })
|
|
218
|
+
await props.onSubmit?.(event)
|
|
219
|
+
dirtyFields.clear()
|
|
178
220
|
} catch (error) {
|
|
179
221
|
if (!(error instanceof FormValidationException)) {
|
|
180
|
-
throw error
|
|
222
|
+
throw error
|
|
181
223
|
}
|
|
182
|
-
|
|
224
|
+
|
|
225
|
+
const errorEvent: FormErrorEvent = {
|
|
183
226
|
...event,
|
|
184
227
|
errors: error.errors,
|
|
185
228
|
children: error.children
|
|
186
|
-
}
|
|
187
|
-
emits(
|
|
229
|
+
}
|
|
230
|
+
emits('error', errorEvent)
|
|
188
231
|
} finally {
|
|
189
|
-
loading.value = false
|
|
232
|
+
loading.value = false
|
|
190
233
|
}
|
|
191
234
|
}
|
|
192
|
-
|
|
235
|
+
|
|
236
|
+
const disabled = computed(() => props.disabled || loading.value)
|
|
237
|
+
|
|
193
238
|
provide(formOptionsInjectionKey, computed(() => ({
|
|
194
239
|
disabled: disabled.value,
|
|
195
240
|
validateOnInputDelay: props.validateOnInputDelay
|
|
196
|
-
})))
|
|
197
|
-
|
|
241
|
+
})))
|
|
242
|
+
|
|
243
|
+
defineExpose<Form<T>>({
|
|
198
244
|
validate: _validate,
|
|
199
245
|
errors,
|
|
200
|
-
|
|
246
|
+
|
|
247
|
+
setErrors(errs: FormError[], name?: keyof T) {
|
|
201
248
|
if (name) {
|
|
202
|
-
errors.value = errors.value
|
|
249
|
+
errors.value = errors.value
|
|
250
|
+
.filter(error => error.name !== name)
|
|
251
|
+
.concat(resolveErrorIds(errs))
|
|
203
252
|
} else {
|
|
204
|
-
errors.value = resolveErrorIds(errs)
|
|
253
|
+
errors.value = resolveErrorIds(errs)
|
|
205
254
|
}
|
|
206
255
|
},
|
|
256
|
+
|
|
207
257
|
async submit() {
|
|
208
|
-
await onSubmitWrapper(new Event(
|
|
258
|
+
await onSubmitWrapper(new Event('submit'))
|
|
209
259
|
},
|
|
210
|
-
|
|
260
|
+
|
|
261
|
+
getErrors(name?: keyof T) {
|
|
211
262
|
if (name) {
|
|
212
|
-
return errors.value.filter(
|
|
263
|
+
return errors.value.filter(err => err.name === name)
|
|
213
264
|
}
|
|
214
|
-
return errors.value
|
|
265
|
+
return errors.value
|
|
215
266
|
},
|
|
216
|
-
|
|
267
|
+
|
|
268
|
+
clear(name?: string) {
|
|
217
269
|
if (name) {
|
|
218
|
-
errors.value = errors.value.filter(
|
|
270
|
+
errors.value = errors.value.filter(err => err.name !== name)
|
|
219
271
|
} else {
|
|
220
|
-
errors.value = []
|
|
272
|
+
errors.value = []
|
|
221
273
|
}
|
|
222
274
|
},
|
|
275
|
+
|
|
223
276
|
disabled,
|
|
224
277
|
dirty: computed(() => !!dirtyFields.size),
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
278
|
+
|
|
279
|
+
dirtyFields: readonly(dirtyFields) as DeepReadonly<Set<keyof T>>,
|
|
280
|
+
blurredFields: readonly(blurredFields) as DeepReadonly<Set<keyof T>>,
|
|
281
|
+
touchedFields: readonly(touchedFields) as DeepReadonly<Set<keyof T>>
|
|
282
|
+
})
|
|
229
283
|
</script>
|
|
230
284
|
|
|
231
285
|
<template>
|
|
@@ -1,83 +1,87 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { VariantProps } from 'tailwind-variants'
|
|
3
|
+
import type { AppConfig } from '@nuxt/schema'
|
|
4
|
+
import _appConfig from '#build/app.config'
|
|
5
|
+
import theme from '#build/b24ui/form-field'
|
|
6
|
+
import { tv } from '../utils/tv'
|
|
7
|
+
|
|
8
|
+
const appConfigFormField = _appConfig as AppConfig & { b24ui: { formField: Partial<typeof theme> } }
|
|
9
|
+
|
|
10
|
+
const formField = tv({ extend: tv(theme), ...(appConfigFormField.b24ui?.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
|
+
/**
|
|
34
|
+
* @defaultValue false
|
|
35
|
+
*/
|
|
36
|
+
required?: boolean
|
|
37
|
+
/** If true, validation on input will be active immediately instead of waiting for a blur event. */
|
|
38
|
+
eagerValidation?: boolean
|
|
39
|
+
/**
|
|
40
|
+
* Delay in milliseconds before validating the form on input events.
|
|
41
|
+
* @defaultValue `300`
|
|
42
|
+
*/
|
|
43
|
+
validateOnInputDelay?: number
|
|
44
|
+
class?: any
|
|
45
|
+
b24ui?: Partial<typeof formField.slots>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface FormFieldSlots {
|
|
49
|
+
label(props: { label?: string }): any
|
|
50
|
+
hint(props: { hint?: string }): any
|
|
51
|
+
description(props: { description?: string }): any
|
|
52
|
+
help(props: { help?: string }): any
|
|
53
|
+
error(props: { error?: string | boolean }): any
|
|
54
|
+
default(props: { error?: string | boolean }): any
|
|
55
|
+
}
|
|
7
56
|
</script>
|
|
8
57
|
|
|
9
|
-
<script setup>
|
|
10
|
-
import { computed, ref, inject, provide, useId } from
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
required: false
|
|
20
|
-
},
|
|
21
|
-
errorPattern: {
|
|
22
|
-
type: null,
|
|
23
|
-
required: false
|
|
24
|
-
},
|
|
25
|
-
label: {
|
|
26
|
-
type: String,
|
|
27
|
-
required: false
|
|
28
|
-
},
|
|
29
|
-
description: {
|
|
30
|
-
type: String,
|
|
31
|
-
required: false
|
|
32
|
-
},
|
|
33
|
-
help: {
|
|
34
|
-
type: String,
|
|
35
|
-
required: false
|
|
36
|
-
},
|
|
37
|
-
error: {
|
|
38
|
-
type: [String, Boolean],
|
|
39
|
-
required: false
|
|
40
|
-
},
|
|
41
|
-
hint: {
|
|
42
|
-
type: String,
|
|
43
|
-
required: false
|
|
44
|
-
},
|
|
45
|
-
size: {
|
|
46
|
-
type: null,
|
|
47
|
-
required: false
|
|
48
|
-
},
|
|
49
|
-
required: {
|
|
50
|
-
type: Boolean,
|
|
51
|
-
required: false
|
|
52
|
-
},
|
|
53
|
-
eagerValidation: {
|
|
54
|
-
type: Boolean,
|
|
55
|
-
required: false
|
|
56
|
-
},
|
|
57
|
-
validateOnInputDelay: {
|
|
58
|
-
type: Number,
|
|
59
|
-
required: false
|
|
60
|
-
},
|
|
61
|
-
class: {
|
|
62
|
-
type: null,
|
|
63
|
-
required: false
|
|
64
|
-
},
|
|
65
|
-
b24ui: {
|
|
66
|
-
type: Object,
|
|
67
|
-
required: false
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
const slots = defineSlots();
|
|
58
|
+
<script setup lang="ts">
|
|
59
|
+
import { computed, ref, inject, provide, type Ref, useId } from 'vue'
|
|
60
|
+
import { Primitive, Label } from 'reka-ui'
|
|
61
|
+
import { formFieldInjectionKey, inputIdInjectionKey } from '../composables/useFormField'
|
|
62
|
+
import type { FormError, FormFieldInjectedOptions } from '../types/form'
|
|
63
|
+
import WarningIcon from '@bitrix24/b24icons-vue/main/WarningIcon'
|
|
64
|
+
|
|
65
|
+
const props = defineProps<FormFieldProps>()
|
|
66
|
+
const slots = defineSlots<FormFieldSlots>()
|
|
67
|
+
|
|
71
68
|
const b24ui = computed(() => formField({
|
|
72
69
|
size: props.size,
|
|
73
70
|
required: props.required,
|
|
74
71
|
useDescription: Boolean(props.description) || !!slots.description
|
|
75
|
-
}))
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
|
|
75
|
+
|
|
76
|
+
const error = computed(() => props.error || formErrors?.value?.find(error => error.name && (error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern))))?.message)
|
|
77
|
+
|
|
78
|
+
const id = ref(useId())
|
|
79
|
+
// Copies id's initial value to bind aria-attributes such as aria-describedby.
|
|
80
|
+
// This is required for the RadioGroup component which unsets the id value.
|
|
81
|
+
const ariaId = id.value
|
|
82
|
+
|
|
83
|
+
provide(inputIdInjectionKey, id)
|
|
84
|
+
|
|
81
85
|
provide(formFieldInjectionKey, computed(() => ({
|
|
82
86
|
error: error.value,
|
|
83
87
|
name: props.name,
|
|
@@ -89,7 +93,7 @@ provide(formFieldInjectionKey, computed(() => ({
|
|
|
89
93
|
description: props.description,
|
|
90
94
|
help: props.help,
|
|
91
95
|
ariaId
|
|
92
|
-
})))
|
|
96
|
+
}) as FormFieldInjectedOptions<FormFieldProps>))
|
|
93
97
|
</script>
|
|
94
98
|
|
|
95
99
|
<template>
|
|
@@ -118,7 +122,7 @@ provide(formFieldInjectionKey, computed(() => ({
|
|
|
118
122
|
<div :class="[(label || !!slots.label || description || !!slots.description) && b24ui.container({ class: props.b24ui?.container })]">
|
|
119
123
|
<slot :error="error" />
|
|
120
124
|
|
|
121
|
-
<div v-if="typeof error === 'string' && error || !!slots.error" :id="`${ariaId}-error`" :class="b24ui.error({ class: props.b24ui?.error })">
|
|
125
|
+
<div v-if="(typeof error === 'string' && error) || !!slots.error" :id="`${ariaId}-error`" :class="b24ui.error({ class: props.b24ui?.error })">
|
|
122
126
|
<slot name="error" :error="error">
|
|
123
127
|
<div class="flex flex-row flex-nowrap gap-0.5">
|
|
124
128
|
<WarningIcon :class="b24ui.errorIcon()" />
|