1 | import WUPBaseElement 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 | validateUntiFirst = 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 SubmitEvent<T extends Record<string, any>> extends Event {
|
24 | /** Model collected from controls */
|
25 | $model: Partial<T>;
|
26 | /** Form related to submit event */
|
27 | $relatedForm: WUPFormElement<T>;
|
28 | /** Event that produced submit event */
|
29 | $relatedEvent: MouseEvent | KeyboardEvent;
|
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: Omit<SubmitEvent<any>, "$model">;
|
38 | /** Fires by user-submit when validation succesfull and model is collected */
|
39 | $submit: SubmitEvent<any>;
|
40 | }
|
41 | interface Defaults {
|
42 | /** Actions that enabled on submit event; You can point several like: `goToError | collectChanged`
|
43 | * @defaultValue goToError | validateUntiFirst | reset | lockOnPending */
|
44 | submitActions: SubmitActions;
|
45 | /** Whether need to store data in localStorage to prevent losing till submitted;
|
46 | * @defaultValue false
|
47 | * @tutorial Troubleshooting
|
48 | * * It doesn't save values that are complex objects. So `wup-select.$options.items = [{text: "N1",value: {id:1,name:'Nik'} }]` is skipped
|
49 | * * Point string-value if default storage-key doesn't fit: based on `url+control.names` @see{@link WUPFormElement.storageKey} */
|
50 | autoSave?: boolean | string;
|
51 | }
|
52 | interface Options extends Defaults {
|
53 | /** Focus first possible element when it's appended to layout */
|
54 | autoFocus?: boolean;
|
55 | /** Disallow edit/copy value; adds attr [disabled] for styling */
|
56 | disabled?: boolean;
|
57 | /** Disallow copy value; adds attr [readonly] for styling */
|
58 | readOnly?: boolean;
|
59 | /** Enable/disable browser-autocomplete; if control has no autocomplete option then it's inherited from form
|
60 | * @defaultValue false */
|
61 | autoComplete?: boolean;
|
62 | }
|
63 | interface Attributes extends Pick<Options, "disabled" | "readOnly" | "autoComplete" | "autoFocus" | "autoSave"> {
|
64 | }
|
65 | interface JSXProps<T extends WUPFormElement> extends WUP.Base.JSXProps<T>, Attributes {
|
66 | /** Whether need to store data in localStorage to prevent losing till submitted;
|
67 | * @defaultValue false
|
68 | * @tutorial Troubleshooting
|
69 | * * It doesn't save values that are complex objects. So `wup-select.$options.items = [{text: "N1",value: {id:1,name:'Nik'} }]` is skipped
|
70 | * * Point string-value if default storage-key doesn't fit: based on `url+control.names` @see{@link WUPFormElement.storageKey} */
|
71 | autoSave?: string;
|
72 | /** @deprecated SyntheticEvent is not supported. Use ref.addEventListener('$change') instead */
|
73 | onChange?: never;
|
74 | /** @deprecated SyntheticEvent is not supported. Use ref.addEventListener('$willSubmit') instead */
|
75 | onWillSubmit?: never;
|
76 | /** @deprecated SyntheticEvent is not supported. Use ref.addEventListener('$submit') instead */
|
77 | onSubmit?: never;
|
78 | }
|
79 | }
|
80 | interface HTMLElementTagNameMap {
|
81 | [tagName]: WUPFormElement;
|
82 | }
|
83 | namespace JSX {
|
84 | interface IntrinsicElements {
|
85 | /** Wrapper of FormHTMLElement that collect values from controls
|
86 | * @see {@link WUPFormElement} */
|
87 | [tagName]: WUP.Form.JSXProps<WUPFormElement>;
|
88 | }
|
89 | }
|
90 | }
|
91 | /** Wrapper of FormHTMLElement that collect values from controls
|
92 | * @example
|
93 | * // init form
|
94 | * const form = document.createElement("wup-form");
|
95 | * form.$options.autoComplete = false;
|
96 | * form.$initModel = { email: "test-me@google.com" };
|
97 | * form.addEventListener("$submit", (e) => console.warn(e.$model) );
|
98 | * form.$onSubmit = async (e)=>{ await postHere(e.$model); } // equal to form.addEventListener
|
99 | * // init control
|
100 | * const el = document.createElement("wup-text");
|
101 | * el.$options.name = "email";
|
102 | * el.$options.validations = { required: true, email: true };
|
103 | * form.appendChild(el);
|
104 | * const btn = form.appendChild(document.createElement("button"));
|
105 | * btn.textContent = "Submit";
|
106 | * btn.type = "submit";
|
107 | * document.body.appendChild(form);
|
108 | * // or HTML
|
109 | * <wup-form autoComplete autoFocus>
|
110 | * <wup-text name="email" />
|
111 | * <button type="submit">Submit</submit>
|
112 | * </wup-form>;
|
113 | * @tutorial Troubleshooting/rules:
|
114 | * * options like $initModel, $model overrides control.$initValue, control.$value (every control that matches by $options.name)
|
115 | * * 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
|
116 | * @example
|
117 | * <wup-form
|
118 | ref={(el) => {
|
119 | if (el) {
|
120 | el.$initModel = { email: "test-me@google.com" };
|
121 | el.$onSubmit = async (ev)=>{
|
122 | await postHere();
|
123 | }
|
124 | }
|
125 | }}
|
126 | >
|
127 | <wup-text
|
128 | ref={(el) => {
|
129 | if (el) {
|
130 | setTimeout(() => {
|
131 | el.$options.name = "email";
|
132 | el.$initValue = "";
|
133 | });
|
134 | }
|
135 | }}
|
136 | <button type="submit">Submit</button>
|
137 | />
|
138 | </wup-form>
|
139 | */
|
140 | 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> {
|
141 | #private;
|
142 | /** Options that need to watch for changes; use gotOptionsChanged() */
|
143 | static get observedOptions(): Array<keyof WUP.Form.Options>;
|
144 | static get observedAttributes(): Array<LowerKeys<WUP.Form.Attributes>>;
|
145 | static get $styleRoot(): string;
|
146 | static get $style(): string;
|
147 | /** Find form related to control,register and apply initModel if initValue undefined */
|
148 | static $tryConnect(control: IBaseControl & HTMLElement): WUPFormElement | undefined;
|
149 | /** Map model to control-values */
|
150 | static $modelToControls<T extends Record<string, any>>(m: T | undefined, controls: IBaseControl[], prop: keyof Pick<IBaseControl, "$value" | "$initValue">): void;
|
151 | /** Collect model from control-values */
|
152 | static $modelFromControls<T>(prevModel: Partial<T>, controls: IBaseControl[], prop: keyof Pick<IBaseControl, "$value" | "$initValue">, isOnlyChanged?: boolean): Partial<T>;
|
153 | /** Default options - applied to every element. Change it to configure default behavior */
|
154 | static $defaults: WUP.Form.Defaults;
|
155 | /** Dispatched on submit. Return promise to lock form and show spinner */
|
156 | $onSubmit?: (ev: WUP.Form.SubmitEvent<Model>) => void | Promise<unknown>;
|
157 | /** Dispatched on submit */
|
158 | /** All controls related to form */
|
159 | $controls: IBaseControl<any>[];
|
160 | /** Returns related to form controls with $options.name != null */
|
161 | get $controlsAttached(): IBaseControl<any>[];
|
162 | _model?: Partial<Model>;
|
163 | /** Model related to every control inside (with $options.name);
|
164 | * @see {@link BaseControl.prototype.$value} */
|
165 | get $model(): Partial<Model>;
|
166 | set $model(m: Partial<Model>);
|
167 | _initModel?: Partial<Model>;
|
168 | /** Default/init model related to every control inside;
|
169 | * @see {@link BaseControl.prototype.$initValue} */
|
170 | get $initModel(): Partial<Model> | undefined;
|
171 | set $initModel(m: Partial<Model> | undefined);
|
172 | /** Pending state (spinner + lock form if SubmitActions.lockOnPending enabled) */
|
173 | get $isPending(): boolean;
|
174 | set $isPending(v: boolean);
|
175 | /** Returns true if all nested controls (with name) are valid */
|
176 | get $isValid(): boolean;
|
177 | /** Returns true if some of controls value is changed by user */
|
178 | get $isChanged(): boolean;
|
179 | /** Called on every spin-render */
|
180 | renderSpin(target: HTMLElement): WUPSpinElement;
|
181 | /** Change pending state */
|
182 | protected changePending(v: boolean): void;
|
183 | /** Called on submit before validation */
|
184 | protected gotSubmit(e: KeyboardEvent | MouseEvent, submitter: HTMLElement): void;
|
185 | protected gotChanges(propsChanged: Array<keyof WUP.Form.Options | LowerKeys<WUP.Form.Options>> | null): void;
|
186 | protected gotOptionsChanged(e: WUP.Base.OptionEvent<Record<string, any>>): void;
|
187 | protected gotAttributeChanged(name: string, oldValue: string, newValue: string): void;
|
188 | protected gotReady(): void;
|
189 | protected connectedCallback(): void;
|
190 | /** Returns storage key based on url+control-names or `$options.autoSave` if `string` */
|
191 | get storageKey(): string;
|
192 | /** Get & parse value from storage according to option `autoSave`, $model */
|
193 | storageGet(): Partial<Model> | null;
|
194 | _preventStorageSave?: boolean;
|
195 | /** Save/remove model (only changes) to storage according to option `autoSave`, $model
|
196 | * @returns model saved to localStorage or Null if removed */
|
197 | storageSave(model?: null | Record<string, any>): Partial<Model> | null;
|
198 | protected disconnectedCallback(): void;
|
199 | }
|
200 | export {};
|