UNPKG

@claromentis/design-system

Version:

Claromentis Design System Component Library

248 lines (247 loc) 7.3 kB
import { h } from '@stencil/core'; /** * @slot - Allows menu items to be added to the dropdown menu. */ export class Cardmenu { constructor() { this.toggle = false; this.isMobile = false; this.closeOnSelect = false; } /** * Listen for mousedown events and handle them */ handleClick(event) { // Close the menu if the user clicks elsewhere on the page if (!this.toggle) return; if (event.composedPath().indexOf(this.element) === -1) this.toggle = false; // Close Menu on Item Select const targetElement = event.target; if (this.closeOnSelect && targetElement.parentElement.closest('cla-menu')) { this.toggle = false; } } /** * Listen for window resize events and handle */ handleWindowResize() { this.checkIfMobile(); } getMenuItems() { const allSlotted = Array.from(this.element.querySelectorAll('*')); return allSlotted.filter(el => { // Exclude non-interactive elements (hr, div, span, p, etc.) const isFocusable = (el instanceof HTMLAnchorElement || el instanceof HTMLButtonElement || el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement || el.tabIndex >= 0 // catches custom elements with explicit tabindex ); if (!isFocusable) return false; // Exclude disabled elements if (el.disabled) return false; // Exclude hidden elements if (el.offsetParent === null) return false; // Exclude the toggle button itself if (el.classList.contains('dropdown-toggle-button')) return false; return true; }); } getFocusedIndex(items) { return items.findIndex(item => item === document.activeElement); } getToggleButton() { return this.element.querySelector('.dropdown-toggle-button'); } /** * Listen for keydown events and handle * */ handleKeyDown(event) { var _a, _b, _c, _d; // Ignore events that didn't originate inside this component instance if (!this.element.contains(event.target)) return; const items = this.getMenuItems(); const currentIndex = this.getFocusedIndex(items); switch (event.key) { case 'Enter': { const target = event.target; // open/close menu when the toggle button is focussed if (target.shadowRoot.activeElement.classList.contains('dropdown-toggle-button')) { this.toggleMenu(); break; } //when closeOnSelect is true close menu when 'Enter' is pressed if (this.closeOnSelect && this.toggle) { this.toggle = false; (_a = this.getToggleButton()) === null || _a === void 0 ? void 0 : _a.focus(); // return focus } break; } case 'Escape': { //close menu if tab away from last item if (this.toggle) { this.toggleMenu(); (_b = this.getToggleButton()) === null || _b === void 0 ? void 0 : _b.focus(); // ← return focus to trigger } break; } case 'Tab': { if (!this.toggle) break; const isFirst = currentIndex === 0; const isLast = currentIndex === items.length - 1; // Tab forward from last item, or shift+Tab from first item → close if ((!event.shiftKey && isLast) || (event.shiftKey && isFirst)) { this.toggleMenu(); // Don't preventDefault — let Tab move focus naturally } break; } case 'ArrowDown': { if (!this.toggle) break; event.preventDefault(); // stop page scroll const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0; (_c = items[nextIndex]) === null || _c === void 0 ? void 0 : _c.focus(); break; } case 'ArrowUp': { if (!this.toggle) break; event.preventDefault(); // stop page scroll const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1; (_d = items[prevIndex]) === null || _d === void 0 ? void 0 : _d.focus(); break; } } } componentWillLoad() { //make non-link menu items tabbable let childrenArray = Array.from(this.element.children); childrenArray.forEach(function (menuItem) { menuItem.setAttribute('tabindex', '0'); }); } componentDidLoad() { this.checkIfMobile(); } /** * Checks if screen width is a mobile width * * @return Boolean */ checkIfMobile() { return window.screen.width < 768 ? this.isMobile = true : this.isMobile = false; } /** * Toggles the opening and closing of the menu. * * @return Boolean */ toggleMenu() { return this.toggle = !this.toggle; } /** * Get the map of CSS classes for the button. * * @return CssClassMap */ getMenuClassMap() { return { 'dropdown-menu': true, 'dropdown-menu-right': this.isMobile ? true : false }; } render() { return (h("div", { class: this.toggle ? "dropdown-wrapper in" : "dropdown-wrapper" }, h("button", { class: "dropdown-toggle-button", onMouseDown: () => this.toggleMenu(), onKeyDown: (e) => this.handleKeyDown(e) }, h("ion-icon", { name: "ellipsis-vertical-circle-outline", "aria-hidden": "true" }), h("span", { class: "sr-only" }, "toggle dropdown")), h("div", { class: this.getMenuClassMap() }, h("slot", null)))); } static get is() { return "cla-menu"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["cla-menu.scss"] }; } static get styleUrls() { return { "$": ["cla-menu.css"] }; } static get properties() { return { "closeOnSelect": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "If 'true' the menu will close when an item is selected" }, "attribute": "close-on-select", "reflect": false, "defaultValue": "false" } }; } static get states() { return { "toggle": {}, "isMobile": {} }; } static get events() { return [{ "method": "toggleEvent", "name": "toggleEvent", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when the menu is toggled." }, "complexType": { "original": "any", "resolved": "any", "references": {} } }]; } static get elementRef() { return "element"; } static get listeners() { return [{ "name": "mousedown", "method": "handleClick", "target": "window", "capture": false, "passive": true }, { "name": "resize", "method": "handleWindowResize", "target": "window", "capture": false, "passive": true }, { "name": "keydown", "method": "handleKeyDown", "target": "document", "capture": false, "passive": false }]; } }