diff --git a/packages/core/FormObserver.js b/packages/core/FormObserver.js index 5bad4ef..16d0ce0 100644 --- a/packages/core/FormObserver.js +++ b/packages/core/FormObserver.js @@ -1,10 +1,6 @@ import { assertElementIsForm } from "./utils/assertions.js"; -/** - * @template {import("./types.d.ts").OneOrMany} T - * @type {import("./types.d.ts").FormObserverConstructor} - */ -const FormObserver = class { +class FormObserver { /* ---------- Constructor-related Fields. (Must be compatible with `document.addEventListener`.) ---------- */ /** @readonly @type {ReadonlyArray} */ #types; @@ -22,8 +18,67 @@ const FormObserver = class { */ #observedForms = new Set(); + /* ---------------------------------------- Constructor Setup ---------------------------------------- */ + /** + * Provides a way to respond to events emitted by the fields belonging to an `HTMLFormElement`. + * + * @template {import("./types.d.ts").EventType} T1 + * @overload + * + * @param {T1} type The type of event to respond to. + * @param {import("./types.d.ts").FormFieldListener} listener The function to call when a form field + * emits an event matching the provided `type`. + * @param {import("./types.d.ts").ListenerOptions} [options] The `addEventListener` options for the provided + * `listener`. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener addEventListener}. + * @returns {FormObserver} + */ + + /** + * Provides a way to respond to events emitted by the fields belonging to an `HTMLFormElement`. + * + * @template {ReadonlyArray} T2 + * @overload + * + * @param {T2} types An array containing the types of events to respond to. + * @param {import("./types.d.ts").FormFieldListener} listener The function to call when a form field emits + * an event specified in the list of `types`. + * @param {import("./types.d.ts").ListenerOptions} [options] The `addEventListener` options for the provided + * `listener`. + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener addEventListener}. + * @returns {FormObserver} + */ + + /** + * Provides a way to respond to events emitted by the fields belonging to an `HTMLFormElement`. + * + * @template {ReadonlyArray} T3 + * @overload + * + * @param {T3} types An array containing the types of events to respond to. + * @param {import("./types.d.ts").TypesToListeners} listeners An array of event listeners corresponding + * to the provided list of `types`. When an event matching one of the `types` is emitted by a form field, its + * corresponding listener function will be called. + * + * For example, when a field emits an event matching the 2nd type in `types`, the 2nd listener will be called. + * @param {import("./types.d.ts").OneOrMany} [options] An array of + * `addEventListener` options corresponding to the provided list of `listeners`. When a listener is attached + * to a form's `Document`, the listener's corresponding set of options will be used to configure it. + * + * For example, when the 2nd listener in `listeners` is attached to the `Document`, it will use the 2nd value + * in the `options` array for its configuration. + * + * If `options` is a single value instead of an array, then that value will be used to configure all of + * the listeners.) + * + * See {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener addEventListener}. + * @returns {FormObserver} + */ + /** - * @param {T} types + * @param {import("./types.d.ts").OneOrMany} types * @param {import("./types.d.ts").OneOrMany>} listeners * @param {import("./types.d.ts").OneOrMany} options */ @@ -69,9 +124,14 @@ const FormObserver = class { else this.#options = Array.from({ length: types.length }, () => options); } + /* ---------------------------------------- Class Methods ---------------------------------------- */ /** + * Instructs the observer to listen for events emitted from the provided `form`'s fields. + * The observer will only listen for events which match the types that were specified + * during its instantiation. + * * @param {HTMLFormElement} form - * @returns {boolean} + * @returns {boolean} `true` if the `form` was not already being observed, and `false` otherwise. */ observe(form) { assertElementIsForm(form); @@ -98,8 +158,10 @@ const FormObserver = class { } /** + * Stops the observer from listening for any events emitted from the provided `form`'s fields. + * * @param {HTMLFormElement} form - * @returns {boolean} + * @returns {boolean} `true` if the `form` was originally being observed, and `false` otherwise. */ unobserve(form) { assertElementIsForm(form); @@ -129,11 +191,11 @@ const FormObserver = class { return true; } - /** @returns {void} */ + /** Stops the observer from listening for any events emitted from all `form` fields. @returns {void} */ disconnect() { this.#observedForms.forEach((form) => this.unobserve(form)); } -}; +} /** * @param {unknown} types diff --git a/packages/core/FormStorageObserver.js b/packages/core/FormStorageObserver.js index 255193c..b2dd815 100644 --- a/packages/core/FormStorageObserver.js +++ b/packages/core/FormStorageObserver.js @@ -2,16 +2,39 @@ import FormObserver from "./FormObserver.js"; import { assertElementIsForm } from "./utils/assertions.js"; /** - * @template {import("./types.d.ts").OneOrMany} T - * @type {import("./types.d.ts").FormStorageObserverConstructor} + * @typedef {Object} FormStorageObserverOptions + * @property {"loading" | "deletion" | "both" | "neither"} [automate] Indicates whether or not the observer should + * automate the loading/removal of a form's `localStorage` data. + * - `loading` (Default): A form's data will automatically be loaded from `localStorage` when it is observed. + * - `deletion`: A form's data will automatically be removed from `localStorage` when it is unobserved. + * - `both`: Behaves as if `loading` and `deletion` were specified simultaneously. + * - `neither`: The observer will not automate any data loading or data removal. + * + * @property {boolean} [useEventCapturing] Indicates that the observer's event listener should be called during the + * event capturing phase instead of the event bubbling phase. Defaults to `false`. + * See {@link https://www.w3.org/TR/DOM-Level-3-Events/#event-flow DOM Event Flow} */ -const FormStorageObserver = class extends FormObserver { - /** @readonly @type {Required["automate"]} */ + +class FormStorageObserver extends FormObserver { + /** @readonly @type {Required["automate"]} */ #automate; /** - * @param {T} types - * @param {import("./types.d.ts").FormStorageObserverOptions} [options] + * @template {import("./types.d.ts").OneOrMany} T + * @overload + * + * Provides a way to store an `HTMLFormElement`'s data in `localStorage` automatically in response to + * the events emitted from its fields. + * + * @param {T} types The type(s) of event(s) that trigger(s) updates to `localStorage`. + * @param {FormStorageObserverOptions} [options] + * @returns {FormStorageObserver} + */ + + /** + * @param {import("./types.d.ts").OneOrMany} types The type(s) of event(s) + * that trigger(s) updates to `localStorage`. + * @param {FormStorageObserverOptions} [options] */ constructor(types, options) { super(types, eventListener, { passive: true, capture: options?.useEventCapturing }); @@ -28,6 +51,21 @@ const FormStorageObserver = class extends FormObserver { return newlyObserved; } + /** + * @overload Loads all of the data in `localStorage` related to the provided `form`. + * @param {HTMLFormElement} form + * @returns {void} + */ + + /** + * @overload Loads the data in `localStorage` for the field that has the provided `name` and belongs to + * the provided `form`. + * + * @param {HTMLFormElement} form + * @param {string} name + * @returns {void} + */ + /** * @param {HTMLFormElement} form * @param {string} [name] @@ -133,6 +171,21 @@ const FormStorageObserver = class extends FormObserver { return newlyUnobserved; } + /** + * @overload Clears all of the data in `localStorage` related to the provided `form`. + * @param {HTMLFormElement} form + * @returns {void} + */ + + /** + * @overload Clears the data in `localStorage` for the field that has the provided `name` and belongs to + * the provided `form`. + * + * @param {HTMLFormElement} form + * @param {string} name + * @returns {void} + */ + /** * @param {HTMLFormElement} form * @param {string} [name] @@ -151,7 +204,7 @@ const FormStorageObserver = class extends FormObserver { if (field.name) localStorage.removeItem(getFieldKey(form.name, field.name)); } } -}; +} /* -------------------- Utility Functions -------------------- */ /* diff --git a/packages/core/__tests__/FormObserver.test.ts b/packages/core/__tests__/FormObserver.test.ts index 8300d2b..e1999c3 100644 --- a/packages/core/__tests__/FormObserver.test.ts +++ b/packages/core/__tests__/FormObserver.test.ts @@ -37,7 +37,7 @@ describe("Form Observer (Class)", () => { * @see {@link testCases} * @throws {TypeError} for any invalid test case */ - function getFormObserverByTestCase(testCase: (typeof testCases)[number]): InstanceType { + function getFormObserverByTestCase(testCase: (typeof testCases)[number]): FormObserver { if (!testCases.includes(testCase)) { throw new TypeError("Expected a standardized test case for generating a `Form Observer`."); }