@duskmoon-dev/el-menu 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -0
- package/dist/cjs/index.js +25 -16
- package/dist/cjs/index.js.map +3 -3
- package/dist/cjs/register.js +25 -16
- package/dist/cjs/register.js.map +3 -3
- package/dist/esm/index.js +21 -12
- package/dist/esm/index.js.map +3 -3
- package/dist/esm/register.js +21 -12
- package/dist/esm/register.js.map +3 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/el-dm-menu.d.ts +1 -1
- package/dist/types/el-dm-menu.d.ts.map +1 -1
- package/package.json +6 -5
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// src/el-dm-menu.ts
|
|
2
|
-
import { BaseElement, css } from "@duskmoon-dev/el-
|
|
2
|
+
import { BaseElement, css } from "@duskmoon-dev/el-base";
|
|
3
|
+
import { css as navigationCSS } from "@duskmoon-dev/core/components/navigation";
|
|
4
|
+
var coreStyles = navigationCSS.replace(/@layer\s+components\s*\{/, "").replace(/\}\s*$/, "");
|
|
3
5
|
var menuStyles = css`
|
|
4
6
|
:host {
|
|
5
7
|
display: inline-block;
|
|
@@ -10,7 +12,11 @@ var menuStyles = css`
|
|
|
10
12
|
display: none !important;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
/* Import core navigation styles */
|
|
16
|
+
${coreStyles}
|
|
17
|
+
|
|
18
|
+
/* Override core .menu for dropdown behavior */
|
|
19
|
+
.menu {
|
|
14
20
|
position: absolute;
|
|
15
21
|
z-index: 1000;
|
|
16
22
|
min-width: 160px;
|
|
@@ -31,9 +37,12 @@ var menuStyles = css`
|
|
|
31
37
|
visibility 150ms ease,
|
|
32
38
|
transform 150ms ease;
|
|
33
39
|
font-family: inherit;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
flex-wrap: nowrap;
|
|
42
|
+
gap: 0;
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
.menu
|
|
45
|
+
.menu.visible {
|
|
37
46
|
opacity: 1;
|
|
38
47
|
visibility: visible;
|
|
39
48
|
transform: scale(1);
|
|
@@ -246,11 +255,11 @@ class ElDmMenu extends BaseElement {
|
|
|
246
255
|
this.hide();
|
|
247
256
|
}
|
|
248
257
|
_updatePosition() {
|
|
249
|
-
const
|
|
250
|
-
if (!
|
|
258
|
+
const menuEl = this.shadowRoot?.querySelector(".menu");
|
|
259
|
+
if (!menuEl)
|
|
251
260
|
return;
|
|
252
261
|
requestAnimationFrame(() => {
|
|
253
|
-
const rect =
|
|
262
|
+
const rect = menuEl.getBoundingClientRect();
|
|
254
263
|
const viewportHeight = window.innerHeight;
|
|
255
264
|
const viewportWidth = window.innerWidth;
|
|
256
265
|
let finalPlacement = this.placement;
|
|
@@ -265,7 +274,7 @@ class ElDmMenu extends BaseElement {
|
|
|
265
274
|
finalPlacement = "right";
|
|
266
275
|
}
|
|
267
276
|
if (finalPlacement !== this.placement) {
|
|
268
|
-
|
|
277
|
+
menuEl.className = `menu placement-${finalPlacement}${this.open ? " visible" : ""}`;
|
|
269
278
|
}
|
|
270
279
|
});
|
|
271
280
|
}
|
|
@@ -305,9 +314,9 @@ class ElDmMenu extends BaseElement {
|
|
|
305
314
|
}
|
|
306
315
|
update() {
|
|
307
316
|
super.update?.();
|
|
308
|
-
const
|
|
309
|
-
if (
|
|
310
|
-
|
|
317
|
+
const menuEl = this.shadowRoot?.querySelector(".menu");
|
|
318
|
+
if (menuEl) {
|
|
319
|
+
menuEl.classList.toggle("visible", this.open);
|
|
311
320
|
if (this.open) {
|
|
312
321
|
this._updatePosition();
|
|
313
322
|
}
|
|
@@ -318,7 +327,7 @@ class ElDmMenu extends BaseElement {
|
|
|
318
327
|
return `
|
|
319
328
|
<slot name="trigger"></slot>
|
|
320
329
|
<div
|
|
321
|
-
class="menu
|
|
330
|
+
class="menu ${placementClass}${this.open ? " visible" : ""}"
|
|
322
331
|
part="menu"
|
|
323
332
|
role="menu"
|
|
324
333
|
aria-hidden="${!this.open}"
|
|
@@ -474,5 +483,5 @@ export {
|
|
|
474
483
|
ElDmMenu
|
|
475
484
|
};
|
|
476
485
|
|
|
477
|
-
//# debugId=
|
|
486
|
+
//# debugId=CF26230AFE7269CB64756E2164756E21
|
|
478
487
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/el-dm-menu.ts", "../../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * DuskMoon Menu Element\n *\n * A dropdown menu component with menu items supporting keyboard navigation.\n *\n * @element el-dm-menu\n *\n * @attr {boolean} open - Whether the menu is open\n * @attr {string} anchor - Element ref or CSS selector for the anchor element\n * @attr {string} placement - Menu placement: top, bottom, left, right, top-start, top-end, bottom-start, bottom-end\n *\n * @slot - Default slot for menu items\n *\n * @csspart menu - The menu container\n * @csspart items - The menu items wrapper\n *\n * @fires open - Fired when the menu opens\n * @fires close - Fired when the menu closes\n * @fires select - Fired when a menu item is selected (detail: { value: string })\n */\n\nimport { BaseElement, css } from '@duskmoon-dev/el-core';\n\nexport type MenuPlacement =\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'top-start'\n | 'top-end'\n | 'bottom-start'\n | 'bottom-end';\n\nconst menuStyles = css`\n :host {\n display: inline-block;\n position: relative;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n .menu-container {\n position: absolute;\n z-index: 1000;\n min-width: 160px;\n max-width: 320px;\n background-color: var(--color-surface, #ffffff);\n border: 1px solid var(--color-outline-variant, #e0e0e0);\n border-radius: 0.5rem;\n box-shadow:\n 0 4px 6px -1px rgba(0, 0, 0, 0.1),\n 0 2px 4px -1px rgba(0, 0, 0, 0.06);\n padding: 0.25rem 0;\n opacity: 0;\n visibility: hidden;\n transform: scale(0.95);\n transform-origin: top left;\n transition:\n opacity 150ms ease,\n visibility 150ms ease,\n transform 150ms ease;\n font-family: inherit;\n }\n\n .menu-container.visible {\n opacity: 1;\n visibility: visible;\n transform: scale(1);\n }\n\n /* Placement styles */\n .placement-bottom,\n .placement-bottom-start,\n .placement-bottom-end {\n top: 100%;\n margin-top: 0.25rem;\n transform-origin: top;\n }\n\n .placement-bottom {\n left: 50%;\n transform: translateX(-50%) scale(0.95);\n }\n\n .placement-bottom.visible {\n transform: translateX(-50%) scale(1);\n }\n\n .placement-bottom-start {\n left: 0;\n }\n\n .placement-bottom-end {\n right: 0;\n left: auto;\n }\n\n .placement-top,\n .placement-top-start,\n .placement-top-end {\n bottom: 100%;\n margin-bottom: 0.25rem;\n transform-origin: bottom;\n }\n\n .placement-top {\n left: 50%;\n transform: translateX(-50%) scale(0.95);\n }\n\n .placement-top.visible {\n transform: translateX(-50%) scale(1);\n }\n\n .placement-top-start {\n left: 0;\n }\n\n .placement-top-end {\n right: 0;\n left: auto;\n }\n\n .placement-left {\n right: 100%;\n top: 0;\n margin-right: 0.25rem;\n transform-origin: right;\n }\n\n .placement-right {\n left: 100%;\n top: 0;\n margin-left: 0.25rem;\n transform-origin: left;\n }\n\n .items-wrapper {\n display: flex;\n flex-direction: column;\n }\n\n ::slotted(el-dm-menu-item) {\n display: block;\n }\n\n ::slotted([role='separator']) {\n height: 1px;\n background-color: var(--color-outline-variant, #e0e0e0);\n margin: 0.25rem 0;\n }\n`;\n\nexport class ElDmMenu extends BaseElement {\n static properties = {\n open: { type: Boolean, reflect: true, default: false },\n anchor: { type: String, reflect: true },\n placement: { type: String, reflect: true, default: 'bottom-start' },\n };\n\n declare open: boolean;\n declare anchor: string;\n declare placement: MenuPlacement;\n\n private _anchorElement: HTMLElement | null = null;\n private _focusedIndex = -1;\n private _boundHandleDocumentClick: (e: MouseEvent) => void;\n private _boundHandleKeydown: (e: KeyboardEvent) => void;\n\n constructor() {\n super();\n this.attachStyles(menuStyles);\n this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n this._boundHandleKeydown = this._handleKeydown.bind(this);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this._setupAnchor();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback?.();\n this._removeGlobalListeners();\n }\n\n private _setupAnchor(): void {\n if (this.anchor) {\n this._anchorElement =\n document.querySelector(this.anchor) ||\n this.closest(this.anchor) ||\n document.getElementById(this.anchor);\n }\n }\n\n private _addGlobalListeners(): void {\n document.addEventListener('click', this._boundHandleDocumentClick);\n document.addEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _removeGlobalListeners(): void {\n document.removeEventListener('click', this._boundHandleDocumentClick);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _handleDocumentClick(event: MouseEvent): void {\n const target = event.target as Node;\n if (!this.contains(target) && !this._anchorElement?.contains(target)) {\n this.hide();\n }\n }\n\n private _handleKeydown(event: KeyboardEvent): void {\n if (!this.open) return;\n\n const items = this._getMenuItems();\n if (items.length === 0) return;\n\n switch (event.key) {\n case 'Escape':\n event.preventDefault();\n this.hide();\n this._anchorElement?.focus();\n break;\n\n case 'ArrowDown':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(this._focusedIndex, 1, items));\n break;\n\n case 'ArrowUp':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(this._focusedIndex, -1, items));\n break;\n\n case 'Home':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(-1, 1, items));\n break;\n\n case 'End':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(items.length, -1, items));\n break;\n\n case 'Enter':\n case ' ':\n event.preventDefault();\n if (this._focusedIndex >= 0 && this._focusedIndex < items.length) {\n const item = items[this._focusedIndex] as ElDmMenuItem;\n if (!item.disabled) {\n this._selectItem(item);\n }\n }\n break;\n\n case 'Tab':\n this.hide();\n break;\n }\n }\n\n private _getMenuItems(): Element[] {\n const slot = this.shadowRoot?.querySelector('slot');\n if (!slot) return [];\n return slot\n .assignedElements()\n .filter((el) => el.tagName === 'EL-DM-MENU-ITEM' && !el.hasAttribute('hidden'));\n }\n\n private _getNextFocusableIndex(\n currentIndex: number,\n direction: number,\n items: Element[],\n ): number {\n let nextIndex = currentIndex + direction;\n\n while (nextIndex >= 0 && nextIndex < items.length) {\n const item = items[nextIndex] as ElDmMenuItem;\n if (!item.disabled) {\n return nextIndex;\n }\n nextIndex += direction;\n }\n\n // Wrap around\n if (direction > 0) {\n return this._getNextFocusableIndex(-1, 1, items);\n } else {\n return this._getNextFocusableIndex(items.length, -1, items);\n }\n }\n\n private _focusItem(index: number): void {\n const items = this._getMenuItems();\n if (index < 0 || index >= items.length) return;\n\n // Remove focus from previous item\n if (this._focusedIndex >= 0 && this._focusedIndex < items.length) {\n (items[this._focusedIndex] as ElDmMenuItem).focused = false;\n }\n\n // Focus new item\n this._focusedIndex = index;\n const item = items[index] as ElDmMenuItem;\n item.focused = true;\n item.focus();\n }\n\n private _selectItem(item: ElDmMenuItem): void {\n this.emit('select', { value: item.value || item.textContent?.trim() });\n this.hide();\n }\n\n private _updatePosition(): void {\n const menuContainer = this.shadowRoot?.querySelector('.menu-container') as HTMLElement;\n if (!menuContainer) return;\n\n // Check if menu would go off screen and flip if necessary\n requestAnimationFrame(() => {\n const rect = menuContainer.getBoundingClientRect();\n const viewportHeight = window.innerHeight;\n const viewportWidth = window.innerWidth;\n\n let finalPlacement = this.placement;\n\n // Flip vertical placement if needed\n if (finalPlacement.startsWith('bottom') && rect.bottom > viewportHeight) {\n finalPlacement = finalPlacement.replace('bottom', 'top') as MenuPlacement;\n } else if (finalPlacement.startsWith('top') && rect.top < 0) {\n finalPlacement = finalPlacement.replace('top', 'bottom') as MenuPlacement;\n }\n\n // Flip horizontal placement if needed\n if (finalPlacement === 'right' && rect.right > viewportWidth) {\n finalPlacement = 'left';\n } else if (finalPlacement === 'left' && rect.left < 0) {\n finalPlacement = 'right';\n }\n\n // Update class if flipped\n if (finalPlacement !== this.placement) {\n menuContainer.className = `menu-container placement-${finalPlacement}${this.open ? ' visible' : ''}`;\n }\n });\n }\n\n /**\n * Show the menu\n */\n show(): void {\n if (this.open) return;\n this.open = true;\n this._focusedIndex = -1;\n this._addGlobalListeners();\n this._updatePosition();\n this.emit('open');\n\n // Focus first item after menu opens\n requestAnimationFrame(() => {\n const items = this._getMenuItems();\n if (items.length > 0) {\n this._focusItem(this._getNextFocusableIndex(-1, 1, items));\n }\n });\n }\n\n /**\n * Hide the menu\n */\n hide(): void {\n if (!this.open) return;\n this.open = false;\n this._focusedIndex = -1;\n this._removeGlobalListeners();\n this.emit('close');\n\n // Clear focus from items\n const items = this._getMenuItems();\n items.forEach((item) => {\n (item as ElDmMenuItem).focused = false;\n });\n }\n\n /**\n * Toggle the menu open/closed\n */\n toggle(): void {\n if (this.open) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n update(): void {\n super.update?.();\n const menuContainer = this.shadowRoot?.querySelector('.menu-container');\n if (menuContainer) {\n menuContainer.classList.toggle('visible', this.open);\n if (this.open) {\n this._updatePosition();\n }\n }\n }\n\n render(): string {\n const placementClass = `placement-${this.placement || 'bottom-start'}`;\n\n return `\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu-container ${placementClass}${this.open ? ' visible' : ''}\"\n part=\"menu\"\n role=\"menu\"\n aria-hidden=\"${!this.open}\"\n >\n <div class=\"items-wrapper\" part=\"items\">\n <slot></slot>\n </div>\n </div>\n `;\n }\n}\n\n/**\n * DuskMoon Menu Item Element\n *\n * A menu item component for use within el-dm-menu.\n *\n * @element el-dm-menu-item\n *\n * @attr {string} value - The value associated with this item\n * @attr {boolean} disabled - Whether the item is disabled\n * @attr {boolean} focused - Whether the item is currently focused (internal)\n *\n * @slot - Default slot for item content\n * @slot icon - Slot for an icon before the content\n *\n * @csspart item - The menu item container\n * @csspart icon - The icon wrapper\n * @csspart content - The content wrapper\n */\n\nconst menuItemStyles = css`\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n .menu-item {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 1rem;\n cursor: pointer;\n color: var(--color-on-surface, #1f1f1f);\n background-color: transparent;\n border: none;\n width: 100%;\n text-align: left;\n font-size: 0.875rem;\n line-height: 1.25rem;\n font-family: inherit;\n transition:\n background-color 150ms ease,\n color 150ms ease;\n outline: none;\n }\n\n .menu-item:hover:not(.disabled) {\n background-color: var(--color-surface-container-highest, #f5f5f5);\n }\n\n .menu-item:focus:not(.disabled),\n .menu-item.focused:not(.disabled) {\n background-color: var(--color-surface-container-highest, #f5f5f5);\n outline: 2px solid var(--color-primary, #6750a4);\n outline-offset: -2px;\n }\n\n .menu-item.disabled {\n cursor: not-allowed;\n opacity: 0.5;\n color: var(--color-on-surface-variant, #49454f);\n }\n\n .icon-wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1.25rem;\n height: 1.25rem;\n flex-shrink: 0;\n }\n\n .icon-wrapper:empty {\n display: none;\n }\n\n .content-wrapper {\n flex: 1;\n min-width: 0;\n }\n\n ::slotted(svg),\n ::slotted(img) {\n width: 1.25rem;\n height: 1.25rem;\n }\n`;\n\nexport class ElDmMenuItem extends BaseElement {\n static properties = {\n value: { type: String, reflect: true },\n disabled: { type: Boolean, reflect: true, default: false },\n focused: { type: Boolean, reflect: true, default: false },\n };\n\n declare value: string;\n declare disabled: boolean;\n declare focused: boolean;\n\n constructor() {\n super();\n this.attachStyles(menuItemStyles);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.setAttribute('role', 'menuitem');\n this.setAttribute('tabindex', '-1');\n this.addEventListener('click', this._handleClick.bind(this));\n }\n\n private _handleClick(event: MouseEvent): void {\n if (this.disabled) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n // Dispatch select event on parent menu\n const menu = this.closest('el-dm-menu') as ElDmMenu;\n if (menu) {\n menu.dispatchEvent(\n new CustomEvent('select', {\n bubbles: true,\n composed: true,\n detail: { value: this.value || this.textContent?.trim() },\n }),\n );\n menu.hide();\n }\n }\n\n update(): void {\n super.update?.();\n this.setAttribute('aria-disabled', String(this.disabled));\n\n const itemEl = this.shadowRoot?.querySelector('.menu-item');\n if (itemEl) {\n itemEl.classList.toggle('disabled', this.disabled);\n itemEl.classList.toggle('focused', this.focused);\n }\n }\n\n render(): string {\n return `\n <div\n class=\"menu-item${this.disabled ? ' disabled' : ''}${this.focused ? ' focused' : ''}\"\n part=\"item\"\n >\n <span class=\"icon-wrapper\" part=\"icon\">\n <slot name=\"icon\"></slot>\n </span>\n <span class=\"content-wrapper\" part=\"content\">\n <slot></slot>\n </span>\n </div>\n `;\n }\n}\n",
|
|
5
|
+
"/**\n * DuskMoon Menu Element\n *\n * A dropdown menu component with menu items supporting keyboard navigation.\n *\n * @element el-dm-menu\n *\n * @attr {boolean} open - Whether the menu is open\n * @attr {string} anchor - Element ref or CSS selector for the anchor element\n * @attr {string} placement - Menu placement: top, bottom, left, right, top-start, top-end, bottom-start, bottom-end\n *\n * @slot - Default slot for menu items\n *\n * @csspart menu - The menu container\n * @csspart items - The menu items wrapper\n *\n * @fires open - Fired when the menu opens\n * @fires close - Fired when the menu closes\n * @fires select - Fired when a menu item is selected (detail: { value: string })\n */\n\nimport { BaseElement, css } from '@duskmoon-dev/el-base';\nimport { css as navigationCSS } from '@duskmoon-dev/core/components/navigation';\n\nexport type MenuPlacement =\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'top-start'\n | 'top-end'\n | 'bottom-start'\n | 'bottom-end';\n\n// Strip @layer wrapper for Shadow DOM compatibility\nconst coreStyles = navigationCSS.replace(/@layer\\s+components\\s*\\{/, '').replace(/\\}\\s*$/, '');\n\nconst menuStyles = css`\n :host {\n display: inline-block;\n position: relative;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n /* Import core navigation styles */\n ${coreStyles}\n\n /* Override core .menu for dropdown behavior */\n .menu {\n position: absolute;\n z-index: 1000;\n min-width: 160px;\n max-width: 320px;\n background-color: var(--color-surface, #ffffff);\n border: 1px solid var(--color-outline-variant, #e0e0e0);\n border-radius: 0.5rem;\n box-shadow:\n 0 4px 6px -1px rgba(0, 0, 0, 0.1),\n 0 2px 4px -1px rgba(0, 0, 0, 0.06);\n padding: 0.25rem 0;\n opacity: 0;\n visibility: hidden;\n transform: scale(0.95);\n transform-origin: top left;\n transition:\n opacity 150ms ease,\n visibility 150ms ease,\n transform 150ms ease;\n font-family: inherit;\n flex-direction: column;\n flex-wrap: nowrap;\n gap: 0;\n }\n\n .menu.visible {\n opacity: 1;\n visibility: visible;\n transform: scale(1);\n }\n\n /* Placement styles */\n .placement-bottom,\n .placement-bottom-start,\n .placement-bottom-end {\n top: 100%;\n margin-top: 0.25rem;\n transform-origin: top;\n }\n\n .placement-bottom {\n left: 50%;\n transform: translateX(-50%) scale(0.95);\n }\n\n .placement-bottom.visible {\n transform: translateX(-50%) scale(1);\n }\n\n .placement-bottom-start {\n left: 0;\n }\n\n .placement-bottom-end {\n right: 0;\n left: auto;\n }\n\n .placement-top,\n .placement-top-start,\n .placement-top-end {\n bottom: 100%;\n margin-bottom: 0.25rem;\n transform-origin: bottom;\n }\n\n .placement-top {\n left: 50%;\n transform: translateX(-50%) scale(0.95);\n }\n\n .placement-top.visible {\n transform: translateX(-50%) scale(1);\n }\n\n .placement-top-start {\n left: 0;\n }\n\n .placement-top-end {\n right: 0;\n left: auto;\n }\n\n .placement-left {\n right: 100%;\n top: 0;\n margin-right: 0.25rem;\n transform-origin: right;\n }\n\n .placement-right {\n left: 100%;\n top: 0;\n margin-left: 0.25rem;\n transform-origin: left;\n }\n\n .items-wrapper {\n display: flex;\n flex-direction: column;\n }\n\n ::slotted(el-dm-menu-item) {\n display: block;\n }\n\n ::slotted([role='separator']) {\n height: 1px;\n background-color: var(--color-outline-variant, #e0e0e0);\n margin: 0.25rem 0;\n }\n`;\n\nexport class ElDmMenu extends BaseElement {\n static properties = {\n open: { type: Boolean, reflect: true, default: false },\n anchor: { type: String, reflect: true },\n placement: { type: String, reflect: true, default: 'bottom-start' },\n };\n\n declare open: boolean;\n declare anchor: string;\n declare placement: MenuPlacement;\n\n private _anchorElement: HTMLElement | null = null;\n private _focusedIndex = -1;\n private _boundHandleDocumentClick: (e: MouseEvent) => void;\n private _boundHandleKeydown: (e: KeyboardEvent) => void;\n\n constructor() {\n super();\n this.attachStyles(menuStyles);\n this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n this._boundHandleKeydown = this._handleKeydown.bind(this);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this._setupAnchor();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback?.();\n this._removeGlobalListeners();\n }\n\n private _setupAnchor(): void {\n if (this.anchor) {\n this._anchorElement =\n document.querySelector(this.anchor) ||\n this.closest(this.anchor) ||\n document.getElementById(this.anchor);\n }\n }\n\n private _addGlobalListeners(): void {\n document.addEventListener('click', this._boundHandleDocumentClick);\n document.addEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _removeGlobalListeners(): void {\n document.removeEventListener('click', this._boundHandleDocumentClick);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _handleDocumentClick(event: MouseEvent): void {\n const target = event.target as Node;\n if (!this.contains(target) && !this._anchorElement?.contains(target)) {\n this.hide();\n }\n }\n\n private _handleKeydown(event: KeyboardEvent): void {\n if (!this.open) return;\n\n const items = this._getMenuItems();\n if (items.length === 0) return;\n\n switch (event.key) {\n case 'Escape':\n event.preventDefault();\n this.hide();\n this._anchorElement?.focus();\n break;\n\n case 'ArrowDown':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(this._focusedIndex, 1, items));\n break;\n\n case 'ArrowUp':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(this._focusedIndex, -1, items));\n break;\n\n case 'Home':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(-1, 1, items));\n break;\n\n case 'End':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(items.length, -1, items));\n break;\n\n case 'Enter':\n case ' ':\n event.preventDefault();\n if (this._focusedIndex >= 0 && this._focusedIndex < items.length) {\n const item = items[this._focusedIndex] as ElDmMenuItem;\n if (!item.disabled) {\n this._selectItem(item);\n }\n }\n break;\n\n case 'Tab':\n this.hide();\n break;\n }\n }\n\n private _getMenuItems(): Element[] {\n const slot = this.shadowRoot?.querySelector('slot');\n if (!slot) return [];\n return slot\n .assignedElements()\n .filter((el) => el.tagName === 'EL-DM-MENU-ITEM' && !el.hasAttribute('hidden'));\n }\n\n private _getNextFocusableIndex(\n currentIndex: number,\n direction: number,\n items: Element[],\n ): number {\n let nextIndex = currentIndex + direction;\n\n while (nextIndex >= 0 && nextIndex < items.length) {\n const item = items[nextIndex] as ElDmMenuItem;\n if (!item.disabled) {\n return nextIndex;\n }\n nextIndex += direction;\n }\n\n // Wrap around\n if (direction > 0) {\n return this._getNextFocusableIndex(-1, 1, items);\n } else {\n return this._getNextFocusableIndex(items.length, -1, items);\n }\n }\n\n private _focusItem(index: number): void {\n const items = this._getMenuItems();\n if (index < 0 || index >= items.length) return;\n\n // Remove focus from previous item\n if (this._focusedIndex >= 0 && this._focusedIndex < items.length) {\n (items[this._focusedIndex] as ElDmMenuItem).focused = false;\n }\n\n // Focus new item\n this._focusedIndex = index;\n const item = items[index] as ElDmMenuItem;\n item.focused = true;\n item.focus();\n }\n\n private _selectItem(item: ElDmMenuItem): void {\n this.emit('select', { value: item.value || item.textContent?.trim() });\n this.hide();\n }\n\n private _updatePosition(): void {\n const menuEl = this.shadowRoot?.querySelector('.menu') as HTMLElement;\n if (!menuEl) return;\n\n // Check if menu would go off screen and flip if necessary\n requestAnimationFrame(() => {\n const rect = menuEl.getBoundingClientRect();\n const viewportHeight = window.innerHeight;\n const viewportWidth = window.innerWidth;\n\n let finalPlacement = this.placement;\n\n // Flip vertical placement if needed\n if (finalPlacement.startsWith('bottom') && rect.bottom > viewportHeight) {\n finalPlacement = finalPlacement.replace('bottom', 'top') as MenuPlacement;\n } else if (finalPlacement.startsWith('top') && rect.top < 0) {\n finalPlacement = finalPlacement.replace('top', 'bottom') as MenuPlacement;\n }\n\n // Flip horizontal placement if needed\n if (finalPlacement === 'right' && rect.right > viewportWidth) {\n finalPlacement = 'left';\n } else if (finalPlacement === 'left' && rect.left < 0) {\n finalPlacement = 'right';\n }\n\n // Update class if flipped\n if (finalPlacement !== this.placement) {\n menuEl.className = `menu placement-${finalPlacement}${this.open ? ' visible' : ''}`;\n }\n });\n }\n\n /**\n * Show the menu\n */\n show(): void {\n if (this.open) return;\n this.open = true;\n this._focusedIndex = -1;\n this._addGlobalListeners();\n this._updatePosition();\n this.emit('open');\n\n // Focus first item after menu opens\n requestAnimationFrame(() => {\n const items = this._getMenuItems();\n if (items.length > 0) {\n this._focusItem(this._getNextFocusableIndex(-1, 1, items));\n }\n });\n }\n\n /**\n * Hide the menu\n */\n hide(): void {\n if (!this.open) return;\n this.open = false;\n this._focusedIndex = -1;\n this._removeGlobalListeners();\n this.emit('close');\n\n // Clear focus from items\n const items = this._getMenuItems();\n items.forEach((item) => {\n (item as ElDmMenuItem).focused = false;\n });\n }\n\n /**\n * Toggle the menu open/closed\n */\n toggle(): void {\n if (this.open) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n update(): void {\n super.update?.();\n const menuEl = this.shadowRoot?.querySelector('.menu');\n if (menuEl) {\n menuEl.classList.toggle('visible', this.open);\n if (this.open) {\n this._updatePosition();\n }\n }\n }\n\n render(): string {\n const placementClass = `placement-${this.placement || 'bottom-start'}`;\n\n return `\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu ${placementClass}${this.open ? ' visible' : ''}\"\n part=\"menu\"\n role=\"menu\"\n aria-hidden=\"${!this.open}\"\n >\n <div class=\"items-wrapper\" part=\"items\">\n <slot></slot>\n </div>\n </div>\n `;\n }\n}\n\n/**\n * DuskMoon Menu Item Element\n *\n * A menu item component for use within el-dm-menu.\n *\n * @element el-dm-menu-item\n *\n * @attr {string} value - The value associated with this item\n * @attr {boolean} disabled - Whether the item is disabled\n * @attr {boolean} focused - Whether the item is currently focused (internal)\n *\n * @slot - Default slot for item content\n * @slot icon - Slot for an icon before the content\n *\n * @csspart item - The menu item container\n * @csspart icon - The icon wrapper\n * @csspart content - The content wrapper\n */\n\nconst menuItemStyles = css`\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n .menu-item {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 1rem;\n cursor: pointer;\n color: var(--color-on-surface, #1f1f1f);\n background-color: transparent;\n border: none;\n width: 100%;\n text-align: left;\n font-size: 0.875rem;\n line-height: 1.25rem;\n font-family: inherit;\n transition:\n background-color 150ms ease,\n color 150ms ease;\n outline: none;\n }\n\n .menu-item:hover:not(.disabled) {\n background-color: var(--color-surface-container-highest, #f5f5f5);\n }\n\n .menu-item:focus:not(.disabled),\n .menu-item.focused:not(.disabled) {\n background-color: var(--color-surface-container-highest, #f5f5f5);\n outline: 2px solid var(--color-primary, #6750a4);\n outline-offset: -2px;\n }\n\n .menu-item.disabled {\n cursor: not-allowed;\n opacity: 0.5;\n color: var(--color-on-surface-variant, #49454f);\n }\n\n .icon-wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1.25rem;\n height: 1.25rem;\n flex-shrink: 0;\n }\n\n .icon-wrapper:empty {\n display: none;\n }\n\n .content-wrapper {\n flex: 1;\n min-width: 0;\n }\n\n ::slotted(svg),\n ::slotted(img) {\n width: 1.25rem;\n height: 1.25rem;\n }\n`;\n\nexport class ElDmMenuItem extends BaseElement {\n static properties = {\n value: { type: String, reflect: true },\n disabled: { type: Boolean, reflect: true, default: false },\n focused: { type: Boolean, reflect: true, default: false },\n };\n\n declare value: string;\n declare disabled: boolean;\n declare focused: boolean;\n\n constructor() {\n super();\n this.attachStyles(menuItemStyles);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.setAttribute('role', 'menuitem');\n this.setAttribute('tabindex', '-1');\n this.addEventListener('click', this._handleClick.bind(this));\n }\n\n private _handleClick(event: MouseEvent): void {\n if (this.disabled) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n // Dispatch select event on parent menu\n const menu = this.closest('el-dm-menu') as ElDmMenu;\n if (menu) {\n menu.dispatchEvent(\n new CustomEvent('select', {\n bubbles: true,\n composed: true,\n detail: { value: this.value || this.textContent?.trim() },\n }),\n );\n menu.hide();\n }\n }\n\n update(): void {\n super.update?.();\n this.setAttribute('aria-disabled', String(this.disabled));\n\n const itemEl = this.shadowRoot?.querySelector('.menu-item');\n if (itemEl) {\n itemEl.classList.toggle('disabled', this.disabled);\n itemEl.classList.toggle('focused', this.focused);\n }\n }\n\n render(): string {\n return `\n <div\n class=\"menu-item${this.disabled ? ' disabled' : ''}${this.focused ? ' focused' : ''}\"\n part=\"item\"\n >\n <span class=\"icon-wrapper\" part=\"icon\">\n <slot name=\"icon\"></slot>\n </span>\n <span class=\"content-wrapper\" part=\"content\">\n <slot></slot>\n </span>\n </div>\n `;\n }\n}\n",
|
|
6
6
|
"/**\n * @duskmoon-dev/el-menu\n *\n * DuskMoon Menu custom elements\n */\n\nimport { ElDmMenu } from './el-dm-menu.js';\nimport { ElDmMenuItem } from './el-dm-menu.js';\n\nexport { ElDmMenu, ElDmMenuItem };\n\n/**\n * Register the el-dm-menu and el-dm-menu-item custom elements\n *\n * @example\n * ```ts\n * import { register } from '@duskmoon-dev/el-menu';\n * register();\n * ```\n */\nexport function register(): void {\n if (!customElements.get('el-dm-menu')) {\n customElements.define('el-dm-menu', ElDmMenu);\n }\n if (!customElements.get('el-dm-menu-item')) {\n customElements.define('el-dm-menu-item', ElDmMenuItem);\n }\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": ";AAqBA;
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "CF26230AFE7269CB64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
package/dist/esm/register.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// src/el-dm-menu.ts
|
|
2
|
-
import { BaseElement, css } from "@duskmoon-dev/el-
|
|
2
|
+
import { BaseElement, css } from "@duskmoon-dev/el-base";
|
|
3
|
+
import { css as navigationCSS } from "@duskmoon-dev/core/components/navigation";
|
|
4
|
+
var coreStyles = navigationCSS.replace(/@layer\s+components\s*\{/, "").replace(/\}\s*$/, "");
|
|
3
5
|
var menuStyles = css`
|
|
4
6
|
:host {
|
|
5
7
|
display: inline-block;
|
|
@@ -10,7 +12,11 @@ var menuStyles = css`
|
|
|
10
12
|
display: none !important;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
/* Import core navigation styles */
|
|
16
|
+
${coreStyles}
|
|
17
|
+
|
|
18
|
+
/* Override core .menu for dropdown behavior */
|
|
19
|
+
.menu {
|
|
14
20
|
position: absolute;
|
|
15
21
|
z-index: 1000;
|
|
16
22
|
min-width: 160px;
|
|
@@ -31,9 +37,12 @@ var menuStyles = css`
|
|
|
31
37
|
visibility 150ms ease,
|
|
32
38
|
transform 150ms ease;
|
|
33
39
|
font-family: inherit;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
flex-wrap: nowrap;
|
|
42
|
+
gap: 0;
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
.menu
|
|
45
|
+
.menu.visible {
|
|
37
46
|
opacity: 1;
|
|
38
47
|
visibility: visible;
|
|
39
48
|
transform: scale(1);
|
|
@@ -246,11 +255,11 @@ class ElDmMenu extends BaseElement {
|
|
|
246
255
|
this.hide();
|
|
247
256
|
}
|
|
248
257
|
_updatePosition() {
|
|
249
|
-
const
|
|
250
|
-
if (!
|
|
258
|
+
const menuEl = this.shadowRoot?.querySelector(".menu");
|
|
259
|
+
if (!menuEl)
|
|
251
260
|
return;
|
|
252
261
|
requestAnimationFrame(() => {
|
|
253
|
-
const rect =
|
|
262
|
+
const rect = menuEl.getBoundingClientRect();
|
|
254
263
|
const viewportHeight = window.innerHeight;
|
|
255
264
|
const viewportWidth = window.innerWidth;
|
|
256
265
|
let finalPlacement = this.placement;
|
|
@@ -265,7 +274,7 @@ class ElDmMenu extends BaseElement {
|
|
|
265
274
|
finalPlacement = "right";
|
|
266
275
|
}
|
|
267
276
|
if (finalPlacement !== this.placement) {
|
|
268
|
-
|
|
277
|
+
menuEl.className = `menu placement-${finalPlacement}${this.open ? " visible" : ""}`;
|
|
269
278
|
}
|
|
270
279
|
});
|
|
271
280
|
}
|
|
@@ -305,9 +314,9 @@ class ElDmMenu extends BaseElement {
|
|
|
305
314
|
}
|
|
306
315
|
update() {
|
|
307
316
|
super.update?.();
|
|
308
|
-
const
|
|
309
|
-
if (
|
|
310
|
-
|
|
317
|
+
const menuEl = this.shadowRoot?.querySelector(".menu");
|
|
318
|
+
if (menuEl) {
|
|
319
|
+
menuEl.classList.toggle("visible", this.open);
|
|
311
320
|
if (this.open) {
|
|
312
321
|
this._updatePosition();
|
|
313
322
|
}
|
|
@@ -318,7 +327,7 @@ class ElDmMenu extends BaseElement {
|
|
|
318
327
|
return `
|
|
319
328
|
<slot name="trigger"></slot>
|
|
320
329
|
<div
|
|
321
|
-
class="menu
|
|
330
|
+
class="menu ${placementClass}${this.open ? " visible" : ""}"
|
|
322
331
|
part="menu"
|
|
323
332
|
role="menu"
|
|
324
333
|
aria-hidden="${!this.open}"
|
|
@@ -472,5 +481,5 @@ function register() {
|
|
|
472
481
|
// src/register.ts
|
|
473
482
|
register();
|
|
474
483
|
|
|
475
|
-
//# debugId=
|
|
484
|
+
//# debugId=1061BBF74117BF7F64756E2164756E21
|
|
476
485
|
//# sourceMappingURL=register.js.map
|
package/dist/esm/register.js.map
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/el-dm-menu.ts", "../../src/index.ts", "../../src/register.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * DuskMoon Menu Element\n *\n * A dropdown menu component with menu items supporting keyboard navigation.\n *\n * @element el-dm-menu\n *\n * @attr {boolean} open - Whether the menu is open\n * @attr {string} anchor - Element ref or CSS selector for the anchor element\n * @attr {string} placement - Menu placement: top, bottom, left, right, top-start, top-end, bottom-start, bottom-end\n *\n * @slot - Default slot for menu items\n *\n * @csspart menu - The menu container\n * @csspart items - The menu items wrapper\n *\n * @fires open - Fired when the menu opens\n * @fires close - Fired when the menu closes\n * @fires select - Fired when a menu item is selected (detail: { value: string })\n */\n\nimport { BaseElement, css } from '@duskmoon-dev/el-core';\n\nexport type MenuPlacement =\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'top-start'\n | 'top-end'\n | 'bottom-start'\n | 'bottom-end';\n\nconst menuStyles = css`\n :host {\n display: inline-block;\n position: relative;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n .menu-container {\n position: absolute;\n z-index: 1000;\n min-width: 160px;\n max-width: 320px;\n background-color: var(--color-surface, #ffffff);\n border: 1px solid var(--color-outline-variant, #e0e0e0);\n border-radius: 0.5rem;\n box-shadow:\n 0 4px 6px -1px rgba(0, 0, 0, 0.1),\n 0 2px 4px -1px rgba(0, 0, 0, 0.06);\n padding: 0.25rem 0;\n opacity: 0;\n visibility: hidden;\n transform: scale(0.95);\n transform-origin: top left;\n transition:\n opacity 150ms ease,\n visibility 150ms ease,\n transform 150ms ease;\n font-family: inherit;\n }\n\n .menu-container.visible {\n opacity: 1;\n visibility: visible;\n transform: scale(1);\n }\n\n /* Placement styles */\n .placement-bottom,\n .placement-bottom-start,\n .placement-bottom-end {\n top: 100%;\n margin-top: 0.25rem;\n transform-origin: top;\n }\n\n .placement-bottom {\n left: 50%;\n transform: translateX(-50%) scale(0.95);\n }\n\n .placement-bottom.visible {\n transform: translateX(-50%) scale(1);\n }\n\n .placement-bottom-start {\n left: 0;\n }\n\n .placement-bottom-end {\n right: 0;\n left: auto;\n }\n\n .placement-top,\n .placement-top-start,\n .placement-top-end {\n bottom: 100%;\n margin-bottom: 0.25rem;\n transform-origin: bottom;\n }\n\n .placement-top {\n left: 50%;\n transform: translateX(-50%) scale(0.95);\n }\n\n .placement-top.visible {\n transform: translateX(-50%) scale(1);\n }\n\n .placement-top-start {\n left: 0;\n }\n\n .placement-top-end {\n right: 0;\n left: auto;\n }\n\n .placement-left {\n right: 100%;\n top: 0;\n margin-right: 0.25rem;\n transform-origin: right;\n }\n\n .placement-right {\n left: 100%;\n top: 0;\n margin-left: 0.25rem;\n transform-origin: left;\n }\n\n .items-wrapper {\n display: flex;\n flex-direction: column;\n }\n\n ::slotted(el-dm-menu-item) {\n display: block;\n }\n\n ::slotted([role='separator']) {\n height: 1px;\n background-color: var(--color-outline-variant, #e0e0e0);\n margin: 0.25rem 0;\n }\n`;\n\nexport class ElDmMenu extends BaseElement {\n static properties = {\n open: { type: Boolean, reflect: true, default: false },\n anchor: { type: String, reflect: true },\n placement: { type: String, reflect: true, default: 'bottom-start' },\n };\n\n declare open: boolean;\n declare anchor: string;\n declare placement: MenuPlacement;\n\n private _anchorElement: HTMLElement | null = null;\n private _focusedIndex = -1;\n private _boundHandleDocumentClick: (e: MouseEvent) => void;\n private _boundHandleKeydown: (e: KeyboardEvent) => void;\n\n constructor() {\n super();\n this.attachStyles(menuStyles);\n this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n this._boundHandleKeydown = this._handleKeydown.bind(this);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this._setupAnchor();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback?.();\n this._removeGlobalListeners();\n }\n\n private _setupAnchor(): void {\n if (this.anchor) {\n this._anchorElement =\n document.querySelector(this.anchor) ||\n this.closest(this.anchor) ||\n document.getElementById(this.anchor);\n }\n }\n\n private _addGlobalListeners(): void {\n document.addEventListener('click', this._boundHandleDocumentClick);\n document.addEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _removeGlobalListeners(): void {\n document.removeEventListener('click', this._boundHandleDocumentClick);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _handleDocumentClick(event: MouseEvent): void {\n const target = event.target as Node;\n if (!this.contains(target) && !this._anchorElement?.contains(target)) {\n this.hide();\n }\n }\n\n private _handleKeydown(event: KeyboardEvent): void {\n if (!this.open) return;\n\n const items = this._getMenuItems();\n if (items.length === 0) return;\n\n switch (event.key) {\n case 'Escape':\n event.preventDefault();\n this.hide();\n this._anchorElement?.focus();\n break;\n\n case 'ArrowDown':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(this._focusedIndex, 1, items));\n break;\n\n case 'ArrowUp':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(this._focusedIndex, -1, items));\n break;\n\n case 'Home':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(-1, 1, items));\n break;\n\n case 'End':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(items.length, -1, items));\n break;\n\n case 'Enter':\n case ' ':\n event.preventDefault();\n if (this._focusedIndex >= 0 && this._focusedIndex < items.length) {\n const item = items[this._focusedIndex] as ElDmMenuItem;\n if (!item.disabled) {\n this._selectItem(item);\n }\n }\n break;\n\n case 'Tab':\n this.hide();\n break;\n }\n }\n\n private _getMenuItems(): Element[] {\n const slot = this.shadowRoot?.querySelector('slot');\n if (!slot) return [];\n return slot\n .assignedElements()\n .filter((el) => el.tagName === 'EL-DM-MENU-ITEM' && !el.hasAttribute('hidden'));\n }\n\n private _getNextFocusableIndex(\n currentIndex: number,\n direction: number,\n items: Element[],\n ): number {\n let nextIndex = currentIndex + direction;\n\n while (nextIndex >= 0 && nextIndex < items.length) {\n const item = items[nextIndex] as ElDmMenuItem;\n if (!item.disabled) {\n return nextIndex;\n }\n nextIndex += direction;\n }\n\n // Wrap around\n if (direction > 0) {\n return this._getNextFocusableIndex(-1, 1, items);\n } else {\n return this._getNextFocusableIndex(items.length, -1, items);\n }\n }\n\n private _focusItem(index: number): void {\n const items = this._getMenuItems();\n if (index < 0 || index >= items.length) return;\n\n // Remove focus from previous item\n if (this._focusedIndex >= 0 && this._focusedIndex < items.length) {\n (items[this._focusedIndex] as ElDmMenuItem).focused = false;\n }\n\n // Focus new item\n this._focusedIndex = index;\n const item = items[index] as ElDmMenuItem;\n item.focused = true;\n item.focus();\n }\n\n private _selectItem(item: ElDmMenuItem): void {\n this.emit('select', { value: item.value || item.textContent?.trim() });\n this.hide();\n }\n\n private _updatePosition(): void {\n const menuContainer = this.shadowRoot?.querySelector('.menu-container') as HTMLElement;\n if (!menuContainer) return;\n\n // Check if menu would go off screen and flip if necessary\n requestAnimationFrame(() => {\n const rect = menuContainer.getBoundingClientRect();\n const viewportHeight = window.innerHeight;\n const viewportWidth = window.innerWidth;\n\n let finalPlacement = this.placement;\n\n // Flip vertical placement if needed\n if (finalPlacement.startsWith('bottom') && rect.bottom > viewportHeight) {\n finalPlacement = finalPlacement.replace('bottom', 'top') as MenuPlacement;\n } else if (finalPlacement.startsWith('top') && rect.top < 0) {\n finalPlacement = finalPlacement.replace('top', 'bottom') as MenuPlacement;\n }\n\n // Flip horizontal placement if needed\n if (finalPlacement === 'right' && rect.right > viewportWidth) {\n finalPlacement = 'left';\n } else if (finalPlacement === 'left' && rect.left < 0) {\n finalPlacement = 'right';\n }\n\n // Update class if flipped\n if (finalPlacement !== this.placement) {\n menuContainer.className = `menu-container placement-${finalPlacement}${this.open ? ' visible' : ''}`;\n }\n });\n }\n\n /**\n * Show the menu\n */\n show(): void {\n if (this.open) return;\n this.open = true;\n this._focusedIndex = -1;\n this._addGlobalListeners();\n this._updatePosition();\n this.emit('open');\n\n // Focus first item after menu opens\n requestAnimationFrame(() => {\n const items = this._getMenuItems();\n if (items.length > 0) {\n this._focusItem(this._getNextFocusableIndex(-1, 1, items));\n }\n });\n }\n\n /**\n * Hide the menu\n */\n hide(): void {\n if (!this.open) return;\n this.open = false;\n this._focusedIndex = -1;\n this._removeGlobalListeners();\n this.emit('close');\n\n // Clear focus from items\n const items = this._getMenuItems();\n items.forEach((item) => {\n (item as ElDmMenuItem).focused = false;\n });\n }\n\n /**\n * Toggle the menu open/closed\n */\n toggle(): void {\n if (this.open) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n update(): void {\n super.update?.();\n const menuContainer = this.shadowRoot?.querySelector('.menu-container');\n if (menuContainer) {\n menuContainer.classList.toggle('visible', this.open);\n if (this.open) {\n this._updatePosition();\n }\n }\n }\n\n render(): string {\n const placementClass = `placement-${this.placement || 'bottom-start'}`;\n\n return `\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu-container ${placementClass}${this.open ? ' visible' : ''}\"\n part=\"menu\"\n role=\"menu\"\n aria-hidden=\"${!this.open}\"\n >\n <div class=\"items-wrapper\" part=\"items\">\n <slot></slot>\n </div>\n </div>\n `;\n }\n}\n\n/**\n * DuskMoon Menu Item Element\n *\n * A menu item component for use within el-dm-menu.\n *\n * @element el-dm-menu-item\n *\n * @attr {string} value - The value associated with this item\n * @attr {boolean} disabled - Whether the item is disabled\n * @attr {boolean} focused - Whether the item is currently focused (internal)\n *\n * @slot - Default slot for item content\n * @slot icon - Slot for an icon before the content\n *\n * @csspart item - The menu item container\n * @csspart icon - The icon wrapper\n * @csspart content - The content wrapper\n */\n\nconst menuItemStyles = css`\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n .menu-item {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 1rem;\n cursor: pointer;\n color: var(--color-on-surface, #1f1f1f);\n background-color: transparent;\n border: none;\n width: 100%;\n text-align: left;\n font-size: 0.875rem;\n line-height: 1.25rem;\n font-family: inherit;\n transition:\n background-color 150ms ease,\n color 150ms ease;\n outline: none;\n }\n\n .menu-item:hover:not(.disabled) {\n background-color: var(--color-surface-container-highest, #f5f5f5);\n }\n\n .menu-item:focus:not(.disabled),\n .menu-item.focused:not(.disabled) {\n background-color: var(--color-surface-container-highest, #f5f5f5);\n outline: 2px solid var(--color-primary, #6750a4);\n outline-offset: -2px;\n }\n\n .menu-item.disabled {\n cursor: not-allowed;\n opacity: 0.5;\n color: var(--color-on-surface-variant, #49454f);\n }\n\n .icon-wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1.25rem;\n height: 1.25rem;\n flex-shrink: 0;\n }\n\n .icon-wrapper:empty {\n display: none;\n }\n\n .content-wrapper {\n flex: 1;\n min-width: 0;\n }\n\n ::slotted(svg),\n ::slotted(img) {\n width: 1.25rem;\n height: 1.25rem;\n }\n`;\n\nexport class ElDmMenuItem extends BaseElement {\n static properties = {\n value: { type: String, reflect: true },\n disabled: { type: Boolean, reflect: true, default: false },\n focused: { type: Boolean, reflect: true, default: false },\n };\n\n declare value: string;\n declare disabled: boolean;\n declare focused: boolean;\n\n constructor() {\n super();\n this.attachStyles(menuItemStyles);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.setAttribute('role', 'menuitem');\n this.setAttribute('tabindex', '-1');\n this.addEventListener('click', this._handleClick.bind(this));\n }\n\n private _handleClick(event: MouseEvent): void {\n if (this.disabled) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n // Dispatch select event on parent menu\n const menu = this.closest('el-dm-menu') as ElDmMenu;\n if (menu) {\n menu.dispatchEvent(\n new CustomEvent('select', {\n bubbles: true,\n composed: true,\n detail: { value: this.value || this.textContent?.trim() },\n }),\n );\n menu.hide();\n }\n }\n\n update(): void {\n super.update?.();\n this.setAttribute('aria-disabled', String(this.disabled));\n\n const itemEl = this.shadowRoot?.querySelector('.menu-item');\n if (itemEl) {\n itemEl.classList.toggle('disabled', this.disabled);\n itemEl.classList.toggle('focused', this.focused);\n }\n }\n\n render(): string {\n return `\n <div\n class=\"menu-item${this.disabled ? ' disabled' : ''}${this.focused ? ' focused' : ''}\"\n part=\"item\"\n >\n <span class=\"icon-wrapper\" part=\"icon\">\n <slot name=\"icon\"></slot>\n </span>\n <span class=\"content-wrapper\" part=\"content\">\n <slot></slot>\n </span>\n </div>\n `;\n }\n}\n",
|
|
5
|
+
"/**\n * DuskMoon Menu Element\n *\n * A dropdown menu component with menu items supporting keyboard navigation.\n *\n * @element el-dm-menu\n *\n * @attr {boolean} open - Whether the menu is open\n * @attr {string} anchor - Element ref or CSS selector for the anchor element\n * @attr {string} placement - Menu placement: top, bottom, left, right, top-start, top-end, bottom-start, bottom-end\n *\n * @slot - Default slot for menu items\n *\n * @csspart menu - The menu container\n * @csspart items - The menu items wrapper\n *\n * @fires open - Fired when the menu opens\n * @fires close - Fired when the menu closes\n * @fires select - Fired when a menu item is selected (detail: { value: string })\n */\n\nimport { BaseElement, css } from '@duskmoon-dev/el-base';\nimport { css as navigationCSS } from '@duskmoon-dev/core/components/navigation';\n\nexport type MenuPlacement =\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'top-start'\n | 'top-end'\n | 'bottom-start'\n | 'bottom-end';\n\n// Strip @layer wrapper for Shadow DOM compatibility\nconst coreStyles = navigationCSS.replace(/@layer\\s+components\\s*\\{/, '').replace(/\\}\\s*$/, '');\n\nconst menuStyles = css`\n :host {\n display: inline-block;\n position: relative;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n /* Import core navigation styles */\n ${coreStyles}\n\n /* Override core .menu for dropdown behavior */\n .menu {\n position: absolute;\n z-index: 1000;\n min-width: 160px;\n max-width: 320px;\n background-color: var(--color-surface, #ffffff);\n border: 1px solid var(--color-outline-variant, #e0e0e0);\n border-radius: 0.5rem;\n box-shadow:\n 0 4px 6px -1px rgba(0, 0, 0, 0.1),\n 0 2px 4px -1px rgba(0, 0, 0, 0.06);\n padding: 0.25rem 0;\n opacity: 0;\n visibility: hidden;\n transform: scale(0.95);\n transform-origin: top left;\n transition:\n opacity 150ms ease,\n visibility 150ms ease,\n transform 150ms ease;\n font-family: inherit;\n flex-direction: column;\n flex-wrap: nowrap;\n gap: 0;\n }\n\n .menu.visible {\n opacity: 1;\n visibility: visible;\n transform: scale(1);\n }\n\n /* Placement styles */\n .placement-bottom,\n .placement-bottom-start,\n .placement-bottom-end {\n top: 100%;\n margin-top: 0.25rem;\n transform-origin: top;\n }\n\n .placement-bottom {\n left: 50%;\n transform: translateX(-50%) scale(0.95);\n }\n\n .placement-bottom.visible {\n transform: translateX(-50%) scale(1);\n }\n\n .placement-bottom-start {\n left: 0;\n }\n\n .placement-bottom-end {\n right: 0;\n left: auto;\n }\n\n .placement-top,\n .placement-top-start,\n .placement-top-end {\n bottom: 100%;\n margin-bottom: 0.25rem;\n transform-origin: bottom;\n }\n\n .placement-top {\n left: 50%;\n transform: translateX(-50%) scale(0.95);\n }\n\n .placement-top.visible {\n transform: translateX(-50%) scale(1);\n }\n\n .placement-top-start {\n left: 0;\n }\n\n .placement-top-end {\n right: 0;\n left: auto;\n }\n\n .placement-left {\n right: 100%;\n top: 0;\n margin-right: 0.25rem;\n transform-origin: right;\n }\n\n .placement-right {\n left: 100%;\n top: 0;\n margin-left: 0.25rem;\n transform-origin: left;\n }\n\n .items-wrapper {\n display: flex;\n flex-direction: column;\n }\n\n ::slotted(el-dm-menu-item) {\n display: block;\n }\n\n ::slotted([role='separator']) {\n height: 1px;\n background-color: var(--color-outline-variant, #e0e0e0);\n margin: 0.25rem 0;\n }\n`;\n\nexport class ElDmMenu extends BaseElement {\n static properties = {\n open: { type: Boolean, reflect: true, default: false },\n anchor: { type: String, reflect: true },\n placement: { type: String, reflect: true, default: 'bottom-start' },\n };\n\n declare open: boolean;\n declare anchor: string;\n declare placement: MenuPlacement;\n\n private _anchorElement: HTMLElement | null = null;\n private _focusedIndex = -1;\n private _boundHandleDocumentClick: (e: MouseEvent) => void;\n private _boundHandleKeydown: (e: KeyboardEvent) => void;\n\n constructor() {\n super();\n this.attachStyles(menuStyles);\n this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n this._boundHandleKeydown = this._handleKeydown.bind(this);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this._setupAnchor();\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback?.();\n this._removeGlobalListeners();\n }\n\n private _setupAnchor(): void {\n if (this.anchor) {\n this._anchorElement =\n document.querySelector(this.anchor) ||\n this.closest(this.anchor) ||\n document.getElementById(this.anchor);\n }\n }\n\n private _addGlobalListeners(): void {\n document.addEventListener('click', this._boundHandleDocumentClick);\n document.addEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _removeGlobalListeners(): void {\n document.removeEventListener('click', this._boundHandleDocumentClick);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n }\n\n private _handleDocumentClick(event: MouseEvent): void {\n const target = event.target as Node;\n if (!this.contains(target) && !this._anchorElement?.contains(target)) {\n this.hide();\n }\n }\n\n private _handleKeydown(event: KeyboardEvent): void {\n if (!this.open) return;\n\n const items = this._getMenuItems();\n if (items.length === 0) return;\n\n switch (event.key) {\n case 'Escape':\n event.preventDefault();\n this.hide();\n this._anchorElement?.focus();\n break;\n\n case 'ArrowDown':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(this._focusedIndex, 1, items));\n break;\n\n case 'ArrowUp':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(this._focusedIndex, -1, items));\n break;\n\n case 'Home':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(-1, 1, items));\n break;\n\n case 'End':\n event.preventDefault();\n this._focusItem(this._getNextFocusableIndex(items.length, -1, items));\n break;\n\n case 'Enter':\n case ' ':\n event.preventDefault();\n if (this._focusedIndex >= 0 && this._focusedIndex < items.length) {\n const item = items[this._focusedIndex] as ElDmMenuItem;\n if (!item.disabled) {\n this._selectItem(item);\n }\n }\n break;\n\n case 'Tab':\n this.hide();\n break;\n }\n }\n\n private _getMenuItems(): Element[] {\n const slot = this.shadowRoot?.querySelector('slot');\n if (!slot) return [];\n return slot\n .assignedElements()\n .filter((el) => el.tagName === 'EL-DM-MENU-ITEM' && !el.hasAttribute('hidden'));\n }\n\n private _getNextFocusableIndex(\n currentIndex: number,\n direction: number,\n items: Element[],\n ): number {\n let nextIndex = currentIndex + direction;\n\n while (nextIndex >= 0 && nextIndex < items.length) {\n const item = items[nextIndex] as ElDmMenuItem;\n if (!item.disabled) {\n return nextIndex;\n }\n nextIndex += direction;\n }\n\n // Wrap around\n if (direction > 0) {\n return this._getNextFocusableIndex(-1, 1, items);\n } else {\n return this._getNextFocusableIndex(items.length, -1, items);\n }\n }\n\n private _focusItem(index: number): void {\n const items = this._getMenuItems();\n if (index < 0 || index >= items.length) return;\n\n // Remove focus from previous item\n if (this._focusedIndex >= 0 && this._focusedIndex < items.length) {\n (items[this._focusedIndex] as ElDmMenuItem).focused = false;\n }\n\n // Focus new item\n this._focusedIndex = index;\n const item = items[index] as ElDmMenuItem;\n item.focused = true;\n item.focus();\n }\n\n private _selectItem(item: ElDmMenuItem): void {\n this.emit('select', { value: item.value || item.textContent?.trim() });\n this.hide();\n }\n\n private _updatePosition(): void {\n const menuEl = this.shadowRoot?.querySelector('.menu') as HTMLElement;\n if (!menuEl) return;\n\n // Check if menu would go off screen and flip if necessary\n requestAnimationFrame(() => {\n const rect = menuEl.getBoundingClientRect();\n const viewportHeight = window.innerHeight;\n const viewportWidth = window.innerWidth;\n\n let finalPlacement = this.placement;\n\n // Flip vertical placement if needed\n if (finalPlacement.startsWith('bottom') && rect.bottom > viewportHeight) {\n finalPlacement = finalPlacement.replace('bottom', 'top') as MenuPlacement;\n } else if (finalPlacement.startsWith('top') && rect.top < 0) {\n finalPlacement = finalPlacement.replace('top', 'bottom') as MenuPlacement;\n }\n\n // Flip horizontal placement if needed\n if (finalPlacement === 'right' && rect.right > viewportWidth) {\n finalPlacement = 'left';\n } else if (finalPlacement === 'left' && rect.left < 0) {\n finalPlacement = 'right';\n }\n\n // Update class if flipped\n if (finalPlacement !== this.placement) {\n menuEl.className = `menu placement-${finalPlacement}${this.open ? ' visible' : ''}`;\n }\n });\n }\n\n /**\n * Show the menu\n */\n show(): void {\n if (this.open) return;\n this.open = true;\n this._focusedIndex = -1;\n this._addGlobalListeners();\n this._updatePosition();\n this.emit('open');\n\n // Focus first item after menu opens\n requestAnimationFrame(() => {\n const items = this._getMenuItems();\n if (items.length > 0) {\n this._focusItem(this._getNextFocusableIndex(-1, 1, items));\n }\n });\n }\n\n /**\n * Hide the menu\n */\n hide(): void {\n if (!this.open) return;\n this.open = false;\n this._focusedIndex = -1;\n this._removeGlobalListeners();\n this.emit('close');\n\n // Clear focus from items\n const items = this._getMenuItems();\n items.forEach((item) => {\n (item as ElDmMenuItem).focused = false;\n });\n }\n\n /**\n * Toggle the menu open/closed\n */\n toggle(): void {\n if (this.open) {\n this.hide();\n } else {\n this.show();\n }\n }\n\n update(): void {\n super.update?.();\n const menuEl = this.shadowRoot?.querySelector('.menu');\n if (menuEl) {\n menuEl.classList.toggle('visible', this.open);\n if (this.open) {\n this._updatePosition();\n }\n }\n }\n\n render(): string {\n const placementClass = `placement-${this.placement || 'bottom-start'}`;\n\n return `\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu ${placementClass}${this.open ? ' visible' : ''}\"\n part=\"menu\"\n role=\"menu\"\n aria-hidden=\"${!this.open}\"\n >\n <div class=\"items-wrapper\" part=\"items\">\n <slot></slot>\n </div>\n </div>\n `;\n }\n}\n\n/**\n * DuskMoon Menu Item Element\n *\n * A menu item component for use within el-dm-menu.\n *\n * @element el-dm-menu-item\n *\n * @attr {string} value - The value associated with this item\n * @attr {boolean} disabled - Whether the item is disabled\n * @attr {boolean} focused - Whether the item is currently focused (internal)\n *\n * @slot - Default slot for item content\n * @slot icon - Slot for an icon before the content\n *\n * @csspart item - The menu item container\n * @csspart icon - The icon wrapper\n * @csspart content - The content wrapper\n */\n\nconst menuItemStyles = css`\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n .menu-item {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 1rem;\n cursor: pointer;\n color: var(--color-on-surface, #1f1f1f);\n background-color: transparent;\n border: none;\n width: 100%;\n text-align: left;\n font-size: 0.875rem;\n line-height: 1.25rem;\n font-family: inherit;\n transition:\n background-color 150ms ease,\n color 150ms ease;\n outline: none;\n }\n\n .menu-item:hover:not(.disabled) {\n background-color: var(--color-surface-container-highest, #f5f5f5);\n }\n\n .menu-item:focus:not(.disabled),\n .menu-item.focused:not(.disabled) {\n background-color: var(--color-surface-container-highest, #f5f5f5);\n outline: 2px solid var(--color-primary, #6750a4);\n outline-offset: -2px;\n }\n\n .menu-item.disabled {\n cursor: not-allowed;\n opacity: 0.5;\n color: var(--color-on-surface-variant, #49454f);\n }\n\n .icon-wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1.25rem;\n height: 1.25rem;\n flex-shrink: 0;\n }\n\n .icon-wrapper:empty {\n display: none;\n }\n\n .content-wrapper {\n flex: 1;\n min-width: 0;\n }\n\n ::slotted(svg),\n ::slotted(img) {\n width: 1.25rem;\n height: 1.25rem;\n }\n`;\n\nexport class ElDmMenuItem extends BaseElement {\n static properties = {\n value: { type: String, reflect: true },\n disabled: { type: Boolean, reflect: true, default: false },\n focused: { type: Boolean, reflect: true, default: false },\n };\n\n declare value: string;\n declare disabled: boolean;\n declare focused: boolean;\n\n constructor() {\n super();\n this.attachStyles(menuItemStyles);\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n this.setAttribute('role', 'menuitem');\n this.setAttribute('tabindex', '-1');\n this.addEventListener('click', this._handleClick.bind(this));\n }\n\n private _handleClick(event: MouseEvent): void {\n if (this.disabled) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n // Dispatch select event on parent menu\n const menu = this.closest('el-dm-menu') as ElDmMenu;\n if (menu) {\n menu.dispatchEvent(\n new CustomEvent('select', {\n bubbles: true,\n composed: true,\n detail: { value: this.value || this.textContent?.trim() },\n }),\n );\n menu.hide();\n }\n }\n\n update(): void {\n super.update?.();\n this.setAttribute('aria-disabled', String(this.disabled));\n\n const itemEl = this.shadowRoot?.querySelector('.menu-item');\n if (itemEl) {\n itemEl.classList.toggle('disabled', this.disabled);\n itemEl.classList.toggle('focused', this.focused);\n }\n }\n\n render(): string {\n return `\n <div\n class=\"menu-item${this.disabled ? ' disabled' : ''}${this.focused ? ' focused' : ''}\"\n part=\"item\"\n >\n <span class=\"icon-wrapper\" part=\"icon\">\n <slot name=\"icon\"></slot>\n </span>\n <span class=\"content-wrapper\" part=\"content\">\n <slot></slot>\n </span>\n </div>\n `;\n }\n}\n",
|
|
6
6
|
"/**\n * @duskmoon-dev/el-menu\n *\n * DuskMoon Menu custom elements\n */\n\nimport { ElDmMenu } from './el-dm-menu.js';\nimport { ElDmMenuItem } from './el-dm-menu.js';\n\nexport { ElDmMenu, ElDmMenuItem };\n\n/**\n * Register the el-dm-menu and el-dm-menu-item custom elements\n *\n * @example\n * ```ts\n * import { register } from '@duskmoon-dev/el-menu';\n * register();\n * ```\n */\nexport function register(): void {\n if (!customElements.get('el-dm-menu')) {\n customElements.define('el-dm-menu', ElDmMenu);\n }\n if (!customElements.get('el-dm-menu-item')) {\n customElements.define('el-dm-menu-item', ElDmMenuItem);\n }\n}\n",
|
|
7
7
|
"/**\n * Auto-register el-dm-menu and el-dm-menu-item custom elements\n *\n * @example\n * ```ts\n * import '@duskmoon-dev/el-menu/register';\n *\n * // Now you can use <el-dm-menu> and <el-dm-menu-item> in HTML\n * ```\n */\nimport { register } from './index.js';\n\nregister();\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": ";AAqBA;
|
|
10
|
-
"debugId": "
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "1061BBF74117BF7F64756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|