UNPKG

@material/web

Version:
138 lines 5.11 kB
/** * @license * Copyright 2023 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { CloseReason, createDefaultCloseMenuEvent, isClosableKey, } from './shared.js'; /** * A controller that provides most functionality of an element that implements * the MenuItem interface. */ export class MenuItemController { /** * @param host The MenuItem in which to attach this controller to. * @param config The object that configures this controller's behavior. */ constructor(host, config) { this.host = host; this.internalTypeaheadText = null; /** * Bind this click listener to the interactive element. Handles closing the * menu. */ this.onClick = () => { if (this.host.keepOpen) return; this.host.dispatchEvent(createDefaultCloseMenuEvent(this.host, { kind: CloseReason.CLICK_SELECTION, })); }; /** * Bind this click listener to the interactive element. Handles closing the * menu. */ this.onKeydown = (event) => { // Check if the interactive element is an anchor tag. If so, click it. if (this.host.href && event.code === 'Enter') { const interactiveElement = this.getInteractiveElement(); if (interactiveElement instanceof HTMLAnchorElement) { interactiveElement.click(); } } if (event.defaultPrevented) return; // If the host has keepOpen = true we should ignore clicks & Space/Enter, // however we always maintain the ability to close a menu with a explicit // `escape` keypress. const keyCode = event.code; if (this.host.keepOpen && keyCode !== 'Escape') return; if (isClosableKey(keyCode)) { event.preventDefault(); this.host.dispatchEvent(createDefaultCloseMenuEvent(this.host, { kind: CloseReason.KEYDOWN, key: keyCode, })); } }; this.getHeadlineElements = config.getHeadlineElements; this.getSupportingTextElements = config.getSupportingTextElements; this.getDefaultElements = config.getDefaultElements; this.getInteractiveElement = config.getInteractiveElement; this.host.addController(this); } /** * The text that is selectable via typeahead. If not set, defaults to the * innerText of the item slotted into the `"headline"` slot, and if there are * no slotted elements into headline, then it checks the _default_ slot, and * then the `"supporting-text"` slot if nothing is in _default_. */ get typeaheadText() { if (this.internalTypeaheadText !== null) { return this.internalTypeaheadText; } const headlineElements = this.getHeadlineElements(); const textParts = []; headlineElements.forEach((headlineElement) => { if (headlineElement.textContent && headlineElement.textContent.trim()) { textParts.push(headlineElement.textContent.trim()); } }); // If there are no headline elements, check the default slot's text content if (textParts.length === 0) { this.getDefaultElements().forEach((defaultElement) => { if (defaultElement.textContent && defaultElement.textContent.trim()) { textParts.push(defaultElement.textContent.trim()); } }); } // If there are no headline nor default slot elements, check the //supporting-text slot's text content if (textParts.length === 0) { this.getSupportingTextElements().forEach((supportingTextElement) => { if (supportingTextElement.textContent && supportingTextElement.textContent.trim()) { textParts.push(supportingTextElement.textContent.trim()); } }); } return textParts.join(' '); } /** * The recommended tag name to render as the list item. */ get tagName() { const type = this.host.type; switch (type) { case 'link': return 'a'; case 'button': return 'button'; default: case 'menuitem': case 'option': return 'li'; } } /** * The recommended role of the menu item. */ get role() { return this.host.type === 'option' ? 'option' : 'menuitem'; } hostConnected() { this.host.toggleAttribute('md-menu-item', true); } hostUpdate() { if (this.host.href) { this.host.type = 'link'; } } /** * Use to set the typeaheadText when it changes. */ setTypeaheadText(text) { this.internalTypeaheadText = text; } } //# sourceMappingURL=menuItemController.js.map