packages/rdom-forms/src/api.ts
import type { Fn, Predicate } from "@thi.ng/api";
import type {
Attribs,
FormAttribs,
InputAttribs,
InputCheckboxAttribs,
InputFileAttribs,
InputNumericAttribs,
InputRadioAttribs,
InputSubmitAttribs,
InputTextAttribs,
SelectAttribs,
StringAttrib,
TextAreaAttribs,
} from "@thi.ng/hiccup-html";
import type { ComponentLike } from "@thi.ng/rdom";
import type { ISubscriber, ISubscription } from "@thi.ng/rstream";
export interface CommonAttribs {
wrapperAttribs: Partial<Attribs>;
labelAttribs: Partial<Attribs>;
descAttribs: Partial<Attribs>;
}
export interface FormItem {
/**
* Value/widget type identifier. Used by {@link compileForm} to delegate
*/
type: string;
/**
* Attrib overrides for the actual input element.
*/
attribs?: Partial<Attribs>;
}
export interface Form {
type: "form";
items: FormItem[];
attribs?: Partial<FormAttribs>;
}
export interface Container extends FormItem {
type: "container";
items: FormItem[];
}
export interface Custom extends Pick<FormItem, "type"> {
type: "custom";
body: ComponentLike;
}
export interface Group extends FormItem {
type: "group";
items: FormItem[];
label?: StringAttrib | false;
}
export interface Value extends FormItem, Partial<CommonAttribs> {
id: string;
name?: string;
/**
* If false, no `<label>` element will be generated for this control and the
* {@link Value.desc} value will be unused.
*/
label?: StringAttrib | false;
desc?: any;
required?: boolean;
/**
* If true, no input/change event handlers will be generated and the
* `readonly` attribute will be set on the input DOM element.
*/
readonly?: boolean;
attribs?: Partial<InputAttribs>;
}
export interface HiddenValue extends Omit<FormItem, "attribs"> {
id?: string;
name: string;
value: StringAttrib;
}
export interface WithPresets<T> {
/**
* Array of possible preset values.
*/
list?: T[];
}
export interface Num extends Value, WithPresets<number> {
type: "num";
min?: number;
max?: number;
placeholder?: StringAttrib;
size?: number;
step?: number;
value?: ISubscription<number, number>;
attribs?: Partial<InputNumericAttribs>;
}
export interface Range extends Omit<Num, "type" | "placeholder" | "size"> {
type: "range";
vlabel?: boolean | number | Fn<number, string>;
}
export interface Str extends Value, WithPresets<string> {
type: "str";
min?: number;
max?: number;
pattern?: string | RegExp | Predicate<string>;
placeholder?: StringAttrib;
size?: number;
value?: ISubscription<string, string>;
attribs?: Partial<InputTextAttribs>;
}
export interface Email extends Omit<Str, "type"> {
type: "email";
autocomplete?: boolean;
}
export interface Phone extends Omit<Str, "type"> {
type: "tel";
autocomplete?: boolean;
}
export interface UrlVal extends Omit<Str, "type"> {
type: "url";
}
export interface Password extends Omit<Str, "type"> {
type: "password";
autocomplete?: boolean;
}
export interface Text extends Value {
type: "text";
cols?: number;
rows?: number;
placeholder?: StringAttrib;
value?: ISubscription<string, string>;
attribs?: Partial<TextAreaAttribs>;
}
export interface Color extends Value, WithPresets<string> {
type: "color";
value?: ISubscription<string, string>;
}
export interface DateTime extends Value, WithPresets<string> {
type: "dateTime";
min?: string;
max?: string;
step?: number;
value?: ISubscription<string, string>;
}
export interface DateVal extends Omit<DateTime, "type"> {
type: "date";
}
export interface Time extends Omit<DateTime, "type"> {
type: "time";
}
export interface Week extends Omit<DateTime, "type" | "list"> {
type: "week";
}
export interface Month extends Omit<DateTime, "type" | "list"> {
type: "month";
}
export interface Select<T> extends Value {
items: (T | SelectItem<T> | SelectItemGroup<T>)[];
value?: ISubscription<T, T>;
attribs?: Partial<Omit<SelectAttribs, "multiple">>;
}
export interface SelectItemGroup<T> {
name: string;
items: (T | SelectItem<T>)[];
}
export interface SelectItem<T> {
value: T;
label?: string;
desc?: string;
}
export interface SelectStr<T extends string = string> extends Select<T> {
type: "selectStr";
}
export interface SelectNum<T extends number = number> extends Select<T> {
type: "selectNum";
}
export interface MultiSelect<T> extends Value {
items: (T | SelectItem<T> | SelectItemGroup<T>)[];
value?: ISubscription<T[], T[]>;
size?: number;
attribs?: Partial<Omit<SelectAttribs, "multiple">>;
}
export interface MultiSelectStr<T extends string = string>
extends MultiSelect<T> {
type: "multiSelectStr";
}
export interface MultiSelectNum<T extends number = number>
extends MultiSelect<T> {
type: "multiSelectNum";
}
export interface Toggle extends Value {
type: "toggle";
value?: ISubscription<boolean, boolean>;
attribs?: Partial<InputCheckboxAttribs>;
}
export interface Trigger extends Value {
type: "trigger";
title: StringAttrib;
value?: ISubscriber<boolean>;
}
export interface Submit extends Omit<Value, "required" | "readonly"> {
type: "submit";
title: StringAttrib;
value?: ISubscriber<boolean>;
attribs?: Partial<InputSubmitAttribs>;
}
export interface Reset extends Omit<Value, "required" | "readonly"> {
type: "submit";
title: StringAttrib;
value?: ISubscriber<boolean>;
}
export interface Radio<T> extends Value {
items: (T | SelectItem<T>)[];
value?: ISubscription<T, T>;
attribs?: Partial<InputRadioAttribs>;
}
export interface RadioNum extends Radio<number> {
type: "radioNum";
}
export interface RadioStr extends Radio<string> {
type: "radioStr";
}
export interface FileVal extends Value {
type: "file";
accept?: string[];
capture?: InputFileAttribs["capture"];
value?: ISubscriber<File>;
attribs?: Partial<InputFileAttribs>;
}
export interface MultiFileVal extends Value {
type: "multiFile";
accept?: string[];
value?: ISubscriber<FileList>;
attribs?: Partial<InputFileAttribs>;
}
type KnownTypes =
| Color
| Container
| DateTime
| DateVal
| Email
| FileVal
| Group
| Month
| MultiFileVal
| MultiSelectNum
| MultiSelectStr
| Num
| Password
| Phone
| Range
| SelectNum
| SelectStr
| Str
| Text
| Time
| Toggle
| Trigger
| UrlVal
| Week;
/**
* Type specific attribute overrides
*/
export interface TypeAttribs
extends Record<KnownTypes["type"], Partial<Attribs>> {
form: Partial<FormAttribs>;
/**
* Attribs for {@link group} label elements
*/
groupLabel: Partial<Attribs>;
/**
* Attribs for the actual {@link radio} `<input>` element
*/
radio: Partial<InputRadioAttribs>;
/**
* Attribs for the outermost {@link radio} group wrapper element (incl. main
* group label)
*/
radioWrapper: Partial<Attribs>;
/**
* Attribs for the wrapper element only containing the {@link radio} items.
*/
radioItems: Partial<Attribs>;
/**
* Attribs for the wrapper element of a single {@link radio} item (incl.
* input element and its label)
*/
radioItem: Partial<Attribs>;
/**
* Attribs for a single {@link radio} item's label
*/
radioItemLabel: Partial<Attribs>;
/**
* Attribs for {@link range} label elements
*/
rangeLabel: Partial<Attribs>;
/**
* Attribs for the wrapper element of a single {@link range} widget (incl.
* input element and optional value label)
*/
rangeWrapper: Partial<Attribs>;
[id: string]: Partial<Attribs>;
}
export interface FormOpts extends CommonAttribs {
/**
* ID prefix to prepend for all {@link FormItem}s. Needed if the a form is
* to used in multiple places at the same time.
*/
prefix: string;
/**
* Type specific behavior options.
*/
behaviors: Partial<BehaviorOpts>;
/**
* Type specific attrib overrides
*/
typeAttribs: Partial<TypeAttribs>;
}
export interface BehaviorOpts {
/**
* If false, no label elements will be generated.
*
* @defaultValue true
*/
labels: boolean;
/**
* If false, no `value` attributes and event handlers will be generated,
* even if a `value` option has been specified.
*
* @defaultValue true
*/
values: boolean;
/**
* Unless false, {@link range} widgets will emit `oninput` events, if false
* only `onchange` events.
*
* @defaultValue true
*/
rangeOnInput: boolean;
/**
* Unless false, {@link str}-like widgets will emit `oninput` events, if
* false only `onchange` events.
*
* @defaultValue true
*/
strOnInput: boolean;
/**
* Unless false, {@link text} elements will emit `oninput` events, if false
* only `onchange` events.
*
* @defaultValue true
*/
textOnInput: boolean;
/**
* If true, the label for individual radio items will come before the actual
* input element. By default, the order is reversed.
*
* @defaultValue false
*/
radioLabelBefore: boolean;
/**
* Number of fractional digits for range sliders.
*
* @defaultValue 2
*/
rangeLabelFmt: number | Fn<number, string>;
/**
* If true, the label for toggle widgets will come before the actual
* input element. By default, the order is reversed.
*
* @defaultValue false
*/
toggleLabelBefore: boolean;
}