@kform/react
Version:
React integration for KForm.
265 lines (264 loc) • 12.3 kB
TypeScript
import { AbsolutePath, CancellablePromise, DisplayStatus, FormManager, Path, Schema, SealedFormManagerEvent, SealedLocatedValidationIssue, SealedValidationIssue, SealedValueEvent, StateEvent, ValidationStatus } from "@kform/core";
/** Options available to the {@link useController} hook. */
export interface ControllerOptions<T = unknown, TState extends ControllerState<T> = ControllerState<T>> {
/**
* Required if no form context is in scope.
*
* If a form context is in scope and this value is also provided, then the
* provided form manager will be used, in which case the current path of the
* form context is ignored.
*/
formManager?: FormManager;
/**
* Whether to enable the controller.
*
* @default true
*/
enabled?: boolean;
/**
* Default extra state.
*
* @internal
*/
_defaultState?: Partial<TState>;
/**
* Function called once the controller has been initialised.
*
* @param info Controller's info.
*/
onInitialized?: (state: TState & InitializedControllerState<T>) => void | PromiseLike<void>;
/** Function called whenever the controller is uninitialised. */
onUninitialized?: (state: TState & UninitializedControllerState<T>) => void;
/**
* Function called whenever an event matching the controller's path is
* emitted.
*
* @param event Form manager event.
*/
onFormManagerEvent?: (event: SealedFormManagerEvent, state: TState & InitializedControllerState<T>) => void | PromiseLike<void>;
onValueChange?: (event: SealedValueEvent, state: TState & InitializedControllerState<T>) => void | PromiseLike<void>;
onValidationStatusChange?: (event: StateEvent.ValidationChange, state: TState & InitializedControllerState<T>) => void | PromiseLike<void>;
onDisplayStatusChange?: (event: StateEvent.DisplayChange, state: TState & InitializedControllerState<T>) => void | PromiseLike<void>;
onDirtyStatusChange?: (event: StateEvent.DirtyChange, state: TState & InitializedControllerState<T>) => void | PromiseLike<void>;
onTouchedStatusChange?: (event: StateEvent.TouchedChange, state: TState & InitializedControllerState<T>) => void | PromiseLike<void>;
}
/** Controller for a value of the form. */
export interface Controller<T = unknown, TState extends ControllerState<T> = ControllerState<T>> {
/** Returns the current state of the controller. */
readonly getState: () => TState;
/**
* Sets the (non-private) state of the controller.
*
* @param state State to set.
* @internal
*/
readonly _setState: (state: Partial<TState> | ((state: TState) => Partial<TState>)) => void;
/**
* Subscribes to changes in the controller's state.
*
* @param selector Selector used to select which part of the controller's
* state to observe.
* @param listener Function called whenever the selected state changes.
* @param options Subscription options.
* @returns Function which should be called to unsubscribe.
*/
readonly subscribe: <TSelected = unknown>(selector: (state: TState) => TSelected, listener: (selectedState: TSelected, prevSelectedState: TSelected | undefined) => void, options?: ControllerSubscriptionOptions<TSelected>) => () => void;
/**
* Hook used to select part of the controller's state.
*
* A component using this hook is re-rendered whenever said part of the state
* changes.
*
* @param selector Selector used to select part of the controller's state.
*/
readonly useState: (() => TState) & (<TResult = unknown>(selector: (state: TState) => TResult, options?: ControllerUseStateOptions<TResult>) => TResult);
/** Hook which returns the form manager being used by the controller. */
readonly useFormManager: () => FormManager;
/** Hook which returns the schema of the value being controlled. */
readonly useSchema: () => Schema<T>;
/**
* Hook which returns the path of the value being controlled by this
* controller.
*
* This path will not contain any provided recursive wildcards.
*/
readonly usePath: () => AbsolutePath;
/** Hook which returns the schema path of the value being controlled. */
readonly useSchemaPath: () => AbsolutePath;
/**
* Hook which returns whether the controller is currently observing
* descendants.
*/
readonly useObservingDescendants: () => boolean;
/** Hook which returns whether the controller has been initialised. */
readonly useInitialized: () => boolean;
/**
* Hook which returns whether a value exists at the path being controlled.
*
* Returns `undefined` when the controller is not initialised.
*/
readonly useExists: () => boolean | undefined;
/**
* Hook which returns the form value being controlled.
*
* Note that, when observing descendants, this hook will cause a component to
* rerender when a descendant is changed, even if the identity of the value
* hasn't changed.
*
* Returns `undefined` when the controller is not initialised.
*/
readonly useValue: () => T | undefined;
/**
* Hook which returns whether the value being controlled is dirty.
*
* Returns `undefined` when the controller is not initialised.
*/
readonly useDirty: () => boolean | undefined;
/**
* Hook which returns whether the value being controlled is touched.
*
* Returns `undefined` when the controller is not initialised.
*/
readonly useTouched: () => boolean | undefined;
/**
* Hook which returns the issues of the value being controlled.
*
* Returns `undefined` when the controller is not initialised.
*/
readonly useIssues: () => SealedValidationIssue[] | undefined;
/**
* Hook which returns the validation status of the value being controlled.
*
* Returns `undefined` when the controller is not initialised.
*/
readonly useValidationStatus: () => ValidationStatus | undefined;
/**
* Hook which returns the display status of the value being controlled.
*
* Returns `undefined` when the controller is not initialised.
*/
readonly useDisplayStatus: () => DisplayStatus | undefined;
readonly get: (<TValue = unknown, TResult = unknown>(path: Path | string, valueHandler: (value: TValue) => TResult | PromiseLike<TResult>) => CancellablePromise<TResult>) & (<TResult = unknown>(valueHandler: (value: T) => TResult | PromiseLike<TResult>) => CancellablePromise<TResult>);
readonly getClone: (() => CancellablePromise<T>) & (<TValue = unknown>(path?: Path | string) => CancellablePromise<TValue>);
readonly set: ((path: Path | string, toSet: unknown) => CancellablePromise<void>) & ((toSet: T) => CancellablePromise<void>);
readonly reset: (path?: Path | string) => CancellablePromise<void>;
readonly remove: (path?: Path | string) => CancellablePromise<void>;
readonly validate: (path?: Path | string) => CancellablePromise<SealedLocatedValidationIssue[]>;
readonly setDirty: (path?: Path | string) => CancellablePromise<void>;
readonly setPristine: (path?: Path | string) => CancellablePromise<void>;
readonly setTouched: (path?: Path | string) => CancellablePromise<void>;
readonly setUntouched: (path?: Path | string) => CancellablePromise<void>;
}
/** Options available to the controller's `useState` hook. */
export interface ControllerUseStateOptions<T = unknown> {
/**
* Function used to specify when two selections are considered equal to each
* other.
*
* @param v1 First selection.
* @param v2 Second selection.
* @returns Whether the two selections are considered equal.
*/
equalityFn?: (v1: T, v2: T) => boolean;
}
/** Options available when subscribing to the state of the controller. */
export interface ControllerSubscriptionOptions<T = unknown> {
/**
* Function used to specify when two selections are considered equal to each
* other.
*
* @param v1 First selection.
* @param v2 Second selection.
* @returns Whether the two selections are considered equal.
*/
equalityFn?: (v1: T, v2: T) => boolean;
/** Whether the subscription's listener should be invoked immediately. */
fireImmediately?: boolean;
}
/** Controller's state. */
export type ControllerState<T = unknown> = UninitializedControllerState<T> | InitializedControllerState<T>;
/** Base controller's state. */
export interface BaseControllerState<T = unknown> {
/** Form manager being used by the controller. */
readonly formManager: FormManager;
/** Schema of the form value being controlled. */
readonly schema: Schema<T>;
/** Path of the form value being controlled. */
readonly path: AbsolutePath;
/** Schema path of the form value being controller. */
readonly schemaPath: AbsolutePath;
/**
* Whether the controller is observing descendants.
*
* This will be `true` when the path provided to the controller ends in a
* recursive wildcard.
*/
readonly observingDescendants: boolean;
/** Whether the controller has been initialised. */
readonly initialized: boolean;
/** Whether the form value being controlled exists. */
readonly exists: boolean | undefined;
/** Form value. */
readonly value: T | undefined;
/** Whether the form value being controlled is dirty. */
readonly dirty: boolean | undefined;
/** Whether the form value being controlled has been touched. */
readonly touched: boolean | undefined;
/** Validation issues associated with the form value being controlled. */
readonly issues: SealedValidationIssue[] | undefined;
/** Validation status of the form value being controlled. */
readonly validationStatus: ValidationStatus | undefined;
/** Display status of the form value being controlled. */
readonly displayStatus: DisplayStatus | undefined;
}
/** Uninitialised controller's state. */
export interface UninitializedControllerState<T = unknown> extends BaseControllerState<T> {
readonly initialized: false;
readonly exists: undefined;
readonly value: undefined;
readonly dirty: undefined;
readonly touched: undefined;
readonly issues: undefined;
readonly validationStatus: undefined;
readonly displayStatus: undefined;
}
/** Initialized controller's state. */
export type InitializedControllerState<T = unknown> = NonexistentValueControllerState<T> | ExistingValueControllerState<T>;
/** Controller state of a nonexistent form value. */
export interface NonexistentValueControllerState<T = unknown> extends BaseControllerState<T> {
readonly initialized: true;
readonly exists: false;
readonly value: undefined;
readonly dirty: undefined;
readonly touched: undefined;
readonly issues: undefined;
readonly validationStatus: undefined;
readonly displayStatus: undefined;
}
/** Controller state of an existing form value. */
export interface ExistingValueControllerState<T = unknown> extends BaseControllerState<T> {
readonly initialized: true;
readonly exists: true;
readonly value: T;
readonly dirty: boolean;
readonly touched: boolean;
readonly issues: SealedValidationIssue[];
readonly validationStatus: ValidationStatus;
readonly displayStatus: DisplayStatus;
}
/**
* Hook providing access to a controller used to read and control a value of the
* form.
*
* @param path Path of the form value to control, relative to the current path.
*
* The path must consist of only identifiers, except for the last fragment,
* which may be a recursive wildcard to indicate that descendants should also
* be observed.
* @param options Available options.
* @returns A controller used to read and control the form value.
* @throws {Error} When {@link path} is invalid or contains fragments other than
* ids.
*/
export declare function useController<T = unknown>(path?: Path | string, options?: undefined): Controller<T>;
export declare function useController<T = unknown, TState extends ControllerState<T> = ControllerState<T>>(path: Path | string | undefined, options: ControllerOptions<T, TState>): Controller<T, TState>;