UNPKG

11 kBTypeScriptView Raw
1import WUPBaseElement, { AttributeMap } from "./baseElement";
2import IBaseControl from "./controls/baseControl.i";
3import WUPSpinElement from "./spinElement";
4export declare const enum SubmitActions {
5 /** Disable any action */
6 none = 0,
7 /** Scroll to first error (if exists) and focus control */
8 goToError = 1,
9 /** Validate until first error is found (otherwise validate all) */
10 validateUntilFirst = 2,
11 /** Collect to model only changed values */
12 collectChanged = 4,
13 /** Reset isDirty and assign $value to $initValue for controls (on success only) */
14 reset = 8,
15 /** Lock the whole form during the pending state (set $isPending = true or provide `promise` to submitEvent.waitFor);
16 * Otherwise user can submit several times in a short time;
17 * If promise resolves during the short-time pending state won't be set, otherwise it takes at least 300ms via helper {@link promiseWait} */
18 lockOnPending = 16
19}
20declare const tagName = "wup-form";
21declare global {
22 namespace WUP.Form {
23 interface SubmitDetails {
24 /** Model collected from controls */
25 model: Record<string | number, any>;
26 /** Form related to submit event; equal to event.target */
27 relatedForm: WUPFormElement;
28 /** Event that produced submit event; null if `form.$submit()` is called */
29 relatedEvent: MouseEvent | KeyboardEvent | null;
30 /** Element that that produced submit event */
31 submitter: HTMLElement | null;
32 /** Point a promise as callback to allow form show pending state during the promise */
33 waitFor?: Promise<unknown>;
34 }
35 interface EventMap extends WUP.Base.EventMap {
36 /** Fires before $submit is happened; can be prevented via `e.preventDefault()` */
37 $willSubmit: CustomEvent<Pick<SubmitDetails, "relatedEvent" | "relatedForm" | "submitter">>;
38 /** Fires by user-submit when validation successful and model is collected */
39 $submit: CustomEvent<SubmitDetails>;
40 /** Fires when submit is end (after http-response) */
41 $submitEnd: CustomEvent<{
42 success: boolean;
43 }>;
44 }
45 interface Options {
46 /** Actions that enabled on submit event; You can point several like: `goToError | collectChanged`
47 * @defaultValue goToError | validateUntilFirst | reset | lockOnPending */
48 submitActions: SubmitActions;
49 /** Enable to tore data in localStorage to prevent losing till submitted;
50 * @defaultValue false
51 * @tutorial Troubleshooting
52 * * It doesn't save values that are complex objects. So `wup-select.$options.items = [{text: "N1",value: {id:1,name:'Nik'} }]` is skipped
53 * * Point string-value if default storage-key doesn't fit: based on `url+control.names` @see{@link WUPFormElement.storageKey}
54 * @defaultValue false */
55 autoStore: boolean | string;
56 /** Focus first possible element when it's appended to layout
57 * @defaultValue false */
58 autoFocus: boolean;
59 /** Disallow edit/copy value; adds attr [disabled] for styling
60 * @defaultValue false */
61 disabled: boolean;
62 /** Disallow copy value; adds attr [readonly] for styling
63 * @defaultValue false */
64 readOnly: boolean;
65 /** Enable/disable browser-autocomplete; if control has no autocomplete option then it's inherited from form
66 * @defaultValue false */
67 autoComplete: boolean;
68 }
69 interface JSXProps extends WUP.Base.OnlyNames<Options> {
70 "w-submitActions"?: SubmitActions | number;
71 "w-autoStore"?: boolean | string;
72 "w-autoFocus"?: boolean | "";
73 "w-autoComplete"?: boolean | "";
74 /** @deprecated use [disabled] instead since related to CSS-styles */
75 "w-disabled"?: boolean | "";
76 disabled?: boolean | "";
77 /** @deprecated use [disabled] instead since related to CSS-styles */
78 "w-readonly"?: boolean | "";
79 readonly?: boolean | "";
80 /** @deprecated SyntheticEvent is not supported. Use ref.addEventListener('$change') instead */
81 onChange?: never;
82 /** @deprecated SyntheticEvent is not supported. Use ref.addEventListener('$willSubmit') instead */
83 onWillSubmit?: never;
84 /** @deprecated SyntheticEvent is not supported. Use ref.addEventListener('$submit') instead */
85 onSubmit?: never;
86 }
87 }
88 interface HTMLElementTagNameMap {
89 [tagName]: WUPFormElement;
90 }
91 namespace JSX {
92 interface IntrinsicElements {
93 /** Wrapper of FormHTMLElement that collect values from controls
94 * @see {@link WUPFormElement} */
95 [tagName]: WUP.Base.ReactHTML<WUPFormElement> & WUP.Form.JSXProps;
96 }
97 }
98}
99declare module "preact/jsx-runtime" {
100 namespace JSX {
101 interface HTMLAttributes<RefType> {
102 }
103 interface IntrinsicElements {
104 /** Wrapper of FormHTMLElement that collect values from controls
105 * @see {@link WUPFormElement} */
106 [tagName]: HTMLAttributes<WUPFormElement> & WUP.Form.JSXProps;
107 }
108 }
109}
110/** Wrapper of FormHTMLElement that collect values from controls
111 * @example
112 * // init form
113 * const form = document.createElement("wup-form");
114 * form.$options.autoComplete = false;
115 * form.$initModel = { email: "test-me@google.com" };
116 * form.addEventListener("$submit", (e) => console.warn(e.detail.model) );
117 * form.$onSubmit = async (e)=>{ await postHere(e.detail.model); } // equal to form.addEventListener
118 * // init control
119 * const el = document.createElement("wup-text");
120 * el.$options.name = "email";
121 * el.$options.validations = { required: true, email: true };
122 * form.appendChild(el);
123 * const btn = form.appendChild(document.createElement("button"));
124 * btn.textContent = "Submit";
125 * btn.type = "submit";
126 * document.body.appendChild(form);
127 * // or HTML
128 * <wup-form w-autocomplete w-autofocus>
129 * <wup-text w-name="email" />
130 * <button type="submit">Submit</submit>
131 * </wup-form>;
132 * @tutorial Troubleshooting/rules:
133 * * options like $initModel, $model overrides control.$initValue, control.$value (every control that matches by $options.name)
134 * * In React ref-parent called after ref-children. So if you want to set control.$initValue over form.$initModel use empty setTimeout on ref-control
135 * @example
136 * <wup-form
137 ref={(el) => {
138 if (el) {
139 el.$initModel = { email: "test-me@google.com" };
140 el.$onSubmit = async (ev)=>{
141 await postHere();
142 }
143 }
144 }}
145 >
146 <wup-text
147 ref={(el) => {
148 if (el) {
149 setTimeout(() => {
150 el.$options.name = "email";
151 el.$initValue = "";
152 });
153 }
154 }}
155 <button type="submit">Submit</button>
156 />
157 </wup-form>
158 */
159export default class WUPFormElement<Model extends Record<string, any> = any, TOptions extends WUP.Form.Options = WUP.Form.Options, Events extends WUP.Form.EventMap = WUP.Form.EventMap> extends WUPBaseElement<TOptions, Events> {
160 #private;
161 static get $styleRoot(): string;
162 static get $style(): string;
163 static get mappedAttributes(): Record<string, AttributeMap>;
164 /** Find form related to control,register and apply initModel if initValue undefined */
165 static $tryConnect(control: IBaseControl & HTMLElement): WUPFormElement | undefined;
166 /** Map model to control-values */
167 static $modelToControls<T extends Record<string, any>>(m: T | undefined, controls: IBaseControl[], prop: keyof Pick<IBaseControl, "$value" | "$initValue">): void;
168 /** Collect model from control-values */
169 static $modelFromControls<T>(prevModel: Partial<T>, controls: IBaseControl[], prop: keyof Pick<IBaseControl, "$value" | "$initValue">, isOnlyChanged?: boolean): Partial<T>;
170 static get observedAttributes(): Array<string>;
171 /** Default options - applied to every element. Change it to configure default behavior */
172 static $defaults: WUP.Form.Options;
173 /** Fires before $submit is happened; can be prevented via `e.preventDefault()` */
174 $onWillSubmit?: (ev: WUP.Form.EventMap["$willSubmit"]) => void;
175 /** Dispatched on submit. Return promise to lock form and show spinner on http-request */
176 $onSubmit?: (ev: WUP.Form.EventMap["$submit"]) => void | Promise<unknown>;
177 /** Fires when submit is end (after http-response) */
178 $onSubmitEnd?: (ev: WUP.Form.EventMap["$submitEnd"]) => void;
179 /** Dispatched on submit */
180 /** All controls related to form */
181 $controls: IBaseControl<any>[];
182 /** Returns related to form controls with $options.name != null */
183 get $controlsAttached(): IBaseControl<any>[];
184 _model?: Partial<Model>;
185 /** Model related to every control inside (with $options.name);
186 * @see {@link BaseControl.prototype.$value} */
187 get $model(): Partial<Model>;
188 set $model(m: Partial<Model>);
189 _initModel?: Partial<Model>;
190 /** Default/init model related to every control inside;
191 * @see {@link BaseControl.prototype.$initValue} */
192 get $initModel(): Partial<Model> | undefined;
193 set $initModel(m: Partial<Model> | undefined);
194 /** Pending state (spinner + lock form if SubmitActions.lockOnPending enabled) */
195 get $isPending(): boolean;
196 set $isPending(v: boolean);
197 /** Returns true if all nested controls (with name) are valid */
198 get $isValid(): boolean;
199 /** Returns true if some of controls value is changed by user */
200 get $isChanged(): boolean;
201 /** Call it to manually trigger submit or better to use `gotSubmit` for handling events properly */
202 $submit(): void;
203 /** Called on every spin-render */
204 renderSpin(target: HTMLElement): WUPSpinElement;
205 /** Change pending state */
206 protected changePending(v: boolean): void;
207 /** Called on submit before validation (to fire validation & $onSubmit if successful) */
208 gotSubmit(e: KeyboardEvent | MouseEvent | null, submitter: HTMLElement): void;
209 protected gotChanges(propsChanged: Array<keyof WUP.Form.Options> | null): void;
210 protected gotReady(): void;
211 protected connectedCallback(): void;
212 /** Returns storage key based on url+control-names or `$options.autoStore` if `string` */
213 get storageKey(): string;
214 /** Get & parse value from storage according to option `autoStore`, $model */
215 storageGet(): Partial<Model> | null;
216 _preventStorageSave?: boolean;
217 /** Save/remove model (only changes) to storage according to option `autoStore`, $model
218 * @returns model saved to localStorage or Null if removed */
219 storageSave(model?: null | Record<string, any>): Partial<Model> | null;
220 protected disconnectedCallback(): void;
221}
222export {};