UNPKG

@trimble-oss/moduswebcomponents

Version:

Modus Web Components is a modern, accessible UI library built with Stencil JS that provides reusable web components following Trimble's Modus design system. This updated version focuses on improved flexibility, enhanced theming options, comprehensive cust

328 lines (327 loc) 12.1 kB
import { h, Host, } from "@stencil/core"; import { convertPropsToClasses } from "./modus-wc-tree-menu.tailwind"; import { handleShadowDOMStyles } from "../base-component"; import { inheritAriaAttributes } from "../utils"; /** * A customizable tree menu component used to display a list of modus-wc-tree-item elements vertically or horizontally. * * The component supports a `<slot>` for injecting custom modus-wc-tree-item elements inside the ul element. */ export class ModusWcTreeMenu { constructor() { this.inheritedAttributes = {}; this.selectedItems = []; /** Custom CSS class to apply to the ul element. */ this.customClass = ''; /** The orientation of the tree menu. */ this.orientation = 'vertical'; /** The selection mode of the tree menu. */ this.selectionMode = 'single'; /** The size of the tree menu. */ this.size = 'md'; this.getMenuRole = () => this.orientation === 'horizontal' ? 'menubar' : 'menu'; } onSelectionModeChange() { this.selectedItems = []; } componentWillLoad() { handleShadowDOMStyles(this.el); if (!this.el.ariaLabel) { this.el.ariaLabel = 'Tree menu'; } this.inheritedAttributes = inheritAriaAttributes(this.el); } getClasses() { if (this.isSubMenu) { const classList = ['modus-wc-menu-dropdown']; if (this.customClass) classList.push(this.customClass); return classList.join(' '); } const classList = ['modus-wc-menu modus-wc-w-full']; const propClasses = convertPropsToClasses({ bordered: this.bordered, orientation: this.orientation, size: this.size, }); if (propClasses) classList.push(propClasses); if (this.customClass) classList.push(this.customClass); return classList.join(' '); } getTreeItems() { return Array.from(this.el.querySelectorAll('modus-wc-tree-item')).filter((item) => item.closest('modus-wc-tree-menu') === this.el); } handleItemSelect(e) { if (this.selectionMode !== 'multiple') return; const item = e.target; const isCurrentlySelected = this.selectedItems.includes(item); if (e.detail.selected && !isCurrentlySelected) { this.selectedItems = [...this.selectedItems, item]; } else if (!e.detail.selected && isCurrentlySelected) { this.selectedItems = this.selectedItems.filter((i) => i !== item); } this.menuSelectionChange.emit({ selectedItems: this.selectedItems }); } handleKeyDown(e) { if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return; e.preventDefault(); const items = this.getTreeItems(); const focusableItems = items.filter((item) => !item.disabled); if (focusableItems.length === 0) return; const activeEl = document.activeElement; const currentTreeItem = activeEl === null || activeEl === void 0 ? void 0 : activeEl.closest('modus-wc-tree-item'); const currentIndex = focusableItems.indexOf(currentTreeItem); let nextIndex; if (e.key === 'ArrowDown') { nextIndex = currentIndex < focusableItems.length - 1 ? currentIndex + 1 : 0; } else { nextIndex = currentIndex > 0 ? currentIndex - 1 : focusableItems.length - 1; } const nextLi = focusableItems[nextIndex].querySelector('li'); if (nextLi) { nextLi.focus(); } } handleFocusout(e) { if (!this.el.contains(e.relatedTarget)) { this.menuFocusout.emit(e); if (this.isSubMenu) { e.stopPropagation(); } } } render() { return (h(Host, { key: '4b1a4834b8e63748efd4e77fd5be7b4ff88e7193', class: this.isSubMenu ? 'modus-wc-menu-submenu' : undefined }, h("ul", Object.assign({ key: 'e007e38b0e97c7774d11ab2391b5f46ed5c07a71', "aria-orientation": this.orientation, class: this.getClasses(), role: this.getMenuRole() }, this.inheritedAttributes), h("slot", { key: '292c95ac103697013a0b6a6eea0b5043dee22f32' })))); } static get is() { return "modus-wc-tree-menu"; } static get originalStyleUrls() { return { "$": ["modus-wc-tree-menu.scss"] }; } static get styleUrls() { return { "$": ["modus-wc-tree-menu.css"] }; } static get properties() { return { "bordered": { "type": "boolean", "attribute": "bordered", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Indicates that the tree menu should have a border." }, "getter": false, "setter": false, "reflect": false }, "customClass": { "type": "string", "attribute": "custom-class", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Custom CSS class to apply to the ul element." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "''" }, "orientation": { "type": "string", "attribute": "orientation", "mutable": false, "complexType": { "original": "Orientation", "resolved": "\"horizontal\" | \"vertical\" | undefined", "references": { "Orientation": { "location": "import", "path": "../types", "id": "src/components/types.ts::Orientation" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "The orientation of the tree menu." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "'vertical'" }, "selectionMode": { "type": "string", "attribute": "selection-mode", "mutable": false, "complexType": { "original": "SelectionMode", "resolved": "\"multiple\" | \"single\" | undefined", "references": { "SelectionMode": { "location": "import", "path": "../types", "id": "src/components/types.ts::SelectionMode" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "The selection mode of the tree menu." }, "getter": false, "setter": false, "reflect": true, "defaultValue": "'single'" }, "size": { "type": "string", "attribute": "size", "mutable": false, "complexType": { "original": "ModusSize", "resolved": "\"lg\" | \"md\" | \"sm\" | undefined", "references": { "ModusSize": { "location": "import", "path": "../types", "id": "src/components/types.ts::ModusSize" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "The size of the tree menu." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "'md'" }, "isSubMenu": { "type": "boolean", "attribute": "is-sub-menu", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Indicates that this tree menu is a submenu (dropdown)." }, "getter": false, "setter": false, "reflect": false } }; } static get events() { return [{ "method": "menuFocusout", "name": "menuFocusout", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Event emitted when the tree menu loses focus." }, "complexType": { "original": "FocusEvent", "resolved": "FocusEvent", "references": { "FocusEvent": { "location": "global", "id": "global::FocusEvent" } } } }, { "method": "menuSelectionChange", "name": "menuSelectionChange", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Event emitted when the selection changes in multiple selection mode. Emits the array of currently selected tree item elements." }, "complexType": { "original": "{\n selectedItems: HTMLElement[];\n }", "resolved": "{ selectedItems: HTMLElement[]; }", "references": { "HTMLElement": { "location": "global", "id": "global::HTMLElement" } } } }]; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "selectionMode", "methodName": "onSelectionModeChange" }]; } static get listeners() { return [{ "name": "itemSelect", "method": "handleItemSelect", "target": undefined, "capture": false, "passive": false }, { "name": "keydown", "method": "handleKeyDown", "target": undefined, "capture": false, "passive": false }, { "name": "focusout", "method": "handleFocusout", "target": undefined, "capture": false, "passive": false }]; } }