@synergy-design-system/components
Version:
187 lines (184 loc) • 6.09 kB
JavaScript
import {
menu_styles_default
} from "./chunk.M3HEPL2F.js";
import {
component_styles_default
} from "./chunk.2NT3B5WJ.js";
import {
SynergyElement
} from "./chunk.L2IYPRPF.js";
import {
__decorateClass
} from "./chunk.VK2FVWOF.js";
// src/components/menu/menu.component.ts
import { html } from "lit";
import { query } from "lit/decorators.js";
import { state } from "lit/decorators.js";
var SynMenu = class extends SynergyElement {
constructor() {
super(...arguments);
this.hasMenuItemsWithCheckmarks = false;
this.checkmarkStyledItems = /* @__PURE__ */ new Set();
this.updateCheckMarksByChildPropChange = (e) => {
e.stopImmediatePropagation();
this.handleUpdateCheckmarks(this.getAllItems());
};
}
handleUpdateCheckmarks(items) {
this.hasMenuItemsWithCheckmarks = items.some((item) => item.type === "checkbox" || item.loading);
this.syncCheckmarkVisibility(items);
}
syncCheckmarkVisibility(items) {
this.checkmarkStyledItems.forEach((item) => {
if (!items.includes(item)) {
item.style.removeProperty("--display-checkmark");
this.checkmarkStyledItems.delete(item);
}
});
if (this.hasMenuItemsWithCheckmarks) {
items.forEach((item) => {
item.style.removeProperty("--display-checkmark");
this.checkmarkStyledItems.delete(item);
});
return;
}
items.forEach((item) => {
item.style.setProperty("--display-checkmark", "none");
this.checkmarkStyledItems.add(item);
});
}
disconnectedCallback() {
this.removeEventListener("syn-attributes-changed", this.updateCheckMarksByChildPropChange);
this.checkmarkStyledItems.forEach((item) => item.style.removeProperty("--display-checkmark"));
this.checkmarkStyledItems.clear();
}
connectedCallback() {
super.connectedCallback();
this.setAttribute("role", "menu");
this.addEventListener("syn-attributes-changed", this.updateCheckMarksByChildPropChange);
}
getMenuItemFromEvent(event) {
const composedPath = event.composedPath();
const target = composedPath.find((el) => el instanceof HTMLElement && this.isMenuItem(el));
if (!target || !(target instanceof HTMLElement)) {
return void 0;
}
const closestMenu = composedPath.find((el) => el instanceof Element && el.getAttribute("role") === "menu");
if (closestMenu !== this) {
return void 0;
}
return target;
}
handleClick(event) {
const item = this.getMenuItemFromEvent(event);
if (!item) return;
if (item.type === "checkbox") {
item.checked = !item.checked;
}
this.emit("syn-select", { detail: { item } });
}
handleKeyDown(event) {
if (event.key === "Enter" || event.key === " ") {
const item = this.getCurrentItem();
event.preventDefault();
event.stopPropagation();
item == null ? void 0 : item.click();
} else if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
const items = this.getAllItems();
const activeItem = this.getCurrentItem();
let index = activeItem ? items.indexOf(activeItem) : 0;
if (items.length > 0) {
event.preventDefault();
event.stopPropagation();
if (event.key === "ArrowDown") {
index++;
} else if (event.key === "ArrowUp") {
index--;
} else if (event.key === "Home") {
index = 0;
} else if (event.key === "End") {
index = items.length - 1;
}
if (index < 0) {
index = items.length - 1;
}
if (index > items.length - 1) {
index = 0;
}
this.setCurrentItem(items[index]);
items[index].focus();
}
}
}
handleMouseDown(event) {
const target = this.getMenuItemFromEvent(event);
if (target) {
this.setCurrentItem(target);
}
}
handleSlotChange() {
const items = this.getAllItems();
this.handleUpdateCheckmarks(items);
if (items.length > 0) {
this.setCurrentItem(items[0]);
}
}
isMenuItem(item) {
var _a;
return item.tagName.toLowerCase() === "syn-menu-item" || ["menuitem", "menuitemcheckbox", "menuitemradio"].includes((_a = item.getAttribute("role")) != null ? _a : "");
}
getMenuItemsFromElement(element) {
if (element.inert) {
return [];
}
if (this.isMenuItem(element)) {
return [element];
}
if (element.tagName.toLowerCase() === "syn-menu") {
return [];
}
return [...element.children].flatMap((child) => this.getMenuItemsFromElement(child));
}
/** @internal Gets all slotted menu items, ignoring dividers, headers, and other elements. */
getAllItems() {
return [...this.defaultSlot.assignedElements({ flatten: true })].flatMap((el) => this.getMenuItemsFromElement(el));
}
/**
* @internal Gets the current menu item, which is the menu item that has `tabindex="0"` within the roving tab index.
* The menu item may or may not have focus, but for keyboard interaction purposes it's considered the "active" item.
*/
getCurrentItem() {
return this.getAllItems().find((i) => i.getAttribute("tabindex") === "0");
}
/**
* @internal Sets the current menu item to the specified element. This sets `tabindex="0"` on the target element and
* `tabindex="-1"` to all other items. This method must be called prior to setting focus on a menu item.
*/
setCurrentItem(item) {
const items = this.getAllItems();
items.forEach((i) => {
i.setAttribute("tabindex", i === item ? "0" : "-1");
});
}
render() {
return html`
<slot
=${this.handleSlotChange}
=${this.handleClick}
=${this.handleKeyDown}
=${this.handleMouseDown}
></slot>
`;
}
};
SynMenu.styles = [component_styles_default, menu_styles_default];
__decorateClass([
query("slot")
], SynMenu.prototype, "defaultSlot", 2);
__decorateClass([
state()
], SynMenu.prototype, "hasMenuItemsWithCheckmarks", 2);
export {
SynMenu
};
//# sourceMappingURL=chunk.VBEG3AW7.js.map