web-ui-pack
Version:
Web package with UI elements
372 lines (371 loc) • 13 kB
JavaScript
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);
}
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;
}
}