UNPKG

web-ui-pack

Version:
336 lines (335 loc) 19 kB
import WUPBaseElement, { AttributeMap } from "../baseElement"; import WUPFormElement from "../formElement"; import WUPPopupElement from "../popup/popupElement"; import IBaseControl from "./baseControl.i"; export declare const enum SetValueReasons { /** When `control.$value = 'some value'` */ manual = 1, /** When clearing happened (by Esc or ClearButton click) OR reset to previous value */ clear = 2, /** When user changes on UI */ userInput = 3, /** When user selected existed option on UI and don't need to call selectMenuItem again (for combobox) */ userSelect = 4, /** When $initValue is changed */ initValue = 5, /** When value changed from storage (on init if `$options.storageKey` is pointed) */ storage = 6 } /** Cases of validation for WUP Controls */ export declare const enum ValidationCases { none = 0, /** Wait for first user-change > wait for valid > wait for invalid >> show error; * When invalid: wait for valid > hide error > wait for invalid > show error * Also you can check $options.validateDebounceMs */ onChangeSmart = 1, /** Validate when user changed value (via type,select etc.); Also you can check $options.validateDebounceMs */ onChange = 2, /** Validate when control lost focus */ onFocusLost = 4, /** Validate if control has value and gets focus (recommended option for password with $options.validationShowAll) */ onFocusWithValue = 8, /** Validate when got state 'isReady' and initValue is not empty */ onInit = 16 } /** Actions when user pressed ESC or button-clear */ export declare const enum ClearActions { /** Only clear without extra logic */ clear = 0, /** Pressing Esc/buttonClear: 1st: clear, 2nd: revert clearing (it helps to avoid accidental action) */ clearBack = 1, /** Pressing Esc/buttonClear: 1st: rollback to init, 2nd: clear, 3rd: revert clearing (it helps to avoid accidental action) */ initClearBack = 2 } /** Points on what called validation */ export declare const enum ValidateFromCases { /** When element appended to layout */ onInit = 0, /** When control gets focus */ onFocus = 1, /** When control loses focus (including document.activeElement) */ onFocusLost = 2, /** When value changed */ onChange = 3, /** When form.submit is called (via button submit or somehow else); It's impossible to disable */ onSubmit = 4, /** When $validate() is called programmatically */ onManualCall = 5 } declare global { namespace WUP.BaseControl { type AutoComplete = AutoFill; interface EventMap extends WUP.Base.EventMap { /** Called on value change */ $change: CustomEvent<{ reason: SetValueReasons; }>; } interface ValidityMap { /** If $value is empty shows message 'This field is required` */ required: boolean; } type ValidityFunction<T> = (value: T | undefined, control: IBaseControl, reason: ValidateFromCases | null) => false | string; interface Options<T = any, VM = ValidityMap> { /** Title/label of control; * @defaultValue null that means auto=>parsed from option [name]. To skip point `label=''` (empty string) */ label: string | undefined | null; /** Property/key of model (collected by form); For name `firstName` >> `model.firstName`; for `nested.firstName` >> `model.nested.firstName` etc. * * @tutorial * * point `null` or `undefined` to completely detach from FormElement * * point `''`(empty string) to partially detach (exclude from `form.$model`, `form.$isChanged`, but include in validations & submit) */ name: string | undefined | null; /** Focus element when it's appended to layout @defaultValue false */ autoFocus: boolean; /** Name to autocomplete by browser; Point `true` to inherit from `$options.name` or some string * if control has no autocomplete option then it's inherited from `form` * @see {@link HTMLInputElement.autocomplete} * @defaultValue null - means false if form.$options.autoComplete false also */ autoComplete: AutoComplete | boolean | null; /** Disallow edit/copy value; adds attr [disabled] for styling */ disabled: boolean; /** Disallow copy value; adds attr [readonly] for styling @defaultValue false */ readOnly: boolean; /** Debounce option for onFocusLost event (for validationCases.onFocusLost); * @see {@link onFocusLostOptions.debounceMs} in helpers/onFocusLost; * @defaultValue 100ms */ focusDebounceMs: number; /** Behavior that expected for clearing value inside control (via pressEsc or btnClear) * @defaultValue ClearActions.initClearBack */ clearActions: ClearActions; /** Rules defined for control. Impossible to override via `$options`. Use static `$defaults` instead * * all functions must return error-message when value === undefined * * all functions must return error-message if setValue is `true/enabled` or value doesn't fit a rule * * value can be undefined only when a rule named as 'required' or need to collect error-messages @see {@link Options.validationShowAll} * @example * ``` * WUPTextControl.$defaults.validationRules.isNumber = (v === undefined || !/^[0-9]*$/.test(v)) && "Please enter a valid number"; * * const el = document.body.appendChild(document.createElement("wup-text")); * el.$options.validations = { isNumber: true, }; * ``` */ validationRules: { [K in keyof VM]?: (value: T, setValue: VM[K], control: IBaseControl, reason: ValidateFromCases | null) => false | string; }; /** Rules enabled for current control (related to $defaults.validationRules) * @example * ``` * const el = document.body.appendChild(document.createElement("wup-text")); el.$options.validations = { min: 10, // set min 10symbols for $default.validationRules.min custom: (value: string | undefined) => (value === un\defined || value === "test-me") && "This is custom error", // custom validation for single element }; * ``` * @tutorial Troubleshooting ** If setup validations via attr it doesn't affect on $options.validations directly. Instead use el.validations getter instead */ validations: { [K in keyof VM]?: VM[K] | ValidityFunction<T>; } | { [k: string]: ValidityFunction<T>; } | null | undefined; /** When to validate control and show error. Validation by onSubmit impossible to disable * @defaultValue onChangeSmart | onFocusLost | onFocusWithValue | onSubmit */ validationCase: ValidationCases; /** Wait for pointed time after valueChange before showError (it's summarized with $options.debounce); WARN: hide error without debounce * @defaultValue 500 */ validateDebounceMs: number; /** Show all validation-rules with checkpoints as list instead of single error @defaultValue false; * @tutorial rules * * All listed rules must return error-message when value === undefined OR * * To skip rule from listing name with underscore, for example `_old: (v,c) => ...` */ validationShowAll: boolean; /** Storage key for auto saving value in storage; * @tutorial rules * * On init value from storage applies to `$value` and triggers onChange event * * Point empty string or `true` to inherit from $options.name * * Expected value can be converted toString & parsed from string itself. * * Override `valueFromStorage` & `valueToStorage` to change serializing (for complex objects, arrays etc.) * * Before API-call gather form.$model on init OR use $onChange event * @see {@link WUP.BaseControl.Options.storage} * @defaultValue emptyString (means `false`) */ storageKey?: boolean | string | null; /** Type of storage for saving value (if pointed storageKey) * @see {@link WUP.BaseControl.Options.storekey} * @defaultValue "local" */ storage?: "local" | "session" | "url"; } interface JSXProps<C = WUPBaseControl> extends WUP.Base.OnlyNames<Options> { /** Default value in string/boolean/number representation (depends on `control.prototype.parse()`) */ "w-initValue"?: string | boolean | number; "w-label"?: string; "w-name"?: string; "w-autoFocus"?: boolean; "w-autoComplete"?: string | 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 | ""; "w-clearActions"?: ClearActions | number; /** @deprecated use static `.$defaults.validationCase` instead */ "w-validationCase"?: never; /** @deprecated use static `.$defaults.validationCase` instead */ "w-focusDebounceMs"?: never; /** @deprecated use static `.$defaults.validationCase` instead */ "w-validationRules"?: never; /** Rules enabled for current control (related to $defaults.validationRules); * * Point Global reference to object * @example * ```js * window.someRules = { required: true }; * <wup-text w-validations="window.someRules"></wup-text> * ``` * @defaultValue [4,4] */ "w-validations"?: string; "w-validateDebounceMs"?: number; "w-validationShowAll"?: boolean | ""; "w-storageKey"?: boolean | string; "w-storage"?: "local" | "session" | "url"; /** @deprecated Use [required] for styling */ readonly required?: ""; /** @readonly Use [invalid] for styling */ readonly invalid?: boolean; /** @deprecated SyntheticEvent is not supported. Use ref.addEventListener('$change') instead */ onChange?: never; } } } /** Base abstract form-control */ export default abstract class WUPBaseControl<ValueType = any, TOptions extends WUP.BaseControl.Options = WUP.BaseControl.Options, Events extends WUP.BaseControl.EventMap = WUP.BaseControl.EventMap> extends WUPBaseElement<TOptions, Events> implements IBaseControl<ValueType> { #private; /** Text announced by screen-readers when control cleared; @defaultValue `cleared` */ static $ariaCleared: string; /** Text announced by screen-readers; @defaultValue `Error for` */ static $ariaError: string; /** CSS-variables related to component */ static get $styleRoot(): string; /** StyleContent related to component */ static get $style(): string; /** Default function to compare values/changes; It compares by valueOf() & by {id} * Redefine/define `valueOf()` for complex values to improve comparison */ static $isEqual(v1: unknown, v2: unknown, control: WUPBaseControl): boolean; /** Provide logic to check if control is empty (by comparison with value) */ static $isEmpty(v: unknown): boolean; static get observedAttributes(): Array<string>; static get mappedAttributes(): Record<string, AttributeMap>; static $defaults: WUP.BaseControl.Options; static cloneDefaults<T extends Record<string, any>>(): T; /** Called on value change */ $onChange?: (e: WUP.BaseControl.EventMap["$change"]) => void; /** Current value of control; You can change it without affecting on $isDirty state */ get $value(): ValueType | undefined; set $value(v: ValueType | undefined); /** Default/init value; used to define isChanged & to reset by keyEsc/buttonClear; * If control not $isDirty and not changed $value is updated according to $initValue */ get $initValue(): ValueType | undefined; set $initValue(v: ValueType | undefined); /** True if control is touched by user */ get $isDirty(): boolean; set $isDirty(v: boolean); /** Returns true if value is empty string or undefined */ get $isEmpty(): boolean; /** Returns if value changed (by comparisson with $initValue via static.isEqual option) * By default values compared by valueOf if it's possible */ get $isChanged(): boolean; _isValid?: boolean; /** Returns true if control is valid */ get $isValid(): boolean; /** Returns if related form or control disabled (true even if form.$options.disabled && !control.$options.disabled) */ get $isDisabled(): boolean; /** Returns if related form or control readonly (true even if form.$options.readOnly && !control.$options.readOnly) */ get $isReadOnly(): boolean; /** Returns if value is required - can't be undefined (depends on $options.validations.required) */ get $isRequired(): boolean; /** Returns autoComplete name if related form or control option is enabled (and control.$options.autoComplete !== false ) */ get $autoComplete(): WUP.BaseControl.AutoComplete | false; /** Check validity and show error if silent is false (by default) * @returns errorMessage or false (if valid) */ $validate(silent?: boolean): string | false; /** Check validity and show error * @returns errorMessage or false (if valid) */ validateBySubmit(): string | false; $showError(err: string): void; $hideError(): void; /** Add (replace) description of control to be announced by screen-readers */ $ariaDetails(text: string | null): void; /** Announce text by screenReaders if element is focused */ $ariaSpeak(text: string, delayMs?: number): void; $form?: WUPFormElement; /** Reference to nested HTMLElement */ $refLabel: HTMLLabelElement; /** Reference to nested HTMLElement */ $refInput: HTMLInputElement; /** Reference to nested HTMLElement tied with $options.label */ $refTitle: HTMLElement; /** Reference to nested HTMLElement tied with errorMessage */ $refError?: WUPPopupElement; protected gotChanges(propsChanged: Array<keyof WUP.BaseControl.Options | any> | null): void; /** Called on control/form Init and every time as control/form options changed. Method contains changes related to form `disabled`,`readonly` etc. */ gotFormChanges(propsChanged: Array<string> | null): void; /** Called to update disabled/readonly/autocomplete options on input */ setupInput(): void; /** Called to update readonly option on input */ setupInputReadonly(): void; /** Called on Init and options/attributes changes to update $initValue or $value (if pointed storageKey) */ setupInitValue(propsChanged: Array<keyof WUP.BaseControl.Options | any> | null): void; /** Returns true on !$isDisabled */ get canShowError(): boolean; /** Use this to append elements; called single time when element isConnected/appended to layout but not ready yet * Attention: this.$refInput is already defined */ protected abstract renderControl(): void; /** Called when need to parse inputValue or attr [initValue] */ abstract parse(text: string): ValueType | undefined; protected gotReady(): void; protected gotRender(): void; protected connectedCallback(): void; protected gotRemoved(): void; /** Returns validations enabled by user & defaults */ protected get validations(): WUP.BaseControl.Options["validations"] | undefined; /** Returns validations functions ready for checking */ protected get validationsRules(): Array<(v: ValueType | undefined, reason: ValidateFromCases | null) => string | false>; /** Current name of failed validation */ _errName?: string; _wasValidNotEmpty?: boolean; protected _validTimer?: ReturnType<typeof setTimeout>; /** Method called to check control based on validation rules and current value */ protected goValidate(fromCase: ValidateFromCases, silent?: boolean): string | false; /** Show (append/update) all validation-rules with checkpoints to existed error-element */ protected renderValidations(parent: WUPPopupElement | HTMLElement, skipRules?: string[]): void; protected renderError(): WUPPopupElement; /** Current error message */ _errMsg?: string; /** Method called to show error and set invalid state on input; point null to show all validation rules with checkpoints */ protected goShowError(err: string, target: HTMLElement): void; /** Method called to hide error and set valid state on input */ protected goHideError(): void; /** Called to serialize value from URL/storage; override it if you have object */ valueFromStorage(str: string): ValueType | undefined; /** Called to serialize value to URL/storage & must return null if need to remove */ valueToStorage(v: ValueType | null): string | null; /** Returns storage key based on options `storageKey` and `name` */ get storageKey(): string | undefined | null | false; /** Get & parse value from storage according to options `storageKey`, `storage` and `name` */ protected storageGet(): ValueType | undefined; /** Save value to storage storage according to options `storageKey`, `storage` and `name` */ protected storageSet(v: ValueType | undefined): void; /** Fire this method to update value & validate; returns null when not $isReady, true if changed */ protected setValue(v: ValueType | undefined, reason: SetValueReasons): boolean | null; /** Called after value is changed */ protected validateAfterChange(): void; _nextClearValue?: ValueType; /** Called every time on value-change to update clear-state & buttonClear * @returns nextClearValue */ protected setClearState(): ValueType | undefined; clearValue(): void; /** Called when element got focus; must return array of RemoveFunctions called on FocusLost */ protected gotFocus(ev: FocusEvent): Array<() => void>; /** Called when element completely lost focus; despite on blur it has debounce filter */ protected gotFocusLost(): void; /** Called when user pressed key */ protected gotKeyDown(e: KeyboardEvent & { submitPrevented?: boolean; }): void; }