@qundus/qform
Version:
The only form library you'll ever need :)
1,139 lines (1,126 loc) • 46.6 kB
TypeScript
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 };