@limetech/lime-elements
Version:
1,139 lines (1,138 loc) • 46.3 kB
JavaScript
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"
}];
}
}