svelte-formup
Version:
form helpers for svelte
660 lines (656 loc) • 22.9 kB
TypeScript
/** Callback to inform of a value updates. */
declare type Subscriber<T> = (value: T) => void;
/** Unsubscribes from value updates. */
declare type Unsubscriber = () => void;
/** Callback to update a value. */
declare type Updater<T> = (value: T) => T;
/** Cleanup logic callback. */
declare type Invalidator<T> = (value?: T) => void;
/**
* Svelte readable store contract.
*/
interface Readable<T> {
/**
* Subscribe on value changes.
* @param run - subscription callback
* @param invalidate - cleanup callback
* @returns function to unsubscribe
*/
subscribe(run: Subscriber<T>, invalidate?: Invalidator<T>): Unsubscriber;
}
/**
* Svelte writable store contract.
*/
interface Writable<T> extends Readable<T> {
/**
* Set value and inform subscribers.
* @param value - to set
*/
set(value: T): void;
/**
* Update value using callback and inform subscribers.
* @param updater - callback
*/
update(updater: Updater<T>): void;
}
interface SvelteActionResult<P> {
update?: (parameters?: P) => void;
destroy?: () => void;
}
/**
* A [svelte action](https://svelte.dev/docs#use_action) to be applied on a element
*/
declare type SvelteAction<P> = (node: Element, parameters?: P) => SvelteActionResult<P>;
/**
* A DOM event. Common events are:
*
* - [change](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event)
* - [input](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event)
* - [focus](https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event)
* - [blur](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event)
*/
declare type EventName = keyof GlobalEventHandlersEventMap | string;
/**
* Options for {@link FormupSchema.validate} and {@link FormupSchema.validateAt}.
*/
interface ValidateOptions<Values = Record<string, unknown>, State = Record<string, unknown>> {
/**
* Only validate the input, and skip and coercion or transformation.
*
* @defaultvalue true
*/
strict?: boolean;
/**
* Return from validation methods on the first error rather than after all validations run.
*
* @defaultvalue true
*/
abortEarly?: boolean;
/**
* Remove unspecified keys from objects.
*
* @defaultvalue true
*/
stripUnknown?: boolean;
/**
* When false validations will not descend into nested schema (relevant for objects or arrays).
*
* @defaultvalue true
*/
recursive?: boolean;
/**
* Any context needed for validating schema conditions.
*/
context: ValidateContext<Values, State>;
}
/**
* Type of {@link ValidateOptions.context}.
*/
interface ValidateContext<Values = Record<string, unknown>, State = Record<string, unknown>> {
/**
* The formup context.
*/
formup: FormupContext<Values, State>;
/**
* To be notified if the validation is longer needed.
*/
signal: AbortSignal;
/**
* The event that caused the validation.
*/
event?: Event;
}
/**
* A [yup](https://www.npmjs.com/package/yup) like schema to perform validation.
*/
interface FormupSchema<Values = Record<string, unknown>, State = Record<string, unknown>> {
/**
* Returns the value (a cast value if `options.strict` is false) if the value is valid,
* and throws the errors otherwise.
*
* This method is asynchronous and returns a Promise object, that is fulfilled with the value, or rejected with a ValidationError.
*
* @param value - the data to validate
* @param options - an object hash containing any schema options you may want to override (or specify for the first time).
* @throws ValidationError
*/
validate(value: unknown, options?: ValidateOptions<Values, State>): Promise<Values>;
/**
* Validate a deeply nested path within the schema. Similar to how reach works, but uses the resulting schema as the subject for validation.
*
* @param path - to validate
* @param value - the root value relative to the starting schema, not the value at the nested path.
* @param options - an object hash containing any schema options you may want to override (or specify for the first time).
* @throws ValidationError
*/
validateAt(path: string, value: Values, options?: ValidateOptions<Values, State>): Promise<Values>;
}
/**
* CSS class name mapping used {@link FormupContext.validity}.
*
* These can be overriden using {@link FormupOptions.classes} when invoking {@link formup}
* or {@link FormupContext.validity}.
*
* - on form: `.is-{dirty,pristine,error,validating,submitting,submitted}`
* - on form elements (controls & fieldsets):
* - `.is-{dirty,pristine,error,success,validating}`
* - `:valid` & `:invalid`
* - other elements: `has-{dirty,pristine,error,success,validating}`
*
* - dirty: field or one child dirty - `$dirty.has(field)`
* - pristine: field or all childs pristine - `!$dirty.has(field)`
* - error: field or one child - `$dirty.has(field) && !$validating.has(field) && $errors.has(field)`
* - success: field or all child - `$dirty.has(field) && !$validating.has(field) && !\$errors.has(field)`
* - validating: `$validating.has(field)`
*/
interface ValidityCSSClasses {
/**
* Set on the form if it is submitting.
* @defaultvalue `"is-submitting"`
*/
readonly ['is-submitting']?: string;
/**
* Set on the form if it is has been submitted.
* @defaultvalue `"is-submitted"`
*/
readonly ['is-submitted']?: string;
/**
* Set on the element if it or any of its children is dirty.
* @defaultvalue `"is-dirty"`
*/
readonly ['is-dirty']?: string;
/**
* Set on the element if it or all its children is pristine.
* @defaultvalue `"is-pristine"`
*/
readonly ['is-pristine']?: string;
/**
* Set on the element if it or any of its children: `$dirty.has(field) && !$validating.has(field) && $errors.has(field)`
* @defaultvalue `"is-error"`
*/
readonly ['is-error']?: string;
/**
* Set on the element if it or all of its children: `$dirty.has(field) && !$validating.has(field) && !$errors.has(field)`
* @defaultvalue `"is-success"`
*/
readonly ['is-success']?: string;
/**
* Set on the element if it or any of its children is validating.
* @defaultvalue `"is-validating"`
*/
readonly ['is-validating']?: string;
/**
* Set on non form element if it or any of its children is dirty.
* @defaultvalue `"has-dirty"`
*/
readonly ['has-dirty']?: string;
/**
* Set on non form element if it or all its children is pristine.
* @defaultvalue `"has-pristine"`
*/
readonly ['has-pristine']?: string;
/**
* Set on non form element if any of its children
* @defaultvalue `"has-error"`
*/
readonly ['has-error']?: string;
/**
* Set on non form element if all of its children
* @defaultvalue `"has-success"`
*/
readonly ['has-success']?: string;
/**
* Set on non form element if it or any of its children is validating.
* @defaultvalue `"has-validating"`
*/
readonly ['has-validating']?: string;
}
/**
* Provides to all form properties.
*/
interface FormupContext<Values = Record<string, unknown>, State = Record<string, unknown>> {
/**
* A [yup](https://www.npmjs.com/package/yup) like schema to perform validation.
*/
readonly schema: FormupSchema<Values, State>;
/**
* The form values as a svelte store.
*
* ```html
* <input id="email" bind:value="{$values.email}" />
* ```
*/
readonly values: Writable<Partial<NonNullable<Values>>>;
/**
* A top-level status object that you can use to represent form state that can't otherwise be expressed/stored with other methods.
*
* This is useful for capturing and passing through API responses to your inner component.
*/
readonly state: Writable<State>;
/**
* Whole form error, not associated with any field
*/
readonly formError: Readable<ValidationError | undefined>;
/**
* The form errors keyed by field path as a svelte store.
*
* If a validate function is provided to a field, then when it is called this map will be modified.
*
* ```html
* {#if $errors.has(email)} $errors.get(email).message {/if}
* ```
*/
readonly errors: Readable<ReadonlyMap<string, ValidationError>>;
/**
* The dirty fields by path as a svelte store.
*
* This allows to show errors conditionally if the user has already visited that field.
*
* ```html
* {#if $dirty.has(email) && $errors.has(email)} $dirty.get(email).message {/if}
* ```
*/
readonly dirty: Readable<ReadonlySet<string>>;
/**
* The currently validating fields by path as a svelte store.
*
* This allows to show a spinner if a field is validated.
*
* ```html
* {#if $validating.has(email)}<Spinner />{/if}
* ```
*/
readonly validating: Readable<ReadonlySet<string>>;
/**
* The valid, meaning dirty and not validating and no error, fields by path as a svelte store.
*/
readonly valid: Readable<ReadonlySet<string>>;
/**
* The invalid, meaning dirty and not validating and error, fields by path as a svelte store.
*
* ```html
* {#if $invalid.has(email)} $invalid.get(email).message {/if}
* ```
*/
readonly invalid: Readable<ReadonlyMap<string, ValidationError>>;
/**
* Determines if the whole form is validating (most likely because of a submit).
*
* This does not reflect individual field validation triggered by validateAt.
*
* When this becames `true` the {@link ValidityCSSClasses.validating} CSS class is added to the form.
*/
readonly isValidating: Readable<boolean>;
/**
* Determines if the form is submitting (most likely because of a submit).
*
* When this becames `true` the {@link ValidityCSSClasses.submitting} CSS class is added to the form.
*/
readonly isSubmitting: Readable<boolean>;
/**
* Determins if the form has been succesfully submitted.
*
* When this becames `true` the {@link ValidityCSSClasses.submitted} CSS class is added to the form.
*/
readonly isSubmitted: Readable<boolean>;
/**
* Number of times the form was submitted.
*
* Resetted to zero after a succesful {@link FormupContext.submit} or a {@link FormupContext.reset}.
*/
readonly submitCount: Readable<number>;
/**
* Boolean that is true when form is pristine.
*
* A form is pristine when it has not been touched && no values have been entered in any field.
*
* When this becames `true` the {@link ValidityCSSClasses.pristine} CSS class is added to the form.
*/
readonly isPristine: Readable<boolean>;
/**
* Boolean that is true when pristine is false
*
* When this becames `true` the {@link ValidityCSSClasses.dirty} CSS class is added to the form.
*/
readonly isDirty: Readable<boolean>;
/**
* Determines if the whole form is valid.
*
* When this becames `true` the {@link ValidityCSSClasses.valid} CSS class is added to the form.
*/
readonly isError: Readable<boolean>;
/**
* This function will submit the form and trigger some lifecycle events.
*
* 1. abort all active field validation
* 1. call {@link FormupSchema.validate}.
* 2. call {@link FormupOptions.onSubmit} if the form is valid.
*
* @remarks
* This function can be called manually however it is also called if you
* have a `<button type='submit'>` within the `<form>`.
*
* @remarks
* Repeated invocation while there is an active submit have no effect (eg are ignored).
*/
readonly submit: (event?: Event) => Promise<void>;
/**
* Function that will reset the form to its initial state.
*
* This will abort all active field validation, reset all stores and call {@link FormupOptions.onReset}.
*
* This may have no effect (eg is ignored) if there is an active submit.
*/
readonly reset: (event?: Event) => void;
/**
* Set the form error manually.
*
* If `error` is falsey means deleting the path from the store.
* @param error - to set
*/
readonly setFormError: (error?: ValidationError | undefined | null | false) => void;
/**
* Set the error message of a field imperatively.
*
* @param path - should match the key of errors you wish to update. Useful for creating custom input error handlers.
* @param error - to set; falsey means deleting the path from the store.
*/
readonly setErrorAt: (path: string, error?: ValidationError | undefined | null | false) => void;
/**
* Set the dirty state of a field imperatively.
*
* @param path - should match the key of dirty you wish to update. Useful for creating custom input handlers.
* @param dirty - to set; falsey means deleting the path from the store.
*/
readonly setDirtyAt: (path: string, dirty?: boolean) => void;
/**
* Set the validating state of a field imperatively.
*
* @param path - should match the key of validating you wish to update. Useful for creating custom input handlers.
* @param validating - to set; falsey means deleting the path from the store.
*/
readonly setValidatingAt: (path: string, validating?: boolean) => void;
/**
* Imperatively call field's validate function if specified for given field.
*/
readonly validateAt: (path: string, options?: ValidateAtOptions) => void;
/**
* A [svelte action](https://svelte.dev/docs#use_action) to validate the element and all its form children it is applied to.
*
* ```html
* <script>
* import { formup } from 'svelte-formup'
*
* const { validate } = formup(options)
* </script>
*
* <form use:validate>
* <!-- .... --->
*
* <input use:validate={{ on: 'input' }}>
*
* <!-- .... --->
* </form>
* ```
*
* @remarks
* The {@link FormupContext.validity} action is applied automatically on that node.
*/
readonly validate: SvelteAction<undefined | string | ValidateActionOptions>;
/**
* A [svelte action](https://svelte.dev/docs#use_action) to update the validity state of
* the element and all its form children it is applied to.
*
* That means updating `setCustomValidity`, `aria-invalid` and the {@link ValidityCSSClasses}.
*
* ```html
* <script>
* import { formup } from 'svelte-formup'
*
* const { validate, validity } = formup(options)
* </script>
*
* <form use:validate>
* <fieldset use:validity>
* <!-- .... --->
* </fieldset>
* </form>
* ```
*/
readonly validity: SvelteAction<undefined | string | ValiditiyActionOptions>;
/**
* Which events should trigger a validation.
*/
readonly validateOn: readonly EventName[];
/**
* Which events should mark a field as dirty.
*/
readonly dirtyOn: readonly EventName[];
/**
* Timeout in milliseconds after which a validation should start.
*/
readonly debounce: number;
/**
* The used CSS class overrides.
*/
readonly classes: ValidityCSSClasses;
/**
* Sets the `autocomplete` on the form.
*/
readonly autocomplete: string;
}
/**
* Options for {@link FormupContext.validateAt}.
*/
interface ValidateAtOptions {
/**
* Timeout in milliseconds after which a validation should start.
*
* @defaultvalue {@link FormupContext.debounce}
*/
debounce?: number;
}
/**
* Options for {@link FormupContext.validate}.
*/
interface ValidateActionOptions {
/**
* What field to validate.
*
* ```html
* <input use:validate={{ at: 'email' }}>
* ```
*
* If `at` is not provided and the element is not a form it defaults to:
*
* - the [data attribute](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) `pathAt`
* - the `name` attribute
* - the path of the element referenced by the `for` attribute
* - the `id` attribute
*
* If the only option is `at` it can be used directly:
*
* ```html
* <input use:validate={'email'}>
* ```
*
* If no field has been found it validates itself and all its children.
*/
at?: string | string[];
/**
* Override which events should trigger a validation and mark a field as dirty.
*/
on?: EventName | EventName[];
/**
* Override which events should trigger a validation.
*
* ```html
* <input use:validate={{ validateOn: ['input', 'change'] }}>
*
* <input use:validate={{ validateOn: 'blur' }}>
* ```
*
* @defaultvalue {@link ValidateActionOptions.on}, {@link FormupContext.validateOn}
*/
validateOn?: EventName | EventName[];
/**
* Override which events should mark a field as dirty.
* @defaultvalue {@link ValidateActionOptions.on}, {@link FormupContext.dirtyOn}
*/
dirtyOn?: EventName | EventName[];
/**
* Timeout in milliseconds after which a validation should start.
* @defaultvalue {@link FormupContext.debounce}
*/
debounce?: number;
}
/**
* Options for {@link FormupContext.validity}.
*/
interface ValiditiyActionOptions {
/**
* For which field to track the validity status.
*
* If the element is a form it uses `isValid` and `isDirty` stores to determines the validity.
*
* ```html
* <input use:validity={{ at: 'email' }}>
* ```
*
* If `at` is not provided and the element is not a form it defaults to:
*
* - the [data attribute](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) `pathAt`
* - the `name` attribute
* - the path of the element referenced by the `for` attribute
* - the `id` attribute
*
* If the only option is `at` it can be used directly:
*
* ```html
* <input use:validity={'email'}>
* ```
*
* If no field has been found it updates the validity for itself and all its children.
*/
at?: string | string[];
/**
* Allow to override the used CSS classes.
*/
classes?: Partial<ValidityCSSClasses>;
}
/**
* Represents a validation error.
*/
interface ValidationError extends Error {
name: string;
message: string;
value?: unknown;
/**
* A string, indicating where there error was thrown. path is empty at the root level.
*/
path?: string;
type?: unknown;
/**
* An array of error messages
*/
errors?: string[];
/**
* In the case of aggregate errors, inner is an array of ValidationErrors throw earlier in the validation chain.
*/
inner?: ValidationError[];
params?: Record<string, unknown>;
}
//# sourceMappingURL=index.d.ts.map
/**
* Returns the FormupContext.
*
* ```html
* <script>
* import { getFormupContext } from 'svelte-formup'
* const context = getFormupContext()
* </script>
* ```
*/
declare const getFormupContext: <Values = Record<string, unknown>, State = Record<string, unknown>>() => FormupContext<Values, State>;
interface FormupOptions<Values = Record<string, unknown>, State = Record<string, unknown>> {
/**
* A [yup](https://www.npmjs.com/package/yup) like schema to perform validation.
*
* The object does not have to be yup schema. This allows custom validation without using yup.
*/
schema: FormupSchema<Values>;
/**
* A function that gets called when form is submitted successfully. The function receives the values as a parameter.
*
* @throws An thrown error is passed to {@link FormupContext.setFormError}.
*/
onSubmit?: (values: Values, context: FormupContext<Values, State>, event?: Event & {
submitter?: HTMLButtonElement | HTMLInputElement;
}) => void | Promise<void>;
/**
* A function that gets called when form is resetted.
*/
onReset?: (context: FormupContext<Values, State>, event?: Event) => void;
/**
* A function called to initialize the form values on creation and reset. The default returns an empty object.
*
* @defaultvalue `() => Object.create(null)`
*/
getInitialValues?: (event?: Event) => Partial<NonNullable<Values>>;
/**
* Use this option to run validations each time after {@link FormupOptions.getInitialValues} has been called.
*
* @defaultvalue `false`
*/
validateInitialValues?: boolean;
/**
* A top-level status object that you can use to represent form state that can't otherwise be expressed/stored with other methods.
*
* This is useful for capturing and passing through API responses to your inner component.
*
* @defaultvalue `Object.create(null)`
*/
state?: State;
/**
* Which events should trigger a validation.
* @defaultvalue `"change"`
*/
validateOn?: EventName | EventName[];
/**
* Which events should mark a field as {@link FormupContext.dirty}.
* @defaultvalue {@link FormupOptions.validateOn}
*/
dirtyOn?: EventName | EventName[];
/**
* Timeout in milliseconds after which field level validation should start.
*
* If platform is Node.JS this defaults to `0`.
* @defaultvalue `100`
*/
debounce?: number;
/**
* Allow to override the used CSS classes.
*/
classes?: ValidityCSSClasses;
/**
* Set the `autocomplete` attribute on the form.
*/
autocomplete?: string;
}
/**
* Creates and registers a new {@link FormupContext} using the options and returns it.
*
* ```html
* <script>
* import { formup } from 'svelte-formup'
*
* const context = formup(options)
* </script>
* ```
*
* @param options - to use
*/
declare const formup: <Values = Record<string, unknown>, State = Record<string, unknown>>({ schema, onSubmit, onReset, getInitialValues, validateInitialValues, state, validateOn, dirtyOn, debounce, classes, autocomplete, }: FormupOptions<Values, State>) => FormupContext<Values, State>;
//# sourceMappingURL=svelte-formup.d.ts.map
export { EventName, FormupContext, FormupOptions, FormupSchema, Invalidator, Readable, Subscriber, SvelteAction, SvelteActionResult, Unsubscriber, Updater, ValidateActionOptions, ValidateAtOptions, ValidateContext, ValidateOptions, ValidationError, ValiditiyActionOptions, ValidityCSSClasses, Writable, formup, getFormupContext };
//# sourceMappingURL=svelte-formup.d.ts.map