Skip to content

Commit f7a66a8

Browse files
asynclizcopybara-github
authored andcommitted
refactor: add constraint validation mixin
This reduces the copy/paste of validation code. Constraint validation must be synchronous, so a `Validator` helps compute the validity and cache it since the validity must be checked when properties change. Implemented in checkbox-like controls. PiperOrigin-RevId: 584380464
1 parent 3d8c7ac commit f7a66a8

File tree

4 files changed

+454
-228
lines changed

4 files changed

+454
-228
lines changed

checkbox/internal/checkbox.ts

Lines changed: 16 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ import {
1919
redispatchEvent,
2020
} from '../../internal/controller/events.js';
2121
import {
22-
internals,
23-
mixinElementInternals,
24-
} from '../../labs/behaviors/element-internals.js';
22+
createValidator,
23+
getValidityAnchor,
24+
mixinConstraintValidation,
25+
} from '../../labs/behaviors/constraint-validation.js';
26+
import {mixinElementInternals} from '../../labs/behaviors/element-internals.js';
2527
import {
2628
getFormState,
2729
getFormValue,
@@ -30,8 +32,8 @@ import {
3032
import {CheckboxValidator} from '../../labs/behaviors/validators/checkbox-validator.js';
3133

3234
// Separate variable needed for closure.
33-
const checkboxBaseClass = mixinFormAssociated(
34-
mixinElementInternals(LitElement),
35+
const checkboxBaseClass = mixinConstraintValidation(
36+
mixinFormAssociated(mixinElementInternals(LitElement)),
3537
);
3638

3739
/**
@@ -83,111 +85,24 @@ export class Checkbox extends checkboxBaseClass {
8385
*/
8486
@property() value = 'on';
8587

86-
/**
87-
* Returns a ValidityState object that represents the validity states of the
88-
* checkbox.
89-
*
90-
* Note that checkboxes will only set `valueMissing` if `required` and not
91-
* checked.
92-
*
93-
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#validation
94-
*/
95-
get validity() {
96-
this.syncValidity();
97-
return this[internals].validity;
98-
}
99-
100-
/**
101-
* Returns the native validation error message.
102-
*
103-
* https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#constraint_validation_process
104-
*/
105-
get validationMessage() {
106-
this.syncValidity();
107-
return this[internals].validationMessage;
108-
}
109-
110-
/**
111-
* Returns whether an element will successfully validate based on forms
112-
* validation rules and constraints.
113-
*
114-
* https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#constraint_validation_process
115-
*/
116-
get willValidate() {
117-
this.syncValidity();
118-
return this[internals].willValidate;
119-
}
120-
12188
@state() private prevChecked = false;
12289
@state() private prevDisabled = false;
12390
@state() private prevIndeterminate = false;
12491
@query('input') private readonly input!: HTMLInputElement | null;
125-
// Needed for Safari, see https://bugs.webkit.org/show_bug.cgi?id=261432
126-
// Replace with this[internals].validity.customError when resolved.
127-
private customValidityError = '';
12892

12993
constructor() {
13094
super();
13195
if (!isServer) {
13296
this.addEventListener('click', (event: MouseEvent) => {
133-
if (!isActivationClick(event)) {
97+
if (!isActivationClick(event) || !this.input) {
13498
return;
13599
}
136100
this.focus();
137-
dispatchActivationClick(this.input!);
101+
dispatchActivationClick(this.input);
138102
});
139103
}
140104
}
141105

142-
/**
143-
* Checks the checkbox's native validation and returns whether or not the
144-
* element is valid.
145-
*
146-
* If invalid, this method will dispatch the `invalid` event.
147-
*
148-
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/checkValidity
149-
*
150-
* @return true if the checkbox is valid, or false if not.
151-
*/
152-
checkValidity() {
153-
this.syncValidity();
154-
return this[internals].checkValidity();
155-
}
156-
157-
/**
158-
* Checks the checkbox's native validation and returns whether or not the
159-
* element is valid.
160-
*
161-
* If invalid, this method will dispatch the `invalid` event.
162-
*
163-
* The `validationMessage` is reported to the user by the browser. Use
164-
* `setCustomValidity()` to customize the `validationMessage`.
165-
*
166-
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/reportValidity
167-
*
168-
* @return true if the checkbox is valid, or false if not.
169-
*/
170-
reportValidity() {
171-
this.syncValidity();
172-
return this[internals].reportValidity();
173-
}
174-
175-
/**
176-
* Sets the checkbox's native validation error message. This is used to
177-
* customize `validationMessage`.
178-
*
179-
* When the error is not an empty string, the checkbox is considered invalid
180-
* and `validity.customError` will be true.
181-
*
182-
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setCustomValidity
183-
*
184-
* @param error The error message to display.
185-
*/
186-
setCustomValidity(error: string) {
187-
this.customValidityError = error;
188-
this.syncValidity();
189-
}
190-
191106
protected override update(changed: PropertyValues<Checkbox>) {
192107
if (
193108
changed.has('checked') ||
@@ -252,12 +167,6 @@ export class Checkbox extends checkboxBaseClass {
252167
`;
253168
}
254169

255-
protected override updated() {
256-
// Sync validity when properties change, since validation properties may
257-
// have changed.
258-
this.syncValidity();
259-
}
260-
261170
private handleChange(event: Event) {
262171
const target = event.target as HTMLInputElement;
263172
this.checked = target.checked;
@@ -266,18 +175,6 @@ export class Checkbox extends checkboxBaseClass {
266175
redispatchEvent(this, event);
267176
}
268177

269-
private syncValidity() {
270-
const {validity, validationMessage} = this.validator.getValidity();
271-
this[internals].setValidity(
272-
{
273-
...validity,
274-
customError: !!this.customValidityError,
275-
},
276-
this.customValidityError || validationMessage,
277-
this.input ?? undefined,
278-
);
279-
}
280-
281178
// Writable mixin properties for lit-html binding, needed for lit-analyzer
282179
declare disabled: boolean;
283180
declare name: string;
@@ -304,5 +201,11 @@ export class Checkbox extends checkboxBaseClass {
304201
this.checked = state === 'true';
305202
}
306203

307-
private readonly validator = new CheckboxValidator(() => this);
204+
[createValidator]() {
205+
return new CheckboxValidator(() => this);
206+
}
207+
208+
[getValidityAnchor]() {
209+
return this.input;
210+
}
308211
}

0 commit comments

Comments
 (0)