web-ui-pack
Version:
Web package with UI elements
236 lines (235 loc) • 11.8 kB
TypeScript
import WUPPopupElement from "../popup/popupElement";
import WUPSpinElement from "../spinElement";
import WUPBaseComboControl, { MenuOpenCases } from "./baseCombo";
import WUPBaseControl, { SetValueReasons } from "./baseControl";
declare const tagName = "wup-select";
declare global {
namespace WUP.Select {
interface MenuItem<T> {
/** Provide ordinary string for rendering as menuItem OR
* Use li.innerHTML to render value & return string required for input
* @example
el.$options.items = [
{ value: 1, text: "Item N 1"}, // this ordinary behavior
{
value: 2,
text: (value, li, i, control) => {
li.innerHTML =
`<button class='delete'><button>Item N ${i+1}` // some custom HTML here
// li.textContent = `Item N ${i+1}`; // or use this for fastest render
const btn = li.querySelector('button')!;
btn.onclick = (e) => {
e.preventDefault();
control.$closeMenu();
}
return `Item N ${value}`; // this is text rendered in input when related item selected
},
},
]; */
text: string | ((value: T, li: HTMLElement, i: number, control: WUPBaseControl) => string);
/** Value tied with menu item */
value: T;
/** Use `(e)=>e.preventDefault()` to prevent selection & closing menu and do custom action */
onClick?: (e: MouseEvent, me: MenuItem<T>) => void;
}
interface MenuItemElement extends HTMLLIElement {
_value: any;
_text: string;
}
interface EventMap extends WUP.BaseCombo.EventMap {
}
interface ValidityMap extends WUP.BaseCombo.ValidityMap {
/** Count of minimal values that must be selected (only for option `multi`) */
minCount: number;
/** Count of minimal values that must be selected (only for option `multi`) */
maxCount: number;
}
interface NewOptions<T = any> {
/** Items showed in dropdown-menu. Provide promise/api-call to show pending status when control retrieves data! */
items: MenuItem<T>[] | Promise<MenuItem<T>[]> | (() => MenuItem<T>[] | Promise<MenuItem<T>[]>);
/** Allow user to create new value if value not found in items
* @defaultValue false */
allowNewValue: boolean;
/** Allow to select multiple values; in this case $value & $initValue must contain Array<ValueType>
* * @defaultValue false */
multiple: boolean;
}
interface Options<T = any, VM = ValidityMap> extends WUP.BaseCombo.Options<T, VM>, NewOptions<T> {
/** Case when menu-popup to show
* @defaultValue onPressArrowKey | onClick | onFocus | onInput */
openCase: MenuOpenCases;
/** Set `true` to make input not editable but allow select items via popup-menu (ordinary dropdown mode)
* @tutorial
* * set number X to enable autoMode where `input.readOnly = items.length < X` */
readOnlyInput: boolean | number;
}
interface JSXProps<C = WUPSelectControl> extends WUP.BaseCombo.JSXProps<C>, WUP.Base.OnlyNames<NewOptions> {
/** Items showed in dropdown-menu. Provide promise/api-call to show pending status when control retrieves data!
* Global reference to object with array
* @see {@link MenuItems}
* @example
* ```js
* window.myItems = [...];
* <wup-select w-items="window.myItems"></wup-select>
* ``` */
"w-items"?: string;
"w-allowNewValue"?: boolean | "";
"w-multiple"?: boolean | "";
}
}
interface HTMLElementTagNameMap {
[]: WUPSelectControl;
}
}
declare module "react" {
namespace JSX {
interface IntrinsicElements {
/** Form-control with dropdown/combobox behavior
* @see {@link WUPSelectControl} */
[]: WUP.Base.ReactHTML<WUPSelectControl> & WUP.Select.JSXProps;
}
}
}
declare module "preact/jsx-runtime" {
namespace JSX {
interface HTMLAttributes<RefType> {
}
interface IntrinsicElements {
/** Form-control with dropdown/combobox behavior
* @see {@link WUPSelectControl} */
[]: HTMLAttributes<WUPSelectControl> & WUP.Select.JSXProps;
}
}
}
/** Form-control with dropdown/combobox behavior
* @see demo {@link https://yegorich555.github.io/web-ui-pack/control/select}
* @example
const el = document.createElement("wup-select");
el.$options.name = "gender";
el.$options.items = [
{ value: 1, text: "Male" },
{ value: 2, text: "Female" },
{ value: 3, text: "Other/Skip" },
];
el.$initValue = 3;
el.$options.validations = { required: true };
const form = document.body.appendChild(document.createElement("wup-form"));
form.appendChild(el);
// or HTML
<wup-form>
<wup-select w-name="gender" w-initvalue="3" w-validations="myValidations" w-items="myDropdownItems" />
</wup-form>;
* @tutorial innerHTML @example
* <label>
* <span> // extra span requires to use with icons via label:before, label:after without adjustments
* <input/>
* <strong>{$options.label}</strong>
* </span>
* <button clear/>
* <wup-popup menu>
* <ul>
* <li>Item 1</li>
* <li>Item 2</li>
* // etc. /
* </ul>
* </wup-popup>
* </label>
*/
export default class WUPSelectControl<ValueType = any | any[], ItemType = ValueType, TOptions extends WUP.Select.Options = WUP.Select.Options, EventMap extends WUP.Select.EventMap = WUP.Select.EventMap> extends WUPBaseComboControl<ValueType, TOptions, EventMap> {
static get $style(): string;
/** Text for listbox when no items are displayed */
static $textNoItems: string | undefined;
/** Text for aria-label of <ul> element */
static $ariaLabelItems: string;
static $isEqual(v1: unknown, v2: unknown, c: WUPSelectControl): boolean;
/** Function to filter menuItems based on inputValue
* @param menuItemText textcontent in lowercase
* @param menuItemValue value related to items[x].value
* @param inputValue normalized input value (trimStart + lowercase)
* @param inputRawValue input value without normalization
* @returns true if menuItem must be visible in menu
*/
static $filterMenuItem(this: WUPSelectControl, menuItemText: string, menuItemValue: any, inputValue: string, inputRawValue: string): boolean;
static $defaults: WUP.Select.Options;
static cloneDefaults<T extends Record<string, any>>(): T;
/** Returns whether control in pending state or not (shows spinner) */
get $isPending(): boolean;
/** Called when need to parse attr [initValue] */
parse(attrValue: string): ValueType | undefined;
parseInput(text: string): ValueType | undefined;
/** Returns string for storage */
valueToStrCompare(a: WUP.Select.MenuItem<ValueType>): string | null;
valueFromStorage(str: string, skipMultiple?: boolean): ValueType | undefined;
/** Store value to storage; if item.text is not function then stored text, otherwise value.toString()
* @see {@link valueToStrCompare} */
valueToStorage(v: ValueType, skipMultiple?: boolean): string | null;
/** It's called with option allowNewValue to find value related to text */
protected findValueByText(txt: string): ValueType | undefined;
/** Called on every spin-render */
renderSpin(): WUPSpinElement;
/** Required to wait for fetching items to setup initValue */
_onPendingInitValue?: () => void;
/** Called to get/fetch items based on $options.items */
fetchItems(): Promise<WUP.Select.MenuItem<ValueType>[]> | WUP.Select.MenuItem<ValueType>[];
protected gotChanges(propsChanged: Array<keyof WUP.Select.Options> | null): void;
setupInputReadonly(): void;
setupInitValue(propsChanged: Array<keyof WUP.Select.Options> | null): void;
/** Change pending state */
protected changePending(v: boolean): void;
/** All items of current menu */
_menuItems?: {
all: WUP.Select.MenuItemElement[];
/** Index of items filtered by input */
filtered?: Array<number>;
/** Index (in 'filtered' otherwise in 'all' array) of item that has virtual-focus; */
focused: number;
};
/** Items resolved from options */
_cachedItems?: WUP.Select.MenuItem<ValueType>[];
/** Called when NoItems need to show */
protected renderMenuNoItems(popup: WUPPopupElement, isReset: boolean): void;
protected renderMenu(popup: WUPPopupElement, menuId: string): HTMLElement;
/** Method returns items defined based on $options.items */
protected getItems(): WUP.Select.MenuItem<ValueType>[];
protected valueToText(v: ItemType, items: WUP.Select.MenuItem<ItemType>[]): string;
protected setInputValue(v: ValueType | undefined, reason: SetValueReasons): void;
protected valueToInput(v: ValueType | undefined): string;
/** Create menuItems as array of HTMLLiElement with option _text required to filtering by input (otherwise content can be html-structure) */
protected renderMenuItems(ul: HTMLUListElement): WUP.Select.MenuItemElement[];
protected gotMenuClick(e: MouseEvent): void;
/** Called when need to setValue & close base on clicked item */
protected gotMenuItemClick(e: MouseEvent, li: WUP.Select.MenuItemElement): void;
protected selectValue(v: ValueType, canCloseMenu?: boolean): void;
canOpenMenu(openCase: MenuOpenCases, e?: MouseEvent | FocusEvent | KeyboardEvent | null): boolean;
protected goOpenMenu(openCase: MenuOpenCases, e?: MouseEvent | FocusEvent | KeyboardEvent | null): Promise<WUPPopupElement | null>;
/** Returns first selected visible item */
protected getFirstSelected(): WUP.Select.MenuItemElement | undefined;
protected selectMenuItem(next: HTMLElement | null): void;
/** Select menu item if value !== undefined and menu is opened */
protected selectMenuItemByValue(v: ValueType | undefined): void;
protected focusMenuItem(next: HTMLElement | null): void;
/** Focus item by index or reset is index is null (via aria-activedescendant).
* If menuItems is filtered by input-text than index must point on filtered array */
protected focusMenuItemByIndex(index: number): void;
protected focusMenuItemByKeydown(e: KeyboardEvent): void;
protected gotKeyDown(e: KeyboardEvent): void;
/** Called to filter menuItems (on input change etc.) */
protected filterMenuItems(): void;
/** Called on showMenu when user opened it without input-change */
protected clearFilterMenuItems(): void;
/** Called to hide/show menuItem */
protected filterMenuItem(el: HTMLElement, hide: boolean): void;
/** Returns if possible to de-select menu items when input is empty */
private get canClearSelection();
protected gotBeforeInput(e: WUP.Text.GotInputEvent): void;
/** Called on input change to update menu if possible */
protected tryUpdateMenu(): void;
clearValue(): void;
protected gotInput(e: WUP.Text.GotInputEvent): void;
protected gotFocus(e: FocusEvent): Array<() => void>;
protected gotFocusLost(): void;
protected setValue(v: ValueType | undefined, reason: SetValueReasons, skipInput?: boolean): boolean | null;
protected removePopup(): void;
}
export {};