@material/web
Version:
Material web components
808 lines • 32.1 kB
JavaScript
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { __decorate } from "tslib";
import '../../focus/md-focus-ring.js';
import '../../elevation/elevation.js';
import { html, isServer, LitElement } from 'lit';
import { property, query, queryAssignedElements, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { polyfillElementInternalsAria, setupHostAria } from '../../internal/aria/aria.js';
import { createAnimationSignal, EASING } from '../../internal/motion/animation.js';
import { ListController, NavigableKeys } from '../../list/internal/list-controller.js';
import { getActiveItem, getFirstActivatableItem, getLastActivatableItem } from '../../list/internal/list-navigation-helpers.js';
import { FocusState, isClosableKey, isElementInSubtree } from './controllers/shared.js';
import { Corner, SurfacePositionController } from './controllers/surfacePositionController.js';
import { TypeaheadController } from './controllers/typeaheadController.js';
export { Corner } from './controllers/surfacePositionController.js';
/**
* The default value for the typeahead buffer time in Milliseconds.
*/
export const DEFAULT_TYPEAHEAD_BUFFER_TIME = 200;
const submenuNavKeys = new Set([
NavigableKeys.ArrowDown,
NavigableKeys.ArrowUp,
NavigableKeys.Home,
NavigableKeys.End,
]);
const menuNavKeys = new Set([
NavigableKeys.ArrowLeft,
NavigableKeys.ArrowRight,
...submenuNavKeys,
]);
/**
* Gets the currently focused element on the page.
*
* @param activeDoc The document or shadowroot from which to start the search.
* Defaults to `window.document`
* @return Returns the currently deeply focused element or `null` if none.
*/
function getFocusedElement(activeDoc = document) {
let activeEl = activeDoc.activeElement;
// Check for activeElement in the case that an element with a shadow root host
// is currently focused.
while (activeEl && activeEl?.shadowRoot?.activeElement) {
activeEl = activeEl.shadowRoot.activeElement;
}
return activeEl;
}
/**
* @fires opening Fired before the opening animation begins
* @fires opened Fired once the menu is open, after any animations
* @fires closing Fired before the closing animation begins
* @fires closed Fired once the menu is closed, after any animations
*/
export class Menu extends LitElement {
/**
* Whether the menu is animating upwards or downwards when opening. This is
* helpful for calculating some animation calculations.
*/
get openDirection() {
const menuCornerBlock = this.menuCorner.split('-')[0];
return menuCornerBlock === 'start' ? 'DOWN' : 'UP';
}
/**
* The element which the menu should align to. If `anchor` is set to a
* non-empty idref string, then `anchorEl` will resolve to the element with
* the given id in the same root node. Otherwise, `null`.
*/
get anchorElement() {
if (this.anchor) {
return this.getRootNode()
.querySelector(`#${this.anchor}`);
}
return this.currentAnchorElement;
}
set anchorElement(element) {
this.currentAnchorElement = element;
this.requestUpdate('anchorElement');
}
constructor() {
super();
/**
* The ID of the element in the same root node in which the menu should align
* to. Overrides setting `anchorElement = elementReference`.
*
* __NOTE__: anchor or anchorElement must either be an HTMLElement or resolve
* to an HTMLElement in order for menu to open.
*/
this.anchor = '';
/**
* Whether the positioning algorithim should calculate relative to the parent
* of the anchor element (absolute) or relative to the window (fixed).
*
* Examples for `position = 'fixed'`:
*
* - If there is no `position:relative` in the given parent tree and the
* surface is `position:absolute`
* - If the surface is `position:fixed`
* - If the surface is in the "top layer"
* - The anchor and the surface do not share a common `position:relative`
* ancestor
*
* When using positioning = fixed, in most cases, the menu should position
* itself above most other `position:absolute` or `position:fixed` elements
* when placed inside of them. e.g. using a menu inside of an `md-dialog`.
*
* __NOTE__: Fixed menus will not scroll with the page and will be fixed to
* the window instead.
*/
this.positioning = 'absolute';
/**
* Skips the opening and closing animations.
*/
this.quick = false;
/**
* Displays overflow content like a submenu.
*
* __NOTE__: This may cause adverse effects if you set
* `md-menu {max-height:...}`
* and have items overflowing items in the "y" direction.
*/
this.hasOverflow = false;
/**
* Opens the menu and makes it visible. Alternative to the `.show()` and
* `.close()` methods
*/
this.open = false;
/**
* Offsets the menu's inline alignment from the anchor by the given number in
* pixels. This value is direction aware and will follow the LTR / RTL
* direction.
*
* e.g. LTR: positive -> right, negative -> left
* RTL: positive -> left, negative -> right
*/
this.xOffset = 0;
/**
* Offsets the menu's block alignment from the anchor by the given number in
* pixels.
*
* e.g. positive -> down, negative -> up
*/
this.yOffset = 0;
/**
* The max time between the keystrokes of the typeahead menu behavior before
* it clears the typeahead buffer.
*/
this.typeaheadDelay = DEFAULT_TYPEAHEAD_BUFFER_TIME;
/**
* The corner of the anchor which to align the menu in the standard logical
* property style of <block>-<inline> e.g. `'end-start'`.
*
* NOTE: This value may not be respected by the menu positioning algorithm
* if the menu would render outisde the viewport.
*/
this.anchorCorner = Corner.END_START;
/**
* The corner of the menu which to align the anchor in the standard logical
* property style of <block>-<inline> e.g. `'start-start'`.
*
* NOTE: This value may not be respected by the menu positioning algorithm
* if the menu would render outisde the viewport.
*/
this.menuCorner = Corner.START_START;
/**
* Keeps the user clicks outside the menu.
*
* NOTE: clicking outside may still cause focusout to close the menu so see
* `stayOpenOnFocusout`.
*/
this.stayOpenOnOutsideClick = false;
/**
* Keeps the menu open when focus leaves the menu's composed subtree.
*
* NOTE: Focusout behavior will stop propagation of the focusout event. Set
* this property to true to opt-out of menu's focuout handling altogether.
*/
this.stayOpenOnFocusout = false;
/**
* After closing, does not restore focus to the last focused element before
* the menu was opened.
*/
this.skipRestoreFocus = false;
/**
* The element that should be focused by default once opened.
*
* NOTE: When setting default focus to 'LIST_ROOT', remember to change
* `tabindex` to `0` and change md-menu's display to something other than
* `display: contents` when necessary.
*/
this.defaultFocus = FocusState.FIRST_ITEM;
this.typeaheadActive = true;
/**
* Whether or not the current menu is a submenu and should not handle specific
* navigation keys.
*
* @exports
*/
this.isSubmenu = false;
/**
* The event path of the last window pointerdown event.
*/
this.pointerPath = [];
this.openCloseAnimationSignal = createAnimationSignal();
this.listController = new ListController({
isItem: (maybeItem) => {
return maybeItem.hasAttribute('md-menu-item');
},
getPossibleItems: () => this.slotItems,
isRtl: () => (getComputedStyle(this).direction === 'rtl'),
deactivateItem: (item) => {
item.selected = false;
item.tabIndex = -1;
},
activateItem: (item) => {
item.selected = true;
item.tabIndex = 0;
},
isNavigableKey: (key) => {
if (!this.isSubmenu) {
return menuNavKeys.has(key);
}
const isRtl = getComputedStyle(this).direction === 'rtl';
// we want md-submenu to handle the submenu's left/right arrow exit
// key so it can close the menu instead of navigate the list.
// Therefore we need to include all keys but left/right arrow close
// key
const arrowOpen = isRtl ? NavigableKeys.ArrowLeft : NavigableKeys.ArrowRight;
if (key === arrowOpen) {
return true;
}
return submenuNavKeys.has(key);
},
});
/**
* The element that was focused before the menu opened.
*/
this.lastFocusedElement = null;
/**
* Handles typeahead navigation through the menu.
*/
this.typeaheadController = new TypeaheadController(() => {
return {
getItems: () => this.items,
typeaheadBufferTime: this.typeaheadDelay,
active: this.typeaheadActive
};
});
this.currentAnchorElement = null;
this.internals = polyfillElementInternalsAria(this, this /* needed for closure */.attachInternals());
/**
* Handles positioning the surface and aligning it to the anchor as well as
* keeping it in the viewport.
*/
this.menuPositionController = new SurfacePositionController(this, () => {
return {
anchorCorner: this.anchorCorner,
surfaceCorner: this.menuCorner,
surfaceEl: this.surfaceEl,
anchorEl: this.anchorElement,
positioning: this.positioning,
isOpen: this.open,
xOffset: this.xOffset,
yOffset: this.yOffset,
onOpen: this.onOpened,
beforeClose: this.beforeClose,
onClose: this.onClosed,
// We can't resize components that have overflow like menus with
// submenus because the overflow-y will show menu items / content
// outside the bounds of the menu. (to be fixed w/ popover API)
repositionStrategy: this.hasOverflow ? 'move' : 'resize',
};
});
this.handleFocusout = async (event) => {
const anchorEl = this.anchorElement;
// Do not close if we focused out by clicking on the anchor element. We
// can't assume anchor buttons can be the related target because of iOS does
// not focus buttons.
if (this.stayOpenOnFocusout || !this.open ||
this.pointerPath.includes(anchorEl)) {
return;
}
if (event.relatedTarget) {
// Don't close the menu if we are switching focus between menu,
// md-menu-item, and md-list or if the anchor was click focused.
if (isElementInSubtree(event.relatedTarget, this) ||
isElementInSubtree(event.relatedTarget, anchorEl)) {
return;
}
}
else if (this.pointerPath.includes(this)) {
// If menu tabindex == -1 and the user clicks on the menu or a divider, we
// want to keep the menu open.
return;
}
const oldRestoreFocus = this.skipRestoreFocus;
// allow focus to continue to the next focused object rather than returning
this.skipRestoreFocus = true;
this.close();
// await for close
await this.updateComplete;
// return to previous behavior
this.skipRestoreFocus = oldRestoreFocus;
};
/**
* Saves the last focused element focuses the new element based on
* `defaultFocus`, and animates open.
*/
this.onOpened = async () => {
this.lastFocusedElement = getFocusedElement();
const items = this.items;
const activeItemRecord = getActiveItem(items);
if (activeItemRecord && this.defaultFocus !== FocusState.NONE) {
activeItemRecord.item.tabIndex = -1;
}
let animationAborted = !this.quick;
if (this.quick) {
this.dispatchEvent(new Event('opening'));
}
else {
animationAborted = !!await this.animateOpen();
}
// This must come after the opening animation or else it may focus one of
// the items before the animation has begun and causes the list to slide
// (block-padding-of-the-menu)px at the end of the animation
switch (this.defaultFocus) {
case FocusState.FIRST_ITEM:
const first = getFirstActivatableItem(items);
if (first) {
first.tabIndex = 0;
first.focus();
await first.updateComplete;
}
break;
case FocusState.LAST_ITEM:
const last = getLastActivatableItem(items);
if (last) {
last.tabIndex = 0;
last.focus();
await last.updateComplete;
}
break;
case FocusState.LIST_ROOT:
this.focus();
break;
default:
case FocusState.NONE:
// Do nothing.
break;
}
if (!animationAborted) {
this.dispatchEvent(new Event('opened'));
}
};
/**
* Animates closed.
*/
this.beforeClose = async () => {
this.open = false;
if (!this.skipRestoreFocus) {
this.lastFocusedElement?.focus?.();
}
if (!this.quick) {
await this.animateClose();
}
};
/**
* Focuses the last focused element.
*/
this.onClosed = () => {
if (this.quick) {
this.dispatchEvent(new Event('closing'));
this.dispatchEvent(new Event('closed'));
}
};
this.onWindowPointerdown = (event) => {
this.pointerPath = event.composedPath();
};
/**
* We cannot listen to window click because Safari on iOS will not bubble a
* click event on window if the item clicked is not a "clickable" item such as
* <body>
*/
this.onDocumentClick = (event) => {
if (!this.open) {
return;
}
const path = event.composedPath();
if (!this.stayOpenOnOutsideClick && !path.includes(this) &&
!path.includes(this.anchorElement)) {
this.open = false;
}
};
if (!isServer) {
this.internals.role = 'menu';
this.addEventListener('keydown', this.handleKeydown);
// Capture so that we can grab the event before it reaches the menu item
// istelf. Specifically useful for the case where typeahead encounters a
// space and we don't want the menu item to close the menu.
this.addEventListener('keydown', this.captureKeydown, { capture: true });
this.addEventListener('focusout', this.handleFocusout);
}
}
/**
* The menu items associated with this menu. The items must be `MenuItem`s and
* have both the `md-menu-item` and `md-list-item` attributes.
*/
get items() {
return this.listController.items;
}
willUpdate(changed) {
if (!changed.has('open')) {
return;
}
if (this.open) {
this.removeAttribute('aria-hidden');
return;
}
this.setAttribute('aria-hidden', 'true');
}
update(changed) {
if (changed.has('open')) {
if (this.open) {
this.setUpGlobalEventListeners();
}
else {
this.cleanUpGlobalEventListeners();
}
}
super.update(changed);
}
connectedCallback() {
super.connectedCallback();
if (this.open) {
this.setUpGlobalEventListeners();
}
}
disconnectedCallback() {
super.disconnectedCallback();
this.cleanUpGlobalEventListeners();
}
render() {
return this.renderSurface();
}
/**
* Renders the positionable surface element and its contents.
*/
renderSurface() {
return html `
<div
class="menu ${classMap(this.getSurfaceClasses())}"
style=${styleMap(this.menuPositionController.surfaceStyles)}>
${this.renderElevation()}
<div class="items">
<div class="item-padding">
${this.renderMenuItems()}
</div>
</div>
</div>
`;
}
/**
* Renders the menu items' slot
*/
renderMenuItems() {
return html `<slot
-menu=${this.onCloseMenu}
-items=${this.onDeactivateItems}
-activation=${this.onRequestActivation}
-typeahead=${this.handleDeactivateTypeahead}
-typeahead=${this.handleActivateTypeahead}
-open-on-focusout=${this.handleStayOpenOnFocusout}
-on-focusout=${this.handleCloseOnFocusout}
=${this.listController.onSlotchange}></slot>`;
}
/**
* Renders the elevation component.
*/
renderElevation() {
return html `<md-elevation part="elevation"></md-elevation>`;
}
getSurfaceClasses() {
return {
open: this.open,
fixed: this.positioning === 'fixed',
'has-overflow': this.hasOverflow,
};
}
captureKeydown(event) {
if (event.target === this && !event.defaultPrevented &&
isClosableKey(event.code)) {
event.preventDefault();
this.close();
}
this.typeaheadController.onKeydown(event);
}
/**
* Performs the opening animation:
*
* https://direct.googleplex.com/#/spec/295000003+271060003
*
* @return A promise that resolve to `true` if the animation was aborted,
* `false` if it was not aborted.
*/
async animateOpen() {
const surfaceEl = this.surfaceEl;
const slotEl = this.slotEl;
if (!surfaceEl || !slotEl)
return true;
const openDirection = this.openDirection;
this.dispatchEvent(new Event('opening'));
// needs to be imperative because we don't want to mix animation and Lit
// render timing
surfaceEl.classList.toggle('animating', true);
const signal = this.openCloseAnimationSignal.start();
const height = surfaceEl.offsetHeight;
const openingUpwards = openDirection === 'UP';
const children = this.items;
const FULL_DURATION = 500;
const SURFACE_OPACITY_DURATION = 50;
const ITEM_OPACITY_DURATION = 250;
// We want to fit every child fade-in animation within the full duration of
// the animation.
const DELAY_BETWEEN_ITEMS = (FULL_DURATION - ITEM_OPACITY_DURATION) / children.length;
const surfaceHeightAnimation = surfaceEl.animate([{ height: '0px' }, { height: `${height}px` }], {
duration: FULL_DURATION,
easing: EASING.EMPHASIZED,
});
// When we are opening upwards, we want to make sure the last item is always
// in view, so we need to translate it upwards the opposite direction of the
// height animation
const upPositionCorrectionAnimation = slotEl.animate([
{ transform: openingUpwards ? `translateY(-${height}px)` : '' },
{ transform: '' }
], { duration: FULL_DURATION, easing: EASING.EMPHASIZED });
const surfaceOpacityAnimation = surfaceEl.animate([{ opacity: 0 }, { opacity: 1 }], SURFACE_OPACITY_DURATION);
const childrenAnimations = [];
for (let i = 0; i < children.length; i++) {
// If we are animating upwards, then reverse the children list.
const directionalIndex = openingUpwards ? children.length - 1 - i : i;
const child = children[directionalIndex];
const animation = child.animate([{ opacity: 0 }, { opacity: 1 }], {
duration: ITEM_OPACITY_DURATION,
delay: DELAY_BETWEEN_ITEMS * i,
});
// Make them all initially hidden and then clean up at the end of each
// animation.
child.classList.toggle('md-menu-hidden', true);
animation.addEventListener('finish', () => {
child.classList.toggle('md-menu-hidden', false);
});
childrenAnimations.push([child, animation]);
}
let resolveAnimation = (value) => { };
const animationFinished = new Promise((resolve) => {
resolveAnimation = resolve;
});
signal.addEventListener('abort', () => {
surfaceHeightAnimation.cancel();
upPositionCorrectionAnimation.cancel();
surfaceOpacityAnimation.cancel();
childrenAnimations.forEach(([child, animation]) => {
child.classList.toggle('md-menu-hidden', false);
animation.cancel();
});
resolveAnimation(true);
});
surfaceHeightAnimation.addEventListener('finish', () => {
surfaceEl.classList.toggle('animating', false);
this.openCloseAnimationSignal.finish();
resolveAnimation(false);
});
return await animationFinished;
}
/**
* Performs the closing animation:
*
* https://direct.googleplex.com/#/spec/295000003+271060003
*/
animateClose() {
let resolve;
let reject;
// This promise blocks the surface position controller from setting
// display: none on the surface which will interfere with this animation.
const animationEnded = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
const surfaceEl = this.surfaceEl;
const slotEl = this.slotEl;
if (!surfaceEl || !slotEl) {
reject();
return animationEnded;
}
const openDirection = this.openDirection;
const closingDownwards = openDirection === 'UP';
this.dispatchEvent(new Event('closing'));
// needs to be imperative because we don't want to mix animation and Lit
// render timing
surfaceEl.classList.toggle('animating', true);
const signal = this.openCloseAnimationSignal.start();
const height = surfaceEl.offsetHeight;
const children = this.items;
const FULL_DURATION = 150;
const SURFACE_OPACITY_DURATION = 50;
// The surface fades away at the very end
const SURFACE_OPACITY_DELAY = FULL_DURATION - SURFACE_OPACITY_DURATION;
const ITEM_OPACITY_DURATION = 50;
const ITEM_OPACITY_INITIAL_DELAY = 50;
const END_HEIGHT_PERCENTAGE = .35;
// We want to fit every child fade-out animation within the full duration of
// the animation.
const DELAY_BETWEEN_ITEMS = (FULL_DURATION - ITEM_OPACITY_INITIAL_DELAY - ITEM_OPACITY_DURATION) /
children.length;
// The mock has the animation shrink to 35%
const surfaceHeightAnimation = surfaceEl.animate([
{ height: `${height}px` },
{ height: `${height * END_HEIGHT_PERCENTAGE}px` }
], {
duration: FULL_DURATION,
easing: EASING.EMPHASIZED_ACCELERATE,
});
// When we are closing downwards, we want to make sure the last item is
// always in view, so we need to translate it upwards the opposite direction
// of the height animation
const downPositionCorrectionAnimation = slotEl.animate([
{ transform: '' }, {
transform: closingDownwards ?
`translateY(-${height * (1 - END_HEIGHT_PERCENTAGE)}px)` :
''
}
], { duration: FULL_DURATION, easing: EASING.EMPHASIZED_ACCELERATE });
const surfaceOpacityAnimation = surfaceEl.animate([{ opacity: 1 }, { opacity: 0 }], { duration: SURFACE_OPACITY_DURATION, delay: SURFACE_OPACITY_DELAY });
const childrenAnimations = [];
for (let i = 0; i < children.length; i++) {
// If the animation is closing upwards, then reverse the list of
// children so that we animate in the opposite direction.
const directionalIndex = closingDownwards ? i : children.length - 1 - i;
const child = children[directionalIndex];
const animation = child.animate([{ opacity: 1 }, { opacity: 0 }], {
duration: ITEM_OPACITY_DURATION,
delay: ITEM_OPACITY_INITIAL_DELAY + DELAY_BETWEEN_ITEMS * i,
});
// Make sure the items stay hidden at the end of each child animation.
// We clean this up at the end of the overall animation.
animation.addEventListener('finish', () => {
child.classList.toggle('md-menu-hidden', true);
});
childrenAnimations.push([child, animation]);
}
signal.addEventListener('abort', () => {
surfaceHeightAnimation.cancel();
downPositionCorrectionAnimation.cancel();
surfaceOpacityAnimation.cancel();
childrenAnimations.forEach(([child, animation]) => {
animation.cancel();
child.classList.toggle('md-menu-hidden', false);
});
reject();
});
surfaceHeightAnimation.addEventListener('finish', () => {
surfaceEl.classList.toggle('animating', false);
childrenAnimations.forEach(([child]) => {
child.classList.toggle('md-menu-hidden', false);
});
this.openCloseAnimationSignal.finish();
this.dispatchEvent(new Event('closed'));
resolve(true);
});
return animationEnded;
}
handleKeydown(event) {
// At any key event, the pointer interaction is done so we need to clear our
// cached pointerpath. This handles the case where the user clicks on the
// anchor, and then hits shift+tab
this.pointerPath = [];
this.listController.handleKeydown(event);
}
setUpGlobalEventListeners() {
document.addEventListener('click', this.onDocumentClick, { capture: true });
window.addEventListener('pointerdown', this.onWindowPointerdown);
}
cleanUpGlobalEventListeners() {
document.removeEventListener('click', this.onDocumentClick, { capture: true });
window.removeEventListener('pointerdown', this.onWindowPointerdown);
}
onCloseMenu() {
this.close();
}
onDeactivateItems(event) {
event.stopPropagation();
this.listController.onDeactivateItems();
}
onRequestActivation(event) {
event.stopPropagation();
this.listController.onRequestActivation(event);
}
handleDeactivateTypeahead(event) {
// stopPropagation so that this does not deactivate any typeaheads in menus
// nested above it e.g. md-sub-menu
event.stopPropagation();
this.typeaheadActive = false;
}
handleActivateTypeahead(event) {
// stopPropagation so that this does not activate any typeaheads in menus
// nested above it e.g. md-sub-menu
event.stopPropagation();
this.typeaheadActive = true;
}
handleStayOpenOnFocusout(event) {
event.stopPropagation();
this.stayOpenOnFocusout = true;
}
handleCloseOnFocusout(event) {
event.stopPropagation();
this.stayOpenOnFocusout = false;
}
close() {
this.open = false;
const maybeSubmenu = this.slotItems;
maybeSubmenu.forEach(item => {
item.close?.();
});
}
show() {
this.open = true;
}
/**
* Activates the next item in the menu. If at the end of the menu, the first
* item will be activated.
*
* @return The activated menu item or `null` if there are no items.
*/
activateNextItem() {
return this.listController.activateNextItem() ?? null;
}
/**
* Activates the previous item in the menu. If at the start of the menu, the
* last item will be activated.
*
* @return The activated menu item or `null` if there are no items.
*/
activatePreviousItem() {
return this.listController.activatePreviousItem() ?? null;
}
}
(() => {
// We want to manage tabindex ourselves.
setupHostAria(Menu, { focusable: false });
})();
__decorate([
query('.menu')
], Menu.prototype, "surfaceEl", void 0);
__decorate([
query('slot')
], Menu.prototype, "slotEl", void 0);
__decorate([
property()
], Menu.prototype, "anchor", void 0);
__decorate([
property()
], Menu.prototype, "positioning", void 0);
__decorate([
property({ type: Boolean })
], Menu.prototype, "quick", void 0);
__decorate([
property({ type: Boolean, attribute: 'has-overflow' })
], Menu.prototype, "hasOverflow", void 0);
__decorate([
property({ type: Boolean, reflect: true })
], Menu.prototype, "open", void 0);
__decorate([
property({ type: Number, attribute: 'x-offset' })
], Menu.prototype, "xOffset", void 0);
__decorate([
property({ type: Number, attribute: 'y-offset' })
], Menu.prototype, "yOffset", void 0);
__decorate([
property({ type: Number, attribute: 'typeahead-delay' })
], Menu.prototype, "typeaheadDelay", void 0);
__decorate([
property({ attribute: 'anchor-corner' })
], Menu.prototype, "anchorCorner", void 0);
__decorate([
property({ attribute: 'menu-corner' })
], Menu.prototype, "menuCorner", void 0);
__decorate([
property({ type: Boolean, attribute: 'stay-open-on-outside-click' })
], Menu.prototype, "stayOpenOnOutsideClick", void 0);
__decorate([
property({ type: Boolean, attribute: 'stay-open-on-focusout' })
], Menu.prototype, "stayOpenOnFocusout", void 0);
__decorate([
property({ type: Boolean, attribute: 'skip-restore-focus' })
], Menu.prototype, "skipRestoreFocus", void 0);
__decorate([
property({ attribute: 'default-focus' })
], Menu.prototype, "defaultFocus", void 0);
__decorate([
queryAssignedElements({ flatten: true })
], Menu.prototype, "slotItems", void 0);
__decorate([
state()
], Menu.prototype, "typeaheadActive", void 0);
//# sourceMappingURL=menu.js.map