PluginProbe
The Innovative Form Builder – IvyForms / 1.1.1
The Innovative Form Builder – IvyForms v1.1.1
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / views / public / fields / EmailFormField.vue

EmailFormField.vue in The Innovative Form Builder – IvyForms 1.1.1, at frontend/src/views/public/fields/EmailFormField.vue

263 lines 7.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div
3 :class="[
4 'ivyforms-field ivyforms-field__email',
5 'ivyforms-field__email_' + field.id,
6 field.cssClasses,
7 { 'is-readonly': !!field.readOnly },
8 ]"
9 >
10 <IvyFormItem
11 :label="field.hideLabel ? '' : field.label"
12 :required="field.required"
13 :error="error"
14 :prop="field.type + '_' + field.fieldIndex"
15 :show-info="!!field.description && !error"
16 :info-text="field.description || ''"
17 :show-info-icon="false"
18 :label-position="field.labelPosition"
19 >
20 <IvyTextInput
21 :id="fieldID"
22 v-model="localModelValue"
23 class="ivyforms-field__email-input"
24 type="email"
25 :aria-label="field.label"
26 :placeholder="field.placeholder"
27 :disabled="isDisabled"
28 :readonly="field.readOnly"
29 :required="field.required"
30 :maxlength="field.limitMaxLength ? field.maxLength : undefined"
31 @blur="syncMainFromDom"
32 />
33 </IvyFormItem>
34
35 <!-- Confirmation field: appears only when enabled -->
36 <IvyFormItem
37 v-if="field.confirmFieldEnabled"
38 :label="field.confirmFieldHideLabel ? '' : confirmLabel"
39 :required="!!localModelValue && localModelValue.toString().trim() !== ''"
40 :prop="confirmKey"
41 >
42 <IvyTextInput
43 :id="confirmFieldID"
44 v-model="confirmModelValue"
45 :aria-label="confirmLabel"
46 class="ivyforms-field__email-input ivyforms-field__email-input--confirm"
47 type="email"
48 :placeholder="confirmPlaceholder"
49 :disabled="isDisabled"
50 :readonly="field.readOnly"
51 :required="!!localModelValue && localModelValue.toString().trim() !== ''"
52 @blur="syncConfirmFromDom"
53 />
54 </IvyFormItem>
55 </div>
56 </template>
57
58 <script setup lang="ts">
59 import { computed, inject, watch, type Ref, onMounted } from 'vue'
60 import { useAutofillDetection, type AutofillField } from '@/composables/useAutofillDetection'
61 import { useLabels } from '@/composables/useLabels'
62
63 const { getLabel } = useLabels()
64
65 interface Field {
66 id: number
67 formId: number
68 fieldIndex: number
69 type: string
70 label: string
71 placeholder?: string
72 required?: boolean
73 hideLabel?: boolean
74 readOnly?: boolean
75 description?: string
76 cssClasses?: string
77 confirmFieldEnabled?: boolean
78 confirmFieldLabel?: string
79 confirmFieldPlaceholder?: string
80 confirmFieldHideLabel?: boolean
81 limitMaxLength?: boolean
82 maxLength?: number
83 labelPosition?: string
84 noDuplicates?: boolean
85 // Any other field properties from your data
86 }
87
88 interface FieldProps {
89 modelValue: string | number | null | undefined
90 field: Field
91 disabled?: boolean
92 error?: string
93 }
94
95 const props = withDefaults(defineProps<FieldProps>(), {
96 modelValue: '',
97 disabled: false,
98 error: '',
99 })
100
101 const emit = defineEmits(['update:modelValue'])
102
103 const isDisabled = computed(() => !!props.disabled || !!props.field.readOnly)
104
105 // Define acceptable form values shape
106 type FormValues = Record<string, string | number | null | undefined>
107 type InjectedFormValues = Ref<FormValues> | FormValues | null
108
109 // Inject global formValues (may be provided as a ref) and error clearer
110 const injectedFormValues = inject<InjectedFormValues>('formValues', null)
111 const formValues = computed(() => {
112 if (!injectedFormValues) return {} as FormValues
113 return (
114 typeof injectedFormValues === 'object' && 'value' in injectedFormValues
115 ? (injectedFormValues as Ref<FormValues>).value
116 : injectedFormValues
117 ) as FormValues
118 })
119 const clearFieldError = inject<(fieldKey: string) => void>('clearFieldError', () => {})
120
121 // Keys
122 const mainKey = computed(() => props.field.type + '_' + props.field.fieldIndex)
123 const confirmKey = computed(() => 'emailConfirm_' + props.field.fieldIndex)
124
125 // Handle main input
126 const handleInput = (value: string | number | null | undefined) => {
127 emit('update:modelValue', value)
128 clearFieldError(mainKey.value)
129 }
130
131 const localModelValue = computed({
132 get() {
133 return props.modelValue
134 },
135 set(value) {
136 handleInput(value)
137 },
138 })
139
140 // Confirmation input model (stored directly in injected formValues)
141 const confirmModelValue = computed<string | number | null | undefined>({
142 get() {
143 return formValues.value[confirmKey.value]
144 },
145 set(value) {
146 formValues.value[confirmKey.value] = value
147 clearFieldError(confirmKey.value)
148 },
149 })
150
151 // Labels / placeholders
152 const confirmLabel = computed(
153 () => props.field.confirmFieldLabel || getLabel('confirmation_label_placeholder'),
154 )
155 const confirmPlaceholder = computed(() => props.field.confirmFieldPlaceholder)
156
157 // IDs
158 const fieldID = computed(
159 () => `ivyforms-field__email-input_${props.field.formId || ''}_${props.field.fieldIndex || ''}`,
160 )
161 const confirmFieldID = computed(
162 () =>
163 `ivyforms-field__email-confirm-input_${props.field.formId || ''}_${props.field.fieldIndex || ''}`,
164 )
165 // Sync main input from DOM on blur (includes autofill)
166 function syncMainFromDom(e: FocusEvent) {
167 const el = e.target as HTMLInputElement | null
168 if (!el) return
169 if (el.value && el.value !== localModelValue.value) {
170 localModelValue.value = el.value
171 }
172 }
173
174 // Sync confirm input from DOM
175 function syncConfirmFromDom(e: FocusEvent) {
176 const el = e.target as HTMLInputElement | null
177 if (!el) return
178 if (el.value !== formValues.value[confirmKey.value]) {
179 formValues.value[confirmKey.value] = el.value
180 clearFieldError(confirmKey.value)
181 }
182 }
183 // Auto clear mismatch error when emails become equal
184 watch(
185 () => [formValues.value[confirmKey.value], localModelValue.value],
186 ([confirmVal, mainVal]) => {
187 if (confirmVal && mainVal && confirmVal === mainVal) {
188 clearFieldError(confirmKey.value)
189 }
190 },
191 )
192
193 // Setup autofill detection using the composable
194 const autofillFields = computed<AutofillField[]>(() => {
195 const fields: AutofillField[] = [
196 {
197 id: fieldID.value,
198 getValue: () => localModelValue.value?.toString() || '',
199 setValue: (value: string) => {
200 localModelValue.value = value
201 },
202 },
203 ]
204
205 // Add confirmation field if enabled and visible
206 if (props.field.confirmFieldEnabled && localModelValue.value) {
207 fields.push({
208 id: confirmFieldID.value,
209 getValue: () => formValues.value[confirmKey.value]?.toString() || '',
210 setValue: (value: string) => {
211 formValues.value[confirmKey.value] = value
212 },
213 onClearError: () => clearFieldError(confirmKey.value),
214 })
215 }
216
217 return fields
218 })
219
220 const { restartDetection, checkForInitialAutofill } = useAutofillDetection({
221 fields: autofillFields.value,
222 pollingDuration: 7000, // Increased for slow autofill
223 pollingInterval: 100,
224 initialCheckDelay: 100, // Faster initial check
225 })
226
227 // Manual check on mount and when fields become available
228 onMounted(() => {
229 // Initial check immediately after mount
230 setTimeout(checkForInitialAutofill, 50)
231 // Additional checks for delayed autofill
232 setTimeout(checkForInitialAutofill, 300)
233 setTimeout(checkForInitialAutofill, 1000)
234 })
235
236 // Re-setup detection when confirmation field becomes available
237 watch(
238 () => props.field.confirmFieldEnabled && !!localModelValue.value,
239 (showConfirm) => {
240 if (showConfirm) {
241 setTimeout(restartDetection, 100)
242 }
243 },
244 )
245 </script>
246
247 <style scoped lang="scss">
248 .ivyforms-field__email-input--confirm {
249 margin-top: 4px;
250 }
251
252 .ivyforms-field__email {
253 &.is-readonly {
254 :deep(.el-input__wrapper),
255 :deep(.el-input__wrapper:hover),
256 :deep(.el-input__wrapper.is-focus) {
257 border-color: var(--map-base-dusk-stroke--2) !important;
258 box-shadow: none !important;
259 }
260 }
261 }
262 </style>
263