1 | import WUPBaseElement, { AttributeMap } from "./baseElement";
|
2 | import IBaseControl from "./controls/baseControl.i";
|
3 | import WUPSpinElement from "./spinElement";
|
4 | export 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 | }
|
20 | declare const tagName = "wup-form";
|
21 | declare 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 | }
|
99 | declare 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 | */
|
159 | export 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 | }
|
222 | export {};
|