@@ -8,23 +8,27 @@ type Constructor<T = {}> = new (...args: any[]) => T;
8
8
type NativeFormControlElement = HTMLInputElement ; // Eventually use a specific interface or list multiple options like appending these types: ... | HTMLTextAreaElement | HTMLSelectElement
9
9
10
10
// TODO: make it possible to define FormDataEntryValue type.
11
- export declare abstract class FormControlMixinInterface extends LitElement {
11
+ // TODO: Prefix with UUI
12
+ export declare abstract class UUIFormControlMixinInterface <
13
+ ValueType ,
14
+ DefaultValueType ,
15
+ > extends LitElement {
12
16
formAssociated : boolean ;
13
- get value ( ) : FormDataEntryValue | FormData ;
14
- set value ( newValue : FormDataEntryValue | FormData ) ;
17
+ protected _internals : ElementInternals ;
18
+ protected _runValidators ( ) : void ;
19
+ get value ( ) : ValueType | DefaultValueType ;
20
+ set value ( newValue : ValueType | DefaultValueType ) ;
15
21
name : string ;
16
22
formResetCallback ( ) : void ;
17
23
checkValidity ( ) : boolean ;
18
24
get validationMessage ( ) : string ;
19
25
get validity ( ) : ValidityState ;
20
26
public setCustomValidity ( error : string ) : void ;
21
27
public submit ( ) : void ;
22
- protected _value : FormDataEntryValue | FormData ;
23
- protected _internals : any ;
24
28
protected abstract getFormElement ( ) : HTMLElement | undefined ;
25
29
protected addValidator : (
26
30
flagKey : FlagTypes ,
27
- getMessageMethod : ( ) => String ,
31
+ getMessageMethod : ( ) => string ,
28
32
checkMethod : ( ) => boolean ,
29
33
) => void ;
30
34
protected addFormControlElement ( element : NativeFormControlElement ) : void ;
@@ -55,7 +59,7 @@ type FlagTypes =
55
59
// Acceptable as an internal interface/type, BUT if exposed externally this should be turned into a public class in a separate file.
56
60
interface Validator {
57
61
flagKey : FlagTypes ;
58
- getMessageMethod : ( ) => String ;
62
+ getMessageMethod : ( ) => string ;
59
63
checkMethod : ( ) => boolean ;
60
64
}
61
65
@@ -65,10 +69,15 @@ interface Validator {
65
69
* @param {Object } superClass - superclass to be extended.
66
70
* @mixin
67
71
*/
68
- export const FormControlMixin = < T extends Constructor < LitElement > > (
72
+ export const UUIFormControlMixin = <
73
+ ValueType = FormDataEntryValue | FormData ,
74
+ T extends Constructor < LitElement > = typeof LitElement ,
75
+ DefaultValueType = unknown ,
76
+ > (
69
77
superClass : T ,
78
+ defaultValue : DefaultValueType ,
70
79
) => {
71
- abstract class FormControlMixinClass extends superClass {
80
+ abstract class UUIFormControlMixinClass extends superClass {
72
81
/**
73
82
* This is a static class field indicating that the element is can be used inside a native form and participate in its events.
74
83
* It may require a polyfill, check support here https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals.
@@ -88,23 +97,24 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
88
97
89
98
/**
90
99
* Value of this form control.
100
+ * If you dont want the setFormValue to be called on the ElementInternals, then prevent calling this method, by not calling super.value = newValue in your implementation of the value setter method.
91
101
* @type {string }
92
- * @attr
102
+ * @attr value
93
103
* @default ''
94
104
*/
95
105
@property ( ) // Do not 'reflect' as the attribute is used as fallback.
96
- get value ( ) {
97
- return this . _value ;
106
+ get value ( ) : ValueType | DefaultValueType {
107
+ return this . #value ;
98
108
}
99
- set value ( newValue ) {
100
- const oldValue = this . _value ;
101
- this . _value = newValue ;
109
+ set value ( newValue : ValueType | DefaultValueType ) {
110
+ const oldValue = this . #value ;
111
+ this . #value = newValue ;
102
112
if (
103
113
'ElementInternals' in window &&
104
114
//@ts -ignore
105
115
'setFormValue' in window . ElementInternals . prototype
106
116
) {
107
- this . _internals . setFormValue ( this . _value ) ;
117
+ this . _internals . setFormValue ( ( this . #value as any ) ?? null ) ;
108
118
}
109
119
this . requestUpdate ( 'value' , oldValue ) ;
110
120
}
@@ -150,15 +160,15 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
150
160
@property ( { type : String , attribute : 'error-message' } )
151
161
errorMessage = 'This field is invalid' ;
152
162
153
- private _value : FormDataEntryValue | FormData = '' ;
154
- private _internals : any ;
155
- private _form : HTMLFormElement | null = null ;
156
- private _validators : Validator [ ] = [ ] ;
157
- private _formCtrlElements : NativeFormControlElement [ ] = [ ] ;
163
+ #value: ValueType | DefaultValueType = defaultValue ;
164
+ _internals : ElementInternals ;
165
+ #form : HTMLFormElement | null = null ;
166
+ #validators : Validator [ ] = [ ] ;
167
+ #formCtrlElements : NativeFormControlElement [ ] = [ ] ;
158
168
159
169
constructor ( ...args : any [ ] ) {
160
170
super ( ...args ) ;
161
- this . _internals = ( this as any ) . attachInternals ( ) ;
171
+ this . _internals = this . attachInternals ( ) ;
162
172
163
173
this . addValidator (
164
174
'valueMissing' ,
@@ -177,12 +187,12 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
177
187
}
178
188
179
189
/**
180
- * Determn wether this FormControl has a value.
190
+ * Determine wether this FormControl has a value.
181
191
* @method hasValue
182
192
* @returns {boolean }
183
193
*/
184
194
public hasValue ( ) : boolean {
185
- return this . value !== '' ;
195
+ return this . value !== this . getDefaultValue ( ) ;
186
196
}
187
197
188
198
/**
@@ -196,11 +206,11 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
196
206
197
207
disconnectedCallback ( ) : void {
198
208
super . disconnectedCallback ( ) ;
199
- this . _removeFormListeners ( ) ;
209
+ this . #removeFormListeners ( ) ;
200
210
}
201
- private _removeFormListeners ( ) {
202
- if ( this . _form ) {
203
- this . _form . removeEventListener ( 'submit' , this . _onFormSubmit ) ;
211
+ #removeFormListeners ( ) {
212
+ if ( this . #form ) {
213
+ this . #form . removeEventListener ( 'submit' , this . #onFormSubmit ) ;
204
214
}
205
215
}
206
216
@@ -221,22 +231,22 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
221
231
*/
222
232
protected addValidator (
223
233
flagKey : FlagTypes ,
224
- getMessageMethod : ( ) => String ,
234
+ getMessageMethod : ( ) => string ,
225
235
checkMethod : ( ) => boolean ,
226
236
) : Validator {
227
237
const obj = {
228
238
flagKey : flagKey ,
229
239
getMessageMethod : getMessageMethod ,
230
240
checkMethod : checkMethod ,
231
241
} ;
232
- this . _validators . push ( obj ) ;
242
+ this . #validators . push ( obj ) ;
233
243
return obj ;
234
244
}
235
245
236
246
protected removeValidator ( validator : Validator ) {
237
- const index = this . _validators . indexOf ( validator ) ;
247
+ const index = this . #validators . indexOf ( validator ) ;
238
248
if ( index !== - 1 ) {
239
- this . _validators . splice ( index , 1 ) ;
249
+ this . #validators . splice ( index , 1 ) ;
240
250
}
241
251
}
242
252
@@ -246,7 +256,7 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
246
256
* @param element {NativeFormControlElement} - element to validate and include as part of this form association.
247
257
*/
248
258
protected addFormControlElement ( element : NativeFormControlElement ) {
249
- this . _formCtrlElements . push ( element ) ;
259
+ this . #formCtrlElements . push ( element ) ;
250
260
}
251
261
252
262
private _customValidityObject ?: Validator ;
@@ -273,11 +283,18 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
273
283
this . _runValidators ( ) ;
274
284
}
275
285
276
- private _runValidators ( ) {
286
+ /**
287
+ * @method _runValidators
288
+ * @description Run all validators and set the validityState of this form control.
289
+ * Run this method when you want to re-run all validators.
290
+ * This can be relevant if you have a validators that is using values that is not triggering the Lit Updated Callback.
291
+ * Such are mainly properties that are not declared as a Lit state and or Lit property.
292
+ */
293
+ protected _runValidators ( ) {
277
294
this . _validityState = { } ;
278
295
279
296
// Loop through inner native form controls to adapt their validityState.
280
- this . _formCtrlElements . forEach ( formCtrlEl => {
297
+ this . #formCtrlElements . forEach ( formCtrlEl => {
281
298
for ( const key in formCtrlEl . validity ) {
282
299
if ( key !== 'valid' && ( formCtrlEl . validity as any ) [ key ] ) {
283
300
( this as any ) . _validityState [ key ] = true ;
@@ -291,7 +308,7 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
291
308
} ) ;
292
309
293
310
// Loop through custom validators, currently its intentional to have them overwritten native validity. but might need to be reconsidered (This current way enables to overwrite with custom messages)
294
- this . _validators . forEach ( validator => {
311
+ this . #validators . forEach ( validator => {
295
312
if ( validator . checkMethod ( ) ) {
296
313
this . _validityState [ validator . flagKey ] = true ;
297
314
this . _internals . setValidity (
@@ -322,33 +339,40 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
322
339
this . _runValidators ( ) ;
323
340
}
324
341
325
- private _onFormSubmit = ( ) => {
342
+ #onFormSubmit = ( ) => {
326
343
this . pristine = false ;
327
344
} ;
328
345
329
346
public submit ( ) {
330
- this . _form ?. requestSubmit ( ) ;
347
+ this . #form ?. requestSubmit ( ) ;
331
348
}
332
349
333
350
public formAssociatedCallback ( ) {
334
- this . _removeFormListeners ( ) ;
335
- this . _form = this . _internals . form ;
336
- if ( this . _form ) {
351
+ this . #removeFormListeners ( ) ;
352
+ this . #form = this . _internals . form ;
353
+ if ( this . #form ) {
337
354
// This relies on the form begin a 'uui-form':
338
- if ( this . _form . hasAttribute ( 'submit-invalid' ) ) {
355
+ if ( this . #form . hasAttribute ( 'submit-invalid' ) ) {
339
356
this . pristine = false ;
340
357
}
341
- this . _form . addEventListener ( 'submit' , this . _onFormSubmit ) ;
358
+ this . #form . addEventListener ( 'submit' , this . #onFormSubmit ) ;
342
359
}
343
360
}
344
361
public formResetCallback ( ) {
345
362
this . pristine = true ;
346
- this . value = this . getAttribute ( 'value' ) || '' ;
363
+ this . value = this . getInitialValue ( ) ?? this . getDefaultValue ( ) ;
364
+ }
365
+
366
+ protected getDefaultValue ( ) : DefaultValueType {
367
+ return defaultValue ;
368
+ }
369
+ protected getInitialValue ( ) : ValueType | DefaultValueType {
370
+ return this . getAttribute ( 'value' ) as ValueType | DefaultValueType ;
347
371
}
348
372
349
373
public checkValidity ( ) {
350
- for ( const key in this . _formCtrlElements ) {
351
- if ( this . _formCtrlElements [ key ] . checkValidity ( ) === false ) {
374
+ for ( const key in this . #formCtrlElements ) {
375
+ if ( this . #formCtrlElements [ key ] . checkValidity ( ) === false ) {
352
376
return false ;
353
377
}
354
378
}
@@ -365,6 +389,8 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
365
389
return this . _internals ?. validationMessage ;
366
390
}
367
391
}
368
- return FormControlMixinClass as unknown as Constructor < FormControlMixinInterface > &
392
+ return UUIFormControlMixinClass as unknown as Constructor <
393
+ UUIFormControlMixinInterface < ValueType , DefaultValueType >
394
+ > &
369
395
T ;
370
396
} ;
0 commit comments