PluginProbe
The Innovative Form Builder – IvyForms / 0.7
The Innovative Form Builder – IvyForms v0.7
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 0.7, at frontend/src/views/public/fields/EmailFormField.vue

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