wj-elements
Version:
WebJET Elements is a modern set of user interface tools harnessing the power of web components designed to simplify web application development.
673 lines (648 loc) • 23.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var _MenuItem_instances, populateCustomEvent_fn;
import { b as bindRouterLinks } from "./router-links-wjqCnncc.js";
import WJElement from "./wje-element.js";
import { WjElementUtils } from "./element-utils.js";
import { event } from "./event.js";
const styles = `/*
[ WJ Menu Item ]
*/
:host {
--wje-menu-item-safe-triangle-cursor-x: 0;
--wje-menu-item-safe-triangle-cursor-y: 0;
--wje-menu-item-safe-triangle-submenu-start-x: 0;
--wje-menu-item-safe-triangle-submenu-start-y: 0;
--wje-menu-item-safe-triangle-submenu-end-x: 0;
--wje-menu-item-safe-triangle-submenu-end-y: 0;
display: block;
.native-menu-item {
font-size: var(--wje-menu-item-font-size);
background: var(--wje-menu-item-background);
position: relative;
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
color: var(--wje-menu-item-color);
padding-top: var(--wje-menu-item-padding-top);
padding-bottom: var(--wje-menu-item-padding-bottom);
transition: var(--wje-transition-fast) fill;
user-select: none;
white-space: nowrap;
cursor: pointer;
width: 100%;
&:hover {
color: var(--wje-menu-item-color-hover);
background: var(--wje-menu-item-background-hover);
}
&:focus {
color: var(--wje-menu-item-color-focus);
background: var(--wje-menu-item-background-focus);
}
&:active {
color: var(--wje-menu-item-color-active);
background: var(--wje-menu-item-background-active);
}
.label {
flex: 1 1 auto;
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
line-height: normal;
}
.check-icon {
flex: 0 0 auto;
display: var(--wje-menu-item-check-icon-display, flex);
align-items: center;
justify-content: center;
width: var(--wje-menu-item-check-icon-width, 1.5rem);
visibility: hidden;
&.checked {
visibility: visible;
}
}
&.expanded-submenu {
color: var(--wje-menu-item-color-active);
background: var(--wje-menu-item-background-active);
&:hover {
color: var(--wje-menu-item-color-hover);
background: var(--wje-menu-item-background-hover);
}
&::after {
content: '';
position: fixed;
z-index: 1;
top: 0;
right: 0;
bottom: 0;
left: 0;
clip-path: polygon(
var(--wje-menu-item-safe-triangle-cursor-x) var(--wje-menu-item-safe-triangle-cursor-y),
var(--wje-menu-item-safe-triangle-submenu-start-x)
var(--wje-menu-item-safe-triangle-submenu-start-y),
var(--wje-menu-item-safe-triangle-submenu-end-x) var(--wje-menu-item-safe-triangle-submenu-end-y)
);
}
}
}
}
:host(.collapse) {
::slotted([slot='start']) {
margin: 0;
/*width: auto;*/
/*display: contents;*/
}
::slotted([slot='end']) {
display: none;
}
.label,
.check-icon,
.submenu-icon {
display: none;
}
}
.submenu-icon {
--wje-icon-size: 14px !important;
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
width: 1.5rem;
visibility: var(--wje-menu-item-icon-visibility);
}
.has-submenu {
.submenu-icon {
--wje-menu-item-icon-visibility: visible;
}
}
.submenu-icon.collapse {
flex: none;
/*right: 10px;*/
position: relative;
}
:host(:focus-visible) {
outline: none;
}
::slotted([slot='start']) {
flex: 0 0 auto;
display: flex;
align-items: center;
margin-inline-end: 0.5rem;
}
::slotted([slot='end']) {
flex: 0 0 auto;
display: flex;
align-items: center;
margin-inline-start: 0.5rem;
}
:host(.wje-menu-variant-nav) {
/*posun 2 a x urovne o 1 rem*/
::slotted([slot='submenu']) {
--wje-menu-border-width: 0 !important;
--wje-menu-margin-inline: 2rem 0 !important;
}
}
/*:host {*/
/* ::slotted([slot="start"]) {*/
/* width: 1.5rem;*/
/* }*/
/*}*/
:host(.wje-menu-variant-context) {
display: block;
}
:host(.active) {
color: var(--wje-menu-item-color-active);
background: var(--wje-menu-item-background-active);
}
:host(.open) {
color: var(--wje-menu-item-color-active);
background: var(--wje-menu-item-background-active);
}
`;
class MenuItem extends WJElement {
/**
* Constructor for MenuItem class.
* @class
*/
constructor() {
super();
__privateAdd(this, _MenuItem_instances);
__publicField(this, "className", "MenuItem");
__publicField(this, "mouseenterHandler", (e) => {
if (this.collapse || this.variant === "CONTEXT" && this.hasSubmenu) {
if (this.hasAttribute("manual") || this.variant === "NAV" && this.collapse) return;
this.activateSubmenu(e);
e.stopPropagation();
this.showSubmenu();
}
});
__publicField(this, "rebindRouterLinks", (e) => {
this.unbindPortalRouterLinks = bindRouterLinks(e.detail.container, { selector: "[route]" });
});
/**
* Handles the click event on the MenuItem.
* @param {object} e
*/
__publicField(this, "clickHandler", (e) => {
if (this.hasAttribute("disabled")) return;
switch (this.variant) {
case "NAV":
if (!this.collapse && this.hasSubmenu) {
this.submenuToggle(e);
this.hideSubmenu();
e.stopPropagation();
} else {
event.dispatchCustomEvent(this, "wje-menu-item:click");
event.dispatchCustomEvent(this, this.dialog);
}
break;
case "CONTEXT":
if (!this.collapse && this.hasSubmenu) {
let submenuElements = this.submenu.assignedElements({ flatten: true })[0];
if (submenuElements == null ? void 0 : submenuElements.hasAttribute("active")) {
this.shouldHideSubmenu(e);
} else {
this.activateSubmenu(e);
this.showSubmenu(e);
}
} else {
event.dispatchCustomEvent(this, "wje-menu-item:click");
event.dispatchCustomEvent(this, this.dialog);
}
break;
}
});
/**
* Checks if the submenu should be hidden based on the event.
* @param {Event} e The event object.
*/
__publicField(this, "shouldHideSubmenu", (e) => {
if (this.collapse || this.variant === "CONTEXT" && this.hasSubmenu) {
if (e.relatedTarget && this.contains(e.relatedTarget) || this.variant === "NAV" && !this.collapse) {
return;
}
this.deactivateSubmenu();
this.hideSubmenu();
}
});
/**
* Dispatches a mousemove event.
*/
__publicField(this, "dispatchMove", (e) => {
this.style.setProperty("--wje-menu-item-safe-triangle-cursor-x", `${e.clientX}px`);
this.style.setProperty("--wje-menu-item-safe-triangle-cursor-y", `${e.clientY}px`);
});
/**
* Dispatches a reposition event.
*/
__publicField(this, "dispatchReposition", (e) => {
if (this.submenu.assignedNodes().length === 0) return;
let submenu = this.submenu.assignedNodes()[0];
const { left, top, width, height } = submenu.getBoundingClientRect();
this.style.setProperty("--wje-menu-item-safe-triangle-submenu-start-x", `${left}px`);
this.style.setProperty("--wje-menu-item-safe-triangle-submenu-start-y", `${top}px`);
this.style.setProperty("--wje-menu-item-safe-triangle-submenu-end-x", `${left}px`);
this.style.setProperty("--wje-menu-item-safe-triangle-submenu-end-y", `${top + height}px`);
});
__publicField(this, "handleActiveClick", (e) => {
var _a, _b, _c, _d;
const target = e.target.closest("wje-menu-item");
if (!target) return;
let rootMenu = target.closest("wje-menu");
let currentMenu = rootMenu;
while (currentMenu == null ? void 0 : currentMenu.parentElement) {
const parent = (_b = (_a = currentMenu.parentElement).closest) == null ? void 0 : _b.call(_a, "wje-menu");
if (!parent) break;
rootMenu = parent;
currentMenu = parent;
}
if (!rootMenu) {
const path = typeof e.composedPath === "function" ? e.composedPath() : [];
let firstMenuInPath = path.find((n) => n && n.tagName === "WJE-MENU");
if (firstMenuInPath) {
let top = firstMenuInPath;
while (top && top.parentElement) {
const parent = (_d = (_c = top.parentElement).closest) == null ? void 0 : _d.call(_c, "wje-menu");
if (!parent) break;
top = parent;
}
rootMenu = top;
}
}
if (!rootMenu) return;
rootMenu.querySelectorAll("wje-menu-item").forEach((item) => {
const activeClass = item.getAttribute("active-class");
if (activeClass) item.classList.remove(activeClass);
});
let current = target;
while (current && current.tagName === "WJE-MENU-ITEM") {
const activeClass = current.getAttribute("active-class");
if (activeClass) current.classList.add(activeClass);
const parentMenu = current.closest("wje-menu");
if (!parentMenu) break;
const candidate = parentMenu.closest("wje-menu-item");
if (!candidate) break;
current = candidate;
}
});
this._bind = false;
}
/**
* Getter for placement attribute.
* @returns {string} The placement attribute of the menu or "right-start" if it doesn't exist.
*/
get placement() {
let menu = this.querySelector("wje-menu");
if (menu == null ? void 0 : menu.hasAttribute("placement")) {
return menu.getAttribute("placement");
}
return "right-start";
}
/**
* Getter for offset attribute.
* @returns {string} The offset attribute of the menu or "0" if it doesn't exist.
*/
get offset() {
let menu = this.querySelector("wje-menu");
if (menu == null ? void 0 : menu.hasAttribute("offset")) {
return menu.getAttribute("offset");
}
return "0";
}
/**
* Getter for variant attribute.
* @returns {string} The variant attribute of the menu or "CONTEXT" if it doesn't exist.
*/
get variant() {
let menu = this.querySelector("wje-menu");
if ((menu == null ? void 0 : menu.hasAttribute("variant")) && !this.collapse) {
return menu.getAttribute("variant").toUpperCase();
}
return "CONTEXT";
}
/**
* Getter for collapse attribute.
* @returns {boolean} True if the closest parent has the collapse attribute, false otherwise.
*/
get collapse() {
if (this.closest("[collapse]")) return true;
return false;
}
/**
* Sets the value of the custom event attribute.
* @param {string} value The value to be assigned to the custom event attribute.
*/
set customEvent(value) {
this.setAttribute("custom-event", value);
}
/**
* Retrieves the value of the 'custom-event' attribute from the element.
* @returns {string | null} The value of the 'custom-event' attribute, or null if the attribute is not set.
*/
get customEvent() {
return this.getAttribute("custom-event");
}
/**
* Retrieves a mapped object containing custom event parameters extracted from the element's attributes.
* Attributes considered are those that begin with 'custom-event-'.
* The mapped object's keys are derived by removing the 'custom-event-' prefix from the attribute names,
* and the values are the corresponding attribute values.
* @returns {object} An object containing key-value pairs of custom event parameters.
*/
get customEventParameters() {
const attributes = Array.from(this.attributes).filter((attr) => attr.name.startsWith("custom-event-"));
return attributes.reduce((acc, attr) => {
const key = attr.name.replace("custom-event-", "");
acc[key] = attr.value;
return acc;
}, {});
}
/**
* Getter for cssStyleSheet.
* @returns {string} The styles imported from styles.css.
*/
static get cssStyleSheet() {
return styles;
}
/**
* Getter for observedAttributes.
* @returns {Array} An empty array as no attributes are being observed.
*/
static get observedAttributes() {
return [];
}
/**
* Sets up the attributes for the MenuItem element.
*/
setupAttributes() {
super.setupAttributes();
this.isShadowRoot = "open";
this.syncAria();
}
/**
* Removes the active attribute from the menu before drawing the MenuItem.
*/
beforeDraw() {
var _a;
(_a = this.querySelector("wje-menu")) == null ? void 0 : _a.removeAttribute("active");
}
/**
* Draws the MenuItem element and sets the variant and collapse attributes.
* @returns {DocumentFragment} The fragment to be appended to the MenuItem.
*/
draw() {
var _a, _b, _c;
this.hasSubmenu = WjElementUtils.hasSlot(this, "submenu");
let fragment = document.createDocumentFragment();
this.setAttribute("tabindex", "0");
this.classList.forEach((className) => {
if (className.startsWith("wje-menu-variant-")) {
this.classList.remove(className);
}
});
this.classList.remove("collapse");
this.classList.add("wje-menu-variant-" + this.variant.toLowerCase());
if (!this.collapse) {
(_a = this.querySelector("wje-menu")) == null ? void 0 : _a.setAttribute("variant", this.variant.toLowerCase());
} else if ((_b = this.parentElement) == null ? void 0 : _b.hasAttribute("collapse")) {
this.classList.add("collapse");
}
let native = document.createElement("div");
native.setAttribute("part", "native");
native.setAttribute("id", "anchor");
native.classList.add("native-menu-item");
let checkedIcon = document.createElement("span");
checkedIcon.setAttribute("part", "check");
checkedIcon.classList.add("check-icon");
checkedIcon.innerHTML = `<wje-icon name="check"></wje-icon>`;
if (this.hasAttribute("checked")) checkedIcon.classList.add("checked");
else checkedIcon.classList.remove("checked");
let start = document.createElement("slot");
start.name = "start";
let slot = document.createElement("slot");
slot.classList.add("label");
let end = document.createElement("slot");
end.setAttribute("part", "end");
end.name = "end";
let submenu = document.createElement("slot");
submenu.setAttribute("part", "submenu");
submenu.name = "submenu";
let submenuIconClass = this.collapse ? "collapse" : "expand";
let submenuIcon = document.createElement("span");
submenuIcon.setAttribute("part", "submenu-icon");
submenuIcon.classList.add("submenu-icon", submenuIconClass);
submenuIcon.innerHTML = this.collapse ? `<wje-icon name="chevron-down"></wje-icon>` : `<wje-icon name="chevron-right"></wje-icon>`;
if (this.hasSubmenu) native.classList.add("has-submenu");
else native.classList.remove("has-submenu");
native.appendChild(checkedIcon);
native.appendChild(start);
native.appendChild(slot);
native.appendChild(end);
native.appendChild(submenuIcon);
let isAppend = false;
if (this.variant === "CONTEXT" && this.hasSubmenu) {
native.setAttribute("slot", "anchor");
let popup = document.createElement("wje-popup");
popup.setAttribute("anchor", "anchor");
popup.setAttribute("placement", this.placement);
popup.setAttribute("offset", this.offset);
popup.appendChild(native);
popup.appendChild(submenu);
this.popup = popup;
fragment.appendChild(popup);
isAppend = true;
}
if (((_c = this.parentElement) == null ? void 0 : _c.hasAttribute("collapse")) && !this.hasSubmenu) {
fragment.appendChild(this.collapseItem(native));
} else if (!isAppend) {
fragment.appendChild(native);
}
if (!this.collapse && this.variant === "NAV" || this.variant === "MEGAMENU" && this.hasSubmenu) {
fragment.appendChild(submenu);
}
this.native = native;
this.submenu = submenu;
this.syncAria();
return fragment;
}
/**
* Adds event listeners after drawing the MenuItem.
*/
afterDraw() {
if (this.parentElement) {
this.unbindRouterLinks = bindRouterLinks(this.parentElement, { selector: "[route]" });
}
document.addEventListener("wje-router:rebind", this.rebindRouterLinks);
this.addEventListener("mousemove", this.dispatchMove);
this.addEventListener("wje-popup:reposition", this.dispatchReposition);
event.addListener(this, "mouseenter", null, this.mouseenterHandler);
event.addListener(this, "mouseleave", null, this.shouldHideSubmenu);
event.addListener(this, "focusout", null, this.shouldHideSubmenu);
event.addListener(this, "click", null, this.clickHandler);
if (this.hasAttribute("custom-event")) {
event.addListener(this, "click", null, __privateMethod(this, _MenuItem_instances, populateCustomEvent_fn));
}
if (this.hasAttribute("active-class")) {
this.addEventListener("click", this.handleActiveClick);
}
}
/**
* Syncs ARIA attributes based on menu item state.
*/
syncAria() {
var _a, _b;
const hasSubmenu = !!this.hasSubmenu;
const expanded = this.classList.contains("expanded-submenu") || ((_b = (_a = this.native) == null ? void 0 : _a.classList) == null ? void 0 : _b.contains("expanded-submenu"));
const disabled = this.hasAttribute("disabled");
this.setAriaState({
role: "menuitem",
disabled,
haspopup: hasSubmenu ? "menu" : void 0,
expanded: hasSubmenu ? expanded : void 0
});
}
/**
* Creates a tooltip for the MenuItem when it is collapsed.
* @param {HTMLElement} native The native MenuItem element.
* @returns {HTMLElement} The tooltip element.
*/
collapseItem(native) {
let tooltipStart = document.createElement("slot");
tooltipStart.setAttribute("slot", "start");
tooltipStart.setAttribute("name", "tooltip-start");
let tooltipEnd = document.createElement("slot");
tooltipEnd.setAttribute("slot", "end");
tooltipEnd.setAttribute("name", "tooltip-end");
let tooltip = document.createElement("wje-tooltip");
tooltip.setAttribute("content", this.getTextFromElement(this));
tooltip.setAttribute("placement", "right");
tooltip.setAttribute("offset", this.offset || "0");
tooltip.appendChild(tooltipStart);
tooltip.appendChild(tooltipEnd);
tooltip.appendChild(native);
return tooltip;
}
/**
* Shows the submenu of the MenuItem.
*/
showSubmenu() {
var _a;
this.tabIndex = -1;
if (this.hasSubmenu) {
(_a = this.popup) == null ? void 0 : _a.show();
this.classList.add("expanded-submenu");
this.native.classList.add("expanded-submenu");
this.syncAria();
}
}
/**
* Hides the submenu of the MenuItem.
*/
hideSubmenu() {
var _a;
this.tabIndex = 0;
if (this.hasSubmenu) {
(_a = this.popup) == null ? void 0 : _a.hide();
this.classList.remove("expanded-submenu");
this.native.classList.remove("expanded-submenu");
this.syncAria();
}
}
/**
* Toggles the active state of the submenu element.
* If the submenu is not active, it sets the "active" attribute.
* If the submenu is already active, it removes the "active" attribute.
* @param {Event} e The event object.
*/
submenuToggle(e) {
if (this.hasSubmenu) {
let submenuElements = this.submenu.assignedElements({ flatten: true })[0];
if (!submenuElements.hasAttribute("active")) {
submenuElements.setAttribute("active", "");
this.syncAria();
} else {
if (this === e.target) submenuElements.removeAttribute("active");
this.syncAria();
}
}
}
/**
* Deactivates the submenu by removing the "active" attribute.
*/
deactivateSubmenu() {
if (this.hasSubmenu) {
let submenuElements = this.submenu.assignedElements({ flatten: true })[0];
if (submenuElements == null ? void 0 : submenuElements.hasAttribute("active")) {
submenuElements == null ? void 0 : submenuElements.removeAttribute("active");
this.syncAria();
}
}
}
/**
* Activates the submenu of the menu item.
*/
activateSubmenu() {
if (this.hasSubmenu) {
let submenuElements = this.submenu.assignedElements({ flatten: true })[0];
if (!(submenuElements == null ? void 0 : submenuElements.hasAttribute("active"))) {
submenuElements == null ? void 0 : submenuElements.setAttribute("active", "");
this.syncAria();
}
}
}
/**
* Gets the text from the element and returns it.
*/
beforeDisconnect() {
var _a, _b;
document.removeEventListener("wje-router:rebind", this.rebindRouterLinks);
this.removeEventListener("mousemove", this.dispatchMove);
this.removeEventListener("wje-popup:reposition", this.dispatchReposition);
event.removeListener(this, "mouseenter", null, this.mouseenterHandler);
event.removeListener(this, "mouseleave", null, this.shouldHideSubmenu);
event.removeListener(this, "focusout", null, this.shouldHideSubmenu);
event.removeListener(this, "click", null, this.clickHandler);
(_a = this.unbindRouterLinks) == null ? void 0 : _a.call(this);
(_b = this.unbindPortalRouterLinks) == null ? void 0 : _b.call(this);
}
/**
* Extracts and returns the concatenated text content from all text nodes within the specified element.
* @param {HTMLElement} element The HTML element from which to extract text content.
* @returns {string} The concatenated and trimmed text content from the element's text nodes.
*/
getTextFromElement(element) {
let text = "";
for (let node of element.childNodes) {
if (node.nodeType === Node.TEXT_NODE) {
text += node.textContent;
}
}
return text.trim();
}
}
_MenuItem_instances = new WeakSet();
/**
* Dispatches a custom event with specified parameters.
* This method uses the `customEvent` and `customEventParameters` properties
* to create and dispatch a `CustomEvent`. The event is configured to be
* composed and bubbles up through the DOM.
* @returns {void} This method does not return a value.
*/
populateCustomEvent_fn = function() {
this.dispatchEvent(
new CustomEvent(this.customEvent, { detail: this.customEventParameters, composed: true, bubbles: true })
);
};
MenuItem.define("wje-menu-item", MenuItem);
export {
MenuItem as default
};
//# sourceMappingURL=wje-menu-item.js.map