@@ -11,6 +11,7 @@ export type FormActions = {
1111 update : ( field : string , value : string | boolean ) => FormActions
1212 setValidations : ( validationRules : Record < string , boolean > ) => FormActions
1313 isValidForm : ( ) => boolean ,
14+ useSmartFill : ( options ?: { patterns ?: Record < string , RegExp > , inputs ?: Record < string , string > } ) => FormActions
1415 onChange : ( callback : ( formValues : Record < string , string > ) => void ) => FormActions
1516 onSubmit : ( callback : ( formValues : Record < string , string > ) => void ) => FormActions
1617 onError : ( callback : ( invalidFields : string [ ] ) => void ) => FormActions
@@ -84,6 +85,113 @@ export const useForm = (selector: string): FormActions | null => {
8485 isValidForm ( ) {
8586 return Object . values ( this . validationRules ) . every ( key => key )
8687 } ,
88+ useSmartFill ( { patterns, inputs } = { } ) {
89+ const textPatterns = {
90+ email : / ^ [ \w - . ] + @ ( [ \w - ] + \. ) + [ \w - ] { 2 , 4 } $ / gm,
91+ phone : / \b (?: \+ ? \d { 1 , 3 } [ \s - ] ? ) ? (?: \( ? \d { 2 , 4 } \) ? [ \s - ] ? ) ? \d { 3 , 4 } [ \s - ] ? \d { 3 , 4 } \b / gm,
92+ postal : / ^ [ 0 - 9 ] { 4 , 5 } (?: - [ 0 - 9 ] { 4 } ) ? $ / gm,
93+ date : / \b \d { 1 , 4 } [ - / . ] \d { 1 , 2 } [ - / . ] \d { 1 , 4 } \b / gm,
94+ url : / \b h t t p s ? : \/ \/ [ ^ \s / $ . ? # ] .[ ^ \s ] * \b / gm,
95+ ...( patterns || { } )
96+ }
97+
98+ const fieldHints : Record < string , string [ ] > = {
99+ email : [ 'mail' , 'email' , 'eaddress' ] ,
100+ phone : [ 'phone' , 'mobile' , 'tel' , 'cell' ] ,
101+ postal : [ 'zip' , 'postal' , 'postcode' ] ,
102+ date : [ 'date' , 'dob' , 'birth' , 'day' ] ,
103+ url : [ 'url' , 'website' , 'link' ]
104+ }
105+
106+ const typeMap = {
107+ email : 'email' ,
108+ url : 'url' ,
109+ date : 'date' ,
110+ tel : 'phone'
111+ } as const
112+
113+ const parseText = ( text : string ) => {
114+ const extracted : Record < string , string > = { }
115+
116+ for ( const [ key , regex ] of Object . entries ( textPatterns ) ) {
117+ const match = text . trim ( ) . match ( regex )
118+
119+ if ( match ) {
120+ extracted [ key ] = match [ 0 ] . trim ( )
121+ }
122+ }
123+
124+ return extracted
125+ }
126+
127+ const inferFieldType = ( name : string , type : string ) => {
128+ const lowered = name . toLowerCase ( ) . replace ( / [ _ \- \d ] / g, '' )
129+
130+ for ( const [ key , mappedName ] of Object . entries ( inputs || { } ) ) {
131+ if ( mappedName . toLowerCase ( ) === name . toLowerCase ( ) ) {
132+ return key
133+ }
134+ }
135+
136+ if ( type in typeMap ) {
137+ return typeMap [ type as keyof typeof typeMap ]
138+ }
139+
140+ for ( const [ key , hints ] of Object . entries ( fieldHints ) ) {
141+ if ( hints . some ( hint => lowered . includes ( hint ) ) ) {
142+ return key
143+ }
144+ }
145+
146+ return null
147+ }
148+
149+ form . addEventListener ( 'paste' , ( event : ClipboardEvent ) => {
150+ const target = event . target
151+
152+ if ( ! ( target instanceof HTMLInputElement ) ) {
153+ return
154+ }
155+
156+ const pastedText = event . clipboardData ?. getData ( 'text' ) || ''
157+
158+ if ( pastedText ?. length < 5 ) {
159+ return
160+ }
161+
162+ const extracted = parseText ( pastedText )
163+
164+ if ( ! Object . keys ( extracted ) . length ) {
165+ return
166+ }
167+
168+ let autoFilled = false
169+
170+ Array . from ( form . elements ) . forEach ( element => {
171+ if ( element instanceof HTMLInputElement
172+ || element instanceof HTMLSelectElement
173+ || element instanceof HTMLTextAreaElement
174+ ) {
175+ const inferred = inferFieldType ( element . name , element . type )
176+
177+ if ( inferred && extracted [ inferred ] ) {
178+ const inputEvent = new Event ( 'input' , { bubbles : true } )
179+
180+ element . value = extracted [ inferred ]
181+ element . dispatchEvent ( inputEvent )
182+
183+ autoFilled = true
184+ }
185+ }
186+ } )
187+
188+ if ( autoFilled ) {
189+ event . preventDefault ( )
190+ }
191+ } )
192+
193+ return this
194+ } ,
87195 onChange ( callback ) {
88196 form . addEventListener ( 'input' , ( ) => {
89197 callback ?.( this . getInputValues ( ) )
0 commit comments