UNPKG

@limetech/lime-elements

Version:
1,139 lines (1,138 loc) 46.3 kB
import { h, } from "@stencil/core"; import { createRandomString } from "../../util/random-string"; import { zipObject, isFunction } from "lodash-es"; import { ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ESCAPE, TAB, } from "../../util/keycodes"; import { focusTriggerElement } from "../../util/focus-trigger-element"; import { hotkeyFromKeyboardEvent, normalizeHotkeyString, tokenizeHotkeyString, } from "../../util/hotkeys"; const DEFAULT_ROOT_BREADCRUMBS_ITEM = { text: '', icon: { name: 'home', }, type: 'icon-only', }; /** * @slot trigger - Element to use as a trigger for the menu. * @exampleComponent limel-example-menu-basic * @exampleComponent limel-example-menu-disabled * @exampleComponent limel-example-menu-open-direction * @exampleComponent limel-example-menu-surface-width * @exampleComponent limel-example-menu-separators * @exampleComponent limel-example-menu-icons * @exampleComponent limel-example-menu-badge-icons * @exampleComponent limel-example-menu-grid * @exampleComponent limel-example-menu-secondary-text * @exampleComponent limel-example-menu-notification * @exampleComponent limel-example-menu-sub-menus * @exampleComponent limel-example-menu-sub-menu-lazy-loading * @exampleComponent limel-example-menu-sub-menu-lazy-loading-infinite * @exampleComponent limel-example-menu-searchable * @exampleComponent limel-example-menu-hotkeys * @exampleComponent limel-example-menu-searchable-hotkeys * @exampleComponent limel-example-menu-composite */ export class Menu { constructor() { /** * A list of items and separators to show in the menu. */ this.items = []; /** * Sets the disabled state of the menu. */ this.disabled = false; /** * Decides the menu's location in relation to its trigger */ this.openDirection = 'bottom-start'; /** * Decides the width of menu's dropdown */ this.surfaceWidth = 'inherit-from-items'; /** * Sets the open state of the menu. */ this.open = false; /** * Defines whether the menu should show badges. */ this.badgeIcons = false; /** * Renders list items in a grid layout, rather than a vertical list */ this.gridLayout = false; /** * :::warning Internal Use Only * This property is for internal use only. We need it for now, but want to * find a better implementation of the functionality it currently enables. * If and when we do so, this property will be removed without prior * notice. If you use it, your code _will_ break in the future. * ::: * * @internal */ this.loading = false; /** * A root breadcrumb item to show above the menu items. * Clicking it navigates back from a sub-menu to the root menu. */ this.rootItem = DEFAULT_ROOT_BREADCRUMBS_ITEM; this.shouldRestoreFocusOnClose = false; this.normalizedHotkeyCache = new Map(); this.cachedSubMenuSource = null; this.cachedSubMenuItems = null; this.handleDocumentKeyDown = (event) => { if (event.key === ESCAPE && this.open) { this.shouldRestoreFocusOnClose = true; } if (!this.open || event.defaultPrevented || event.repeat) { return; } if (this.isFromTextInput(event) && !this.hasModifier(event)) { return; } const pressedHotkey = hotkeyFromKeyboardEvent(event); if (!pressedHotkey) { return; } if (this.isReservedMenuHotkey(pressedHotkey)) { return; } const matchedItem = this.findMenuItemByHotkey(pressedHotkey); if (!matchedItem) { return; } event.stopPropagation(); event.preventDefault(); this.handleSelect(matchedItem); }; this.renderLoader = () => { if (!this.loadingSubItems && !this.loading) { return; } const cssProperties = this.getCssProperties(); return (h("div", { style: { width: cssProperties['--menu-surface-width'], display: 'flex', 'align-items': 'center', 'justify-content': 'center', padding: '0.5rem 0', } }, h("limel-spinner", { size: "mini", limeBranded: false }))); }; this.renderBreadcrumb = () => { const breadcrumbsItems = this.getBreadcrumbsItems(); if (breadcrumbsItems.length === 0) { return; } return (h("limel-breadcrumbs", { ref: this.setBreadcrumbsElement, style: { 'border-bottom': 'solid 1px rgb(var(--contrast-500))', 'flex-shrink': '0', }, onSelect: this.handleBreadcrumbsSelect, onKeyDown: this.handleBreadcrumbsKeyDown, items: breadcrumbsItems })); }; this.handleBreadcrumbsSelect = (event) => { if (!event.detail.menuItem) { this.currentSubMenu = null; this.clearSearch(); this.navigateMenu.emit(null); this.setFocus(); return; } this.handleSelect(event.detail.menuItem); }; this.renderSearchField = () => { if (!this.searcher) { return; } return (h("limel-input-field", { tabindex: "0", ref: this.setSearchElement, placeholder: this.searchPlaceholder, type: "search", leadingIcon: "search", style: { padding: '0.25rem', 'box-sizing': 'border-box', }, value: this.searchValue, onChange: this.handleTextInput, onKeyDown: this.handleInputKeyDown })); }; this.renderEmptyMessage = () => { var _a; if (this.loading || this.loadingSubItems || !this.emptyResultMessage || !Array.isArray(this.searchResults) || ((_a = this.searchResults) === null || _a === void 0 ? void 0 : _a.length)) { return null; } return (h("p", { style: { padding: '0 1rem', 'text-align': 'center', } }, this.emptyResultMessage)); }; this.renderMenuList = () => { let items = this.visibleItems; if (this.loadingSubItems || this.loading) { items = []; } return (h("limel-menu-list", { style: { 'overflow-y': 'auto', 'flex-grow': '1', }, class: { 'has-grid-layout has-interactive-items': this.gridLayout, }, items: items, badgeIcons: this.badgeIcons, onSelect: this.onSelect, ref: this.setListElement, onKeyDown: this.handleMenuKeyDown })); }; this.handleTextInput = async (event) => { event.stopPropagation(); const query = event.detail; this.searchValue = query; if (query === '') { this.searchResults = null; this.loadingSubItems = false; return; } this.loadingSubItems = true; const result = await this.searcher(query); if (this.searchValue !== query) { return; } this.searchResults = result; this.loadingSubItems = false; }; // Key handler for the input search field // Will change focus to breadcrumbs (if present) or the first/last item // in the dropdown list to enable selection with the keyboard this.handleInputKeyDown = (event) => { if (event.defaultPrevented) { return; } const isForwardTab = event.key === TAB && !event.altKey && !event.metaKey && !event.shiftKey; const isUp = event.key === ARROW_UP; const isDown = event.key === ARROW_DOWN; if (!isForwardTab && !isUp && !isDown) { return; } event.stopPropagation(); event.preventDefault(); if (isForwardTab || isDown) { if (this.focusBreadcrumbs()) { return; } this.focusFirstListItem(); return; } if (isUp) { // Focus the last list item (wrapping behavior) this.focusLastListItem(); } }; // Key handler for the menu list (capture phase) // Handles Up arrow on first item and Down arrow on last item // Must run in capture phase to intercept before MDC Menu wraps focus // Only intercepts when there's a search input or breadcrumbs to navigate to this.handleListKeyDownCapture = (event) => { const isUp = event.key === ARROW_UP; const isDown = event.key === ARROW_DOWN; if (!isUp && !isDown) { return; } // Up on first item: go to breadcrumbs or search input (if they exist) if (isUp && this.isFirstListItemFocused()) { // Try to focus breadcrumbs first if (this.focusBreadcrumbs()) { event.stopPropagation(); event.preventDefault(); return; } // Then try search input if (this.searchInput) { event.stopPropagation(); event.preventDefault(); this.searchInput.focus(); } // If neither exists, let MDC Menu handle wrap-around return; } // Down on last item: go to search input (if it exists) if (isDown && this.isLastListItemFocused() && this.searchInput) { event.stopPropagation(); event.preventDefault(); this.searchInput.focus(); } // If no search input, let MDC Menu handle wrap-around }; // Key handler for the menu list (bubble phase) // Will change focus to the search field if using shift+tab // And can go forward/back with right/left arrow keys this.handleMenuKeyDown = (event) => { var _a; const isBackwardTab = event.key === TAB && !event.altKey && !event.metaKey && event.shiftKey; const isLeft = event.key === ARROW_LEFT; const isRight = event.key === ARROW_RIGHT; if (!isBackwardTab && !isLeft && !isRight) { return; } if (isBackwardTab) { event.stopPropagation(); event.preventDefault(); (_a = this.searchInput) === null || _a === void 0 ? void 0 : _a.focus(); return; } if (!this.gridLayout && (isLeft || isRight)) { const currentItem = this.getCurrentItem(); event.stopPropagation(); event.preventDefault(); if (isRight) { this.goForward(currentItem); } else if (isLeft) { this.goBack(); } } }; // Key handler for breadcrumbs // Up arrow: focus search input // Down arrow: focus first list item this.handleBreadcrumbsKeyDown = (event) => { var _a; const isUp = event.key === ARROW_UP; const isDown = event.key === ARROW_DOWN; if (!isUp && !isDown) { return; } event.stopPropagation(); event.preventDefault(); if (isUp) { (_a = this.searchInput) === null || _a === void 0 ? void 0 : _a.focus(); return; } if (isDown) { this.focusFirstListItem(); } }; this.clearSearch = () => { this.searchValue = ''; this.searchResults = null; this.loadingSubItems = false; }; this.getCurrentItem = () => { var _a, _b, _c, _d, _e, _f; let menuElement = (_c = (_b = (_a = this.list) === null || _a === void 0 ? void 0 : _a.shadowRoot) === null || _b === void 0 ? void 0 : _b.activeElement) !== null && _c !== void 0 ? _c : null; if (menuElement && menuElement.getAttribute('role') !== 'menuitem') { menuElement = menuElement.closest('[role="menuitem"]'); } if (!menuElement) { menuElement = (_e = (_d = this.list) === null || _d === void 0 ? void 0 : _d.shadowRoot) === null || _e === void 0 ? void 0 : _e.querySelector('[role="menuitem"][tabindex="0"]'); } const dataIndex = Number.parseInt((_f = menuElement === null || menuElement === void 0 ? void 0 : menuElement.dataset.index) !== null && _f !== void 0 ? _f : '0', 10); const item = this.visibleItems[dataIndex]; return (item !== null && item !== void 0 ? item : this.visibleItems[0]); }; this.goForward = (currentItem) => { this.handleSelect(currentItem, false); }; this.goBack = () => { if (!this.currentSubMenu) { // Already in the root of the menu return; } const parent = this.currentSubMenu.parentItem; if (!parent) { // If only one step down, go to the root of the menu. // No need to load a sub-menu. this.currentSubMenu = null; this.clearSearch(); this.navigateMenu.emit(null); this.setFocus(); return; } this.handleSelect(parent); }; this.setTriggerAttributes = (element) => { const attributes = { 'aria-haspopup': true, 'aria-expanded': this.open, 'aria-controls': this.portalId, disabled: this.disabled, role: 'button', }; for (const [key, value] of Object.entries(attributes)) { if (value) { element.setAttribute(key, String(value)); } else { element.removeAttribute(key); } } }; this.onClose = () => { const restoreFocus = this.shouldRestoreFocusOnClose; this.shouldRestoreFocusOnClose = false; this.cancel.emit(); this.open = false; this.currentSubMenu = null; if (restoreFocus) { setTimeout(this.focusTrigger, 0); } }; this.onTriggerClick = (event) => { event.stopPropagation(); if (this.disabled) { return; } this.open = !this.open; }; this.handleSelect = async (menuItem, selectOnEmptyChildren = true) => { if (Array.isArray(menuItem === null || menuItem === void 0 ? void 0 : menuItem.items) && menuItem.items.length > 0) { this.selectedMenuItem = menuItem; this.clearSearch(); this.currentSubMenu = menuItem; this.navigateMenu.emit(menuItem); this.setFocus(); return; } else if (isFunction(menuItem === null || menuItem === void 0 ? void 0 : menuItem.items)) { const menuLoader = menuItem.items; this.selectedMenuItem = menuItem; this.loadingSubItems = true; const subItems = await menuLoader(menuItem); if (this.selectedMenuItem !== menuItem) { return; } menuItem.items = subItems; this.loadingSubItems = false; if (subItems === null || subItems === void 0 ? void 0 : subItems.length) { this.currentSubMenu = menuItem; this.clearSearch(); this.navigateMenu.emit(menuItem); this.setFocus(); return; } } if (!selectOnEmptyChildren) { return; } this.selectedMenuItem = menuItem; this.loadingSubItems = false; this.select.emit(menuItem); this.open = false; this.currentSubMenu = null; setTimeout(this.focusTrigger, 0); }; this.onSelect = (event) => { event.stopPropagation(); this.handleSelect(event.detail); }; this.setListElement = (element) => { if (this.list) { this.list.removeEventListener('keydown', this.handleListKeyDownCapture, true); } this.list = element; if (this.list) { this.list.addEventListener('keydown', this.handleListKeyDownCapture, true); } }; this.setFocus = () => { setTimeout(() => { if (!this.open) { return; } if (this.searchInput && this.searcher) { const observer = new IntersectionObserver(() => { observer.unobserve(this.searchInput); if (this.searchInput === window.document.activeElement) { return; } this.searchInput.focus(); }); observer.observe(this.searchInput); } else if (this.list) { const observer = new IntersectionObserver(() => { observer.unobserve(this.list); this.focusMenuItem(); }); observer.observe(this.list); } }, 0); }; this.focusTrigger = () => { var _a, _b; const trigger = (_b = (_a = this.triggerElement) === null || _a === void 0 ? void 0 : _a.assignedElements()) === null || _b === void 0 ? void 0 : _b[0]; focusTriggerElement(trigger); }; this.setSearchElement = (element) => { this.searchInput = element; }; this.setBreadcrumbsElement = (element) => { this.breadcrumbs = element; }; /** * Focuses the first focusable element inside breadcrumbs. * Returns true if breadcrumbs exist and were focused, * false otherwise. */ this.focusBreadcrumbs = () => { var _a; if (!this.breadcrumbs) { return false; } const focusableElement = (_a = this.breadcrumbs.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('button, a'); if (focusableElement) { focusableElement.focus(); return true; } return false; }; this.focusFirstListItem = () => { const listItems = this.getListItems(); const firstItem = listItems === null || listItems === void 0 ? void 0 : listItems[0]; firstItem === null || firstItem === void 0 ? void 0 : firstItem.focus(); }; this.focusLastListItem = () => { const listItems = this.getListItems(); const lastItem = listItems === null || listItems === void 0 ? void 0 : listItems.at(-1); lastItem === null || lastItem === void 0 ? void 0 : lastItem.focus(); }; this.isFirstListItemFocused = () => { var _a; const listItems = this.getListItems(); if (!listItems) { return false; } const firstItem = listItems[0]; const activeElement = (_a = this.list.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement; return firstItem === activeElement; }; this.isLastListItemFocused = () => { var _a; const listItems = this.getListItems(); if (!listItems) { return false; } const lastItem = listItems.at(-1); const activeElement = (_a = this.list.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement; return lastItem === activeElement; }; this.getListItems = () => { var _a; if (!this.list) { return null; } const items = (_a = this.list.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.mdc-deprecated-list-item'); if (!(items === null || items === void 0 ? void 0 : items.length)) { return null; } return [...items]; }; this.focusMenuItem = () => { var _a; if (!this.list) { return; } const activeElement = this.list.shadowRoot.activeElement; activeElement === null || activeElement === void 0 ? void 0 : activeElement.blur(); const menuItems = this.visibleItems.filter(this.isMenuItem); const selectedIndex = Math.max(menuItems.findIndex((item) => item.selected), 0); const menuElements = [ ...this.list.shadowRoot.querySelectorAll('[role="menuitem"]'), ]; (_a = menuElements[selectedIndex]) === null || _a === void 0 ? void 0 : _a.focus(); }; this.renderNotificationBadge = () => { if (this.items.some(this.hasNotificationBadge)) { return h("limel-badge", null); } }; this.hasNotificationBadge = (item) => this.isMenuItem(item) && item.badge !== undefined; this.setTriggerRef = (elm) => { this.triggerElement = elm; }; this.portalId = createRandomString(); } componentDidRender() { const slotElement = this.host.shadowRoot.querySelector('slot'); // eslint-disable-next-line unicorn/no-array-for-each slotElement.assignedElements().forEach(this.setTriggerAttributes); } render() { const cssProperties = this.getCssProperties(); const dropdownZIndex = getComputedStyle(this.host).getPropertyValue('--dropdown-z-index'); const menuSurfaceWidth = this.getMenuSurfaceWidth(cssProperties['--menu-surface-width']); return (h("div", { key: '720330cf7d07c491a5a9e58a2567896f5526c45a', class: "mdc-menu-surface--anchor", onClick: this.onTriggerClick }, h("slot", { key: 'd3db72fa9e12bafbb039e6a94a1052edf64e060e', ref: this.setTriggerRef, name: "trigger" }), this.renderNotificationBadge(), h("limel-portal", { key: '591f97a745488fbff7eb8fc06b14d2f6e0d21fa6', visible: this.open, containerId: this.portalId, openDirection: this.openDirection, position: "absolute", containerStyle: { 'z-index': dropdownZIndex } }, h("limel-menu-surface", { key: '829a9578894165b28100c4cab519ced0cb7689a0', open: this.open, onDismiss: this.onClose, style: Object.assign(Object.assign({}, cssProperties), { '--menu-surface-width': menuSurfaceWidth, '--limel-menu-surface-display': 'flex', '--limel-menu-surface-flex-direction': 'column' }), class: { 'has-grid-layout': this.gridLayout, } }, this.renderSearchField(), this.renderBreadcrumb(), this.renderLoader(), this.renderEmptyMessage(), this.renderMenuList())))); } itemsWatcher() { this.clearSearch(); this.normalizedHotkeyCache.clear(); this.setFocus(); } connectedCallback() { if (this.open) { document.addEventListener('keydown', this.handleDocumentKeyDown, true); } } disconnectedCallback() { document.removeEventListener('keydown', this.handleDocumentKeyDown, true); } openWatcher(newValue) { const opened = newValue; if (opened) { document.addEventListener('keydown', this.handleDocumentKeyDown, true); this.setFocus(); } else { document.removeEventListener('keydown', this.handleDocumentKeyDown, true); this.clearSearch(); } } isFromTextInput(event) { const path = typeof event.composedPath === 'function' ? event.composedPath() : []; for (const node of path) { if (!(node instanceof HTMLElement)) { continue; } if (node.isContentEditable) { return true; } const tagName = node.tagName; if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') { return true; } } return false; } // Only Ctrl and Meta count as "real" modifiers for the text-input bypass. // Alt/Option is intentionally excluded because it is used for typing // special characters on international keyboards and macOS (e.g. Option+e // for é, AltGr+e for € on Windows). This means alt-only hotkeys like // "alt+x" will NOT fire while a text input (e.g. the search field) is // focused — only Ctrl/Meta combos will. AltGraph is also explicitly // rejected because Windows synthesizes ctrlKey=true for AltGr keypresses. hasModifier(event) { var _a; if ((_a = event.getModifierState) === null || _a === void 0 ? void 0 : _a.call(event, 'AltGraph')) { return false; } return event.ctrlKey || event.metaKey; } isReservedMenuHotkey(hotkey) { const tokens = tokenizeHotkeyString(hotkey); const key = tokens.at(-1); if (!key) { return false; } const hasModifiers = tokens.length > 1; if (hasModifiers) { return false; } return (key === 'arrowup' || key === 'arrowdown' || key === 'arrowleft' || key === 'arrowright' || key === 'tab' || key === 'enter' || key === 'space' || key === 'escape'); } findMenuItemByHotkey(pressedHotkey) { for (const item of this.visibleItems) { if (!this.isMenuItem(item) || item.disabled) { continue; } const rawHotkey = item.hotkey; if (!rawHotkey) { continue; } const normalized = this.getNormalizedHotkey(rawHotkey); if (normalized && normalized === pressedHotkey) { return item; } } return null; } getNormalizedHotkey(raw) { var _a; const cacheKey = raw.trim(); if (this.normalizedHotkeyCache.has(cacheKey)) { return (_a = this.normalizedHotkeyCache.get(cacheKey)) !== null && _a !== void 0 ? _a : null; } const normalized = normalizeHotkeyString(cacheKey); this.normalizedHotkeyCache.set(cacheKey, normalized); return normalized; } getBreadcrumbsItems() { const breadCrumbItems = []; let currentItem = this.currentSubMenu; while (currentItem) { breadCrumbItems.push({ text: currentItem.text, icon: currentItem.icon, menuItem: currentItem, }); currentItem = currentItem.parentItem; } if (breadCrumbItems.length > 0 || this.rootItem !== DEFAULT_ROOT_BREADCRUMBS_ITEM) { breadCrumbItems.push(this.rootItem); } return breadCrumbItems.reverse(); } getCssProperties() { const propertyNames = [ '--menu-surface-width', '--list-grid-item-max-width', '--list-grid-item-min-width', '--list-grid-gap', '--notification-badge-background-color', '--notification-badge-text-color', ]; const style = getComputedStyle(this.host); const values = propertyNames.map((property) => { return style.getPropertyValue(property); }); return zipObject(propertyNames, values); } isMenuItem(item) { return !('separator' in item); } getMenuSurfaceWidth(customWidth) { var _a, _b, _c, _d; if (customWidth) { return customWidth; } if (this.surfaceWidth === 'inherit-from-trigger') { const assignedTriggers = (_a = this.triggerElement) === null || _a === void 0 ? void 0 : _a.assignedElements(); if (!(assignedTriggers === null || assignedTriggers === void 0 ? void 0 : assignedTriggers.length) || !((_b = assignedTriggers[0]) === null || _b === void 0 ? void 0 : _b.clientWidth)) { return ''; } return `${assignedTriggers[0].clientWidth}px`; } else if (this.surfaceWidth === 'inherit-from-menu') { if (!((_c = this.host) === null || _c === void 0 ? void 0 : _c.clientWidth)) { return ''; } return `${(_d = this.host) === null || _d === void 0 ? void 0 : _d.clientWidth}px`; } return ''; } get visibleItems() { var _a; if (Array.isArray(this.searchResults) && this.searchValue) { return this.searchResults; } else if (Array.isArray((_a = this.currentSubMenu) === null || _a === void 0 ? void 0 : _a.items)) { if (this.cachedSubMenuSource !== this.currentSubMenu) { this.cachedSubMenuSource = this.currentSubMenu; this.cachedSubMenuItems = this.currentSubMenu.items.map((item) => (Object.assign(Object.assign({}, item), { parentItem: this.currentSubMenu }))); } return this.cachedSubMenuItems; } return this.items; } static get is() { return "limel-menu"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["menu.scss"] }; } static get styleUrls() { return { "$": ["menu.css"] }; } static get properties() { return { "items": { "type": "unknown", "mutable": false, "complexType": { "original": "Array<MenuItem | ListSeparator>", "resolved": "(ListSeparator | MenuItem<any>)[]", "references": { "Array": { "location": "global", "id": "global::Array" }, "MenuItem": { "location": "import", "path": "./menu.types", "id": "src/components/menu/menu.types.ts::MenuItem", "referenceLocation": "MenuItem" }, "ListSeparator": { "location": "import", "path": "../list-item/list-item.types", "id": "src/components/list-item/list-item.types.ts::ListSeparator", "referenceLocation": "ListSeparator" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "A list of items and separators to show in the menu." }, "getter": false, "setter": false, "defaultValue": "[]" }, "disabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Sets the disabled state of the menu." }, "getter": false, "setter": false, "reflect": true, "attribute": "disabled", "defaultValue": "false" }, "openDirection": { "type": "string", "mutable": false, "complexType": { "original": "OpenDirection", "resolved": "\"bottom\" | \"bottom-end\" | \"bottom-start\" | \"left\" | \"left-end\" | \"left-start\" | \"right\" | \"right-end\" | \"right-start\" | \"top\" | \"top-end\" | \"top-start\"", "references": { "OpenDirection": { "location": "import", "path": "./menu.types", "id": "src/components/menu/menu.types.ts::OpenDirection", "referenceLocation": "OpenDirection" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Decides the menu's location in relation to its trigger" }, "getter": false, "setter": false, "reflect": true, "attribute": "open-direction", "defaultValue": "'bottom-start'" }, "surfaceWidth": { "type": "string", "mutable": false, "complexType": { "original": "SurfaceWidth", "resolved": "\"inherit-from-items\" | \"inherit-from-menu\" | \"inherit-from-trigger\"", "references": { "SurfaceWidth": { "location": "import", "path": "./menu.types", "id": "src/components/menu/menu.types.ts::SurfaceWidth", "referenceLocation": "SurfaceWidth" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Decides the width of menu's dropdown" }, "getter": false, "setter": false, "reflect": true, "attribute": "surface-width", "defaultValue": "'inherit-from-items'" }, "open": { "type": "boolean", "mutable": true, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Sets the open state of the menu." }, "getter": false, "setter": false, "reflect": true, "attribute": "open", "defaultValue": "false" }, "badgeIcons": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Defines whether the menu should show badges." }, "getter": false, "setter": false, "reflect": true, "attribute": "badge-icons", "defaultValue": "false" }, "gridLayout": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Renders list items in a grid layout, rather than a vertical list" }, "getter": false, "setter": false, "reflect": true, "attribute": "grid-layout", "defaultValue": "false" }, "loading": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": ":::warning Internal Use Only\nThis property is for internal use only. We need it for now, but want to\nfind a better implementation of the functionality it currently enables.\nIf and when we do so, this property will be removed without prior\nnotice. If you use it, your code _will_ break in the future.\n:::" }, "getter": false, "setter": false, "reflect": true, "attribute": "loading", "defaultValue": "false" }, "currentSubMenu": { "type": "unknown", "mutable": true, "complexType": { "original": "MenuItem", "resolved": "MenuItem<any>", "references": { "MenuItem": { "location": "import", "path": "./menu.types", "id": "src/components/menu/menu.types.ts::MenuItem", "referenceLocation": "MenuItem" } } }, "required": false, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": ":::warning Internal Use Only\nThis property is for internal use only. We need it for now, but want to\nfind a better implementation of the functionality it currently enables.\nIf and when we do so, this property will be removed without prior\nnotice. If you use it, your code _will_ break in the future.\n:::" }, "getter": false, "setter": false }, "rootItem": { "type": "unknown", "mutable": false, "complexType": { "original": "BreadcrumbsItem", "resolved": "BreadcrumbsItem", "references": { "BreadcrumbsItem": { "location": "import", "path": "../breadcrumbs/breadcrumbs.types", "id": "src/components/breadcrumbs/breadcrumbs.types.ts::BreadcrumbsItem", "referenceLocation": "BreadcrumbsItem" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "A root breadcrumb item to show above the menu items.\nClicking it navigates back from a sub-menu to the root menu." }, "getter": false, "setter": false, "defaultValue": "DEFAULT_ROOT_BREADCRUMBS_ITEM" }, "searcher": { "type": "unknown", "mutable": false, "complexType": { "original": "MenuSearcher", "resolved": "(query: string) => Promise<(ListSeparator | MenuItem<any>)[]>", "references": { "MenuSearcher": { "location": "import", "path": "./menu.types", "id": "src/components/menu/menu.types.ts::MenuSearcher", "referenceLocation": "MenuSearcher" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "A search function that takes a search-string as an argument,\nand returns a promise that will eventually be resolved with\nan array of `MenuItem`:s.\n\nSee the docs for the type `MenuSearcher` for type information on\nthe searcher function itself." }, "getter": false, "setter": false }, "searchPlaceholder": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Placeholder text for the search input field." }, "getter": false, "setter": false, "reflect": false, "attribute": "search-placeholder" }, "emptyResultMessage": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Message to display when search returns 0 results." }, "getter": false, "setter": false, "reflect": false, "attribute": "empty-result-message" } }; } static get states() { return { "loadingSubItems": {}, "searchValue": {}, "searchResults": {} }; } static get events() { return [{ "method": "cancel", "name": "cancel", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Is emitted when the menu is cancelled." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "select", "name": "select", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Is emitted when a menu item is selected." }, "complexType": { "original": "MenuItem", "resolved": "MenuItem<any>", "references": { "MenuItem": { "location": "import", "path": "./menu.types", "id": "src/components/menu/menu.types.ts::MenuItem", "referenceLocation": "MenuItem" } } } }, { "method": "navigateMenu", "name": "navigateMenu", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Is emitted when a menu item with a sub-menu is selected." }, "complexType": { "original": "MenuItem | null", "resolved": "MenuItem<any>", "references": { "MenuItem": { "location": "import", "path": "./menu.types", "id": "src/components/menu/menu.types.ts::MenuItem", "referenceLocation": "MenuItem" } } } }]; } static get elementRef() { return "host"; } static get watchers() { return [{ "propName": "items", "methodName": "itemsWatcher" }, { "propName": "open", "methodName": "openWatcher" }]; } }