-
Notifications
You must be signed in to change notification settings - Fork 937
/
Copy pathconstraint-validation.ts
259 lines (228 loc) · 7.85 KB
/
constraint-validation.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {isServer, LitElement, PropertyDeclaration, PropertyValues} from 'lit';
import {internals, WithElementInternals} from './element-internals.js';
import {FormAssociated} from './form-associated.js';
import {MixinBase, MixinReturn} from './mixin.js';
import {Validator} from './validators/validator.js';
/**
* A form associated element that provides constraint validation APIs.
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation
*/
export interface ConstraintValidation extends FormAssociated {
/**
* Returns a ValidityState object that represents the validity states of the
* element.
*
* https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
*/
readonly validity: ValidityState;
/**
* Returns a validation error message or an empty string if the element is
* valid.
*
* https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/validationMessage
*/
readonly validationMessage: string;
/**
* Returns whether an element will successfully validate based on forms
* validation rules and constraints.
*
* Disabled and readonly elements will not validate.
*
* https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/willValidate
*/
readonly willValidate: boolean;
/**
* Checks the element's constraint validation and returns true if the element
* is valid or false if not.
*
* If invalid, this method will dispatch an `invalid` event.
*
* https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/checkValidity
*
* @return true if the element is valid, or false if not.
*/
checkValidity(): boolean;
/**
* Checks the element's constraint validation and returns true if the element
* is valid or false if not.
*
* If invalid, this method will dispatch a cancelable `invalid` event. If not
* canceled, a the current `validationMessage` will be reported to the user.
*
* https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/reportValidity
*
* @return true if the element is valid, or false if not.
*/
reportValidity(): boolean;
/**
* Sets the element's constraint validation error message. When set to a
* non-empty string, `validity.customError` will be true and
* `validationMessage` will display the provided error.
*
* Use this method to customize error messages reported.
*
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setCustomValidity
*
* @param error The error message to display, or an empty string.
*/
setCustomValidity(error: string): void;
/**
* Creates and returns a `Validator` that is used to compute and cache
* validity for the element.
*
* A validator that caches validity is important since constraint validation
* must be computed synchronously and frequently in response to constraint
* validation property changes.
*/
[createValidator](): Validator<unknown>;
/**
* Returns shadow DOM child that is used as the anchor for the platform
* `reportValidity()` popup. This is often the root element or the inner
* focus-delegated element.
*/
[getValidityAnchor](): HTMLElement | null;
}
/**
* A symbol property used to create a constraint validation `Validator`.
* Required for all `mixinConstraintValidation()` elements.
*/
export const createValidator = Symbol('createValidator');
/**
* A symbol property used to return an anchor for constraint validation popups.
* Required for all `mixinConstraintValidation()` elements.
*/
export const getValidityAnchor = Symbol('getValidityAnchor');
// Private symbol members, used to avoid name clashing.
const privateValidator = Symbol('privateValidator');
const privateSyncValidity = Symbol('privateSyncValidity');
const privateCustomValidationMessage = Symbol('privateCustomValidationMessage');
/**
* Mixes in constraint validation APIs for an element.
*
* See https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation
* for more details.
*
* Implementations must provide a validator to cache and compute its validity,
* along with a shadow root element to anchor validation popups to.
*
* @example
* ```ts
* const baseClass = mixinConstraintValidation(
* mixinFormAssociated(mixinElementInternals(LitElement))
* );
*
* class MyCheckbox extends baseClass {
* \@property({type: Boolean}) checked = false;
* \@property({type: Boolean}) required = false;
*
* [createValidator]() {
* return new CheckboxValidator(() => this);
* }
*
* [getValidityAnchor]() {
* return this.renderRoot.querySelector('.root');
* }
* }
* ```
*
* @param base The class to mix functionality into.
* @return The provided class with `ConstraintValidation` mixed in.
*/
export function mixinConstraintValidation<
T extends MixinBase<LitElement & FormAssociated & WithElementInternals>,
>(base: T): MixinReturn<T, ConstraintValidation> {
abstract class ConstraintValidationElement
extends base
implements ConstraintValidation
{
get validity() {
this[privateSyncValidity]();
return this[internals].validity;
}
get validationMessage() {
this[privateSyncValidity]();
return this[internals].validationMessage;
}
get willValidate() {
this[privateSyncValidity]();
return this[internals].willValidate;
}
/**
* A validator instance created from `[createValidator]()`.
*/
[privateValidator]?: Validator<unknown>;
/**
* Needed for Safari, see https://bugs.webkit.org/show_bug.cgi?id=261432
* Replace with this[internals].validity.customError when resolved.
*/
[privateCustomValidationMessage] = '';
checkValidity() {
this[privateSyncValidity]();
return this[internals].checkValidity();
}
reportValidity() {
this[privateSyncValidity]();
return this[internals].reportValidity();
}
setCustomValidity(error: string) {
this[privateCustomValidationMessage] = error;
this[privateSyncValidity]();
}
override requestUpdate(
name?: PropertyKey,
oldValue?: unknown,
options?: PropertyDeclaration,
) {
super.requestUpdate(name, oldValue, options);
this[privateSyncValidity]();
}
override firstUpdated(changed: PropertyValues) {
super.firstUpdated(changed);
// Sync the validity again when the element first renders, since the
// validity anchor is now available.
//
// Elements that `delegatesFocus: true` to an `<input>` will throw an
// error in Chrome and Safari when a form tries to submit or call
// `form.reportValidity()`:
// "An invalid form control with name='' is not focusable"
//
// The validity anchor MUST be provided in `internals.setValidity()` and
// MUST be the `<input>` element rendered.
//
// See https://lit.dev/playground/#gist=6c26e418e0010f7a5aac15005cde8bde
// for a reproduction.
this[privateSyncValidity]();
}
[privateSyncValidity]() {
if (isServer) {
return;
}
if (!this[privateValidator]) {
this[privateValidator] = this[createValidator]();
}
const {validity, validationMessage: nonCustomValidationMessage} =
this[privateValidator].getValidity();
const customError = !!this[privateCustomValidationMessage];
const validationMessage =
this[privateCustomValidationMessage] || nonCustomValidationMessage;
this[internals].setValidity(
{...validity, customError},
validationMessage,
this[getValidityAnchor]() ?? undefined,
);
}
[createValidator](): Validator<unknown> {
throw new Error('Implement [createValidator]');
}
[getValidityAnchor](): HTMLElement | null {
throw new Error('Implement [getValidityAnchor]');
}
}
return ConstraintValidationElement;
}