UNPKG

10.5 kBTypeScriptView Raw
1import WUPBaseElement 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 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}
20declare const tagName = "wup-form";
21declare 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 */
140export 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}
200export {};