var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; var __privateMethod = (obj, member, method) => { __accessCheck(obj, member, "access private method"); return method; }; var _mediaController, _previouslyFocused, _invokerElement, _previousItems, _mutationObserver, _isPopover, _cssRule, _handleSlotChange, handleSlotChange_fn, _handleMenuItems, _updateLayoutStyle, updateLayoutStyle_fn, _handleInvoke, handleInvoke_fn, _handleOpen, handleOpen_fn, _handleClosed, handleClosed_fn, _handleBoundsResize, _handleMenuResize, _positionMenu, positionMenu_fn, _resizeMenu, resizeMenu_fn, _handleClick, handleClick_fn, _backButtonElement, backButtonElement_get, _handleToggle, handleToggle_fn, _checkSubmenuHasExpanded, checkSubmenuHasExpanded_fn, _handleFocusOut, handleFocusOut_fn, _handleKeyDown, handleKeyDown_fn, _getItem, getItem_fn, _getTabItem, getTabItem_fn, _setTabItem, setTabItem_fn, _selectItem, selectItem_fn; import { MediaStateReceiverAttributes } from "../constants.js"; import { globalThis, document } from "../utils/server-safe-globals.js"; import { computePosition } from "../utils/anchor-utils.js"; import { observeResize, unobserveResize } from "../utils/resize-observer.js"; import { ToggleEvent, InvokeEvent } from "../utils/events.js"; import { getActiveElement, containsComposedNode, closestComposedNode, insertCSSRule, getMediaController, getAttributeMediaController, getDocumentOrShadowRoot } from "../utils/element-utils.js"; function createMenuItem({ type, text, value, checked }) { const item = document.createElement( "media-chrome-menu-item" ); item.type = type != null ? type : ""; item.part.add("menu-item"); if (type) item.part.add(type); item.value = value; item.checked = checked; const label = document.createElement("span"); label.textContent = text; item.append(label); return item; } function createIndicator(el, name) { let customIndicator = el.querySelector(`:scope > [slot="${name}"]`); if ((customIndicator == null ? void 0 : customIndicator.nodeName) == "SLOT") customIndicator = customIndicator.assignedElements({ flatten: true })[0]; if (customIndicator) { customIndicator = customIndicator.cloneNode(true); return customIndicator; } const fallbackIndicator = el.shadowRoot.querySelector( `[name="${name}"] > svg` ); if (fallbackIndicator) { return fallbackIndicator.cloneNode(true); } return ""; } const template = document.createElement("template"); template.innerHTML = /*html*/ `
`; const Attributes = { STYLE: "style", HIDDEN: "hidden", DISABLED: "disabled", ANCHOR: "anchor" }; class MediaChromeMenu extends globalThis.HTMLElement { constructor() { super(); __privateAdd(this, _handleSlotChange); /** * Sets the layout style for the menu. * It can be a row or column layout. e.g. playback-rate-menu */ __privateAdd(this, _updateLayoutStyle); __privateAdd(this, _handleInvoke); __privateAdd(this, _handleOpen); __privateAdd(this, _handleClosed); /** * Updates the popover menu position based on the anchor element. * @param {number} [menuWidth] */ __privateAdd(this, _positionMenu); /** * Resize this menu to fit the submenu. * @param {boolean} animate */ __privateAdd(this, _resizeMenu); __privateAdd(this, _handleClick); __privateAdd(this, _backButtonElement); /** * Handle the toggle event of submenus. * Closes all other open submenus when opening a submenu. * Resizes this menu to fit the submenu. * * @param {ToggleEvent} event */ __privateAdd(this, _handleToggle); /** * Check if any submenu is expanded and update the container class accordingly. * When the CSS :has() selector is supported, this can be done with CSS only. */ __privateAdd(this, _checkSubmenuHasExpanded); __privateAdd(this, _handleFocusOut); __privateAdd(this, _handleKeyDown); __privateAdd(this, _getItem); __privateAdd(this, _getTabItem); __privateAdd(this, _setTabItem); __privateAdd(this, _selectItem); __privateAdd(this, _mediaController, null); __privateAdd(this, _previouslyFocused, null); __privateAdd(this, _invokerElement, null); __privateAdd(this, _previousItems, /* @__PURE__ */ new Set()); __privateAdd(this, _mutationObserver, void 0); __privateAdd(this, _isPopover, false); __privateAdd(this, _cssRule, null); /** * Fires an event when a menu item is added or removed. * This is needed to update the description slot of an ancestor menu item. */ __privateAdd(this, _handleMenuItems, () => { const previousItems = __privateGet(this, _previousItems); const currentItems = new Set(this.items); for (const item of previousItems) { if (!currentItems.has(item)) { this.dispatchEvent(new CustomEvent("removemenuitem", { detail: item })); } } for (const item of currentItems) { if (!previousItems.has(item)) { this.dispatchEvent(new CustomEvent("addmenuitem", { detail: item })); } } __privateSet(this, _previousItems, currentItems); }); __privateAdd(this, _handleBoundsResize, () => { __privateMethod(this, _positionMenu, positionMenu_fn).call(this); __privateMethod(this, _resizeMenu, resizeMenu_fn).call(this, false); }); __privateAdd(this, _handleMenuResize, () => { __privateMethod(this, _positionMenu, positionMenu_fn).call(this); }); if (!this.shadowRoot) { this.attachShadow({ mode: "open" }); this.nativeEl = this.constructor.template.content.cloneNode(true); this.shadowRoot.append(this.nativeEl); } this.container = this.shadowRoot.querySelector("#container"); this.defaultSlot = this.shadowRoot.querySelector( "slot:not([name])" ); this.shadowRoot.addEventListener("slotchange", this); __privateSet(this, _mutationObserver, new MutationObserver(__privateGet(this, _handleMenuItems))); __privateGet(this, _mutationObserver).observe(this.defaultSlot, { childList: true }); } static get observedAttributes() { return [ Attributes.DISABLED, Attributes.HIDDEN, Attributes.STYLE, Attributes.ANCHOR, MediaStateReceiverAttributes.MEDIA_CONTROLLER ]; } static formatMenuItemText(text) { return text; } enable() { this.addEventListener("click", this); this.addEventListener("focusout", this); this.addEventListener("keydown", this); this.addEventListener("invoke", this); this.addEventListener("toggle", this); } disable() { this.removeEventListener("click", this); this.removeEventListener("focusout", this); this.removeEventListener("keyup", this); this.removeEventListener("invoke", this); this.removeEventListener("toggle", this); } handleEvent(event) { switch (event.type) { case "slotchange": __privateMethod(this, _handleSlotChange, handleSlotChange_fn).call(this, event); break; case "invoke": __privateMethod(this, _handleInvoke, handleInvoke_fn).call(this, event); break; case "click": __privateMethod(this, _handleClick, handleClick_fn).call(this, event); break; case "toggle": __privateMethod(this, _handleToggle, handleToggle_fn).call(this, event); break; case "focusout": __privateMethod(this, _handleFocusOut, handleFocusOut_fn).call(this, event); break; case "keydown": __privateMethod(this, _handleKeyDown, handleKeyDown_fn).call(this, event); break; } } connectedCallback() { var _a, _b; __privateSet(this, _cssRule, insertCSSRule(this.shadowRoot, ":host")); __privateMethod(this, _updateLayoutStyle, updateLayoutStyle_fn).call(this); if (!this.hasAttribute("disabled")) { this.enable(); } if (!this.role) { this.role = "menu"; } __privateSet(this, _mediaController, getAttributeMediaController(this)); (_b = (_a = __privateGet(this, _mediaController)) == null ? void 0 : _a.associateElement) == null ? void 0 : _b.call(_a, this); if (!this.hidden) { observeResize(getBoundsElement(this), __privateGet(this, _handleBoundsResize)); observeResize(this, __privateGet(this, _handleMenuResize)); } } disconnectedCallback() { var _a, _b; unobserveResize(getBoundsElement(this), __privateGet(this, _handleBoundsResize)); unobserveResize(this, __privateGet(this, _handleMenuResize)); this.disable(); (_b = (_a = __privateGet(this, _mediaController)) == null ? void 0 : _a.unassociateElement) == null ? void 0 : _b.call(_a, this); __privateSet(this, _mediaController, null); } attributeChangedCallback(attrName, oldValue, newValue) { var _a, _b, _c, _d; if (attrName === Attributes.HIDDEN && newValue !== oldValue) { if (!__privateGet(this, _isPopover)) __privateSet(this, _isPopover, true); if (this.hidden) { __privateMethod(this, _handleClosed, handleClosed_fn).call(this); } else { __privateMethod(this, _handleOpen, handleOpen_fn).call(this); } this.dispatchEvent( new ToggleEvent({ oldState: this.hidden ? "open" : "closed", newState: this.hidden ? "closed" : "open", bubbles: true }) ); } else if (attrName === MediaStateReceiverAttributes.MEDIA_CONTROLLER) { if (oldValue) { (_b = (_a = __privateGet(this, _mediaController)) == null ? void 0 : _a.unassociateElement) == null ? void 0 : _b.call(_a, this); __privateSet(this, _mediaController, null); } if (newValue && this.isConnected) { __privateSet(this, _mediaController, getAttributeMediaController(this)); (_d = (_c = __privateGet(this, _mediaController)) == null ? void 0 : _c.associateElement) == null ? void 0 : _d.call(_c, this); } } else if (attrName === Attributes.DISABLED && newValue !== oldValue) { if (newValue == null) { this.enable(); } else { this.disable(); } } else if (attrName === Attributes.STYLE && newValue !== oldValue) { __privateMethod(this, _updateLayoutStyle, updateLayoutStyle_fn).call(this); } } formatMenuItemText(text, data) { return this.constructor.formatMenuItemText(text, data); } get anchor() { return this.getAttribute("anchor"); } set anchor(value) { this.setAttribute("anchor", `${value}`); } /** * Returns the anchor element when it is a floating menu. */ get anchorElement() { var _a; if (this.anchor) { return (_a = getDocumentOrShadowRoot(this)) == null ? void 0 : _a.querySelector(`#${this.anchor}`); } return null; } /** * Returns the menu items. */ get items() { return this.defaultSlot.assignedElements({ flatten: true }).filter(isMenuItem); } get radioGroupItems() { return this.items.filter((item) => item.role === "menuitemradio"); } get checkedItems() { return this.items.filter((item) => item.checked); } get value() { var _a, _b; return (_b = (_a = this.checkedItems[0]) == null ? void 0 : _a.value) != null ? _b : ""; } set value(newValue) { const item = this.items.find((item2) => item2.value === newValue); if (!item) return; __privateMethod(this, _selectItem, selectItem_fn).call(this, item); } focus() { __privateSet(this, _previouslyFocused, getActiveElement()); if (this.items.length) { __privateMethod(this, _setTabItem, setTabItem_fn).call(this, this.items[0]); this.items[0].focus(); return; } const focusable = this.querySelector( '[autofocus], [tabindex]:not([tabindex="-1"]), [role="menu"]' ); focusable == null ? void 0 : focusable.focus(); } handleSelect(event) { var _a; const item = __privateMethod(this, _getItem, getItem_fn).call(this, event); if (!item) return; __privateMethod(this, _selectItem, selectItem_fn).call(this, item, item.type === "checkbox"); if (__privateGet(this, _invokerElement) && !this.hidden) { (_a = __privateGet(this, _previouslyFocused)) == null ? void 0 : _a.focus(); this.hidden = true; } } get keysUsed() { return [ "Enter", "Escape", "Tab", " ", "ArrowDown", "ArrowUp", "Home", "End" ]; } handleMove(event) { var _a, _b; const { key } = event; const items = this.items; const currentItem = (_b = (_a = __privateMethod(this, _getItem, getItem_fn).call(this, event)) != null ? _a : __privateMethod(this, _getTabItem, getTabItem_fn).call(this)) != null ? _b : items[0]; const currentIndex = items.indexOf(currentItem); let index = Math.max(0, currentIndex); if (key === "ArrowDown") { index++; } else if (key === "ArrowUp") { index--; } else if (event.key === "Home") { index = 0; } else if (event.key === "End") { index = items.length - 1; } if (index < 0) { index = items.length - 1; } if (index > items.length - 1) { index = 0; } __privateMethod(this, _setTabItem, setTabItem_fn).call(this, items[index]); items[index].focus(); } } _mediaController = new WeakMap(); _previouslyFocused = new WeakMap(); _invokerElement = new WeakMap(); _previousItems = new WeakMap(); _mutationObserver = new WeakMap(); _isPopover = new WeakMap(); _cssRule = new WeakMap(); _handleSlotChange = new WeakSet(); handleSlotChange_fn = function(event) { const slot = event.target; for (const node of slot.assignedNodes({ flatten: true })) { if (node.nodeType === 3 && node.textContent.trim() === "") { node.remove(); } } if (["header", "title"].includes(slot.name)) { const header = this.shadowRoot.querySelector( 'slot[name="header"]' ); header.hidden = slot.assignedNodes().length === 0; } if (!slot.name) { __privateGet(this, _handleMenuItems).call(this); } }; _handleMenuItems = new WeakMap(); _updateLayoutStyle = new WeakSet(); updateLayoutStyle_fn = function() { var _a; const layoutRowStyle = this.shadowRoot.querySelector("#layout-row"); const menuLayout = (_a = getComputedStyle(this).getPropertyValue("--media-menu-layout")) == null ? void 0 : _a.trim(); layoutRowStyle.setAttribute("media", menuLayout === "row" ? "" : "width:0"); }; _handleInvoke = new WeakSet(); handleInvoke_fn = function(event) { __privateSet(this, _invokerElement, event.relatedTarget); if (!containsComposedNode(this, event.relatedTarget)) { this.hidden = !this.hidden; } }; _handleOpen = new WeakSet(); handleOpen_fn = function() { var _a; (_a = __privateGet(this, _invokerElement)) == null ? void 0 : _a.setAttribute("aria-expanded", "true"); this.addEventListener("transitionend", () => this.focus(), { once: true }); observeResize(getBoundsElement(this), __privateGet(this, _handleBoundsResize)); observeResize(this, __privateGet(this, _handleMenuResize)); }; _handleClosed = new WeakSet(); handleClosed_fn = function() { var _a; (_a = __privateGet(this, _invokerElement)) == null ? void 0 : _a.setAttribute("aria-expanded", "false"); unobserveResize(getBoundsElement(this), __privateGet(this, _handleBoundsResize)); unobserveResize(this, __privateGet(this, _handleMenuResize)); }; _handleBoundsResize = new WeakMap(); _handleMenuResize = new WeakMap(); _positionMenu = new WeakSet(); positionMenu_fn = function(menuWidth) { if (this.hasAttribute("mediacontroller") && !this.anchor) return; if (this.hidden || !this.anchorElement) return; const { x, y } = computePosition({ anchor: this.anchorElement, floating: this, placement: "top-start" }); menuWidth != null ? menuWidth : menuWidth = this.offsetWidth; const bounds = getBoundsElement(this); const boundsRect = bounds.getBoundingClientRect(); const right = boundsRect.width - x - menuWidth; const bottom = boundsRect.height - y - this.offsetHeight; const { style } = __privateGet(this, _cssRule); style.setProperty("position", "absolute"); style.setProperty("right", `${Math.max(0, right)}px`); style.setProperty("--_menu-bottom", `${bottom}px`); const computedStyle = getComputedStyle(this); const isBottomCalc = style.getPropertyValue("--_menu-bottom") === computedStyle.bottom; const realBottom = isBottomCalc ? bottom : parseFloat(computedStyle.bottom); const maxHeight = boundsRect.height - realBottom - parseFloat(computedStyle.marginBottom); this.style.setProperty("--_menu-max-height", `${maxHeight}px`); }; _resizeMenu = new WeakSet(); resizeMenu_fn = function(animate) { const expandedMenuItem = this.querySelector( '[role="menuitem"][aria-haspopup][aria-expanded="true"]' ); const expandedSubmenu = expandedMenuItem == null ? void 0 : expandedMenuItem.querySelector( '[role="menu"]' ); const { style } = __privateGet(this, _cssRule); if (!animate) { style.setProperty("--media-menu-transition-in", "none"); } if (expandedSubmenu) { const height = expandedSubmenu.offsetHeight; const width = Math.max( expandedSubmenu.offsetWidth, expandedMenuItem.offsetWidth ); this.style.setProperty("min-width", `${width}px`); this.style.setProperty("min-height", `${height}px`); __privateMethod(this, _positionMenu, positionMenu_fn).call(this, width); } else { this.style.removeProperty("min-width"); this.style.removeProperty("min-height"); __privateMethod(this, _positionMenu, positionMenu_fn).call(this); } style.removeProperty("--media-menu-transition-in"); }; _handleClick = new WeakSet(); handleClick_fn = function(event) { var _a; event.stopPropagation(); if (event.composedPath().includes(__privateGet(this, _backButtonElement, backButtonElement_get))) { (_a = __privateGet(this, _previouslyFocused)) == null ? void 0 : _a.focus(); this.hidden = true; return; } const item = __privateMethod(this, _getItem, getItem_fn).call(this, event); if (!item || item.hasAttribute("disabled")) return; __privateMethod(this, _setTabItem, setTabItem_fn).call(this, item); this.handleSelect(event); }; _backButtonElement = new WeakSet(); backButtonElement_get = function() { var _a; const headerSlot = this.shadowRoot.querySelector( 'slot[name="header"]' ); return (_a = headerSlot.assignedElements({ flatten: true })) == null ? void 0 : _a.find( (el) => el.matches('button[part~="back"]') ); }; _handleToggle = new WeakSet(); handleToggle_fn = function(event) { if (event.target === this) return; __privateMethod(this, _checkSubmenuHasExpanded, checkSubmenuHasExpanded_fn).call(this); const menuItemsWithSubmenu = Array.from( this.querySelectorAll('[role="menuitem"][aria-haspopup]') ); for (const item of menuItemsWithSubmenu) { if (item.invokeTargetElement == event.target) continue; if (event.newState == "open" && item.getAttribute("aria-expanded") == "true" && !item.invokeTargetElement.hidden) { item.invokeTargetElement.dispatchEvent( new InvokeEvent({ relatedTarget: item }) ); } } for (const item of menuItemsWithSubmenu) { item.setAttribute("aria-expanded", `${!item.submenuElement.hidden}`); } __privateMethod(this, _resizeMenu, resizeMenu_fn).call(this, true); }; _checkSubmenuHasExpanded = new WeakSet(); checkSubmenuHasExpanded_fn = function() { const selector = '[role="menuitem"] > [role="menu"]:not([hidden])'; const expandedMenuItem = this.querySelector(selector); this.container.classList.toggle("has-expanded", !!expandedMenuItem); }; _handleFocusOut = new WeakSet(); handleFocusOut_fn = function(event) { var _a; if (!containsComposedNode(this, event.relatedTarget)) { if (__privateGet(this, _isPopover)) { (_a = __privateGet(this, _previouslyFocused)) == null ? void 0 : _a.focus(); } if (__privateGet(this, _invokerElement) && __privateGet(this, _invokerElement) !== event.relatedTarget && !this.hidden) { this.hidden = true; } } }; _handleKeyDown = new WeakSet(); handleKeyDown_fn = function(event) { var _a, _b, _c, _d, _e; const { key, ctrlKey, altKey, metaKey } = event; if (ctrlKey || altKey || metaKey) { return; } if (!this.keysUsed.includes(key)) { return; } event.preventDefault(); event.stopPropagation(); if (key === "Tab") { if (__privateGet(this, _isPopover)) { this.hidden = true; return; } if (event.shiftKey) { (_b = (_a = this.previousElementSibling) == null ? void 0 : _a.focus) == null ? void 0 : _b.call(_a); } else { (_d = (_c = this.nextElementSibling) == null ? void 0 : _c.focus) == null ? void 0 : _d.call(_c); } this.blur(); } else if (key === "Escape") { (_e = __privateGet(this, _previouslyFocused)) == null ? void 0 : _e.focus(); if (__privateGet(this, _isPopover)) { this.hidden = true; } } else if (key === "Enter" || key === " ") { this.handleSelect(event); } else { this.handleMove(event); } }; _getItem = new WeakSet(); getItem_fn = function(event) { return event.composedPath().find((el) => { return ["menuitemradio", "menuitemcheckbox"].includes( el.role ); }); }; _getTabItem = new WeakSet(); getTabItem_fn = function() { return this.items.find((item) => item.tabIndex === 0); }; _setTabItem = new WeakSet(); setTabItem_fn = function(tabItem) { for (const item of this.items) { item.tabIndex = item === tabItem ? 0 : -1; } }; _selectItem = new WeakSet(); selectItem_fn = function(item, toggle) { const oldCheckedItems = [...this.checkedItems]; if (item.type === "radio") { this.radioGroupItems.forEach((el) => el.checked = false); } if (toggle) { item.checked = !item.checked; } else { item.checked = true; } if (this.checkedItems.some((opt, i) => opt != oldCheckedItems[i])) { this.dispatchEvent( new Event("change", { bubbles: true, composed: true }) ); } }; MediaChromeMenu.template = template; function isMenuItem(element) { return ["menuitem", "menuitemradio", "menuitemcheckbox"].includes( element == null ? void 0 : element.role ); } function getBoundsElement(host) { var _a; return (_a = host.getAttribute("bounds") ? closestComposedNode(host, `#${host.getAttribute("bounds")}`) : getMediaController(host) || host.parentElement) != null ? _a : host; } if (!globalThis.customElements.get("media-chrome-menu")) { globalThis.customElements.define("media-chrome-menu", MediaChromeMenu); } var media_chrome_menu_default = MediaChromeMenu; export { Attributes, MediaChromeMenu, createIndicator, createMenuItem, media_chrome_menu_default as default };