convert to JSDoc comments

This commit is contained in:
Elijah Duffy
2025-07-01 15:07:57 -07:00
parent c01dd71023
commit ff880c3cbf
2 changed files with 100 additions and 61 deletions

151
index.ts
View File

@@ -1,60 +1,70 @@
import type { Action } from 'svelte/action'; import type { Action } from 'svelte/action';
// InputElement is a union of all input types that can be validated. /** InputElement is a union of all input types that can be validated. */
export type InputElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement; export type InputElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
// InputNodeList is a union of all node lists that can be validated. /** InputNodeList is a union of all node lists that can be validated. */
export type InputNodeList = NodeListOf<HTMLInputElement> | NodeListOf<HTMLTextAreaElement> | []; export type InputNodeList = NodeListOf<HTMLInputElement> | NodeListOf<HTMLTextAreaElement> | [];
// ValidatorOptions configures the behavior of a validator. /** ValidatorOptions configures the behavior of a validator. */
export type ValidatorOptions = { export type ValidatorOptions = {
required?: boolean; // required is a flag that indicates whether the input is required. /** required is a flag that indicates whether the input is required. */
pattern?: RegExp; // pattern is a regex pattern that the input value must match. required?: boolean;
minlength?: number; // minlength is the minimum length of the input value. /** pattern is a regex pattern that the input value must match. */
maxlength?: number; // maxlength is the maximum length of the input value. pattern?: RegExp;
length?: number; // length is the exact length of the input value. /** minlength is the minimum length of the input value. */
type?: 'email' | 'phone' | 'score'; // NOT IMPLEMENTED. type is a predefined validation type. minlength?: number;
baseval?: string; // baseval is a value that the input value must not match (an alternate zero-value). /** maxlength is the maximum length of the input value. */
autovalOnInvalid?: boolean; // autovalOnInvalid is a flag that automatically adds a keyup listener to the input on invalid. maxlength?: number;
/** length is the exact length of the input value. */
length?: number;
/** NOT IMPLEMENTED. type is a predefined validation type. */
type?: 'email' | 'phone' | 'score';
/** baseval is a value that the input value must not match (an alternate zero-value). */
baseval?: string;
/** autovalOnInvalid is a flag that automatically adds a keyup listener to the input on invalid. */
autovalOnInvalid?: boolean;
func?: (node: InputElement) => boolean | Promise<boolean>; func?: (node: InputElement) => boolean | Promise<boolean>;
valfunc?: (val: string) => boolean | Promise<boolean>; valfunc?: (val: string) => boolean | Promise<boolean>;
}; };
// InputValidatorPayload carries the current value of an input element when it is validated. /** InputValidatorPayload carries the current value of an input element when it is validated. */
export type InputValidatorPayload = { value: string; valid: boolean }; export type InputValidatorPayload = { value: string; valid: boolean };
// InputValidatorEvent is a custom event that is dispatched when an input is validated. /** InputValidatorEvent is a custom event that is dispatched when an input is validated. */
export class InputValidatorEvent extends CustomEvent<InputValidatorPayload> {} export class InputValidatorEvent extends CustomEvent<InputValidatorPayload> {}
// FormValidatorPayload carries a list of invalid and valid inputs when a form is validated. /** FormValidatorPayload carries a list of invalid and valid inputs when a form is validated. */
export type FormValidatorPayload = { export type FormValidatorPayload = {
invalidInputs: InputElement[]; invalidInputs: InputElement[];
validInputs: InputElement[]; validInputs: InputElement[];
valid: boolean; valid: boolean;
}; };
// FormValidatorEvent is a custom event that is dispatched when a form is validated. /** FormValidatorEvent is a custom event that is dispatched when a form is validated. */
export class FormValidatorEvent extends CustomEvent<FormValidatorPayload> {} export class FormValidatorEvent extends CustomEvent<FormValidatorPayload> {}
// ValidatorState is the state of a validator. /** ValidatorState is the state of a validator. */
export type ValidatorState = 'unknown' | 'valid' | 'invalid'; export type ValidatorState = 'unknown' | 'valid' | 'invalid';
const VALIDATOR_VALID: ValidatorState = 'valid'; const VALIDATOR_VALID: ValidatorState = 'valid';
const VALIDATOR_INVALID: ValidatorState = 'invalid'; const VALIDATOR_INVALID: ValidatorState = 'invalid';
const VALIDATOR_UNKNOWN: ValidatorState = 'unknown'; const VALIDATOR_UNKNOWN: ValidatorState = 'unknown';
// QUERY_INPUTS is the query string for all input elements that can be validated. /** QUERY_INPUTS is the query string for all input elements that can be validated. */
const QUERY_INPUTS = 'input[data-validate-identifier], textarea[data-validate-identifier]'; const QUERY_INPUTS = 'input[data-validate-identifier], textarea[data-validate-identifier]';
// DATA_ID is the key for the validator index in the input element's dataset. /** DATA_ID is the key for the validator index in the input element's dataset. */
const DATA_ID = 'validateIdentifier'; const DATA_ID = 'validateIdentifier';
// DATA_STATE is the key for the validation state in the input element's dataset. /** DATA_STATE is the key for the validation state in the input element's dataset. */
const DATA_STATE = 'validateState'; const DATA_STATE = 'validateState';
// email is a regex pattern for validating email addresses. /** email is a regex pattern for validating email addresses. */
export const email: RegExp = new RegExp(String.raw`^[^@\s]+@[^@\s]+\.[^@\s]+$`); export const email: RegExp = new RegExp(String.raw`^[^@\s]+@[^@\s]+\.[^@\s]+$`);
// phone is a regex pattern for validating phone numbers. /** phone is a regex pattern for validating phone numbers. */
export const phone: RegExp = new RegExp(String.raw`^(\+)?(\(?\d+\)?)(([\s-]+)?(\d+)){0,}$`); export const phone: RegExp = new RegExp(String.raw`^(\+)?(\(?\d+\)?)(([\s-]+)?(\d+)){0,}$`);
// score is a regex pattern for validating lesson grades. /** score is a regex pattern for validating lesson grades. */
export const score: RegExp = new RegExp(String.raw`^(-?\d{0,2}|100)$`); export const score: RegExp = new RegExp(String.raw`^(-?\d{0,2}|100)$`);
// InputState is some historical state of an input element, tracking its value, /**
// selectionStart and selectionEnd. * InputState is some historical state of an input element, tracking its value,
* selectionStart and selectionEnd.
*/
export type InputState = { export type InputState = {
value: string; value: string;
selectionStart: number; selectionStart: number;
@@ -65,8 +75,10 @@ let nextValidatorID = 0;
const validators: Record<number, ValidatorOptions> = {}; const validators: Record<number, ValidatorOptions> = {};
const lastState: Record<number, InputState> = {}; const lastState: Record<number, InputState> = {};
// validate attaches rules to an input element that can be used to validate the input. /**
// If opts is undefined, no validation rules will be attached. * validate attaches rules to an input element that can be used to validate the input.
* If opts is undefined, no validation rules will be attached.
*/
export const validate: Action< export const validate: Action<
InputElement, InputElement,
ValidatorOptions | undefined, ValidatorOptions | undefined,
@@ -105,10 +117,12 @@ export const validate: Action<
}; };
}; };
// keydownValidator attaches a keydown event listener to an input element that will /**
// process the input value according to any attached validation rules, triggering * keydownValidator attaches a keydown event listener to an input element that will
// valid or invalid depending on validation success. If opts.constrain is true, * process the input value according to any attached validation rules, triggering
// the input value will be constrained to valid characters. * valid or invalid depending on validation success. If opts.constrain is true,
* the input value will be constrained to valid characters.
*/
export const keydownValidator: Action< export const keydownValidator: Action<
InputElement, InputElement,
{ {
@@ -131,9 +145,11 @@ export const keydownValidator: Action<
}); });
}; };
// keyupValidator attaches a keydown event listener to an input element that will /**
// process the input value according to any attached validation rules, triggering * keyupValidator attaches a keydown event listener to an input element that will
// the `validate` event with the input value and validation state. * process the input value according to any attached validation rules, triggering
* the `validate` event with the input value and validation state.
*/
export const keyupValidator: Action<InputElement> = (el) => { export const keyupValidator: Action<InputElement> = (el) => {
const node = el as HTMLInputElement; const node = el as HTMLInputElement;
@@ -147,9 +163,11 @@ export const keyupValidator: Action<InputElement> = (el) => {
}); });
}; };
// inputValidator attaches an oninput event listener to an input element that will /**
// process the input value according to any attached validation rules, triggering * inputValidator attaches an oninput event listener to an input element that will
// the `validate` event with the input value and validation state. * process the input value according to any attached validation rules, triggering
* the `validate` event with the input value and validation state.
*/
export const inputValidator: Action<InputElement> = (el) => { export const inputValidator: Action<InputElement> = (el) => {
const node = el as HTMLInputElement; const node = el as HTMLInputElement;
@@ -162,10 +180,12 @@ export const inputValidator: Action<InputElement> = (el) => {
}); });
}; };
// liveValidator attaches multiple event listeners to an input element that will /**
// process the input value according to any attached validation rules, triggering * liveValidator attaches multiple event listeners to an input element that will
// the `validate` event with the input value and validation state. liveValidator * process the input value according to any attached validation rules, triggering
// supports the constrain option without introducing edge cases with text selection. * the `validate` event with the input value and validation state. liveValidator
* supports the constrain option without introducing edge cases with text selection.
*/
export const liveValidator: Action< export const liveValidator: Action<
InputElement, InputElement,
{ {
@@ -227,11 +247,13 @@ export const liveValidator: Action<
node.addEventListener('input', inputHandler); node.addEventListener('input', inputHandler);
}; };
// submitValidator attaches a submit event listener to a form element that will /**
// process the input values of all children according to any attached validation * submitValidator attaches a submit event listener to a form element that will
// rules, triggering the validate event for individual input elements * process the input values of all children according to any attached validation
// depending on validation success. Similarly, the validate event * rules, triggering the validate event for individual input elements
// event will be triggered on the form element itself. * depending on validation success. Similarly, the validate event
* event will be triggered on the form element itself.
*/
export const submitValidator: Action< export const submitValidator: Action<
HTMLFormElement, HTMLFormElement,
undefined, undefined,
@@ -254,8 +276,7 @@ export const submitValidator: Action<
}); });
}; };
// disableFormDefault helper action prevents the default form submission behavior /** disableFormDefault helper action prevents the default form submission behavior and disables all browser validation. */
// and disables all browser validation.
export const disableFormDefault: Action<HTMLFormElement> = (form) => { export const disableFormDefault: Action<HTMLFormElement> = (form) => {
form.setAttribute('novalidate', 'true'); form.setAttribute('novalidate', 'true');
form.addEventListener('submit', (event) => { form.addEventListener('submit', (event) => {
@@ -263,10 +284,12 @@ export const disableFormDefault: Action<HTMLFormElement> = (form) => {
}); });
}; };
// validateInput checks an input element's value against its validation rules, triggering /**
// the validate events with the current value and validation state, updating the input's * validateInput checks an input element's value against its validation rules, triggering
// dataset accordingly. If opts.autovalOnInvalid is true, the input will be revalidated on * the validate events with the current value and validation state, updating the input's
// on keyup automatically from now on. * dataset accordingly. If opts.autovalOnInvalid is true, the input will be revalidated on
* on keyup automatically from now on.
*/
export const validateInput = async ( export const validateInput = async (
input: InputElement, input: InputElement,
valueOverride?: string valueOverride?: string
@@ -289,9 +312,11 @@ export const validateInput = async (
return valid; return valid;
}; };
// validateForm checks all input elements in a form element against their validation rules, /**
// triggering valid or invalid events for individual input elements depending on validation * validateForm checks all input elements in a form element against their validation rules,
// success. Similarly, valid and invalid events will be triggered on the form element. * triggering valid or invalid events for individual input elements depending on validation
* success. Similarly, valid and invalid events will be triggered on the form element.
*/
export const validateForm = async (form: HTMLFormElement): Promise<boolean> => { export const validateForm = async (form: HTMLFormElement): Promise<boolean> => {
const inputs = form.querySelectorAll(QUERY_INPUTS); const inputs = form.querySelectorAll(QUERY_INPUTS);
@@ -321,7 +346,7 @@ export const validateForm = async (form: HTMLFormElement): Promise<boolean> => {
return valid; return valid;
}; };
// isInputValid checks an input element's value against its validation rules. /** isInputValid checks an input element's value against its validation rules. */
export const isInputValid = async ( export const isInputValid = async (
input: InputElement, input: InputElement,
valueOverride?: string valueOverride?: string
@@ -360,7 +385,7 @@ export const isInputValid = async (
return true; return true;
}; };
// isFormValid checks all input elements in a form element against their validation rules. /** isFormValid checks all input elements in a form element against their validation rules. */
export const isFormValid = async (form: HTMLFormElement): Promise<boolean> => { export const isFormValid = async (form: HTMLFormElement): Promise<boolean> => {
const inputs = form.querySelectorAll(QUERY_INPUTS); const inputs = form.querySelectorAll(QUERY_INPUTS);
@@ -375,8 +400,10 @@ export const isFormValid = async (form: HTMLFormElement): Promise<boolean> => {
return valid; return valid;
}; };
// isValueValid checks a value against a set of validation rules. Note: node-based /**
// custom validator functions are not supported by this function. * isValueValid checks a value against a set of validation rules. Note: node-based
* custom validator functions are not supported by this function.
*/
export const isValueValid = async (val: string, opts: ValidatorOptions): Promise<boolean> => { export const isValueValid = async (val: string, opts: ValidatorOptions): Promise<boolean> => {
console.debug('isValueValid', `val="${val}"`, 'opts:', opts); console.debug('isValueValid', `val="${val}"`, 'opts:', opts);
// if input is required and empty, return false // if input is required and empty, return false
@@ -424,15 +451,17 @@ export const isValueValid = async (val: string, opts: ValidatorOptions): Promise
return true; return true;
}; };
// getid returns the validator ID attached to an input element. Returns -1 if /**
// no ID is attached. * getid returns the validator ID attached to an input element. Returns -1 if
* no ID is attached.
*/
export const getid = (input: InputElement): number => { export const getid = (input: InputElement): number => {
const idstr = input.dataset[DATA_ID] || '-1'; const idstr = input.dataset[DATA_ID] || '-1';
const id = parseInt(idstr); const id = parseInt(idstr);
return id; return id;
}; };
// getopts returns the validation rules attached to an input element. /** getopts returns the validation rules attached to an input element. */
export const getopts = (input: InputElement): ValidatorOptions | undefined => { export const getopts = (input: InputElement): ValidatorOptions | undefined => {
const id = getid(input); const id = getid(input);
return validators[id]; return validators[id];

View File

@@ -1,3 +1,13 @@
/**
* Sanitizes a FormData value by key, returning a trimmed string.
* If the key is not present, it throws an error or returns a fallback value.
* @param data - The FormData object to sanitize.
* @param key - The key to look for in the FormData.
* @param fallback - Optional fallback value to return if the key is not found.
* @returns The sanitized string value or the fallback value.
* @throws Error if the key is required and not found.
* @template Fallback - The type of the fallback value.
*/
export function sanitize(data: FormData, key: string): string; export function sanitize(data: FormData, key: string): string;
export function sanitize<Fallback>( export function sanitize<Fallback>(
data: FormData, data: FormData,