PluginProbe
The Innovative Form Builder – IvyForms / 0.9.1
The Innovative Form Builder – IvyForms v0.9.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 / composables / useAutofillDetection.ts

useAutofillDetection.ts in The Innovative Form Builder – IvyForms 0.9.1, at frontend/src/composables/useAutofillDetection.ts

203 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { onMounted, onUnmounted, nextTick } from 'vue'
2
3 export interface AutofillField {
4 id: string
5 getValue: () => string
6 setValue: (value: string) => void
7 onClearError?: () => void
8 }
9
10 export interface AutofillDetectionOptions {
11 fields: AutofillField[]
12 pollingDuration?: number
13 pollingInterval?: number
14 enableMutationObserver?: boolean
15 enableVisibilityChange?: boolean
16 initialCheckDelay?: number
17 }
18
19 export function useAutofillDetection(options: AutofillDetectionOptions) {
20 const {
21 fields,
22 pollingDuration = 5000,
23 pollingInterval = 100,
24 enableMutationObserver = true,
25 enableVisibilityChange = true,
26 initialCheckDelay = 200,
27 } = options
28
29 let autofillCleanup: (() => void) | null = null
30
31 const checkForInitialAutofill = () => {
32 // Check immediately for any pre-filled values on page load
33 fields.forEach((field) => {
34 const input = document.getElementById(field.id) as HTMLInputElement
35 if (input && input.value && input.value !== field.getValue()) {
36 field.setValue(input.value)
37 field.onClearError?.()
38 }
39 })
40 }
41
42 const setupAutofillDetection = () => {
43 // Clean up previous detection if exists
44 if (autofillCleanup) {
45 autofillCleanup()
46 autofillCleanup = null
47 }
48
49 const cleanupFunctions: (() => void)[] = []
50
51 // Initial check for pre-filled values (important for page load autofill)
52 setTimeout(checkForInitialAutofill, initialCheckDelay)
53
54 // Additional checks at different intervals for slow autofill
55 setTimeout(checkForInitialAutofill, 500)
56 setTimeout(checkForInitialAutofill, 1000)
57 setTimeout(checkForInitialAutofill, 2000)
58
59 // Strategy 1: MutationObserver for DOM changes
60 let observer: MutationObserver | null = null
61 if (enableMutationObserver) {
62 observer = new MutationObserver((mutations) => {
63 mutations.forEach((mutation) => {
64 if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
65 const input = mutation.target as HTMLInputElement
66 const field = fields.find((f) => f.id === input.id)
67 if (field && input.value !== field.getValue()) {
68 field.setValue(input.value)
69 field.onClearError?.()
70 }
71 }
72 })
73 })
74 }
75
76 // Strategy 2: Direct DOM polling (enhanced)
77 const checkForAutofillChanges = () => {
78 fields.forEach((field) => {
79 const input = document.getElementById(field.id) as HTMLInputElement
80 if (input && input.value !== field.getValue()) {
81 field.setValue(input.value)
82 field.onClearError?.()
83 }
84 })
85 }
86
87 const intervalId = setInterval(checkForAutofillChanges, pollingInterval)
88 const timeoutId = setTimeout(() => clearInterval(intervalId), pollingDuration)
89
90 // Strategy 3: Multiple event listeners
91 fields.forEach((field) => {
92 const input = document.getElementById(field.id) as HTMLInputElement
93 if (input) {
94 // Observe this input for changes
95 if (observer) {
96 observer.observe(input, {
97 attributes: true,
98 attributeFilter: ['value'],
99 subtree: false,
100 })
101 }
102
103 // Add event listeners for various autofill scenarios
104 const events = [
105 'input',
106 'change',
107 'blur',
108 'focus',
109 'animationstart',
110 'animationend',
111 'transitionstart',
112 'transitionend',
113 'DOMAutoComplete',
114 'autocompleteerror',
115 ]
116
117 const handleInputChange = () => {
118 if (input.value !== field.getValue()) {
119 field.setValue(input.value)
120 field.onClearError?.()
121 }
122 }
123
124 events.forEach((eventType) => {
125 input.addEventListener(eventType, handleInputChange, { passive: true })
126 })
127
128 // Cleanup function for this input
129 cleanupFunctions.push(() => {
130 events.forEach((eventType) => {
131 input.removeEventListener(eventType, handleInputChange)
132 })
133 })
134 }
135 })
136
137 // Strategy 4: Page visibility change detection
138 let handleVisibilityChange: (() => void) | null = null
139 if (enableVisibilityChange) {
140 handleVisibilityChange = () => {
141 if (!document.hidden) {
142 setTimeout(checkForAutofillChanges, 100)
143 }
144 }
145 document.addEventListener('visibilitychange', handleVisibilityChange)
146 }
147
148 // Strategy 5: Focus/blur on window (for browser autofill detection)
149 const handleWindowFocus = () => {
150 setTimeout(checkForAutofillChanges, 100)
151 }
152
153 window.addEventListener('focus', handleWindowFocus, { passive: true })
154 cleanupFunctions.push(() => {
155 window.removeEventListener('focus', handleWindowFocus)
156 })
157
158 // Combine all cleanup functions
159 autofillCleanup = () => {
160 clearInterval(intervalId)
161 clearTimeout(timeoutId)
162 observer?.disconnect()
163 if (handleVisibilityChange) {
164 document.removeEventListener('visibilitychange', handleVisibilityChange)
165 }
166 cleanupFunctions.forEach((cleanup) => cleanup())
167 }
168 }
169
170 const restartDetection = () => {
171 // Wait for DOM to be ready
172 nextTick(() => {
173 setupAutofillDetection()
174 })
175 }
176
177 // Setup autofill detection on mount
178 onMounted(() => {
179 // Multiple setup attempts to catch different timing scenarios
180 setTimeout(setupAutofillDetection, 50)
181 setTimeout(restartDetection, 200)
182 setTimeout(restartDetection, 500)
183 })
184
185 // Cleanup on unmount
186 onUnmounted(() => {
187 if (autofillCleanup) {
188 autofillCleanup()
189 }
190 })
191
192 return {
193 setupAutofillDetection,
194 restartDetection,
195 checkForInitialAutofill,
196 cleanup: () => {
197 if (autofillCleanup) {
198 autofillCleanup()
199 }
200 },
201 }
202 }
203