web-ui-pack
Version:
Web package with UI elements
246 lines (245 loc) • 12.5 kB
TypeScript
import WUPBaseElement, { AttributeMap } from "./baseElement";
import IBaseControl from "./controls/baseControl.i";
export declare const enum SubmitActions {
/** Disable any action */
none = 0,
/** Scroll to first error (if exists) and focus control */
goToError = 1,
/** Validate until first error (otherwise validate all) */
validateUntilFirst = 2,
/** Validate only that user can change and skip controls that notVisible/disabled/readonly (values from such items still collected) */
validateChangeable = 4,
/** Collect to model only changed values */
collectChanged = 8,
/** Reset isDirty and assign $value to $initValue for controls (on success only) */
reset = 16,
/** Lock the whole form during the pending state (set $isPending = true or provide `promise` to submitEvent.waitFor);
* Otherwise user can submit several times in a short time;
* If promise resolves during the short-time pending state won't be set, otherwise it takes at least 300ms via helper {@link promiseWait} */
lockOnPending = 32
}
declare const tagName = "wup-form";
declare global {
namespace WUP.Form {
interface SubmitDetails {
/** Model collected from controls */
model: Record<string | number, any>;
/** Form related to submit event; equal to event.target */
relatedForm: WUPFormElement;
/** Event that produced submit event; null if `form.$submit()` is called */
relatedEvent: MouseEvent | KeyboardEvent | null;
/** Element that that produced submit event */
submitter: HTMLElement | null;
/** Point a promise as callback to allow form show pending state during the promise */
waitFor?: Promise<unknown>;
}
interface EventMap extends WUP.Base.EventMap {
/** Fires before $submit is happened; can be prevented via `e.preventDefault()` */
$willSubmit: CustomEvent<Pick<SubmitDetails, "relatedEvent" | "relatedForm" | "submitter">>;
/** Fires by user-submit when validation successful and model is collected
* * @tutorial
* call `e.preventDefault()` to prevent dispatching `$submitEnd` & closing modal (if form in modal) */
$submit: CustomEvent<SubmitDetails>;
/** Fires when submit is end (after http-response);
* @tutorial
* call `e.preventDefault()` to prevent closing modal (if form in modal) */
$submitEnd: CustomEvent<{
success: boolean;
}>;
}
interface Options {
/** Actions that enabled on submit event; You can point several like: `goToError | collectChanged`
* @defaultValue goToError | validateUntilFirst | reset | lockOnPending */
submitActions: SubmitActions;
/** Enable to store data in localStorage to prevent losing till submitted;
* @defaultValue false
* @tutorial Troubleshooting
* * It doesn't save values that are complex objects. So `wup-select.$options.items = [{text: "N1",value: {id:1,name:'Nik'} }]` is skipped
* * Point string-value if default storage-key doesn't fit: based on `url+control.names` @see{@link WUPFormElement.storageKey}
* @defaultValue false */
autoStore: boolean | string;
/** Focus first possible element when it's appended to layout
* @defaultValue false */
autoFocus: boolean;
/** Disallow edit/copy value; adds attr [disabled] for styling
* @defaultValue false */
disabled: boolean;
/** Disallow copy value; adds attr [readonly] for styling
* @defaultValue false */
readOnly: boolean;
/** Enable/disable browser-autocomplete; if control has no autocomplete option then it's inherited from form
* @defaultValue false */
autoComplete: boolean;
}
interface JSXProps extends WUP.Base.OnlyNames<Options> {
"w-submitActions"?: SubmitActions | number;
"w-autoStore"?: boolean | string;
"w-autoFocus"?: boolean | "";
"w-autoComplete"?: boolean | "";
/** @deprecated use [disabled] instead since related to CSS-styles */
"w-disabled"?: boolean | "";
disabled?: boolean | "";
/** @deprecated use [disabled] instead since related to CSS-styles */
"w-readonly"?: boolean | "";
readonly?: boolean | "";
/** @deprecated SyntheticEvent is not supported. Use ref.addEventListener('$change') instead */
onChange?: never;
/** @deprecated SyntheticEvent is not supported. Use ref.addEventListener('$willSubmit') instead */
onWillSubmit?: never;
/** @deprecated SyntheticEvent is not supported. Use ref.addEventListener('$submit') instead */
onSubmit?: never;
}
}
interface HTMLElementTagNameMap {
[tagName]: WUPFormElement;
}
}
declare module "react" {
namespace JSX {
interface IntrinsicElements {
/** Wrapper of FormHTMLElement that collect values from controls
* @see {@link WUPFormElement} */
[tagName]: WUP.Base.ReactHTML<WUPFormElement> & WUP.Form.JSXProps;
}
}
}
declare module "preact/jsx-runtime" {
namespace JSX {
interface HTMLAttributes<RefType> {
}
interface IntrinsicElements {
/** Wrapper of FormHTMLElement that collect values from controls
* @see {@link WUPFormElement} */
[tagName]: HTMLAttributes<WUPFormElement> & WUP.Form.JSXProps;
}
}
}
/** Wrapper of FormHTMLElement that collect values from controls
* @see demo {@link https://yegorich555.github.io/web-ui-pack/controls}
* @example
* // init form
* const form = document.createElement("wup-form");
* form.$options.autoComplete = false;
* form.$initModel = { email: "test-me@google.com" };
* form.addEventListener("$submit", (e) => console.warn(e.detail.model) );
* form.$onSubmit = async (e)=>{ await postHere(e.detail.model); } // equal to form.addEventListener
* // init control
* const el = document.createElement("wup-text");
* el.$options.name = "email";
* el.$options.validations = { required: true, email: true };
* form.appendChild(el);
* const btn = form.appendChild(document.createElement("button"));
* btn.textContent = "Submit";
* btn.type = "submit";
* document.body.appendChild(form);
* // or HTML
* <wup-form w-autocomplete w-autofocus>
* <wup-text w-name="email" />
* <button type="submit">Submit</submit>
* </wup-form>;
* @tutorial Troubleshooting/rules:
* * options like $initModel, $model overrides control.$initValue, control.$value (every control that matches by $options.name)
* * 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
* @example
* <wup-form
ref={(el) => {
if (el) {
el.$initModel = { email: "test-me@google.com" };
el.$onSubmit = async (ev)=>{
await postHere();
}
}
}}
>
<wup-text
ref={(el) => {
if (el) {
setTimeout(() => {
el.$options.name = "email";
el.$initValue = "";
});
}
}}
<button type="submit">Submit</button>
/>
</wup-form>
*/
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> {
#private;
static get $styleRoot(): string;
static get $style(): string;
static get mappedAttributes(): Record<string, AttributeMap>;
/** Find form related to control,register and apply initModel if initValue undefined */
static $tryConnect(control: IBaseControl & HTMLElement): WUPFormElement | undefined;
/** Map model to control-values */
static $modelToControls<T extends Record<string, any>>(m: T | undefined, controls: IBaseControl[], prop: keyof Pick<IBaseControl, "$value" | "$initValue">): void;
/** Collect model from control-values */
static $modelFromControls<T>(prevModel: Partial<T>, controls: IBaseControl[], prop: keyof Pick<IBaseControl, "$value" | "$initValue">, isOnlyChanged?: boolean): Partial<T>;
static get observedAttributes(): Array<string>;
/** Default options - applied to every element. Change it to configure default behavior */
static $defaults: WUP.Form.Options;
/** Fires before $submit is happened; can be prevented via `e.preventDefault()` */
$onWillSubmit?: (ev: WUP.Form.EventMap["$willSubmit"]) => void;
/** Dispatched on submit
* @tutorial
* * need to return promise to lock form and show spinner on http-request
* * call `e.preventDefault()` to prevent dispatching `$submitEnd` & closing modal (if form in modal) */
$onSubmit?: (ev: WUP.Form.EventMap["$submit"]) => void | Promise<unknown>;
/** Fires when submit is end (after http-response);
* @tutorial
* call `e.preventDefault()` to prevent closing modal (if form in modal) */
$onSubmitEnd?: (ev: WUP.Form.EventMap["$submitEnd"]) => void;
/** Dispatched on submit */
/** All controls related to form */
$controls: IBaseControl<any>[];
/** Returns related to form controls with $options.name != null */
get $controlsAttached(): IBaseControl<any>[];
_model?: Partial<Model>;
/** Model related to every control inside (mapped object via control.$options.name);
* @see {@link BaseControl.prototype.$value}
* @tutorial rules
* * `form.$model = { firstName: 'Hell' }` updates only control with $options.name==='firstName'
* * `form.$model = { firstName: undefined }` reset only control with $options.name==='firstName' */
get $model(): Partial<Model>;
set $model(m: Partial<Model>);
_initModel?: Partial<Model>;
/** Default/init model related to every control inside;
* @see {@link BaseControl.prototype.$initValue} */
get $initModel(): Partial<Model> | undefined;
set $initModel(m: Partial<Model> | undefined);
/** Pending state (spinner + lock form if SubmitActions.lockOnPending enabled) */
get $isPending(): boolean;
set $isPending(v: boolean);
/** Returns true if all nested controls (with name) are valid */
get $isValid(): boolean;
/** Returns true if some of controls value is changed by user */
get $isChanged(): boolean;
/** Call it to manually trigger submit or better to use `gotSubmit` for handling events properly */
$submit(): void;
/** Validate all attached controls
* @param tillFirstInvalid point `true` if need to find first invalid control; if skipped then will be defined from `$options.submitActions: SubmitActions.validateUntilFirst`
* @param includeLocked collect & validate also hidden/readonly/disabled controls
* @returns array of invalid controls (empty if all are valid) */
$validate(tillFirstInvalid?: boolean, includeLocked?: boolean): Array<IBaseControl>;
/** Called on every spin-render */
renderSpin(target: HTMLElement): {
dispose: () => void;
};
/** Change pending state */
protected changePending(v: boolean): void;
/** Called on submit before validation (to fire validation & $onSubmit if successful) */
gotSubmit(e: KeyboardEvent | MouseEvent | null, submitter: HTMLElement): void;
protected gotChanges(propsChanged: Array<keyof WUP.Form.Options> | null): void;
protected gotReady(): void;
protected connectedCallback(): void;
/** Returns storage key based on url+control-names or `$options.autoStore` if `string` */
get storageKey(): string;
/** Get & parse value from storage according to option `autoStore`, $model */
storageGet(): Partial<Model> | null;
_preventStorageSave?: boolean;
/** Save/remove model (only changes) to storage according to option `autoStore`, $model
* @returns model saved to localStorage or Null if removed */
storageSave(model?: null | Record<string, any>): Partial<Model> | null;
protected disconnectedCallback(): void;
}
export {};