UNPKG

@synergy-design-system/components

Version:

This package provides the base of the Synergy Design System as native web components. It uses [lit](https://www.lit.dev) and parts of [shoelace](https://shoelace.style/). Synergy officially supports the latest two versions of all major browsers (as define

242 lines (240 loc) 8.58 kB
// src/components/menu-item/submenu-controller.ts import { createRef, ref } from "lit/directives/ref.js"; import { html } from "lit"; var SubmenuController = class { constructor(host, hasSlotController) { this.popupRef = createRef(); this.enableSubmenuTimer = -1; this.isConnected = false; this.isPopupConnected = false; this.skidding = 0; this.submenuOpenDelay = 100; // Set the safe triangle cursor position this.handleMouseMove = (event) => { this.host.style.setProperty("--safe-triangle-cursor-x", `${event.clientX}px`); this.host.style.setProperty("--safe-triangle-cursor-y", `${event.clientY}px`); }; this.handleMouseOver = () => { if (this.hasSlotController.test("submenu")) { this.enableSubmenu(); } }; // Focus on the first menu-item of a submenu. this.handleKeyDown = (event) => { switch (event.key) { case "Escape": case "Tab": this.disableSubmenu(); break; case "ArrowLeft": if (event.target !== this.host) { event.preventDefault(); event.stopPropagation(); this.host.focus(); this.disableSubmenu(); } break; case "ArrowRight": case "Enter": case " ": this.handleSubmenuEntry(event); break; default: break; } }; this.handleClick = (event) => { var _a; if (event.target === this.host) { event.preventDefault(); event.stopPropagation(); } else if (event.target instanceof Element && (event.target.tagName === "syn-menu-item" || ((_a = event.target.role) == null ? void 0 : _a.startsWith("menuitem")))) { this.disableSubmenu(); } }; // Close this submenu on focus outside of the parent or any descendants. this.handleFocusOut = (event) => { if (event.relatedTarget && event.relatedTarget instanceof Element && this.host.contains(event.relatedTarget)) { return; } this.disableSubmenu(); }; // Prevent the parent menu-item from getting focus on mouse movement on the submenu this.handlePopupMouseover = (event) => { event.stopPropagation(); }; // Set the safe triangle values for the submenu when the position changes this.handlePopupReposition = () => { const submenuSlot = this.host.renderRoot.querySelector("slot[name='submenu']"); const menu = submenuSlot == null ? void 0 : submenuSlot.assignedElements({ flatten: true }).filter((el) => el.localName === "syn-menu")[0]; const isRtl = getComputedStyle(this.host).direction === "rtl"; if (!menu) { return; } const { left, top, width, height } = menu.getBoundingClientRect(); this.host.style.setProperty("--safe-triangle-submenu-start-x", `${isRtl ? left + width : left}px`); this.host.style.setProperty("--safe-triangle-submenu-start-y", `${top}px`); this.host.style.setProperty("--safe-triangle-submenu-end-x", `${isRtl ? left + width : left}px`); this.host.style.setProperty("--safe-triangle-submenu-end-y", `${top + height}px`); }; (this.host = host).addController(this); this.hasSlotController = hasSlotController; } hostConnected() { if (this.hasSlotController.test("submenu") && !this.host.disabled) { this.addListeners(); } } hostDisconnected() { this.removeListeners(); } hostUpdated() { if (this.hasSlotController.test("submenu") && !this.host.disabled) { this.addListeners(); this.updateSkidding(); } else { this.removeListeners(); } } addListeners() { if (!this.isConnected) { this.host.addEventListener("mousemove", this.handleMouseMove); this.host.addEventListener("mouseover", this.handleMouseOver); this.host.addEventListener("keydown", this.handleKeyDown); this.host.addEventListener("click", this.handleClick); this.host.addEventListener("focusout", this.handleFocusOut); this.isConnected = true; } if (!this.isPopupConnected) { if (this.popupRef.value) { this.popupRef.value.addEventListener("mouseover", this.handlePopupMouseover); this.popupRef.value.addEventListener("syn-reposition", this.handlePopupReposition); this.isPopupConnected = true; } } } removeListeners() { if (this.isConnected) { this.host.removeEventListener("mousemove", this.handleMouseMove); this.host.removeEventListener("mouseover", this.handleMouseOver); this.host.removeEventListener("keydown", this.handleKeyDown); this.host.removeEventListener("click", this.handleClick); this.host.removeEventListener("focusout", this.handleFocusOut); this.isConnected = false; } if (this.isPopupConnected) { if (this.popupRef.value) { this.popupRef.value.removeEventListener("mouseover", this.handlePopupMouseover); this.popupRef.value.removeEventListener("syn-reposition", this.handlePopupReposition); this.isPopupConnected = false; } } } handleSubmenuEntry(event) { const submenuSlot = this.host.renderRoot.querySelector("slot[name='submenu']"); if (!submenuSlot) { console.error("Cannot activate a submenu if no corresponding menuitem can be found.", this); return; } let menuItems = null; for (const elt of submenuSlot.assignedElements()) { menuItems = elt.querySelectorAll("syn-menu-item, [role^='menuitem']"); if (menuItems.length !== 0) { break; } } if (!menuItems || menuItems.length === 0) { return; } menuItems[0].setAttribute("tabindex", "0"); for (let i = 1; i !== menuItems.length; ++i) { menuItems[i].setAttribute("tabindex", "-1"); } if (this.popupRef.value) { event.preventDefault(); event.stopPropagation(); if (this.popupRef.value.active) { if (menuItems[0] instanceof HTMLElement) { menuItems[0].focus(); } } else { this.enableSubmenu(false); this.host.updateComplete.then(() => { if (menuItems[0] instanceof HTMLElement) { menuItems[0].focus(); } }); this.host.requestUpdate(); } } } setSubmenuState(state) { if (this.popupRef.value) { if (this.popupRef.value.active !== state) { this.popupRef.value.active = state; this.host.requestUpdate(); } } } // Shows the submenu. Supports disabling the opening delay, e.g. for keyboard events that want to set the focus to the // newly opened menu. enableSubmenu(delay = true) { if (delay) { window.clearTimeout(this.enableSubmenuTimer); this.enableSubmenuTimer = window.setTimeout(() => { this.setSubmenuState(true); }, this.submenuOpenDelay); } else { this.setSubmenuState(true); } } disableSubmenu() { window.clearTimeout(this.enableSubmenuTimer); this.setSubmenuState(false); } // Calculate the space the top of a menu takes-up, for aligning the popup menu-item with the activating element. updateSkidding() { var _a; if (!((_a = this.host.parentElement) == null ? void 0 : _a.computedStyleMap)) { return; } const styleMap = this.host.parentElement.computedStyleMap(); const attrs = ["padding-top", "border-top-width", "margin-top"]; const skidding = attrs.reduce((accumulator, attr) => { var _a2; const styleValue = (_a2 = styleMap.get(attr)) != null ? _a2 : new CSSUnitValue(0, "px"); const unitValue = styleValue instanceof CSSUnitValue ? styleValue : new CSSUnitValue(0, "px"); const pxValue = unitValue.to("px"); return accumulator - pxValue.value; }, 0); this.skidding = skidding; } isExpanded() { return this.popupRef.value ? this.popupRef.value.active : false; } renderSubmenu() { const isRtl = getComputedStyle(this.host).direction === "rtl"; if (!this.isConnected) { return html` <slot name="submenu" hidden></slot> `; } return html` <syn-popup ${ref(this.popupRef)} placement=${isRtl ? "left-start" : "right-start"} anchor="anchor" flip flip-fallback-strategy="best-fit" skidding="${this.skidding}" strategy="fixed" auto-size="vertical" auto-size-padding="10" > <slot name="submenu"></slot> </syn-popup> `; } }; export { SubmenuController }; //# sourceMappingURL=chunk.V4HPWQE7.js.map