Skip to content

Commit 8020e3b

Browse files
committed
refactor: Only Allow 1 Event Type to Be Specified for the FormValidityObserver
Although this is a breaking change, it will more easily allow us to support the `revalidateOn` feature that we mentioned on GitHub while also maintaining the parent-child relationship between the `FormObserver` and the `FormValidityObserver`. (We could technically choose to break that relationship, but the reusable `observe` and `unobserve` methods seem to be proving helpful right now.) This change also seems more logical because -- practically speaking -- developers are likely going to choose one event anyway. They'll have to choose an event that actually relates to user interaction. And the most common of those events are `input`, `focusout`, and `change`. However, the final `input` triggers before `focusout` happens, so it doesn't make sense to specify both simultaneously. Similarly, the `change` event will only be relevant for a subset of the times that the `focusout` event is triggered, so supplying those two simultaneously also doesn't make sense. So developers will likely only supply 1 event at a time.
1 parent c1d2ce7 commit 8020e3b

19 files changed

+296
-290
lines changed

docs/extras/design-decisions.md

+18
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,24 @@ This file is similar to a `Changelog`: It specifies the dates (in descending ord
1212

1313
It's possible that the need for this is already captured in the concept of `PR` (Pull Request) history. We will try to run with this approach _and_ the approach of PRs before coming to a final decision on what to use to accomplish this history-preserving goal.
1414

15+
## 2024-04-28
16+
17+
### Only Allow One (1) Event Type to Be Passed to the `FormValidityObserver` Constructor
18+
19+
Although this is a breaking change, it is one that will more easily allow us to support the `revalidateOn` feature that we mentioned on GitHub while also maintaining the parent-child relationship between the `FormObserver` and the `FormValidityObserver`. (We could technically choose to break that relationship, but the reusable `observe` and `unobserve` methods seem to be proving helpful right now.)
20+
21+
It technically isn't _that_ difficult to continue supporting an array of strings for the `types` argument of the `FormValidityObserver` constructor. But the (very small) additional effort for this support did not seem worth it.
22+
23+
Practically speaking, most (if not all) developers are likely going to choose only one event type anyway. And they'll have to choose one of the events that actually relate to user interaction (so that the form is only validated as users interact with it). The most common of these events are `input`, `focusout` (the bubbling version of the `blur` event), and `change`. But each of these events renders the other ones irrelevant.
24+
25+
For example, the last `input` event that's triggered for a form field will always be triggered _before_ `change` or `focusout`. Consequently, it doesn't make sense to add `change` _or_ `focusout` to the `types` argument of the `FormValidityObserver` constructor when `input` is supplied. The developer would only need to specify the `input` event itself.
26+
27+
Similarly, the `change` event is only fired a _subset_ of the times that the `focusout` event is fired. (There are technically exceptions to this rule, such as `<select>` and `<input type="checkbox">`. But practically speaking, this rule holds for all form controls -- including those.) So instead of specifying both events, a developer would only need to specify the `focusout` event. Finally, if a developer wants to respond to `change` events exclusively, then they will only specify _that_ event. Neither `focusout` nor `input` could be added in that scenario because it would cause the form controls to be validated too frequently.
28+
29+
In the end, whichever event type is chosen, it is only practical for the developer to specify 1 event. Technically speaking, developers could try to specify custom events. But those events would likely be emitted in _response_ to `input`/`focusout`/`change` events anyway. So everything ultimately comes down to those 3 events.
30+
31+
In conclusion, it will be simpler for us to only allow 1 event to be specified for the `types` argument moving forward. Since this is also what will happen naturally (and since enforcing this might protect some developers from unexpected footguns), this seems like a good change.
32+
1533
## 2023-08-20
1634

1735
### Deprecate the `FormValidityObserver.validateFields(names: string[])` Overload

docs/form-validity-observer/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ As expected for any form validation library, we also support the following featu
8787
The `FormValidityObserver()` constructor creates a new observer and configures it with the `options` that you pass in. Because the `FormValidityObserver` only focuses on one task, it has a simple constructor with no overloads.
8888

8989
<dl>
90-
<dt id="form-validity-observer-parameters-types"><code>types</code></dt>
90+
<dt id="form-validity-observer-parameters-types"><code>type: EventType</code></dt>
9191
<dd>
92-
A string <em>or</em> an array of strings representing the type(s) of event(s) that should cause a form's field to be validated. As with the <code>FormObserver</code>, the string(s) can be <a href="https://developer.mozilla.org/en-US/docs/Web/Events">commonly recognized</a> event types <em>or</em> your own <a href="../form-observer/guides.md#supporting-custom-event-types">custom</a> event types.
92+
A string representing the type of event that should cause a form's field to be validated. As with the <code>FormObserver</code>, the string can be a <a href="https://developer.mozilla.org/en-US/docs/Web/Events">commonly recognized</a> event type <em>or</em> your own <a href="../form-observer/guides.md#supporting-custom-event-types">custom</a> event type. But in the case of the <code>FormValidityObserver</code>, only one event type may be specified.
9393
</dd>
9494

9595
<dt id="form-validity-observer-parameters-options"><code>options</code> (Optional)</dt>

packages/core/FormValidityObserver.d.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* - https://github.com/microsoft/TypeScript/issues/55919 (generic constructors)
55
* - https://github.com/microsoft/TypeScript/issues/40451 (generic constructors)
66
*/
7-
import type { OneOrMany, EventType, ValidatableField } from "./types.d.ts";
7+
import type { EventType, ValidatableField } from "./types.d.ts";
88

99
export type ErrorMessage<M, E extends ValidatableField = ValidatableField> = M | ((field: E) => M);
1010

@@ -95,15 +95,10 @@ interface FormValidityObserverConstructor {
9595
* Provides a way to validate an `HTMLFormElement`'s fields (and to display _accessible_ errors for those fields)
9696
* in response to the events that the fields emit.
9797
*
98-
* @param types The type(s) of event(s) that trigger(s) form field validation.
98+
* @param type The type of event that triggers form field validation.
9999
*/
100-
new <
101-
T extends OneOrMany<EventType>,
102-
M = string,
103-
E extends ValidatableField = ValidatableField,
104-
R extends boolean = false,
105-
>(
106-
types: T,
100+
new <T extends EventType, M = string, E extends ValidatableField = ValidatableField, R extends boolean = false>(
101+
type: T,
107102
options?: FormValidityObserverOptions<M, E, R>,
108103
): FormValidityObserver<M, R>;
109104
}

packages/core/FormValidityObserver.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class FormValidityObserver extends FormObserver {
115115
* illegal generic constructors?
116116
*/
117117
/**
118-
* @template {import("./types.d.ts").OneOrMany<import("./types.d.ts").EventType>} T
118+
* @template {import("./types.d.ts").EventType} T
119119
* @template [M=string]
120120
* @template {import("./types.d.ts").ValidatableField} [E=import("./types.d.ts").ValidatableField]
121121
* @template {boolean} [R=false]
@@ -124,16 +124,16 @@ class FormValidityObserver extends FormObserver {
124124
* Provides a way to validate an `HTMLFormElement`'s fields (and to display _accessible_ errors for those fields)
125125
* in response to the events that the fields emit.
126126
*
127-
* @param {T} types The type(s) of event(s) that trigger(s) form field validation.
127+
* @param {T} type The type of event that triggers form field validation.
128128
* @param {FormValidityObserverOptions<M, E, R>} [options]
129129
* @returns {FormValidityObserver<M, R>}
130130
*/
131131

132132
/**
133-
* @param {import("./types.d.ts").OneOrMany<import("./types.d.ts").EventType>} types
133+
* @param {import("./types.d.ts").EventType} type
134134
* @param {FormValidityObserverOptions<M, import("./types.d.ts").ValidatableField, R>} [options]
135135
*/
136-
constructor(types, options) {
136+
constructor(type, options) {
137137
/**
138138
* Event listener used to validate form fields in response to user interactions
139139
*
@@ -145,7 +145,7 @@ class FormValidityObserver extends FormObserver {
145145
if (fieldName) this.validateField(fieldName);
146146
};
147147

148-
super(types, eventListener, { passive: true, capture: options?.useEventCapturing });
148+
super(type, eventListener, { passive: true, capture: options?.useEventCapturing });
149149
this.#scrollTo = options?.scroller ?? defaultScroller;
150150
this.#renderError = /** @type {any} Necessary because of double `M`s */ (options?.renderer ?? defaultErrorRenderer);
151151
this.#renderByDefault = /** @type {any} Necessary because of double `R`s */ (options?.renderByDefault);

0 commit comments

Comments
 (0)