web-ui-pack
Version:
Web package with UI elements
336 lines (335 loc) • 19 kB
TypeScript
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: {
[]?: (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: {
[]?: VM[K] | ValidityFunction<T>;
} | {
[]: 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> {
/** 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;
}