Skip to content

Commit 0537cfb

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 0537cfb

17 files changed

+276
-288
lines changed

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);

packages/core/__tests__/FormValidityObserver.test.ts

+159-166
Large diffs are not rendered by default.

packages/lit/__tests__/createFormValidityObserver.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import createFormValidityObserver from "../createFormValidityObserver.js";
44
import type { EventType } from "../index.d.ts";
55

66
describe("Create Form Validity Observer (Function)", () => {
7-
const types = Object.freeze(["input", "focusout"] as const) satisfies ReadonlyArray<EventType>;
7+
const type = "input" satisfies EventType;
88

99
// TODO: Can we get rid of the weird `void` thing?
1010
// Keep things clean between each test by automatically restoring anything we may have spied on
1111
beforeEach(vi.restoreAllMocks as () => void);
1212

1313
it("Generates a `FormValidityObserver` (enhanced)", () => {
14-
expect(createFormValidityObserver(types)).toEqual(expect.any(FormValidityObserver));
14+
expect(createFormValidityObserver(type)).toEqual(expect.any(FormValidityObserver));
1515
});
1616

1717
it("Exposes `bound` versions of the `FormValidityObserver`'s methods", () => {
@@ -29,7 +29,7 @@ describe("Create Form Validity Observer (Function)", () => {
2929
boundMethods.forEach((method) => vi.spyOn(FormValidityObserver.prototype[method], "bind"));
3030

3131
/* ---------- Run Assertions ---------- */
32-
const observer = createFormValidityObserver(types);
32+
const observer = createFormValidityObserver(type);
3333

3434
boundMethods.forEach((method) => {
3535
expect(FormValidityObserver.prototype[method].bind).toHaveBeenCalledTimes(1);

packages/lit/createFormValidityObserver.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import FormValidityObserver from "@form-observer/core/FormValidityObserver";
33
/**
44
* Creates a version of the {@link FormValidityObserver} that's more convenient for `Lit` apps
55
*
6-
* @template {import("./index.d.ts").OneOrMany<import("./index.d.ts").EventType>} T
6+
* @template {import("./index.d.ts").EventType} T
77
* @template [M=string]
88
* @template {import("./index.d.ts").ValidatableField} [E=import("./index.d.ts").ValidatableField]
99
* @template {boolean} [R=false]
10-
* @param {T} types
10+
* @param {T} type
1111
* @param {import("./index.d.ts").FormValidityObserverOptions<M, E, R>} [options]
1212
* @returns {FormValidityObserver<M, R>}
1313
*/
14-
export default function createFormValidityObserver(types, options) {
15-
const observer = new FormValidityObserver(types, options);
14+
export default function createFormValidityObserver(type, options) {
15+
const observer = new FormValidityObserver(type, options);
1616

1717
/* -------------------- Bindings -------------------- */
1818
// Form Observer Methods

packages/preact/__tests__/createFormValidityObserver.test.tsx

+17-17
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import type { PreactValidationErrors } from "../types.d.ts";
99
import type { EventType, FormField, ValidatableField } from "../index.d.ts";
1010

1111
describe("Create Form Validity Observer (Function)", () => {
12-
const types = Object.freeze(["input", "focusout"] as const) satisfies ReadonlyArray<EventType>;
12+
const type = "input" satisfies EventType;
1313

1414
// Keep things clean between each test by automatically restoring anything we may have spied on
1515
beforeEach(vi.restoreAllMocks as () => void);
1616

1717
it("Generates a `FormValidityObserver` (enhanced)", () => {
18-
expect(createFormValidityObserver(types)).toEqual(expect.any(FormValidityObserver));
18+
expect(createFormValidityObserver(type)).toEqual(expect.any(FormValidityObserver));
1919
});
2020

2121
it("Exposes `bound` versions of the `FormValidityObserver`'s methods (excluding `configure`)", () => {
@@ -33,7 +33,7 @@ describe("Create Form Validity Observer (Function)", () => {
3333
boundMethods.forEach((method) => vi.spyOn(FormValidityObserver.prototype[method], "bind"));
3434

3535
/* ---------- Run Assertions ---------- */
36-
const observer = createFormValidityObserver(types);
36+
const observer = createFormValidityObserver(type);
3737

3838
boundMethods.forEach((method) => {
3939
expect(FormValidityObserver.prototype[method].bind).toHaveBeenCalledTimes(1);
@@ -48,7 +48,7 @@ describe("Create Form Validity Observer (Function)", () => {
4848
vi.spyOn(FormValidityObserver.prototype[configure], "bind");
4949

5050
// Run Assertions
51-
const observer = createFormValidityObserver(types);
51+
const observer = createFormValidityObserver(type);
5252
expect(FormValidityObserver.prototype[configure].bind).toHaveBeenCalledTimes(1);
5353
expect(FormValidityObserver.prototype[configure].bind).toHaveBeenCalledWith(observer);
5454
expect(FormValidityObserver.prototype[configure].bind).not.toHaveReturnedWith(observer[configure]);
@@ -58,7 +58,7 @@ describe("Create Form Validity Observer (Function)", () => {
5858
// eslint-disable-next-line vitest/no-disabled-tests
5959
it.skip("Uses a default `renderer` that accepts `Renderable Preact Values`", () => {
6060
/* ---------- Setup ---------- */
61-
const { autoObserve, setFieldError, clearFieldError } = createFormValidityObserver(types[0]);
61+
const { autoObserve, setFieldError, clearFieldError } = createFormValidityObserver(type);
6262

6363
const { unmount } = render(
6464
<form ref={autoObserve()}>
@@ -132,7 +132,7 @@ describe("Create Form Validity Observer (Function)", () => {
132132
const message = "Only numbers are allowed!";
133133
vi.spyOn(FormValidityObserver.prototype, "observe");
134134
vi.spyOn(FormValidityObserver.prototype, "unobserve");
135-
const { autoObserve, configure } = createFormValidityObserver(types[0]);
135+
const { autoObserve, configure } = createFormValidityObserver(type);
136136

137137
/* ---------- Assertions ---------- */
138138
const { unmount } = render(
@@ -170,9 +170,9 @@ describe("Create Form Validity Observer (Function)", () => {
170170
const labels = { default: "Default Config", true: "Explicit True Option", false: "Explicit False Option" };
171171
const novalidate = "novalidate";
172172

173-
const { autoObserve: autoObserveDefault } = createFormValidityObserver(types[0]);
174-
const { autoObserve: autoObserveTrue } = createFormValidityObserver(types[0]);
175-
const { autoObserve: autoObserveFalse } = createFormValidityObserver(types[0]);
173+
const { autoObserve: autoObserveDefault } = createFormValidityObserver(type);
174+
const { autoObserve: autoObserveTrue } = createFormValidityObserver(type);
175+
const { autoObserve: autoObserveFalse } = createFormValidityObserver(type);
176176

177177
// Assertions
178178
render(
@@ -196,7 +196,7 @@ describe("Create Form Validity Observer (Function)", () => {
196196

197197
it("ONLY configures the error messages for the custom validation properties", () => {
198198
vi.spyOn(FormValidityObserver.prototype, "configure");
199-
const observer = createFormValidityObserver(types[0]);
199+
const observer = createFormValidityObserver(type);
200200

201201
const errorMessages: Pick<ConstraintValues, "badinput" | "validate"> = {
202202
badinput: (field) => `This ${field.tagName} element isn't looking good` as const,
@@ -209,7 +209,7 @@ describe("Create Form Validity Observer (Function)", () => {
209209

210210
it("ONLY configures the props for the HTML attributes when the value-only variant is used", () => {
211211
vi.spyOn(FormValidityObserver.prototype, "configure");
212-
const observer = createFormValidityObserver(types[0]);
212+
const observer = createFormValidityObserver(type);
213213

214214
const errorMessages: Omit<ConstraintValues, "badinput" | "validate"> = {
215215
required: true,
@@ -228,7 +228,7 @@ describe("Create Form Validity Observer (Function)", () => {
228228

229229
it("Configures the prop AND the error for the `required` constraint when its value is an error message", () => {
230230
vi.spyOn(FormValidityObserver.prototype, "configure");
231-
const observer = createFormValidityObserver(types[0]);
231+
const observer = createFormValidityObserver(type);
232232

233233
// Regular Error Message
234234
const configWithErrorMessage = { required: "This field is bad" };
@@ -243,7 +243,7 @@ describe("Create Form Validity Observer (Function)", () => {
243243

244244
it("Configures the props AND the error messages for the HTML attributes when the object variant is used", () => {
245245
vi.spyOn(FormValidityObserver.prototype, "configure");
246-
const observer = createFormValidityObserver(types[0]);
246+
const observer = createFormValidityObserver(type);
247247

248248
const errorMessages: Omit<ConstraintValues, "badinput" | "validate"> = {
249249
required: { value: true, message: (field) => `<p>${field.tagName} required</p>`, render: true },
@@ -264,7 +264,7 @@ describe("Create Form Validity Observer (Function)", () => {
264264

265265
it("DOES NOT configure any props OR error messages when `undefined` is used", () => {
266266
vi.spyOn(FormValidityObserver.prototype, "configure");
267-
const observer = createFormValidityObserver(types[0]);
267+
const observer = createFormValidityObserver(type);
268268

269269
// Undefined Configurations
270270
const undefinedErrorMessages: PreactValidationErrors<string> = {
@@ -286,7 +286,7 @@ describe("Create Form Validity Observer (Function)", () => {
286286

287287
it("DOES NOT configure the prop OR the error for the `required` constraint when its value is `false`", () => {
288288
vi.spyOn(FormValidityObserver.prototype, "configure");
289-
const observer = createFormValidityObserver(types[0]);
289+
const observer = createFormValidityObserver(type);
290290

291291
// `required = false` via Value-Only Variant
292292
expect(observer.configure(name, { required: false })).toStrictEqual({ name });
@@ -299,7 +299,7 @@ describe("Create Form Validity Observer (Function)", () => {
299299

300300
it("Always returns the `name` prop for the field", () => {
301301
vi.spyOn(FormValidityObserver.prototype, "configure");
302-
const observer = createFormValidityObserver(types[0]);
302+
const observer = createFormValidityObserver(type);
303303

304304
expect(observer.configure(name, {})).toStrictEqual({ name });
305305
expect(FormValidityObserver.prototype.configure).toHaveBeenCalledWith(name, {});
@@ -312,7 +312,7 @@ describe("Create Form Validity Observer (Function)", () => {
312312
const renderer = (_errorContainer: HTMLElement, _errorMessage: StringOrElement | null) => undefined;
313313

314314
vi.spyOn(FormValidityObserver.prototype, "configure");
315-
const observer = createFormValidityObserver(types[0], { renderer, renderByDefault: true });
315+
const observer = createFormValidityObserver(type, { renderer, renderByDefault: true });
316316

317317
// Test a Renderable Error Message
318318
const renderable = { type: "DOMString", value: "No" } as const;

packages/preact/createFormValidityObserver.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ import FormValidityObserver from "@form-observer/core/FormValidityObserver";
33
/**
44
* Creates an enhanced version of the {@link FormValidityObserver} that's more convenient for `Preact` apps
55
*
6-
* @template {import("./index.d.ts").OneOrMany<import("./index.d.ts").EventType>} T
6+
* @template {import("./index.d.ts").EventType} T
77
* @template [M=string]
88
* @template {import("./index.d.ts").ValidatableField} [E=import("./index.d.ts").ValidatableField]
99
* @template {boolean} [R=false]
10-
* @param {T} types
10+
* @param {T} type
1111
* @param {import("./index.d.ts").FormValidityObserverOptions<M, E, R>} [options]
1212
* @returns {import("./types.d.ts").PreactFormValidityObserver<M, R>}
1313
*/
14-
export default function createFormValidityObserver(types, options) {
14+
export default function createFormValidityObserver(type, options) {
1515
const observer = /** @type {import("./types.d.ts").PreactFormValidityObserver<M, R>} */ (
16-
/** @type {unknown} */ (new FormValidityObserver(types, options))
16+
/** @type {unknown} */ (new FormValidityObserver(type, options))
1717
);
1818

1919
/* -------------------- Bindings -------------------- */

0 commit comments

Comments
 (0)