UNPKG

web-ui-pack

Version:
372 lines (371 loc) 13 kB
import { onEvent } from "../indexHelpers"; import WUPPopupElement from "../popup/popupElement"; import WUPBaseControl from "./baseControl"; import WUPTextControl from "./text"; export var MenuOpenCases; (function (MenuOpenCases) { MenuOpenCases[MenuOpenCases["onManualCall"] = 0] = "onManualCall"; MenuOpenCases[MenuOpenCases["onFocus"] = 2] = "onFocus"; MenuOpenCases[MenuOpenCases["onFocusAuto"] = 4] = "onFocusAuto"; MenuOpenCases[MenuOpenCases["onClick"] = 8] = "onClick"; MenuOpenCases[MenuOpenCases["onClickInput"] = 16] = "onClickInput"; MenuOpenCases[MenuOpenCases["onInput"] = 32] = "onInput"; MenuOpenCases[MenuOpenCases["onPressArrowKey"] = 64] = "onPressArrowKey"; })(MenuOpenCases || (MenuOpenCases = {})); export var MenuCloseCases; (function (MenuCloseCases) { MenuCloseCases[MenuCloseCases["onManualCall"] = 0] = "onManualCall"; MenuCloseCases[MenuCloseCases["onClick"] = 1] = "onClick"; MenuCloseCases[MenuCloseCases["onSelect"] = 2] = "onSelect"; MenuCloseCases[MenuCloseCases["onFocusLost"] = 3] = "onFocusLost"; MenuCloseCases[MenuCloseCases["OnPressEsc"] = 4] = "OnPressEsc"; MenuCloseCases[MenuCloseCases["OnPressEnter"] = 5] = "OnPressEnter"; MenuCloseCases[MenuCloseCases["OnOptionsChange"] = 6] = "OnOptionsChange"; })(MenuCloseCases || (MenuCloseCases = {})); export default class WUPBaseComboControl extends WUPTextControl { #ctr = this.constructor; static get $style() { return `${super.$style} :host { cursor: pointer; } :host input { cursor: text; } :host input:placeholder-shown, :host input:read-only { cursor: pointer; } :host label:after { content: ""; -webkit-mask-image: var(--ctrl-icon-img); mask-image: var(--ctrl-icon-img); } @media not all and (prefers-reduced-motion) { :host label:after { transition: transform var(--anim); } } :host button[clear] { margin: 0; } :host > [menu] { padding: 0; max-height: 300px; z-index: 8010; }`; } static $defaults = { ...WUPTextControl.$defaults, validationRules: { ...WUPBaseControl.$defaults.validationRules, }, openCase: 8 | 2 | 64, readOnlyInput: false, }; $onOpenMenu; $onCloseMenu; #isOpened = false; get $isOpened() { return this.#isOpened; } async $closeMenu() { await this.goCloseMenu(0); } async $openMenu() { await this.goOpenMenu(0); this.#isOpened && (await this.$refPopup.$open()); } $refPopup; renderControl() { super.renderControl(); const i = this.$refInput; i.setAttribute("role", "combobox"); i.setAttribute("aria-haspopup", "listbox"); i.setAttribute("aria-expanded", false); i.focus = this.inputFocus; } gotChanges(propsChanged) { super.gotChanges(propsChanged); this._opts.readOnlyInput ? this.$refInput.removeAttribute("aria-autocomplete") : this.$refInput.setAttribute("aria-autocomplete", "list"); } gotFormChanges(propsChanged) { super.gotFormChanges(propsChanged); const isMenuEnabled = !this.$isDisabled && !this.$isReadOnly; !isMenuEnabled && this.removePopup(); } setupInputReadonly() { this.$refInput.readOnly = this.$isReadOnly || !!this._opts.readOnlyInput; } isLockInputDropdown(e) { return (!(this._opts.openCase & 16) && e.type === "click" && e.target instanceof HTMLInputElement && !e.target.readOnly && e.target.value !== ""); } canOpenMenu(openCase, e) { if (this.$isReadOnly || this.$isDisabled) { return false; } if (e && this.isLockInputDropdown(e)) { return false; } const can = !!(this._opts.openCase & openCase) || openCase === 0; return can; } renderPopup(menuId) { const p = document.createElement("wup-popup"); this.$refPopup = p; p.$options.openCase = 0; p.$options.target = this; p.$options.offsetFitElement = [1, 1]; p.$options.minWidthByTarget = true; p.$options.placement = [ WUPPopupElement.$placements.$bottom.$start, WUPPopupElement.$placements.$bottom.$end, WUPPopupElement.$placements.$top.$start, WUPPopupElement.$placements.$top.$end, WUPPopupElement.$placements.$bottom.$start.$resizeHeight, WUPPopupElement.$placements.$bottom.$end.$resizeHeight, WUPPopupElement.$placements.$top.$start.$resizeHeight, WUPPopupElement.$placements.$top.$end.$resizeHeight, ]; p.setAttribute("menu", ""); p.$options.animation = 1; const i = this.$refInput; i.setAttribute("aria-owns", menuId); i.setAttribute("aria-controls", menuId); return p; } async goOpenMenu(openCase, e) { if (this.#isOpened) { return this.$refPopup; } const can = this.canOpenMenu(openCase, e); if (!can) { return null; } this.#isOpened = true; if (!this.$refPopup) { const menuId = this.#ctr.$uniqueId; this.$refPopup = this.renderPopup(menuId); this.renderMenu(this.$refPopup, menuId); this.appendChild(this.$refPopup); await new Promise((res) => setTimeout(res)); } if (!this.#isOpened) { return null; } this.$refPopup.$open().then(() => this.fireEvent("$openMenu", { cancelable: false })); this.setAttribute("opened", ""); this.$refInput.setAttribute("aria-expanded", true); return this.$refPopup; } canCloseMenu(closeCase, e) { if (e && this.isLockInputDropdown(e)) { return false; } if (closeCase === 1) { if (!(this._opts.openCase & 8)) { return false; } if (this.$refPopup.includesTarget(e)) { return false; } } return true; } _isClosing; async goCloseMenu(closeCase, e) { if (!this.#isOpened || this._isClosing) { return false; } if (!this.canCloseMenu(closeCase, e)) { return false; } this.#isOpened = false; this._isClosing = true; await this.$refPopup?.$close(); delete this._isClosing; if (this.#isOpened) { return false; } if (closeCase === 3) { this.removePopup(); } this.removeAttribute("opened"); this.$refInput.setAttribute("aria-expanded", false); this.focusMenuItem(null); this.selectMenuItem(null); setTimeout(() => this.fireEvent("$closeMenu", { cancelable: false })); return true; } _focusedMenuItem; focusMenuItem(next) { this._focusedMenuItem?.removeAttribute("focused"); if (next) { next.id = next.id || this.#ctr.$uniqueId; next.setAttribute("focused", ""); this.$refInput.setAttribute("aria-activedescendant", next.id); this.tryScrollTo(next); } else { this.$refInput.removeAttribute("aria-activedescendant"); } this._focusedMenuItem = next; } tryScrollTo(el) { const scroll = el.scrollIntoViewIfNeeded; scroll ? scroll.call(el, false) : el.scrollIntoView({ behavior: "instant" }); } _selectedMenuItem; selectMenuItem(next) { this._selectedMenuItem?.setAttribute("aria-selected", false); if (next) { next.setAttribute("aria-selected", true); this.tryScrollTo(next); } this._selectedMenuItem = next; } gotKeyDown(e) { const isEscPrevent = this.#isOpened && e.key === "Escape"; !isEscPrevent && super.gotKeyDown(e); if (e.altKey || e.shiftKey || e.ctrlKey) { return; } if (!this.#isOpened) { if (this._opts.openCase & 64) { if (e.key === "ArrowDown" || e.key === "ArrowUp") { e.preventDefault(); this.goOpenMenu(64, e).then(() => this.focusMenuItemByKeydown(e)); return; } } return; } switch (e.key) { case "Escape": e.preventDefault(); this.resetInputValue(); this.goCloseMenu(4); break; case "Enter": e.preventDefault(); { const el = this._focusedMenuItem; setTimeout(() => { const isPrevented = el && !el.dispatchEvent(new MouseEvent("click", { cancelable: true, bubbles: true })); if (!isPrevented) { this.$refInput.value || this.$isRequired ? this.resetInputValue() : this.setValue(undefined, 3, true); this.goCloseMenu(5); } }); } break; case " ": break; default: this.focusMenuItemByKeydown(e); break; } } gotInput(e, preventValueChange) { !this.$isOpened && this._opts.openCase & 32 && this.$isFocused && this.goOpenMenu(32); if (preventValueChange) { this._refHistory?.handleInput(e); } else { super.gotInput(e); } } gotFocus(ev) { const arr = super.gotFocus(ev); const sc = this.$refInput._isFocusCall ? 4 : 2; delete this.$refInput._isFocusCall; let isPreventedFromClick = false; setTimeout(() => this.$isFocused && !isPreventedFromClick && this.goOpenMenu(sc, ev), 1); let clickAfterFocus = true; let onInputStartClick = false; const dsps0 = onEvent(this.$refInput, "mousedown", () => { const allow = !this.$refInput.readOnly && !(this._opts.openCase & 16); onInputStartClick = allow; }); let isInsideClick = false; const dsps1 = onEvent(this, "click", (e) => { isPreventedFromClick = e.defaultPrevented; const skip = e.defaultPrevented || e.button || isInsideClick || onInputStartClick; if (!skip) { !this.#isOpened || clickAfterFocus ? this.goOpenMenu(8, e) : this.goCloseMenu(1, e); } isInsideClick = true; setTimeout(() => (isInsideClick = false), 1); }); const dsps2 = onEvent(document, "click", (e) => { !onInputStartClick && !isInsideClick && this.goCloseMenu(3, e); onInputStartClick = false; }); setTimeout(() => document.addEventListener("pointerdown", () => { clickAfterFocus = false; }, { once: true, passive: true, capture: true }), 10); arr.push(dsps0, dsps1, dsps2); return arr; } gotFocusLost() { !this.#isOpened && !this._isClosing && this.removePopup(); this.goCloseMenu(3); super.gotFocusLost(); } selectValue(v, canCloseMenu = true) { this.setValue(v, 4); canCloseMenu && setTimeout(() => this.goCloseMenu(2)); } resetInputValue() { if (this._inputError) { this.$showError(this._inputError); return; } const isClear = !this.$refInput.value && !this._selectedMenuItem; isClear ? this.setValue(undefined, 2) : this.setInputValue(this.$value, 4); } setInputValue(v, reason) { const p = this.valueToInput(v); this.setInputValueDirect(p, reason); } setInputValueDirect(v, reason) { super.setInputValue(v, reason); } removePopup() { this.$refPopup?.remove(); this.$refPopup = undefined; this._focusedMenuItem = undefined; this._selectedMenuItem = undefined; if (this.#isOpened) { this.#isOpened = false; this.removeAttribute("opened"); this.$refInput.setAttribute("aria-expanded", false); this.$refInput.removeAttribute("aria-activedescendant"); } } gotRemoved() { this.removePopup(); super.gotRemoved(); } inputFocus() { this._isFocusCall = true; HTMLInputElement.prototype.focus.call(this); delete this._isFocusCall; } }