UNPKG

@qundus/qform

Version:

The only form library you'll ever need :)

1,139 lines (1,126 loc) 46.6 kB
import * as _QSTATE from '@qundus/qstate'; import { deriveAddon, hooksInUseAddon } from '@qundus/qstate/addons'; declare const IGNORED_SETUP_KEYS: Pick<Field.Setup, "type" | "validate" | "value" | "onMount" | "onChange" | "onAttrs" | "labelReplace" | "props" | "select" | "checkbox" | "tel" | "date" | "attrs">; declare namespace FORM { enum STATUS { INIT = 0, IDLE = 1, INCOMPLETE = 2, ERROR = 3, VALID = 4, SUBMIT = 5 } } declare namespace FIELD { enum CYCLE { INIT = 0, IDLE = 1, SUBMIT = 2, LOAD = 3, SKELETON = 4 } enum DOM { INIT = "DOM.INIT", IDLE = "DOM.IDLE", FOCUS = "DOM.FOCUS", BLUR = "DOM.BLUR", INPUT = "DOM.INPUT", CHANGE = "DOM.CHANGE", CLICK = "DOM.CLICK", CLICK_OPTION = "DOM.CLICK.OPTION", CLICK_DATE_EVENT = "DOM.CLICK.DATE.EVENT", CLICK_DATE_CELL = "DOM.CLICK.DATE.CELL", CLICK_DATE_OPTION = "DOM.CLICK.DATE.OPTION", FILE_PROGRESS = "DOM.FILE.PROGRESS", FILE_DONE = "DOM.FILE.DONE" } enum MUTATE { INIT = "MUTATE.INIT", IDLE = "MUTATE.IDLE", VALUE = "MUTATE.VALUE", CONDITION = "MUTATE.CONDITION", ELEMENT = "MUTATE.ELEMENT", CYCLE = "MUTATE.CYCLE", PROPS = "MUTATE.PROPS", EXTRAS = "MUTATE.EXTRAS", __EXTRAS = "MUTATE.__EXTRAS", __ATTRIBUTE = "MUTATE.__ATTRIBUTE", __RESET = "MUTATE.__RESET", __ABORT_VALIDATION = "MUTATE.__ABORT_VALIDATION" } enum ATTRIBUTE { INIT = 0, READY = 1 } } declare namespace MISC { const PLACEHOLDERS: { readonly select: { value: string; disabled: boolean; selected: boolean; }; readonly selectButton: { value: string; }; }; const COUNTRIES: { name: string; flag: string; code: string; dial_code: string; }[]; } declare namespace CALENDAR { /** * calendar modes, by default this is calculated based on the format. * if a mode is not offered by the format it won't be displayed and the next * logical mode is going to take place */ enum MODE { YEAR = 0, MONTH = 1, DAY = 2, HOUR = 3, MINUTE = 4, SECOND = 5 } enum MODE_TYPE { DATE = "DATE", TIME = "TIME" } enum OPTIONS { TIME_PERIOD = 11 } enum EVENTS { NAV_PREV = 11, NAV_NEXT = 12, MODE_YEARS = 21, MODE_MONTHS = 22, MODE_DAYS = 23 } } type Configs = { preprocess?: boolean; validate?: boolean; }; interface FieldAddonUpdate<S extends Field.Setup, O extends Form.Options, V = S["value"]> { value: { (value: V | undefined | null, configs?: Pick<Configs, "preprocess" | "validate">): void; (value: (prev: V) => V | undefined | null, configs?: Pick<Configs, "preprocess" | "validate">): void; }; condition: (value: Partial<Field.Condition> | ((prev: Field.Condition) => Partial<Field.Condition>)) => void; element: (value: Partial<Field.Element<S>> | ((prev: Field.Element<S>) => Partial<Field.Element<S>>), configs?: { validate?: boolean; }) => void; props: <G extends S["props"]>(value: Partial<G> | ((prev: G) => Partial<G> | undefined) | undefined) => void; /** * update current cycle to send signal to field listeners of what cycle this field * is in. * @param cycle the requested cycle, updated immediatly. * @returns a function to end the requested cycle and go back to CHANGE cycle only if no * other requests have been made */ cycle: <G extends FIELD.CYCLE>(cycle: G | undefined | null) => G extends FIELD.CYCLE.IDLE ? void : () => void; select: <E extends Extras.Select.In>(props: Partial<E> | ((prev: E) => Partial<E>)) => void; selectByValue: (values: any | any[]) => void; selectByIndex: (indecies: number | number[]) => void; checkbox: <E extends Extras.Checkbox.In>(props: Partial<E> | ((prev: E) => Partial<E>)) => void; tel: (props: { country?: (typeof MISC.COUNTRIES)[number] | string; value?: string; }, configs?: { preprocess?: boolean; validate?: boolean; }) => void; date: (props: Partial<Extras.Date.In>, configs?: { preprocess?: boolean; validate?: boolean; }) => void; } type FieldAddonRemove<_S extends Field.Setup, _O extends Form.Options> = { option: (option: any) => void; optionByIndex: (index: number) => void; }; type FieldAddonReset<_S extends Field.Setup, _O extends Form.Options> = { /** reset field to setup.value, use configs.clear to force clearing = undefined */ value: (configs?: { clear?: boolean; }) => void; tel: (configs?: { clear?: boolean; keepCountry?: boolean; }) => void; /** reset all data to field start setup */ origin: () => void; }; type FormAddonSubmit<F extends Form.Fields, O extends Form.Options<F>> = ReturnType<typeof formSubmitAddon<F, O>>; declare function formSubmitAddon<F extends Form.Fields, O extends Form.Options<F>>(props: FunctionProps.FormAddon<F, O>): { /** * check if it's possible to submit or not, this checks if all required * non-hidden fields have been fulfilled. */ possible: () => boolean; /** * you can start submission by calling this function and control it's end * with the returned function, as opposed to task which controls * everything internally. * @returns a function to end submission, must be called */ start: () => (() => void) | null; /** * a submission cycle internally controlled and allows access to * result after it's fulfillment, as opposed to start function which * gives total freedom. * @param runner the method to run for submission, maybe an api call or something. * @returns */ task: <W, E = Record<string, any>>(runner: () => W | Promise<W>) => Promise<[W, null] | [null, { message: string; } | E]>; }; type FormAddonUpdate<F extends Form.Fields, O extends Form.Options<F>> = ReturnType<typeof formUpdateAddon<F, O>>; declare function formUpdateAddon<F extends Form.Fields, O extends Form.Options<F>>(props: FunctionProps.FormAddon<F, O>): { values: <G extends Object>(values: G, paths?: Record<string, string | { value: string; key?: string; }>, configs?: { preprocess?: boolean; }) => void; conditions: <G extends Record<keyof F, Partial<Field.Condition>>>(conditions: G) => void; elements: <G extends Record<keyof F, Field.Element<any>>>(elements: G) => void; elementsForAll: (element: Partial<Field.Element<any>>) => void; props: <G extends O["props"]>(props: Partial<G>) => void; }; type FormAddonValues<F extends Form.Fields, O extends Form.Options<F>> = ReturnType<typeof formValuesAddon<F, O>>; declare function formValuesAddon<F extends Form.Fields, O extends Form.Options<F>>(props: FunctionProps.FormAddon<F, O>): { get<RV extends FieldsReturnedValues<F>, KC extends FieldsKeysCaseMap<F>>(rv?: RV, special?: KC): FieldsValues<F, "same", KC>; getLowercase<KC extends FieldsKeysCaseMap<F>>(special?: KC): FieldsValues<F, "lowercase", KC>; getUppercase<KC extends FieldsKeysCaseMap<F>>(special?: KC): FieldsValues<F, "uppercase", KC>; getSnakecase<KC extends FieldsKeysCaseMap<F>>(special?: KC): FieldsValues<F, "snake", KC>; getSnakecaseAggressive<KC extends FieldsKeysCaseMap<F>>(special?: KC): FieldsValues<F, "snake_aggressive", KC>; getKebabcase<KC extends FieldsKeysCaseMap<F>>(special?: KC): FieldsValues<F, "kebab", KC>; getKebabcaseAggressive<KC extends FieldsKeysCaseMap<F>>(special?: KC): FieldsValues<F, "kebab_aggressive", KC>; }; type FieldsKeysCaseMap<T extends Form.Fields> = { [K in keyof T]?: FieldKeyCase; }; type FieldsReturnedValues<T extends Form.Fields> = { [K in keyof T as T[K]["setup"]["type"] extends "select" | "select.radio" ? K : never]?: "index" | "value"; }; type CamelToSnakeCase<S extends string, Sep extends string = "_", UppercaseIsNewWord extends boolean = false, Start extends boolean = true, PreviousLetterIsCapital = false> = S extends `${infer T}${infer U}` ? `${T extends Capitalize<T> ? Start extends false ? UppercaseIsNewWord extends true ? Sep : PreviousLetterIsCapital extends false ? Sep : "" : "" : ""}${Lowercase<T>}${T extends Capitalize<T> ? CamelToSnakeCase<U, Sep, UppercaseIsNewWord, false, true> : CamelToSnakeCase<U, Sep, UppercaseIsNewWord, false, false>}` : S; type FieldKeyCase = "same" | "snake" | "snake_aggressive" | "kebab" | "kebab_aggressive" | "lowercase" | "uppercase"; type IsValueKeyOfCase<T extends FieldKeyCase, V extends FieldKeyCase | undefined> = true extends Check.IsUnknown<V> | Check.IsUndefined<V> | Check.IsNull<V> ? false : V extends T ? true : false; type ValueKeyCaseType<K, V extends FieldKeyCase | undefined> = IsValueKeyOfCase<"snake", V> extends true ? CamelToSnakeCase<string & K> : IsValueKeyOfCase<"snake_aggressive", V> extends true ? CamelToSnakeCase<string & K, "_", true> : IsValueKeyOfCase<"kebab_aggressive", V> extends true ? CamelToSnakeCase<string & K, "-", true> : IsValueKeyOfCase<"kebab", V> extends true ? CamelToSnakeCase<string & K, "-"> : IsValueKeyOfCase<"lowercase", V> extends true ? Lowercase<string & K> : IsValueKeyOfCase<"uppercase", V> extends true ? Uppercase<string & K> : K; type GetValueKeyCase<V extends FieldKeyCase | undefined, Default extends FieldKeyCase> = true extends Check.IsUnknown<V> | Check.IsUndefined<V> | Check.IsNull<V> ? Default : FieldKeyCase extends V ? Default : V; type FieldsValues<T extends Form.Fields, V extends FieldKeyCase, O extends FieldsKeysCaseMap<T>> = { [K in keyof T as ValueKeyCaseType<K, GetValueKeyCase<O[K], V>>]: T[K]["setup"]["value"]; }; type ButtonObject = { status: FORM.STATUS; disabled: boolean; canSubmit: boolean; submitting: boolean; }; type ButtonStore<F extends Form.Fields, O extends Form.Options<F>> = _QSTATE.StoreDerived.Factory<ButtonObject, { hooks: O["storeHooks"]; }>; type FormAddonButton<F extends Form.Fields, O extends Form.Options<F>> = { store: ButtonStore<F, O>; }; type SelectedList = ReturnType<typeof createSelectedList>; declare function createSelectedList(extras: Extras.Date.Out<any>): { list: Extras.Date.ParsedResult[]; readonly validCount: number; requiresDate: boolean; requiresTime: boolean; append: (parsed: Extras.Date.ParsedResult) => void; hasYear: (year: number) => boolean; hasYearMonth: (year: number, month: number) => boolean; hasDate: (year: number, month: number, day: number) => boolean; populateSelectedTime(year: number, month: number, day: number): { list: Extras.Date.ParsedTime[]; hasHour: (_hour: number) => boolean; hasHourMinute: (_hour: number, minute: number) => boolean; hasTime: (_hour: number, minute: number, second: number) => boolean; }; }; type DateAttributeCells = { YEAR?: Extras.Date.CellDate; MONTH?: Extras.Date.CellDate; DAY?: Extras.Date.CellDate; HOUR?: Extras.Date.CellTime; MINUTE?: Extras.Date.CellTime; SECOND?: Extras.Date.CellTime; }; declare namespace Check { type IsUnknown<T> = unknown extends T ? true : false; type IsUndefined<T> = Exclude<T, undefined> extends never ? true : false; type IsNull<T> = Exclude<T, null> extends never ? true : false; type IsEmpty<T> = {} extends T ? true : false; type IsSetupOfType<S extends Field.Setup, T extends Field.Type> = S extends { type: T; } ? true : never; } declare namespace Field { type Type = "checkbox" | "color" | "date" | "email" | "file" | "image" | "month" | "number" | "password" | "range" | "reset" | "search" | "tel" | "text" | "time" | "url" | "week" | "select" | "select.radio"; type Condition = { valid: boolean; error: false | "validation" | "incomplete" | "optional"; updated: boolean; by: false | "user" | "manual"; }; type Errors = string[] | null | undefined; type Validate<T extends Type> = (props: { value: any; prev: any; readonly extras: Extras.Factory<Setup<T>>; readonly form: Form.StoreObject<Form.Fields, Form.Options> | undefined; }) => string | string[] | undefined | null | void; type VMCM = "normal" | "bypass" | "force-valid"; type ValidateOn = "input" | "change"; type OnMount<T extends Type, V> = (props: { setup: Setup; update: Addon.FieldUpdate<Setup, Form.Options>; SSR: boolean; }) => void | (() => void) | Promise<void | (() => void)>; type OnChange<T extends Type, V> = (props: { $next: StoreObject<Setup, Form.Options>; prev: StoreObject<Setup, Form.Options>; setup: Setup; form: Form.StoreObject<any, any> | undefined; update: Addon.FieldUpdate<Setup, Form.Options>; SSR: boolean; }) => void | Promise<void>; type OnAttrs<T extends Type> = (props: { key: string; state: StoreObject<any, Form.Options>; } /** this is the final attributes passed to the element */ & ({ attrFor: "input"; attrs: Attributes.Standard.Input; } | { attrFor: "trigger"; attrs: Attributes.Select.Trigger; } | { attrFor: "option"; attrs: Attributes.Select.Option; })) => void; type Event = { value?: any; checked?: any; files?: FileList; }; type Setup<T extends Type = Type, V = any> = { type: T; /** initial value */ value?: V; placeholder?: string; label?: string; /** * when labels are taken from key because they're null, this replaces * certain chars like '_' or '-' with ' ' */ labelReplace?: string | string[]; /** validate value function or array of functions */ validate?: Validate<T> | Validate<T>[] | null; /** * some developers like to validate on field.blur and others on field.change. * @option {change} run checks only on field.blur * @option {input} run checks on everychange occurs */ validateOn?: ValidateOn; onMount?: OnMount<T, V>; /** * event used in case of complex data values * need to be extracted from field element and the basic * element.value event isn't accurate enough. * some processors run when events like onfocus and onblur fire, * this gives the chance to modify field state like required, * disabled..etc, according to specific needs or logic. */ onChange?: OnChange<T, V> | OnChange<T, V>[]; /** * html bare element attributes passed to dom. */ onAttrs?: OnAttrs<T>; hidden?: boolean; required?: boolean; disabled?: boolean; /** * can be used for when a field is mandatory, like a checkbox. */ mandatory?: boolean; /** * usually the cycle starts with INIT then IDLE moving to whatever developer logic * prefers to set through onMount function, this alters the MOUNT cycle allowing for any * cycle to replace it. * @default CYCLE.IDLE */ initCycle?: FIELD.CYCLE; ssr?: boolean; /** * Value Manual Change Mechanism (VMCM), happens when values are updated from api * fetch data or just manual programmatic interferrence. * this affects whether the updated values go through the proper * channels of validation, proccessing and affects on form status or just * updates values without affecting anything else. * @option normal: value updates go through the proper channeels of validation, proccessing, error handling and condition report. * @option bypass: value updates are not validated so no proccessing or error handling, condition is changed though. * @option force-valid: value updates defaults fields value condition to valid. * @default "normal" */ vmcm?: VMCM; /** * all values go through preprocessing phase, use this to prevent that * @default true */ preprocessValue?: boolean; /** * checks for missing/required value and mark condition.error as * incomplete. * @default true */ incompleteStatus?: boolean; /** * allowing the field value to be null */ valueNullable?: boolean; /** * abort state changes when an exception is thrown from onChange * method or not * @default false */ onChangeException?: boolean; /** coming soon, a way to defing nested field fields */ props?: Record<string, any>; multiple?: boolean; tel?: T extends "tel" ? Extras.Tel.In : never; select?: T extends "select" | "select.radio" ? Extras.Select.In : never; checkbox?: T extends "checkbox" ? Extras.Checkbox.In : never; date?: T extends "date" ? Partial<Extras.Date.In> : never; attrs?: Attributes.Setup; }; type ValueFromType<T extends Type, S extends Setup<T> = Setup<T>> = T extends "file" ? FileList : T extends "checkbox" ? S["checkbox"] extends Extras.Checkbox.In ? (undefined | unknown extends S["checkbox"]["yes"] ? true : S["checkbox"]["yes"]) | (undefined | unknown extends S["checkbox"]["no"] ? false : S["checkbox"]["no"]) : boolean : T extends "select" | "select.radio" ? (Extras.Factory<S, "select" | "select.radio">["options"][number] extends infer G ? { [K in keyof G as K extends `__${infer _internal}` ? never : K]: G[K]; } : never) extends infer G ? S["multiple"] extends true ? G[] : G : never : T extends "tel" ? string : string; type ValueFromOptions<T extends Type, V, S extends Setup<T, V> = Setup<T, V>> = (true extends Check.IsUnknown<V> | Check.IsUndefined<V> | Check.IsNull<V> ? { value: ValueFromType<T, S>; } : T extends "file" | "select" | "select.radio" ? { value: ValueFromType<T, S>; } : { value: V; }) extends infer G ? { [K in keyof G]: S["required"] extends false ? S["valueNullable"] extends false ? G[K] : G[K] | undefined : S["hidden"] extends true ? S["valueNullable"] extends false ? G[K] : G[K] | undefined : G[K]; } : never; type SetupIn = Type | Setup | null | undefined; type SetupInToSetup<S extends SetupIn> = S extends Setup<infer type, infer value> ? ValueFromOptions<type, value, S> & Omit<S, "value"> : S extends Type ? ValueFromOptions<S, undefined> & Omit<Field.Setup<S>, "value"> : ValueFromOptions<Type, string> & Omit<Setup<Type, string>, "value">; type Element<S extends Setup> = { focused: boolean; visited: boolean; entered: boolean; left: boolean; } & { [K in keyof Setup as K extends keyof typeof IGNORED_SETUP_KEYS ? never : K]: Setup[K]; }; type StoreObject<S extends Setup, O extends Form.Options> = { readonly __internal: { key: string; manual: boolean; preprocess?: boolean; validate?: boolean; }; readonly event: { DOM: FIELD.DOM; MUTATE: FIELD.MUTATE; CYCLE: FIELD.CYCLE; ATTRIBUTE: FIELD.ATTRIBUTE; ev: undefined | Event; }; value: S["value"] | undefined; condition: Condition; element: Element<S>; /** user defined data */ props: undefined | unknown extends S["props"] ? any : S["props"]; extras: Extras.Factory<S>; errors?: string[]; attrs: Attributes.Factory<S, O>; }; type StoreState<S extends Setup, O extends Form.Options> = _QSTATE.Nano.Atom<StoreObject<S, O>>; type Store<S extends Setup, O extends Form.Options> = _QSTATE.Store.Factory<StoreState<S, O>, { hooks: O["storeHooks"]; addons: { hooksUsed: typeof hooksInUseAddon; }; }>; type Factory<S extends Setup, O extends Form.Options> = { readonly key: string; readonly setup: S; readonly update: Addon.FieldUpdate<S, O>; readonly remove: Addon.FieldRemove<S, O>; readonly reset: Addon.FieldReset<S, O>; readonly store: Store<S, O>; readonly storeh: "hooks" extends keyof Store<S, O> ? Store<S, O>["hooks"] : undefined; }; type Component<T extends Type> = Omit<Factory<Setup<T>, Form.Options>, "storeh" | "update"> & { readonly update: FieldAddonUpdate<Setup<T>, Form.Options, any>; }; } declare namespace Extras { namespace Checkbox { type In<Y = any, N = any> = { yes?: Y; no?: N; }; type Out<S extends Field.Setup> = { checked: boolean; yes?: Exclude<S["checkbox"], undefined>["yes"]; no?: Exclude<S["checkbox"], undefined>["no"]; }; } namespace File { type Out<S extends Field.Setup> = { count: { upload: number; failed: number; done: number; }; fallback?: { name: string; url: string; }[]; files?: { file: File; name: string; loading: boolean; stage: "start" | "success" | "fail" | "abort"; progress: { loadedBytes: number; totalBytes: number; percentage: number; }; buffer?: string | ArrayBuffer | null; url?: string; error?: DOMException | null; }[]; }; } namespace Select { type In = { options?: (string | number | Record<string, unknown> | { label: string; value: string; })[]; valueKey?: string; labelKey?: string; /** * incase the valueKey is not found in the option object, the dafault * behvior is to find a key dynamically and store it in the option * @default false */ throwOnKeyNotFound?: boolean; /** * allows for dynamic creation of selection options by deriving the options * from the selected option at runtime, useful for adding options to an existing * list or creating a whole selection options dynamically. */ dynamic?: boolean; /** * what should happen if an option is selected and it's clicked/chosen again?. * by default, the option will get deselected, you can prevent that here by setting * this option to false which will disallow option deselection. * @default true */ removeOnReselect?: boolean; }; type Out<S extends Field.Setup> = { dynamic: boolean | undefined; valueKey: string; labelKey: string; selected: number; throwOnKeyNotFound: boolean; prev: number[]; current: number[]; removeOnReselect: boolean; options: ((S["select"] extends In ? S["select"]["options"] extends (infer option)[] ? option extends string | number ? { label: option; value: option; } : option : { label: string; value: string; } : { label: string; value: string; }) & { __selected: boolean; __key: string; __valueKey?: string; __labelKey?: string; })[]; }; } namespace Tel { type In = { /** * some phone numbers may include chars like '-', by default these chars * get sanitized, you can ignore some sanitization chars here to be included * in the final tel value. * @default undefined */ preserveChars?: string; international?: { /** * international numbers prefixes, use this if your field accepts * weird international prefixes like (00) or (+00). this option replaces defaults. * @default ["+", "00", "+(00)"] */ prefixes?: string | string[]; /** * unify international prefixes with only + and 00 while preserving user * input international prefix such as +(00), this is useful for clear user * experience and UI while maintaining user data in the background. * @default false */ prefixNormalization?: boolean; /** * this option allows removes international code from the value * displaying only the phone number. useful for components that display * international code seprately. * @default 'normal' */ displayMode?: "normal" | "no-prefix" | "keep-prefix"; }; }; type Out<S extends Field.Setup> = { preserveChars: string | undefined; international: { prefixes: string | string[] | null; prefixNormalization: boolean | undefined; displayMode: "normal" | "no-prefix" | "keep-prefix"; prefix: string | null; country: null | { name: string; flag: string; code: string; dial_code: string; dial_code_no_id: string; index: number; }; }; value: { number?: string | null; numberNoCode?: string | null; numberNoZero?: string | null; numberNoCodeNoZero?: string | null; preserved?: string | null; preservedNoCode?: string | null; preservedNoZero?: string | null; preservedNoCodeNoZero?: string | null; }; }; } namespace Date { interface Cell { key: string; mode: CALENDAR.MODE; modeName: keyof typeof CALENDAR.MODE; value: string; valueNumber: number; isSelected: boolean; name: string; shortName: string; } export interface CellDate extends Cell { isToday?: boolean; isOtherMonth?: boolean; } export interface CellTime extends Cell { is24Hour?: boolean; suffix: string; shortSuffix: string; } export interface Header { value: string; name: string; shortName: string; } export interface Option { type: CALENDAR.OPTIONS; typeName: keyof typeof CALENDAR.OPTIONS; value: string; name: string; shortName: string; isSelected: boolean; } export interface ParsedDate { year: string | null; month: string | null; day: string | null; yearNumber: number | null; monthNumber: number | null; dayNumber: number | null; valid: boolean; formatted: string | null; } export interface ParsedTime { hour: string | null; minute: string | null; second: string | null; hourNumber: number | null; minuteNumber: number | null; secondNumber: number | null; period: "AM" | "PM" | null; valid: boolean; formatted: string | null; formatted24h: string | null; } export interface ParsedResult { date: ParsedDate; time: ParsedTime[]; valid: boolean; others: string | null; error?: string; } export type In = { defaultMode: keyof typeof CALENDAR.MODE; /** * format of the date, use char n to denote minutes, everything else is universal standard. * @option {yyyy or yy} for years * @option {mm or m} for months, mm won't detect user typed single digits * @option {dd or d} for days, dd won't detect user typed single digits * @option {hh or h} for hours, hh won't detect user typed single digits * @option {nn or n} for minutes, nn won't detect user typed single digits * @option {ss or s} for seconds, ss won't detect user typed single digits * @example * format: "d-m-yyyy hh:nn"; * // note the '/', the detection allows free familiar human language. * value: "meeting on 22-12/2025 at exactly 14:30" * @default "d-m-yyyy h:n:s" */ format: string; /** * separators used for dates * @default ['-', '/', '.'] */ dateSeparators: string | string[]; /** * separators used for time * @default [':'] */ timeSeparators: string | string[]; /** * separator used to split multiple dates * @default | */ multipleDateSeparator: string; /** * separator used to split multiple times for selected dates * @default , */ multipleTimeSeparator: string; multipleTime: boolean; /** * locale used in date objects parsing. * @default 'en-US' */ locale: string; yearSpan: number; firstDayOfWeek: number; timeFormat: "12h" | "24h"; }; export type Out<S extends Field.Setup> = { format: string; dateSeparators: string[]; timeSeparators: string[]; multipleDateSeparator: string; multipleTimeSeparator: string; multipleTime: boolean; rangeSeparator: string; locale: string; yearSpan: number; firstDayOfWeek: number; timeFormat: "12h" | "24h"; selected: SelectedList; mode: { active: CALENDAR.MODE; activeType: CALENDAR.MODE_TYPE; default: CALENDAR.MODE; defaultType: CALENDAR.MODE_TYPE; applyDate: CALENDAR.MODE | null; applyTime: CALENDAR.MODE | null; activeName: keyof typeof CALENDAR.MODE; activeTypeName: keyof typeof CALENDAR.MODE_TYPE; defaultName: keyof typeof CALENDAR.MODE; defaultTypeName: keyof typeof CALENDAR.MODE; applyDateName: keyof typeof CALENDAR.MODE | null; applyTimeName: keyof typeof CALENDAR.MODE | null; sequence: CALENDAR.MODE[]; }; DATE: { cells?: CellDate[]; }; TIME: { cells?: CellTime[]; periods?: Extras.Date.Option[]; activePeriod?: string; suffix: { dir: "ltr" | "rtl"; short: { hour: string; minute: string; second: string; }; long: { hour: string; minute: string; second: string; }; }; }; YEAR: { cells?: CellDate[]; active: number; current: number; start: number; end: number; }; MONTH: { cells?: CellDate[]; active: number; current: number; name: string; shortName: string; }; DAY: { headers: Header[]; cells?: CellDate[]; active: number; current: number; name: string; shortName: string; }; HOUR: { cells?: CellTime[]; active: number; }; MINUTE: { cells?: CellTime[]; active: number; }; SECOND: { cells?: CellTime[]; active: number; }; }; export { }; } /** * sometimes some field types require additional information beyond the * the standard field "value", this offers that through value processing * and places it under FieldState or State */ type Factory<S extends Field.Setup<Field.Type>, T = S["type"]> = T extends "file" ? File.Out<S> : T extends "select" | "select.radio" ? Select.Out<S> : T extends "checkbox" ? Checkbox.Out<S> : T extends "tel" ? Tel.Out<S> : T extends "date" ? Date.Out<S> : never; } declare namespace Form { type FieldsIn = Record<string, Field.SetupIn> | undefined | null; type Fields<I extends FieldsIn = FieldsIn, O extends Options = Options> = I extends Record<string, Field.SetupIn> ? { [K in keyof I]: Field.SetupInToSetup<I[K]> extends Field.Setup ? Field.Factory<Field.SetupInToSetup<I[K]>, O> : never; } : Record<string, Field.Factory<Field.Setup, O>>; type Options<F extends Fields = any> = { vmcm?: Field.VMCM; /** * when labels are taken from key because they're null, this replaces * certain chars like '_' or '-' with ' ' */ labelReplace?: string | string[]; /** * global options to optin or out of values preprocessing based on field type. * this option precedes individual ones */ preprocessValues?: boolean; /** * usually all values are immediatly updated in the state, * by setting this to true, only valid values will be commited. * @default false */ preventErroredValues?: boolean; /** * wheather the preferred method of checking values * is ran oninput or onchange, oninput checks for validation * for every change happens, onchange checks once there's a * state change like blur or focus. * field specific validateOn takes precedence here. * @default input */ validateOn?: Field.ValidateOn; /** store and pass any data around */ props?: Record<string, any>; /** * how's the props passed/merged with field's specific props. * @option 'none' each props is kept independantly * @option 'form-override' global form props trumps/overrides field's props * @option 'field-override' field specific props trumps/overrides form's props * @default 'none' */ propsMergeStrategy?: "none" | "form-override" | "field-override"; storeHooks?: _QSTATE.Option.Hooks.In<any>; onMount?: (props: { readonly SSR: boolean; readonly form: StoreObject<F, Options<F>>; readonly prev: StoreObject<F, Options<F>>; readonly getForm: () => StoreObject<F, Options<F>>; readonly update: Addon.FormUpdate<F, Options<F>>; readonly fields: F; }, listen: <Value, OriginStores extends _QSTATE.Nano.Abstract[]>(stores: [...OriginStores], cb: (...values: _QSTATE.Nano.Values<OriginStores>) => void | Promise<void | Value>) => void) => void | Promise<void>; onEffect?: (props: { readonly SSR: boolean; readonly form: StoreObject<F, Options<F>>; readonly prev: StoreObject<F, Options<F>>; readonly fields: F; }) => void; ssr?: boolean; /** * default behavior of form is to consider all fields required, * use this to change that default, indvidual fields 'required' * options supercedes this. * @default true */ fieldsRequired?: boolean; /** * default behavior of form is to consider all fields enabled, * use this to change that default, indvidual fields 'disabled' * options supercedes this. * @default true */ fieldsDisabled?: boolean; /** * default behavior of form is to start with CYCLES.IDLE, * use this to change that default, indvidual fields 'initCycle' * options supercedes this. * @default CYCLE.INIT */ fieldsInitCycle?: FIELD.CYCLE; /** * listen to all changes occured on any field and alter it's data if necessary, * this gets called before the individual field's onChange method if any. */ fieldsOnChange?: Field.OnChange<Field.Type, any>; /** * global per element dom attributes listener, gets called after field's specific * onAttrs event if it exists. */ fieldsOnAttrs?: Field.OnAttrs<Field.Type>; /** * the last step of checking for form validity is to check for * required fields' values and add them to the incompleteList, * this option allows for choosing how big or small this list goes. * @option true collect all incomplete fields * @option false don't collect any incomplete fields * @option number collect this many of incomplete fields * @default false */ incompleteListCount?: boolean | number; /** * how should the form react to updating values using form.actions.update * @default "silent" */ onUpdateKeyNotFound?: "silent" | "warn"; /** * if the desired data extracted from the form is in the * shape of a nested object, then this splitter is used * to determine which keys are nested, for now only * the dot is supported. * @default '.' */ flatObjectKeysChar?: "."; /** * when no label is provided, the key is used as fallback but sometimes * the key is meant to be unflattened later on when fetching the form data, * so this offers a way to replace the key flatObjectKeysChar with any * character. * @default ' ' or empty spcace */ flatLabelJoinChar?: string; attrs?: Attributes.Setup; }; type OptionsMerged<G extends Options, D extends Options> = D & G; interface StoreObject<F extends Fields, O extends Options<F>> extends _QSTATE.NanoType.DeepMapObject { status: FORM.STATUS; incomplete: string[]; values: { [K in keyof F]: F[K]["setup"]["value"]; }; elements: { [K in keyof F]: F[K]["store"]["value"]["element"]; }; conditions: { [K in keyof F]: Field.Condition; }; errors: { [K in keyof F]?: string[]; }; extras: { [K in keyof F as Extras.Factory<F[K]["setup"]> extends never ? never : K]: Extras.Factory<F[K]["setup"]>; }; props: O["props"]; attrs: { [K in keyof F]: ReturnType<F[K]["store"]["get"]>["attrs"]; }; } type StoreState<F extends Fields, O extends Options<F>> = _QSTATE.Nano.Map<StoreObject<F, O>>; type Store<F extends Fields, O extends Options<F>> = _QSTATE.Store.Factory<StoreState<F, O>, { hooks: O["storeHooks"]; addons: { derive: typeof deriveAddon; }; }>; type Factory<I extends FieldsIn, F extends Fields<I>, O extends Options<F>> = { readonly fields: Fields<I, O>; readonly options: O; get keys(): () => (keyof F)[]; submit: Addon.FormSubmit<F, O>; update: Addon.FormUpdate<F, O>; values: Addon.FormValues<F, O>; button: Addon.FormButton<F, O>; /** * extreme low-level control of form, use setters with extreme care as this affects * the core logic of the form, it's advised to not modify form store * directly, instead use update, submit..etc to safely mutate form. */ readonly store: Store<F, O>; readonly storeh: "hooks" extends keyof Store<F, O> ? Store<F, O>["hooks"] : undefined; }; } declare namespace FunctionProps { interface Field<S extends Field.Setup, O extends Form.Options> { key: string; setup: S; options: O | undefined; store: Field.Store<S, O>; } type FieldCycle<S extends Field.Setup, O extends Form.Options> = Field<S, O>; type FieldAddon<S extends Field.Setup, O extends Form.Options> = Field<S, O>; interface FieldProcessor<S extends Field.Setup, O extends Form.Options> { value: any; el: Field.Event | undefined; manualUpdate: boolean; preprocessValue: boolean; $next: Field.StoreObject<S, O>; } type FormCycle<F extends Form.Fields, O extends Form.Options> = { fields: F; options: O; store: Form.Store<F, O>; }; type FormAddon<F extends Form.Fields, O extends Form.Options> = FormCycle<F, O>; type RenderAttributes<S extends Field.Setup, O extends Form.Options> = { reactive: Field.StoreObject<S, O>; }; } declare namespace Addon { type FieldUpdate<S extends Field.Setup, O extends Form.Options> = FieldAddonUpdate<S, O>; type FieldRemove<S extends Field.Setup, O extends Form.Options> = FieldAddonRemove<S, O>; type FieldReset<S extends Field.Setup, O extends Form.Options> = FieldAddonReset<S, O>; type FormSubmit<F extends Form.Fields, O extends Form.Options<F>> = FormAddonSubmit<F, O>; type FormUpdate<F extends Form.Fields, O extends Form.Options<F>> = FormAddonUpdate<F, O>; type FormValues<F extends Form.Fields, O extends Form.Options<F>> = FormAddonValues<F, O>; type FormButton<F extends Form.Fields, O extends Form.Options<F>> = FormAddonButton<F, O>; } declare namespace Attributes { export type Type = "dom" | "vdom"; export type Setup = { map?: Record<string, Type>; }; type ToDom<T extends Record<string, unknown>> = { [K in keyof T as `${Lowercase<K & string>}`]: T[K]; }; export namespace Standard { interface Input extends Record<string, unknown> { id: string; required: boolean; disabled: boolean; autoComplete: "on" | "off"; type: string; name: string; multiple: boolean; value: any; onInput: (event: any) => void; onChange: (event: any) => void; onFocus: (event: any) => void; onBlur: (event: any) => void; } } export namespace Select { interface Trigger extends Record<string, unknown> { onClick: (event: any) => void; name: string; value: any; } interface Option extends Record<string, unknown> { value: any; selected: boolean; onClick: (event: any) => void; } interface Radio extends Record<string, unknown> { value: any; selected: boolean; } } export namespace Date { interface Input extends Record<string, unknown> { id: string; required: boolean; disabled: boolean; autoComplete: "on" | "off"; type: string; name: string; } interface Event extends Record<string, unknown> { id: string; name: string; onClick: (event: any) => void; } interface Cell extends Record<string, unknown> { id: string; name: string; onClick: (event: any) => void; } interface Option extends Record<string, unknown> { id: string; name: string; onClick: (event: any) => void; } } type Attrs<S extends Field.Setup, O extends Form.Options, T extends Record<string, any>> = (S["attrs"] extends object ? S["attrs"]["map"] extends Record<any, any> ? { [K in keyof S["attrs"]["map"]]: "vdom" extends S["attrs"]["map"][K] ? T : ToDom<T>; } : {} : {}) & (O["attrs"] extends object ? O["attrs"]["map"] extends Record<any, any> ? { [K in keyof O["attrs"]["map"]]: "vdom" extends O["attrs"]["map"][K] ? T : ToDom<T>; } : {} : {}) extends infer G ? Omit<G, "dom" | "vdom" | "ref"> & { ref: (element: any) => void; vdom: T; dom: ToDom<T>; } extends infer L ? { [K in keyof L]: L[K]; } : never : never; export type Factory<S extends Field.Setup, O extends Form.Options> = S extends { type: "select"; } ? { trigger: Attrs<S, O, Select.Trigger>; option: (option: any) => Attrs<S, O, Select.Option>; } : S extends { type: "select.radio"; } ? { trigger: Attrs<S, O, Select.Trigger>; option: (option: any) => Attrs<S, O, Select.Radio>; } : S extends { type: "date"; } ? { input: Attrs<S, O, Date.Input>; event(event: CALENDAR.EVENTS | keyof typeof CALENDAR.EVENTS): Attrs<S, O, Date.Event>; cell(cell: Extras.Date.CellDate | Extras.Date.CellTime | DateAttributeCells): Attrs<S, O, Date.Cell>; option(option: Extras.Date.Option): Attrs<S, O, Date.Option>; } : { input: Attrs<S, O, Standard.Input>; }; export { }; } export { Addon as A, Check as C, Extras as E, Form as F, IGNORED_SETUP_KEYS as I, MISC as M, Field as a, FunctionProps as b, Attributes as c, FORM as d, FIELD as e, CALENDAR as f };