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

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