@greypan/web-ui 10.0.0 → 10.1.1

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.
Files changed (64) hide show
  1. package/README.CN.md +39 -1
  2. package/README.md +43 -1
  3. package/dist/components/autocomplete/index.js +62 -60
  4. package/dist/components/autocomplete/index.js.map +1 -1
  5. package/dist/components/avatar/index.js +5 -5
  6. package/dist/components/avatar/index.js.map +1 -1
  7. package/dist/components/back-top/index.js.map +1 -1
  8. package/dist/components/badge/index.js +5 -5
  9. package/dist/components/button/index.js +5 -5
  10. package/dist/components/button/index.js.map +1 -1
  11. package/dist/components/checkbox/index.js.map +1 -1
  12. package/dist/components/collapse/index.js +1 -1
  13. package/dist/components/collapse/index.js.map +1 -1
  14. package/dist/components/collapse/style.js +1 -1
  15. package/dist/components/collapse/style.js.map +1 -1
  16. package/dist/components/context-menu/index.d.ts +16 -0
  17. package/dist/components/context-menu/index.js +71 -66
  18. package/dist/components/context-menu/index.js.map +1 -1
  19. package/dist/components/dialog/index.js +39 -37
  20. package/dist/components/dialog/index.js.map +1 -1
  21. package/dist/components/drawer/index.js +64 -62
  22. package/dist/components/drawer/index.js.map +1 -1
  23. package/dist/components/dropdown-item/index.js.map +1 -1
  24. package/dist/components/editable-text/index.js +4 -4
  25. package/dist/components/empty/index.js.map +1 -1
  26. package/dist/components/empty/style.js +1 -1
  27. package/dist/components/empty/style.js.map +1 -1
  28. package/dist/components/image-preview/index.js.map +1 -1
  29. package/dist/components/input/index.js +5 -5
  30. package/dist/components/input/index.js.map +1 -1
  31. package/dist/components/input-number/index.js.map +1 -1
  32. package/dist/components/layout/index.js.map +1 -1
  33. package/dist/components/option/index.js.map +1 -1
  34. package/dist/components/segmented/index.js +17 -7
  35. package/dist/components/segmented/index.js.map +1 -1
  36. package/dist/components/select/index.js +45 -43
  37. package/dist/components/select/index.js.map +1 -1
  38. package/dist/components/switch/index.js.map +1 -1
  39. package/dist/components/textarea/index.js +8 -8
  40. package/dist/components/textarea/index.js.map +1 -1
  41. package/dist/components/toast/manager.js +1 -1
  42. package/dist/components/toast/manager.js.map +1 -1
  43. package/dist/components/toast/toast.js.map +1 -1
  44. package/dist/scrollbars/index.js.map +1 -1
  45. package/dist/scrollbars/internal.js +6 -0
  46. package/dist/scrollbars/internal.js.map +1 -0
  47. package/dist/scrollbars/style.css +1 -1
  48. package/dist/shared/menu-portal/menu-portal.js +16 -14
  49. package/dist/shared/menu-portal/menu-portal.js.map +1 -1
  50. package/dist/shared/overlay/overlay-root.js +13 -12
  51. package/dist/shared/overlay/overlay-root.js.map +1 -1
  52. package/dist/shared/overlay/overlay.js +33 -33
  53. package/dist/vite/index.d.ts +28 -0
  54. package/dist/vite/index.js +16 -0
  55. package/dist/vite/index.js.map +1 -0
  56. package/package.json +5 -1
  57. package/dist/node_modules/.pnpm/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js +0 -246
  58. package/dist/node_modules/.pnpm/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js.map +0 -1
  59. package/dist/node_modules/.pnpm/@floating-ui_dom@1.8.0/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +0 -324
  60. package/dist/node_modules/.pnpm/@floating-ui_dom@1.8.0/node_modules/@floating-ui/dom/dist/floating-ui.dom.js.map +0 -1
  61. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js +0 -100
  62. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js.map +0 -1
  63. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +0 -102
  64. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/context-menu/index.ts"],"sourcesContent":["import { computePosition, flip, shift } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { UserChangeController } from '@/shared/events/user-change'\nimport {\n createClosingSubmenuStack,\n createMenuHoverBinder,\n createMenuOutsideClickGuard,\n getEnabledMenuLevelItems,\n getFocusedMenuItemFromPanels,\n handleMenuKeyboard,\n type MenuHoverDelegate,\n type MenuKeyboardDelegate\n} from '@/shared/menu-behavior'\nimport { createMenuPortalOverlay, type MenuPortalOverlay } from '@/shared/menu-portal/menu-portal'\nimport {\n captureFrameworkAnchors,\n findFocusedMenuItem,\n focusMenuItem,\n getMenuChildren,\n getMenuItemFromEvent,\n hideNestedMenuChildren,\n moveMenuChildren,\n orderManagedMenuItems,\n reconcileManagedMenuItems,\n restoreFrameworkAnchors,\n returnManagedMenuItemsToSlot\n} from '@/shared/menu-portal/menu-tree'\nimport { normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { defineOverlayPositioningGeneration } from '@/shared/overlay/positioning-generation'\nimport { hideOverlayPresence, showOverlayPresence } from '@/shared/overlay/presence'\nimport { findNearestTheme } from '@/shared/overlay/theme-overlay-scope'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\n\nimport scrimStyle from './scrim.css?inline'\nimport style from './style.css?inline'\n\nconst MARKER_TEXT = 'wui-context-menu-item'\n\n/** 与 iOS / Android 原生长按一致的默认时长。 */\nconst DEFAULT_LONG_PRESS_DELAY = 500\n/** long-press-delay 的上界:超过 5s 的长按不再是「长按」,等同没有该属性。 */\nconst MAX_LONG_PRESS_DELAY = 5000\n/** 按住期间的容差位移;超出即视为滚动/拖拽意图,取消长按。 */\nconst LONG_PRESS_MOVE_TOLERANCE = 10\n/**\n * 长按开菜单后吸收浏览器补发事件的窗口上限。\n *\n * 这不是时序保证:实测补发几乎紧跟抬手(touchend(1019ms) → click(1022ms),间隔 3ms),\n * 1000ms 远大于实际需要,只是一个陈旧兜底上限。真正的边界是下一次 `pointerdown`\n * (`_onPointerDown` 无条件清零,含鼠标)与菜单关闭,两条硬边界都在窗口判定之外,\n * 所以窗口只负责兜住「补发事件既没等到下一次按下、菜单也没关闭」这种不该发生的路径。\n */\nconst LONG_PRESS_FOLLOW_UP_WINDOW_MS = 1000\n\n/**\n * 菜单打开期间铺在整屏上的 scrim `<dialog>`。\n *\n * 用 `<dialog>` + `showModal()` 而不是 plain `div`:模态 dialog 进 top layer,\n * 于是「z-index 谁高谁低」这道约束整体消失 —— 面板与 scrim 同在 top layer,\n * 而 top layer 之下的任何 z-index 都碰不到它们。plain `div` 也能修好 light-dismiss\n * 穿透,但那样 scrim 必须靠 z-index 压过 `--wui-layer-menu`(100),是另一套易碎的约定。\n */\nconst SCRIM_ATTR = 'data-wui-menu-scrim'\n\ntype CloseSource = 'menu-item' | 'default'\n\n/** 首项聚焦的最大尝试次数(含首次)。见 `_focusFirstItem`。 */\nconst FOCUS_FIRST_ITEM_ATTEMPTS = 5\n\n/** 面板与视口边缘的最小间距,与既有夹取算术同源。 */\nconst VIEWPORT_PADDING = 8\n\n@customElement('web-ui-context-menu')\nexport class WebUiContextMenu extends LitElement {\n static override styles = unsafeCSS(style)\n\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true, attribute: 'no-scroll-lock' }) noScrollLock = false\n /** opt-in:触屏长按打开菜单。默认关闭,鼠标路径靠原生 contextmenu,无需此属性。 */\n @property({ type: Boolean, reflect: true, attribute: 'long-press' }) longPress = false\n\n private _longPressDelay = DEFAULT_LONG_PRESS_DELAY\n\n /**\n * 走包内既有的 `normalizeNumber` 通道(与 `dropdown.offset`、`popover.offset` 同形)。\n * 不能只靠 `Math.max(0, …)` 挡负数:`Math.max(0, NaN) === NaN`,而属性写错\n * (`long-press-delay=\"abc\"`)时 Lit 的 Number converter 给的正是 NaN,\n * `setTimeout(fn, NaN)` 会立刻触发,长按就在 pointerdown 同一帧开菜单、绕过全部时长语义。\n */\n @property({ type: Number, attribute: 'long-press-delay' })\n get longPressDelay(): number {\n return this._longPressDelay\n }\n set longPressDelay(v: number) {\n const old = this._longPressDelay\n this._longPressDelay = normalizeNumber(v, 0, MAX_LONG_PRESS_DELAY, DEFAULT_LONG_PRESS_DELAY)\n this.requestUpdate('longPressDelay', old)\n }\n\n @state() private _isOpen = false\n @state() private _x = 0\n @state() private _y = 0\n\n private _activeSubmenus: MenuPortalOverlay[] = []\n private _activeSubmenuItems: HTMLElement[] = []\n private _longPressTimer: ReturnType<typeof setTimeout> | undefined\n private _longPressOrigin: { x: number; y: number } | undefined\n /**\n * 长按自行开菜单的时刻。浏览器会为**同一次触摸**补发两个事件,二者都属于\n * 那一次意图,不是两次独立操作:\n *\n * - 原生 `contextmenu`:长按被识别时立刻派发,会重复打开(`_openAt` 走已开分支)。\n * - 合成 `click`:`touchend` 之后由引擎补发,会把刚打开的菜单立刻 light-dismiss。\n * 实测真实触控管线下的顺序为 pointerdown(9ms) → 长按开菜单(≈509ms) →\n * touchend(1019ms) → click(1022ms),缺这一步菜单开了就被自己的补发事件关掉。\n *\n * 因此在窗口内吸收这两个补发事件;窗口由下一次 `pointerdown` 关闭。\n */\n private _longPressOpenedAt: number | null = null\n // 子菜单 dialog 定位的唯一写者代数(按 panel 键控):同帧关闭→重开复用同一 panel\n // 时只允许最新一次定位写入;不同层级子菜单各有 panel,互不作废。\n private readonly _submenuPositionEpochs = new WeakMap<HTMLElement, number>()\n private _menu?: MenuPortalOverlay\n /**\n * 模态 scrim。菜单打开期间存在并处于 `showModal()` 状态,关闭时随面板一起移除。\n *\n * 面板必须**显式**挂进它(`createMenuPortalOverlay` 的第三参):`resolveOverlayContainer`\n * 查的是 target 的**祖先**里有无已打开的 dialog,而 scrim 是面板的反方向,祖先发现查不到。\n */\n private _scrim?: HTMLDialogElement\n /**\n * 本次开启是否**水平居中于触发点**(触屏长按路径)。鼠标 / 键盘路径仍左上角对齐光标。\n *\n * 只切换 placement 的对齐量,不切换定位算法 —— 两条入口共用 `_positionMenu` 里的\n * 同一套 `computePosition` + `flip` / `shift`,垂直契约(上缘对齐触发点、向下展开、\n * 放不下翻到上方)完全一致。\n *\n * 触屏要居中:手指按下去的位置是「这块内容在这里」的意思,菜单在它正下方居中展开,\n * 正是 iOS / Android 原生长按菜单的形态;左对齐会让菜单整体偏到手指右侧,看起来像\n * 弹在了别的东西上面。\n *\n * 鼠标不居中:桌面右键的肌肉记忆是菜单**左上角**出现在光标处。更要紧的是居中会让光标\n * 落在面板上缘正中 —— 也就是落在第一项的命中区里。后果是菜单一开就有项处于 hover 高亮,\n * 并且紧接着再右键一次(想开子菜单)会打在菜单项上而不是空白处。`overlay-in-dialog`\n * 里 `openAt(100, 400)` 那条用例钉的 `'top left'` 正是这条契约。\n *\n * 判据是**开启来源**(调用点显式传入),不靠运行时嗅探 pointerType 或视口宽度:\n * 后两者会让同一块屏幕上的两种输入拿到两套布局,且与本组件 opt-in 的 `long-press`\n * 属性不同源。\n */\n private _centerOnPressPoint = false\n\n /**\n * 本次关闭的来源,决定焦点归还走哪条路。语义见 `close()` 里的说明。\n *\n * 只有菜单项激活这一条需要抢在退场之前出顶层;点外面 / Escape / 程序式关闭一律\n * 保留 dialog 默认效果 —— 那正是用户「回到原处」的期望。\n */\n private _closeSource: CloseSource = 'default'\n /** 根面板的开启会话句柄;未开启时为 null。查询走它。 */\n private _menuHandle: OpenOverlayHandle | null = null\n /**\n * 子菜单 panel → 会话句柄。子层经 `adopt` 进根句柄子树(默认不成为独立候选),\n * 但仍各自持有句柄:整层关闭或收尾动画结束时按 panel 精确撤销。\n */\n private readonly _submenuHandles = new Map<HTMLElement, OpenOverlayHandle>()\n // 行为层(hover / outside-click / 键盘 / submenu 收尾)由 shared/menu-behavior 驱动\n private readonly _outsideClickGuard = createMenuOutsideClickGuard(\n this,\n // 根句柄的 contains 递归覆盖全部已 adopt 的子层,一次查询替代逐 panel 判定。\n node => this._menuHandle?.contains(node) ?? false\n )\n private readonly _closingSubmenus = createClosingSubmenuStack<MenuPortalOverlay>({\n getPanel: container => container.panel,\n restoreItems: (container, parentItem) => this._restoreSubmenuItems(container, parentItem),\n dispose: container => {\n this._releaseSubmenu(container.panel)\n container.panel.remove()\n }\n })\n private readonly _hoverDelegate: MenuHoverDelegate = {\n getOpenDepth: () => this._activeSubmenus.length,\n getLevelItems: level => this._getLevelItems(level),\n getActiveItem: level => this._activeSubmenuItems[level],\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenu: item => this._openSubmenu(item),\n closeFrom: level => {\n this._closeSubmenusFrom(level)\n this._hoverBinder.bind()\n }\n }\n private readonly _hoverBinder = createMenuHoverBinder(this._hoverDelegate, 'web-ui-dropdown-item')\n private readonly _keyboardDelegate: MenuKeyboardDelegate = {\n getFocusedItem: event =>\n getFocusedMenuItemFromPanels(\n event,\n [this._menu?.content, ...this._activeSubmenus.map(submenu => submenu.content)],\n 'web-ui-dropdown-item'\n ),\n getLevelOf: item => {\n const level = this._getItemLevel(item)\n return level === -1 ? undefined : level\n },\n getEnabledItems: level => getEnabledMenuLevelItems(this._getLevelContainer(level)?.content),\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenuInstant: item => this._openSubmenu(item, true),\n closeToParent: level => {\n const parent = this._activeSubmenuItems[level - 1]\n this._closeSubmenusFrom(level - 1)\n this._hoverBinder.bind()\n return parent\n },\n closeDeepestOrAll: () => this._closeLastSubmenuOrMenu()\n }\n /*\n * 开启态浮层(issue #120 Block 1)。根面板是登记对象:子菜单经 adopt 成为它的逻辑\n * 后代;关闭动作仍走 closeDeepestOrAll(最深子菜单优先)。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._keyboardDelegate.closeDeepestOrAll(),\n isConnected: () => this.isConnected\n })\n private readonly _menuItemAnchors = new Map<HTMLElement, Comment>()\n private readonly _scrollLock = defineScrollLockLease().make()\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n private _suppressInitialFocusVisible = true\n private _refreshScheduled = false\n // 主菜单 dialog 路径与子菜单一样使用代数令牌;快速 openAt/refresh 或 close 后,\n // 迟到的 positioning promise 只能被丢弃,不能覆盖最新坐标。\n private readonly _menuPositionGeneration = defineOverlayPositioningGeneration().make()\n // 菜单打开期间宿主可能不经重定位直接改写子内容(网络推送、定时器等),\n // 新节点缺隐藏 slot 会可见叠加到菜单上;观察 portal 内容并在下一帧重新同步。\n private readonly _contentObserver = new MutationObserver(() => {\n if (this._isOpen) this._scheduleRefresh()\n })\n\n get isOpen(): boolean {\n return this._isOpen\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._hideMenuItems()\n this.addEventListener('contextmenu', this._onContextMenu)\n this.addEventListener('keydown', this._onKeydown)\n this.addEventListener('click', this._onMenuClick)\n this.addEventListener('pointerdown', this._onPointerDown)\n this.addEventListener('pointermove', this._onPointerMove)\n this.addEventListener('pointerup', this._onPointerUp)\n this.addEventListener('pointercancel', this._onPointerCancel)\n document.addEventListener('contextmenu', this._onContextMenuOutside)\n document.addEventListener('wheel', this._onWheel, { capture: true, passive: false })\n document.addEventListener('touchmove', this._onTouchMove, { capture: true, passive: false })\n document.addEventListener('keydown', this._onDocumentKeydown)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('contextmenu', this._onContextMenu)\n this.removeEventListener('keydown', this._onKeydown)\n this.removeEventListener('click', this._onMenuClick)\n this.removeEventListener('pointerdown', this._onPointerDown)\n this.removeEventListener('pointermove', this._onPointerMove)\n this.removeEventListener('pointerup', this._onPointerUp)\n this.removeEventListener('pointercancel', this._onPointerCancel)\n document.removeEventListener('contextmenu', this._onContextMenuOutside)\n document.removeEventListener('wheel', this._onWheel, true)\n document.removeEventListener('touchmove', this._onTouchMove, true)\n document.removeEventListener('keydown', this._onDocumentKeydown)\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._outsideClickGuard.dispose()\n this._hoverBinder.dispose()\n this._scrollLock.release()\n this._menuHandle?.release()\n this._menuHandle = null\n this._returnItemsToSlot()\n this._menu?.panel.remove()\n this._menu = undefined\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n /*\n * scrim 挂在宿主以外的容器里(theme overlay root,无主题时 document.body),不随宿主一起\n * 脱离文档。不在这里收掉就会留下一张仍然 `showModal()` 的全屏 dialog:top layer 不受宿主\n * 移除影响,整个页面就此锁死。\n */\n this._closeScrim()\n // 脱离文档即视为关闭:否则重连后 _isOpen 仍为 true 而 _menu 已清空,\n // 下次 openAt 会走已打开分支静默失败,菜单无法再打开。\n this._isOpen = false\n this._cancelLongPress()\n this._longPressOpenedAt = null\n }\n\n protected override updated(changed: Map<string, unknown>) {\n // Lit 的已排队更新可在卸载后执行,不能让已失效实例重新获取全局滚动锁。\n if (!this.isConnected) {\n this._syncScrollLock(false)\n return\n }\n\n if (changed.has('_isOpen')) {\n if (this._isOpen) {\n this._syncScrollLock()\n if (!this._menu) {\n /*\n * 顺序:先建 scrim 并 `showModal()`,再把面板挂进去。\n *\n * `showModal()` 会把此前聚焦的元素记为还原目标;此刻焦点仍在 opener 上,\n * 正是归还目标。面板随后 `_focusFirstItem()` 把焦点带进菜单,关闭时 UA 还回去。\n *\n * 面板挂进 scrim 是承重的,不是「顺手放一起」:top layer 只对**自身子树内的\n * 元素**做命中拦截,面板若留在 overlay root(scrim 之外),模态期间它自己就不可点。\n */\n const scrim = this._openScrim()\n this._menu = createMenuPortalOverlay('context-menu', this, scrim)\n this._menu.panel.setAttribute('role', 'menu')\n this._menu.panel.setAttribute('aria-label', '上下文菜单')\n this._menu.panel.addEventListener('click', this._onMenuClick)\n }\n /*\n * 登记与面板建立解耦。退场动画被打断时(`_closeMenuAfterPresence` 在\n * `hideOverlayPresence` 返回 false 后提前退出)`_menu` 还在,而关闭分支已经把句柄\n * 撤了;此时重开必须补 claim。否则菜单开着且可见却没有登记:Escape 关不掉它\n * (仲裁看不到它),而 `_menuHandle === null` 会让每次 document click——包括面板\n * 内部与子菜单父项——都被 `_outsideClickGuard` 判成外部点击而关闭菜单。\n */\n if (!this._menuHandle) {\n this._menuHandle = this._overlay.claim(this._menu.panel)\n // 重新 claim = 新会话:根层的子层随之作废,仍在场的子菜单面板要重新 adopt 回\n // 新句柄的子树,否则根句柄的 contains 看不到它们。\n for (const submenu of this._activeSubmenus) {\n this._submenuHandles.delete(submenu.panel)\n this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n }\n // 收尾栈里的子菜单面板同样「可见但已脱离快照」:`_closeSubmenusFrom` 只把它们\n // 移出 `_activeSubmenus`,面板仍在 DOM 里退场。**这个循环承重**:不补 adopt,\n // 它们就会落在句柄的登记树之外,被判成「面板外」,点它内部会关掉整张菜单。\n // 用例 `退场窗口内重开后,收尾中的子菜单面板在取回前后都不可被当成面板外` 守着\n // 这一条 —— 把循环体整段删掉,那条会红(实测 1 failed | 95 passed)。\n // 写进 `_submenuHandles` 还因为收尾结束时 `_releaseSubmenu` 按 panel 取句柄释放。\n for (const container of this._closingSubmenus.closing()) {\n this._submenuHandles.set(container.panel, this._menuHandle.adopt(container.panel))\n }\n }\n // 父项始终留在 menu.content 内,观察它即可覆盖各级子菜单在打开期间的内容重建。\n this._contentObserver.observe(this._menu.content, { childList: true, subtree: true })\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n showOverlayPresence(this._menu.panel, { isInstant: this._shouldOpenInstantly })\n this._shouldOpenInstantly = true\n /*\n * 首项聚焦必须再等一帧,不能和 `_refreshMenu()` 同帧。\n *\n * 两个原因都指向「太早」:\n *\n * 1. `showModal()` 的 dialog focusing steps 由 UA 排队执行,会在我们已经聚焦首项\n * **之后**再把焦点挪到 dialog 上 —— 模态化之前没有这一步,也就没有这场竞争。\n * 2. `_setupMenuItems()` 刚创建的 `web-ui-dropdown-item` 尚未完成 Lit 的 shadow\n * 渲染,`focusItem()` 此刻还找不到 `.item-inner`,静默无操作。\n *\n * 实测:同帧聚焦后 `document.activeElement` 是 `<dialog>` 而非首项;再等一帧聚焦,\n * 焦点稳定落在菜单项内。\n */\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem()\n })\n })\n } else {\n this._syncScrollLock(false)\n // 吸收窗口只覆盖「长按之后浏览器补发的那几个事件」。菜单已关,\n // 之后任何一次真实右键或点击都必须走正常路径,不能被这个窗口吞掉。\n this._longPressOpenedAt = null\n // release ⟺ 关闭:登记与仲裁归属同时撤销,退场动画只是视觉收尾。\n this._menuHandle?.release()\n this._menuHandle = null\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._refreshScheduled = false\n void this._closeMenuAfterPresence()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this._isOpen)\n }\n if (changed.has('noScrollLock')) this._syncScrollLock()\n }\n\n /**\n * 在指定视口坐标打开菜单。\n * @param x 水平坐标(px)。\n * @param y 垂直坐标(px)。\n * @returns 无返回值;命令式打开不派发 `open-change` 事件。\n */\n openAt(x: number, y: number) {\n this._openAt(x, y, true, true)\n }\n\n private _openAt(\n x: number,\n y: number,\n isInstant: boolean,\n suppressFocusVisible: boolean,\n centerOnPressPoint = false\n ): boolean {\n if (this.disabled) return false\n this._x = x\n this._y = y\n this._shouldOpenInstantly = isInstant\n this._suppressInitialFocusVisible = suppressFocusVisible\n this._centerOnPressPoint = centerOnPressPoint\n this._outsideClickGuard.arm()\n if (this._isOpen) {\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n // 先失效尚未完成的旧定位,再调度下一帧新代;避免新请求前的旧 promise 胜出。\n this._menuPositionGeneration.invalidate()\n this._scheduleRefresh()\n return false\n }\n this._isOpen = true\n return true\n }\n\n /**\n * 关闭菜单。\n * @returns 无返回值;命令式关闭不派发 `open-change` 事件。\n */\n close() {\n if (!this._isOpen) return\n this._isOpen = false\n this._menuPositionGeneration.invalidate()\n /*\n * 菜单项激活触发的关闭要**现在**就让 scrim 出顶层,不能等退场结束。\n *\n * 焦点归还由 UA 负责(`close()` 把焦点还给 `showModal()` 之前的 activeElement),\n * 而菜单项的回调常紧接着把焦点送到别处 —— 典型是右键重命名:回调把行换成编辑态、\n * 下一帧 `select()` 进 textarea。等到退场结束才关,UA 的归还会把焦点从 textarea\n * 拽回那一行,blur 掉编辑器,而 editable-text 的 blur 契约会**提交未改动的标题**。\n * 那是 interweave 上真实存在的 bug,不能放回来。\n *\n * 此刻焦点还在菜单里,UA 的归还正好落在 opener 上——那本来就是用户期望的落点,\n * 应用随后的焦点转移不会被覆盖。元素本身要留到面板真正移除(见 `_releaseScrimModality`)。\n */\n if (this._closeSource === 'menu-item') this._releaseScrimModality()\n this._closeSource = 'default'\n }\n\n private readonly _closeFromUser = (source: CloseSource = 'default') => {\n if (!this._isOpen) return\n this._userOpenChange.mark()\n this._closeSource = source\n this.close()\n }\n\n // 同一帧内多次内容变化只刷新一次,避免观察者与刷新自身 append 形成循环。\n private _scheduleRefresh() {\n if (this._refreshScheduled || !this._isOpen) return\n this._refreshScheduled = true\n requestAnimationFrame(() => {\n this._refreshScheduled = false\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n })\n }\n\n // 重新同步 portal 内容并重定位;fresh-open、重定位与观察者触发的刷新共用。\n private _refreshMenu() {\n if (!this._menu) return\n this._setupMenuItems()\n this._positionMenu()\n this._hoverBinder.bind()\n }\n\n /**\n * 定位:**触屏长按与鼠标右键共用这一条 Floating UI 路径**。\n *\n * 两条入口现在只差「触发点的 x/y 从哪来」,布局契约完全一致:\n * `bottom-start` 表达「面板上缘 == 触发点 y、向下展开」,水平以触发点 x 为基准。\n * 之前有一条手写的贴底分支(先按点、再按视口下缘算 `top`),那是把「贴视口底」硬编码\n * 成算术;真机反馈证明它错(菜单跑到屏幕最底部,与手指按下的位置完全脱节)。现在\n * 落点交给 `flip` / `shift` 按**实际空间**决定,`placement` 只表达意图。\n *\n * 不再按「是否触屏」分流:窄视口的鼠标右键同样期望菜单贴着光标出现,按输入类型或视口\n * 宽度分流会让同一块屏幕上的两种输入拿到两套布局。分流所需的 `_anchorAtPressPoint`\n * 连同 `_positionMenuInViewport` / `_positionMenuAtPressPoint` 两个方法一起删掉 ——\n * 面板恒为 open scrim `<dialog>` 的直接子节点(`createMenuPortalOverlay` 第三参传入\n * scrim,`menu-portal.ts` 里 `container = scrim ?? …`),所以旧代码那条\n * 「非 dialog 容器」的分支对 context-menu 本来就不可达。\n *\n * 算出来的 left/top 是**视口坐标**。面板是 `position: fixed`,而 scrim 刻意不带\n * transform / filter / contain(见 style.css),因此不构成 fixed 的包含块 ——\n * 坐标直接相对视口解析。这条等价关系由 style.css 的满屏声明保证,并由 R6 的\n * 「scrim 无 transform/filter/contain」+ viewport-fit 的几何断言兜底。\n */\n private _positionMenu() {\n const panel = this._menu?.panel\n if (!panel) return\n\n /*\n * 先读安全区,再碰面板:`getComputedStyle` 会强制一次同步布局,让它发生在面板进入\n * 「可测」状态之前,而不是夹在「面板刚可测」与「Floating UI 量它」之间。\n *\n * 需要说清楚的是,**这不是** viewport-fit 那条横向断言里 2px 抖动的成因。把这一行移到\n * 最前面实测并没有让那个偏差消失(改前改后都是 204 对期望 206)。那个 2px 来自 flip 的\n * reset 会多跑一轮测量:那一刻面板宽 202px、落定后 200px。相关用例已经不再钉与视口\n * 边界的精确距离。\n */\n const safeAreaBottom = this._safeAreaBottom()\n\n panel.style.visibility = 'hidden'\n panel.style.display = ''\n\n const generation = this._menuPositionGeneration.next()\n /*\n * 可用矩形 = 视口 − 安全区 − 最小间距。flip 与 shift 必须吃**同一份** padding:\n *\n * - flip 用它判断「这一侧放不放得下」,决定翻不翻;\n * - shift 用它做实际钳制,且 `crossAxis: true` 会**同时**钳主轴与交叉轴\n * (core 的 shift 默认 mainAxis=true / crossAxis=false,开了 crossAxis 才两边都管)。\n *\n * 安全区必须进 shift 而不只是 flip:flip 只挑「哪一侧」,挑完就把面板贴在触发点上,\n * 不再管这一侧内部的位置。触发点本身落在安全区里时(按在屏幕最底部),挑出来的\n * 「上方」那一侧仍然把面板下缘按在安全区里 —— 只有 shift 的主轴钳制会把它推上来。\n * 这条正是 R7 用例在钉的行为。\n */\n const boundaryPadding = {\n top: VIEWPORT_PADDING,\n bottom: VIEWPORT_PADDING + safeAreaBottom,\n left: VIEWPORT_PADDING,\n right: VIEWPORT_PADDING\n }\n /*\n * 限制 flip 的候选集为「下方 / 上方」两个,**刻意不含 `-start` / `-end` 的另一侧**。\n *\n * 为什么必须限制:`flip` 对对齐过的 placement(`bottom-start`)默认展开成\n * `getExpandedPlacements()`,候选里带着 `bottom-end` / `top-end`。于是鼠标贴视口右缘、\n * `bottom-start` 的对齐侧溢出时,flip 会认定「`bottom-end` 才放得下」并把面板整个挪到\n * 光标左侧。落点仍在视口内,但那是一次没人要求的契约变更 —— 水平夹取本来就归 shift,\n * 而且对齐量一变,transform-origin 的推导前提(对齐量不变 ⇒ 水平 origin 只由 shift 位移\n * 决定)就不成立了。\n *\n * 用的是 `fallbackPlacements` 而不是 `allowedPlacements`:后者是 `autoPlacement` 的选项,\n * `flip` 根本不读,传进去只会被静默忽略(TS 会报错,运行时会当成 padding 之外的字段丢掉)。\n * `fallbackPlacements` 正是 flip 用来**替换**上面那个默认展开列表的字段,指定后\n * `placements = [initialPlacement, ...fallbackPlacements]`,连 bestFit 兜底也只在这份\n * 候选里挑,`-end` 因此永远不可能出现。\n *\n * 用例「鼠标贴视口右缘时对齐量不被 flip 改成 -end」钉的就是这一条。\n */\n const placements = this._centerOnPressPoint\n ? (['bottom', 'top'] as const)\n : (['bottom-start', 'top-start'] as const)\n\n void computePosition({ getBoundingClientRect: () => new DOMRect(this._x, this._y, 0, 0) }, panel, {\n strategy: 'fixed',\n /*\n * `bottom`(触屏):面板上缘对齐触发点 y、水平居中于触发点 x。\n * `bottom-start`(鼠标):同样上缘对齐 y,但左缘对齐光标 x。理由见\n * `_centerOnPressPoint` —— 居中会把光标落进面板上缘的命中区。\n *\n * 居中在贴边时**必然**被 shift 的交叉轴钳制打破(按点 x 太靠边,居中的面板会溢出视口)。\n * 这不是缺陷而是取舍:完整可见优先于几何居中。shift 推完面板仍在视口内,\n * 只是不再居中 —— 用例「按点贴视口右缘时仍完整可见」钉的就是这一条。\n */\n placement: placements[0],\n middleware: [\n /*\n * flip 负责「向下放不下就翻到按点上方」—— 面板下缘对齐按点、向上长。\n * 两侧都放不下(按点极低且面板极高)时它按 bestFit 选空间多的一侧并夹住主轴,\n * 所以不需要自己再写一遍兜底夹取。\n */\n flip({ fallbackPlacements: [...placements], padding: boundaryPadding }),\n shift({ padding: boundaryPadding, crossAxis: true })\n ]\n }).then(({ x, y, placement, middlewareData }) => {\n if (!this._isOpen || this._menu?.panel !== panel || !this._menuPositionGeneration.isCurrent(generation)) return\n const shiftX = middlewareData.shift?.x ?? 0\n /*\n * 垂直 origin 取 flip 后的实际 placement,不看 shift 位移:placement 直接说明面板\n * 落在触发点的哪一侧,而「离触发点最近的那条边」就是 scale 应该长出来的地方。\n *\n * 水平:默认跟 placement 的对齐量;被 shift 推动时改用推动方向那一侧,菜单才是朝\n * 光标「长过来」而不是从视口边缘滑进去。居中(placement 不带 `-` 对齐后缀)且未被\n * 推动时保持 `center`。\n */\n const horizontalOrigin = !placement.includes('-')\n ? shiftX === 0\n ? 'center'\n : shiftX < 0\n ? 'right'\n : 'left'\n : shiftX < 0\n ? 'right'\n : 'left'\n this._applyMenuPosition(panel, x, y, horizontalOrigin, placement.startsWith('bottom') ? 'top' : 'bottom')\n })\n }\n\n /**\n * 视口下缘的安全区高度(px)。\n *\n * 改按压点展开之后它**仍然必需**,而且不再只服务触屏:菜单默认从触发点向下长,触发点\n * 落在屏幕下半部时面板下缘会进入 home indicator 区域。它现在折进 flip 与 shift 共用的\n * 边界 padding(见 `_positionMenu`),不再需要单独一条 `maxBottom` 算术。\n *\n * 读宿主的自定义属性而不是 `env()`:自定义属性读回的是**计算后**的解析值,\n * 而 `env()` 在 JS 里没有对应的求值入口,只能间接经由声明它的属性取。\n * 属性未解析时 `parseFloat` 得 NaN,落到 0 —— 与 `env(..., 0px)` 的缺省一致。\n */\n private _safeAreaBottom(): number {\n const value = Number.parseFloat(getComputedStyle(this).getPropertyValue('--wui-context-menu-safe-area-bottom'))\n return Number.isFinite(value) ? value : 0\n }\n\n /**\n * 写下面板坐标与进场 transform 的原点。\n *\n * 两个 origin 必传且**不**再回退成坐标比较:flip 会改变垂直方向,回退表达式\n * `y < this._y ? 'bottom' : 'top'` 在「面板顶边与触发点重合」时两边都成立,等于随机取。\n * 唯一调用方从 flip 后的 placement 与 shift 位移推出真实侧边,见 `_positionMenu`。\n */\n private _applyMenuPosition(\n panel: HTMLElement,\n x: number,\n y: number,\n horizontalOrigin: 'left' | 'right' | 'center',\n verticalOrigin: 'top' | 'bottom'\n ) {\n panel.style.left = `${x}px`\n panel.style.top = `${y}px`\n panel.style.setProperty('--wui-internal-overlay-transform-origin', `${verticalOrigin} ${horizontalOrigin}`)\n panel.style.visibility = ''\n }\n\n /**\n * 把焦点带进菜单的第一项。\n *\n * `attempt` 是重试计数:菜单项是刚被 `reconcileManagedMenuItems` 搬进来的自定义元素,\n * 它们的 shadow(`.item-inner`)由各自的 Lit 更新**异步**渲染。`focusMenuItem()` 委托给\n * 项的 `focusItem()`,那一刻 `.item-inner` 还不存在,于是**静默无操作** —— 不抛错、\n * 也不留痕,只表现为「菜单开了但焦点没进去」。\n *\n * 所以这里不假设一次成功:调用后用 `findFocusedMenuItem` 复核,没落上就在下一帧再试,\n * 最多 `FOCUS_FIRST_ITEM_ATTEMPTS` 次。上界是必要的 —— 菜单项若始终渲染不出来\n * (空菜单、内容被外部换掉),不能让重试链一直跑下去。\n */\n private _focusFirstItem(attempt = 0) {\n const items = this._menu?.content.querySelectorAll<HTMLElement>('web-ui-dropdown-item:not([disabled])')\n const firstItem = items?.[0]\n if (!firstItem) return\n focusMenuItem(firstItem, { suppressFocusVisible: this._suppressInitialFocusVisible })\n if (attempt + 1 >= FOCUS_FIRST_ITEM_ATTEMPTS) return\n if (findFocusedMenuItem([this._menu?.panel])) return\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem(attempt + 1)\n })\n }\n\n private _setupMenuItems() {\n const menu = this._menu\n if (!menu) return\n const content = menu.content\n\n reconcileManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n this._hideMenuItems()\n // 宿主可能在菜单打开期间动态改写 slot 子内容(如切换上下文后重建嵌套子项),\n // 新节点没有隐藏 slot 会落入父项默认 slot 可见渲染并叠到一级菜单上;\n // 因此对已移入 content 的嵌套子项也要重新隐藏。\n hideNestedMenuChildren(content, 'context-menu-hidden')\n\n // 框架 v-if 注释锚点的复位独立于元素重排:无论元素序是否已变都要执行。\n // 若挂在「元素序已变才重排」之下,顺序恰好正确时会跳过复位,锚点漂移无法收敛,\n // 而 Vue 下次翻转正依赖锚点引导新分支项的插入点。\n const anchors = captureFrameworkAnchors(content, MARKER_TEXT)\n orderManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n restoreFrameworkAnchors(content, anchors)\n }\n\n private _returnItemsToSlot() {\n const menu = this._menu\n if (!menu) return\n\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n\n returnManagedMenuItemsToSlot(this, menu.content, this._menuItemAnchors)\n }\n\n /**\n * 建 scrim 并让它进入模态态,返回容器供面板挂载。\n *\n * 每次开启都新建而不是复用:scrim 的生命周期必须与「这一次开启」严格同进同出。\n * 复用一个 dialog 意味着上一轮的 `close()` 事件、`open` 属性与 UA 记下的焦点还原目标\n * 会跨会话残留,而这些都不是本组件能重置的。\n */\n private _openScrim(): HTMLDialogElement {\n const scrim = document.createElement('dialog')\n scrim.className = 'wui-context-menu-scrim'\n /*\n * scrim 的样式必须**显式挂到 scrim 元素上**。\n *\n * `static styles`(style.css)只进宿主的 shadow root,scrim 又是本组件自己造的节点、不进\n * 宿主 shadow root,拿不到那份样式;`scrim.css` 若只躺在文件里而不注入,就是死代码。\n * 实测未注入时 scrim 完全是 UA 默认 `dialog:modal` 的样子:`margin: auto` +\n * `width/height: fit-content` 让它在 414x896 视口里缩成 **38x38 的白盒子**正中,\n * `background` 还是不透明白 —— 菜单一开就在屏幕中间糊一块白色方块。\n *\n * 用子 `<style>` 而不是行内 `cssText`:与 `menu-portal.ts` 对 dialog 容器注入共享样式\n * 是同一手法(那边已在 dialog 容器上验证过),随元素一起移除,不留全局残留。\n */\n const scrimStyles = document.createElement('style')\n scrimStyles.textContent = scrimStyle\n scrim.append(scrimStyles)\n scrim.setAttribute(SCRIM_ATTR, '')\n scrim.addEventListener('click', this._onScrimClick)\n scrim.addEventListener('cancel', this._onScrimCancel)\n // showModal() 要求元素已在文档中。\n this._scrimParent().append(scrim)\n scrim.showModal()\n this._scrim = scrim\n return scrim\n }\n\n /**\n * scrim 的挂载点:**与面板在没有 scrim 时会落到的容器同源** —— 最近主题的 theme-owned\n * overlay root。scrim 就是那个容器的替身(面板必须挂进 scrim,见 `updated()` 的顺序注释),\n * 所以它得站在容器该站的位置上。\n *\n * 为什么不能在整棵 DOM 里随便挑位置:`--wui-color-*` 由 `<web-ui-theme>` 写在自己的 `:host`\n * 上,只沿 DOM 树向下继承。scrim 若挂到 `document.body`,而 `<web-ui-theme>` 是 body 的**子节点**,\n * 这条继承链就断了 —— 面板与 scrim 双双落回 `var(--wui-color-*, <浅色字面量>)` 的 fallback,\n * 深色外观下菜单是一块浅色玻璃(app 侧的 `dark:` 变体也够不到它)。挂进 theme overlay root\n * 之后,面板与 scrim 跟 dropdown / select 一样吃同一份 token。\n *\n * 无主题时保持历史行为挂在 `document.body`:那条路径本来就没有 token 作用域可继承,改用\n * fallback overlay root 只会改变既有测试与消费者的观察面,不换来任何东西。\n */\n private _scrimParent(): HTMLElement {\n return findNearestTheme(this)?.getOverlayRoot() ?? document.body\n }\n\n /**\n * 关掉并摘除 scrim。幂等:关闭分支与 `disconnectedCallback` 都会调。\n *\n * `close()` 触发 UA 的焦点归还,**必须先于面板 `remove()`** —— 理由见 `_closeMenuAfterPresence`。\n */\n private _closeScrim() {\n const scrim = this._scrim\n if (!scrim) return\n this._scrim = undefined\n if (scrim.open) scrim.close()\n scrim.remove()\n }\n\n /**\n * 只让 scrim 出顶层,不摘除元素。面板挂在 scrim 里,摘了就把退场动画一起摘掉。\n *\n * 提前出顶层是为了焦点(理由见 `close()`)。代价是这个尾巴上 scrim 已经在 top layer\n * 之外:下层恢复可命中,且面板的层级退回普通流 —— 在「菜单开在 drawer/dialog 之上」\n * 的场景里,退场动画会被下层的 top layer 盖住。\n *\n * 这条尾巴只有一次退场过渡的时长(默认 160ms),且只发生在菜单项激活时。\n * 摘除仍在 `_closeScrim()`,那时面板已经走完。\n */\n private _releaseScrimModality() {\n const scrim = this._scrim\n if (scrim?.open) scrim.close()\n }\n\n /**\n * 点 scrim = 关菜单(Q12:必关,不给 prop 选项),与 dialog / drawer 的实现同形:\n * 只有落点就是 scrim 本身才算「点外面」,落在面板(scrim 的后代)上不算。\n *\n * 落点判据之所以只能是这两个,是模态给的:菜单打开期间下层收不到命中,事件只可能\n * 落在 scrim 或面板上,两者都由本组件判定。\n *\n * 更早的实现在 document 捕获阶段挂 click 监听,把「点外面」理解成「命中测试落在菜单\n * 面板之外」—— 宿主 light DOM 的每一行都满足这个条件,于是判定与行激活成了同一个\n * click 的两个后果,中间没有任何仲裁,下层目标先被激活。那条监听器连同它的实现已一并\n * 删除:模态化之后它没有任何还能生效的场景,别再挂回来。\n */\n private _onScrimClick = (e: MouseEvent) => {\n if (e.target !== this._scrim) return\n // 长按抬手后浏览器补发的 click 属于那一次触摸意图,不能把刚开的菜单关掉。\n if (this._isLongPressFollowUp()) return\n this._closeFromUser()\n }\n\n /**\n * Escape 的**唯一**关闭路径仍是共享仲裁者(`_overlay.requestClose` → `closeDeepestOrAll`),\n * 它按层深决定是关最深子菜单还是关整张菜单。原生 dialog 自己也监听 Escape 并派发\n * `cancel`,若放任默认行为,UA 会直接关掉 scrim —— 那条路径不经过组件状态机,\n * 既可能漏派 `open-change`,也会把「关最深一层」的语义降级成「整张菜单没了」。\n *\n * 因此这里只吞掉默认行为,不重复关闭。仲裁者那条路径已经会走 `_closeFromUser()`。\n */\n private _onScrimCancel = (e: Event) => {\n e.preventDefault()\n }\n\n private async _closeMenuAfterPresence() {\n const menu = this._menu\n if (menu && !(await hideOverlayPresence(menu.panel))) return\n if (this._isOpen || !this.isConnected || this._menu !== menu) return\n\n /*\n * 归还判定必须在移除面板之前做完:面板一脱离文档,「焦点仍在菜单内」就再也读不到了。\n * 子菜单层此刻仍可能持有焦点,一并计入。\n */\n /*\n * 先关 scrim 再摘面板,顺序不能反。\n *\n * 焦点归还是 `showModal()` / `close()` 的 UA 行为,不再由组件自己实现:UA 记的是\n * `showModal()` 之前的 `document.activeElement`,`close()` 时还回去。反过来做就成了\n * 实测里那条「open 中直接移除 dialog → 焦点丢到 body,不归还」——面板先脱离文档,\n * 焦点链断了,UA 的还原目标也就落空。\n *\n * 菜单项激活那条路径的 scrim 早在 `close()` 里就出了顶层(`_releaseScrimModality`),\n * 这里的 `close()` 对它是幂等空转,摘除照常发生。\n *\n * 「关闭期间焦点被外部接管时不抢回」那道防护不是被删除,是被**移到了更早的位置**:\n * UA 自己没有这个分支(实测:外部接管后 close,焦点必被抢回 opener,`close()` /\n * `remove()` / `open = false` / 摘 `open` / 置 `display:none` / opener 移除或不可聚焦,\n * 十种写法全都照旧夺焦点)。所以只能在焦点**还没被送走**的时候就把顶层让掉 ——\n * 也就是菜单项激活那一刻。点外面 / Escape / 程序式关闭不抢焦点是用户期望的语义,\n * 那三条保留 dialog 默认效果。\n */\n this._closeScrim()\n\n // 登记已在关闭分支撤销(release ⟺ 关闭),这里只做内容归还与 DOM 收尾。\n this._returnItemsToSlot()\n menu?.panel.remove()\n this._menu = undefined\n }\n\n private _hideMenuItems() {\n getMenuChildren(this).forEach(child => child.setAttribute('slot', 'context-menu-hidden'))\n hideNestedMenuChildren(this, 'context-menu-hidden')\n }\n\n private _openSubmenu(item: HTMLElement, isInstant = false) {\n if (!this._isOpen || !item.hasAttribute('submenu') || item.hasAttribute('disabled')) return\n\n const level = this._getItemLevel(item)\n if (level === -1 || this._activeSubmenuItems[level] === item) return\n\n this._closeSubmenusFrom(level)\n const closingSubmenu = this._closingSubmenus.take(item)\n if (closingSubmenu) {\n this._activeSubmenus[level] = closingSubmenu\n this._activeSubmenuItems[level] = item\n /*\n * 取回的面板写回 `_submenuHandles`,让收尾结束时的 `_releaseSubmenu` 释放到正确的句柄。\n * 注:「面板在根句柄子树里」这一条已由上面的 claim 分支保证 —— 该分支会把仍在\n * closing 栈里的面板一并重挂(`_closingSubmenus.closing()`)。实测单独摘掉这一行\n * 全部用例仍绿,所以它维护的是释放账本,不是树的成员关系。\n *\n * **这一行没有任何测试守护**:把 `adopt` 与 `set` 一起摘掉,context-menu 的 96 条\n * 用例仍然全绿(实测 96 passed)。所以下面写的是「当前如此」—— 这一行维护的是\n * 释放账本(收尾结束时 `_releaseSubmenu` 按 panel 取句柄),不是树的成员关系;\n * 成员关系由上面 claim 分支的 `_closingSubmenus.closing()` 循环负责,那一条**是**\n * 有测试的。改动这一行前请自己判断要不要补一条能红的用例,别把这里的沉默当成覆盖。\n */\n if (this._menuHandle) this._submenuHandles.set(closingSubmenu.panel, this._menuHandle.adopt(closingSubmenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, closingSubmenu)\n showOverlayPresence(closingSubmenu.panel, { isInstant })\n this._hoverBinder.bind()\n return\n }\n if (getMenuChildren(item).length === 0) return\n\n /*\n * 子菜单与根菜单挂进**同一个** scrim。只改根菜单会让两层行为分裂:根层被 scrim\n * 罩住时,子菜单若仍留在 overlay root,它自己就成了 scrim 子树外的元素 —— 模态期间\n * 不可点,且点它内部会被判成「点外面」而关掉整张菜单。\n */\n const submenu = createMenuPortalOverlay('context-submenu', this, this._scrim)\n submenu.panel.dataset.level = String(level)\n submenu.panel.setAttribute('role', 'menu')\n submenu.panel.setAttribute('aria-label', '子菜单')\n submenu.panel.style.visibility = 'hidden'\n submenu.panel.addEventListener('click', this._onMenuClick)\n moveMenuChildren(item, submenu.content)\n\n this._activeSubmenus[level] = submenu\n this._activeSubmenuItems[level] = item\n /*\n * 子层进根句柄的子树,默认不是独立候选 —— 「它在树里、但不参与这一层裁决」由此\n * 表达。原先按 level===0 选择根面板还是上一层子面板的父级判断随之消失:contains\n * 沿子树递归,拍平与嵌套对任何现有查询结果等价。\n */\n if (this._menuHandle) this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, submenu)\n showOverlayPresence(submenu.panel, { isInstant })\n this._hoverBinder.bind()\n }\n\n private _closeSubmenusFrom(level: number, isInstant = false) {\n for (let index = this._activeSubmenus.length - 1; index >= level; index--) {\n const submenu = this._activeSubmenus[index]\n const item = this._activeSubmenuItems[index]\n item?.removeAttribute('active')\n if (!item || isInstant) {\n if (submenu) this._releaseSubmenu(submenu.panel)\n this._restoreSubmenuItems(submenu, item)\n submenu.panel.remove()\n } else {\n this._closingSubmenus.closeAsync(item, submenu)\n }\n }\n this._activeSubmenus.length = level\n this._activeSubmenuItems.length = level\n }\n\n private _restoreSubmenuItems(submenu: MenuPortalOverlay, item?: HTMLElement) {\n if (!item) return\n moveMenuChildren(submenu.content, item)\n // 子菜单打开期间宿主可能重建了嵌套子项,归还时补隐藏,避免可见叠加。\n hideNestedMenuChildren(item, 'context-menu-hidden')\n }\n\n private _getItemLevel(item: HTMLElement): number {\n const menu = this._menu\n if (menu?.panel.contains(item)) return 0\n const parentLevel = this._activeSubmenus.findIndex(submenu => submenu.panel.contains(item))\n return parentLevel === -1 ? -1 : parentLevel + 1\n }\n\n private _positionSubmenu(item: HTMLElement, submenu: MenuPortalOverlay) {\n // 与主菜单同因:panel 进入 open native dialog 后处于 transformed containing\n // block,viewport 坐标的 left/top 会相对 dialog padding box 解析而整体偏移,\n // 需改走 Floating UI 换算为 dialog 相对坐标;开合方向仍按视口坐标度量预判。\n if (submenu.panel.parentElement instanceof HTMLDialogElement && submenu.panel.parentElement.open) {\n const itemRect = item.getBoundingClientRect()\n const padding = 8\n const canOpenRight = itemRect.right + submenu.panel.getBoundingClientRect().width + padding <= window.innerWidth\n // 同帧关闭→重开会复用同一 panel 产生两个 in-flight 定位 promise,完成序不保证\n // 后者胜出;按 panel 键控的代数 token 只允许最新一次调用写入,迟到旧 promise 丢弃。\n const epoch = (this._submenuPositionEpochs.get(submenu.panel) ?? 0) + 1\n this._submenuPositionEpochs.set(submenu.panel, epoch)\n void computePosition(item, submenu.panel, {\n strategy: 'fixed',\n placement: canOpenRight ? 'right-start' : 'left-start',\n middleware: [shift({ padding, crossAxis: true })]\n }).then(({ x, y, middlewareData }) => {\n if (!this._activeSubmenus.includes(submenu) || epoch !== this._submenuPositionEpochs.get(submenu.panel)) return\n // 与主菜单 dialog 路径一致:origin 由 shift 增量推导,视口预判只决定 placement。\n const shiftX = middlewareData.shift?.x ?? 0\n const shiftY = middlewareData.shift?.y ?? 0\n const horizontalOrigin = shiftX < 0 ? 'right' : 'left'\n const verticalOrigin = shiftY < 0 ? 'bottom' : 'top'\n submenu.panel.style.left = `${x}px`\n submenu.panel.style.top = `${y}px`\n submenu.panel.style.setProperty(\n '--wui-internal-overlay-transform-origin',\n `${verticalOrigin} ${horizontalOrigin}`\n )\n submenu.panel.style.visibility = ''\n })\n return\n }\n\n const itemRect = item.getBoundingClientRect()\n const submenuRect = submenu.panel.getBoundingClientRect()\n const padding = 8\n const canOpenRight = itemRect.right + submenuRect.width + padding <= window.innerWidth\n const left = canOpenRight ? itemRect.right : Math.max(padding, itemRect.left - submenuRect.width)\n const top = Math.min(Math.max(padding, itemRect.top), window.innerHeight - submenuRect.height - padding)\n\n submenu.panel.style.left = `${left}px`\n submenu.panel.style.top = `${top}px`\n submenu.panel.style.setProperty('--wui-internal-overlay-transform-origin', canOpenRight ? 'top left' : 'top right')\n submenu.panel.style.visibility = ''\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _onPointerDown = (e: PointerEvent) => {\n // 任何一次新的按下都结束「上一次长按的补发事件」窗口:抬手后浏览器合成的\n // click 一定早于下一次 pointerdown,所以这个边界既能吸收补发,又不会误吞\n // 用户之后真正的点击。\n this._longPressOpenedAt = null\n // 鼠标走原生 contextmenu,无需长按;只认触屏指针。\n if (!this.longPress || this.disabled || e.pointerType !== 'touch') return\n this._cancelLongPress()\n this._longPressOrigin = { x: e.clientX, y: e.clientY }\n this._longPressTimer = setTimeout(\n () => {\n this._longPressTimer = undefined\n const origin = this._longPressOrigin\n if (!origin) return\n this._longPressOrigin = undefined\n if (this._isOpen) return\n this._longPressOpenedAt = performance.now()\n // 触屏长按:面板上缘对齐按点、向下展开,水平居中于按点(判据见 `_centerOnPressPoint`)。\n if (this._openAt(origin.x, origin.y, false, true, true)) this._userOpenChange.mark()\n },\n // 属性已被 normalizeNumber 钳到 [0, 5000],这里直接用;不再二次 Math.max。\n this.longPressDelay\n )\n }\n\n private _onPointerMove = (e: PointerEvent) => {\n if (e.pointerType !== 'touch') return\n const origin = this._longPressOrigin\n if (!origin) return\n // 触屏有隐式指针捕获,pointermove 会持续派发到 pointerdown 的落点,\n // 因此位移超阈值即判定为滚动意图,取消长按。\n const distance = Math.hypot(e.clientX - origin.x, e.clientY - origin.y)\n if (distance > LONG_PRESS_MOVE_TOLERANCE) this._cancelLongPress()\n }\n\n private _onPointerUp = (e: PointerEvent) => {\n if (e.pointerType === 'touch') this._cancelLongPress()\n }\n\n private _onPointerCancel = () => {\n this._cancelLongPress()\n }\n\n private _cancelLongPress() {\n if (this._longPressTimer !== undefined) {\n clearTimeout(this._longPressTimer)\n this._longPressTimer = undefined\n }\n this._longPressOrigin = undefined\n }\n\n /** 该事件是否是长按开菜单之后、浏览器为同一次触摸补发的事件。 */\n private _isLongPressFollowUp(): boolean {\n const openedAt = this._longPressOpenedAt\n if (openedAt === null) return false\n // 超期只说明「这次长按的补发没来」,不代表窗口承担时序保证;见常量注释。\n if (performance.now() - openedAt > LONG_PRESS_FOLLOW_UP_WINDOW_MS) {\n this._longPressOpenedAt = null\n return false\n }\n return true\n }\n\n private _onContextMenu = (e: MouseEvent) => {\n if (this.disabled) return\n if (this._isLongPressFollowUp()) {\n e.preventDefault()\n return\n }\n e.preventDefault()\n if (this._openAt(e.clientX, e.clientY, false, true)) this._userOpenChange.mark()\n }\n\n private _onContextMenuOutside = (e: MouseEvent) => {\n if (this._isOpen && !this._outsideClickGuard.isInside(e)) {\n this._closeFromUser()\n }\n }\n\n private _onKeydown = (e: KeyboardEvent) => {\n if (this.disabled) return\n\n if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {\n e.preventDefault()\n const focused = document.activeElement\n if (focused && focused !== document.body) {\n const rect = focused.getBoundingClientRect()\n if (this._openAt(rect.left, rect.bottom, true, false)) this._userOpenChange.mark()\n } else {\n if (this._openAt(window.innerWidth / 2, window.innerHeight / 2, true, false)) this._userOpenChange.mark()\n }\n return\n }\n }\n\n private _onMenuClick = (e: MouseEvent) => {\n const item = getMenuItemFromEvent(e)\n if (!item || item.hasAttribute('disabled')) return\n if (item.hasAttribute('submenu')) {\n this._openSubmenu(item)\n return\n }\n this._closeFromUser('menu-item')\n }\n\n private _getLevelContainer(level: number) {\n return level === 0 ? this._menu : this._activeSubmenus[level - 1]\n }\n\n private _getLevelItems(level: number): HTMLElement[] {\n const container = this._getLevelContainer(level)\n if (!container) return []\n return getMenuChildren(container.content)\n }\n\n private _onWheel = (e: WheelEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onTouchMove = (e: TouchEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onDocumentKeydown = (e: KeyboardEvent) => {\n if (!this._isOpen) return\n // 鼠标右键打开的菜单焦点不在菜单内,e.target !== this 时也必须能 Escape 关闭;\n // 菜单为模态浮层,按 Escape 即关闭,无需限定焦点位置\n handleMenuKeyboard(this._keyboardDelegate, e)\n }\n\n private _closeLastSubmenuOrMenu() {\n if (this._activeSubmenus.length > 0) {\n const level = this._activeSubmenus.length - 1\n const parent = this._activeSubmenuItems[level]\n this._closeSubmenusFrom(level)\n focusMenuItem(parent)\n this._hoverBinder.bind()\n } else {\n this._closeFromUser()\n }\n }\n\n private _preventBackgroundScroll(e: Event) {\n if (!this._isOpen || this.noScrollLock || this._isMenuPanelEvent(e)) return\n e.preventDefault()\n }\n\n private _syncScrollLock(isOpen = this._isOpen) {\n this._scrollLock.sync(isOpen && !this.noScrollLock)\n }\n\n /** 按 panel 撤销一层子菜单的登记;句柄已随根层递归撤销时幂等无操作。 */\n private _releaseSubmenu(panel: HTMLElement) {\n this._submenuHandles.get(panel)?.release()\n this._submenuHandles.delete(panel)\n }\n\n private _isMenuPanelEvent(e: Event): boolean {\n // 在监听器内部判定:composedPath() 在派发结束后会被清空。\n return this._menuHandle?.containsEvent(e) ?? false\n }\n\n override render() {\n return html`\n <div class=\"context-menu-anchor\">\n <slot @slotchange=${this._onSlotChange}></slot>\n </div>\n `\n }\n\n private _onSlotChange() {\n this._hideMenuItems()\n // 打开期间宿主重建顶层项时,新成员要移入 portal 才对用户可见;\n // 关闭状态下无需移动,等下次打开时由 _setupMenuItems 统一处理。\n if (this._isOpen) this._scheduleRefresh()\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-context-menu': WebUiContextMenu\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwCA,IAAM,IAAc,yBAGd,IAA2B,KAE3B,IAAuB,KAEvB,IAA4B,IAS5B,IAAiC,KAUjC,IAAa,uBAKb,IAA4B,GAG5B,IAAmB,GAGZ,IAAN,cAA+B,EAAW;;EA0gCjB,aAvgCyB,KAAA,WAAA,IACiC,KAAA,eAAA,IAEP,KAAA,YAAA,IAEvD,KAAA,kBAAA,GAkBC,KAAA,UAAA,IACL,KAAA,KAAA,GACA,KAAA,KAAA,GAEyB,KAAA,kBAAA,CAAC,GACH,KAAA,sBAAA,CAAC,GAcF,KAAA,qBAAA,MAGF,KAAA,yCAAA,IAAI,QAA6B,GA6B7C,KAAA,sBAAA,IAQM,KAAA,eAAA,WAEY,KAAA,cAAA,MAKb,KAAA,kCAAA,IAAI,IAAoC,GAErC,KAAA,qBAAA,EACpC,OAEA,MAAQ,KAAK,aAAa,SAAS,CAAI,KAAK,EAC9C,GACoC,KAAA,mBAAA,EAA6C;GAC/E,WAAU,MAAa,EAAU;GACjC,eAAe,GAAW,MAAe,KAAK,qBAAqB,GAAW,CAAU;GACxF,UAAS,MAAa;IAEpB,AADA,KAAK,gBAAgB,EAAU,KAAK,GACpC,EAAU,MAAM,OAAO;GACzB;EACF,CAAC,GACoD,KAAA,iBAAA;GACnD,oBAAoB,KAAK,gBAAgB;GACzC,gBAAe,MAAS,KAAK,eAAe,CAAK;GACjD,gBAAe,MAAS,KAAK,oBAAoB;GACjD,gBAAe,MAAQ,EAAK,aAAa,SAAS;GAClD,cAAa,MAAQ,KAAK,aAAa,CAAI;GAC3C,YAAW,MAAS;IAElB,AADA,KAAK,mBAAmB,CAAK,GAC7B,KAAK,aAAa,KAAK;GACzB;EACF,GACgC,KAAA,eAAA,EAAsB,KAAK,gBAAgB,sBAAsB,GACtC,KAAA,oBAAA;GACzD,iBAAgB,MACd,EACE,GACA,CAAC,KAAK,OAAO,SAAS,GAAG,KAAK,gBAAgB,KAAI,MAAW,EAAQ,OAAO,CAAC,GAC7E,sBACF;GACF,aAAY,MAAQ;IAClB,IAAM,IAAQ,KAAK,cAAc,CAAI;IACrC,OAAO,MAAU,KAAK,KAAA,IAAY;GACpC;GACA,kBAAiB,MAAS,EAAyB,KAAK,mBAAmB,CAAK,CAAC,EAAE,OAAO;GAC1F,gBAAe,MAAQ,EAAK,aAAa,SAAS;GAClD,qBAAoB,MAAQ,KAAK,aAAa,GAAM,EAAI;GACxD,gBAAe,MAAS;IACtB,IAAM,IAAS,KAAK,oBAAoB,IAAQ;IAGhD,OAFA,KAAK,mBAAmB,IAAQ,CAAC,GACjC,KAAK,aAAa,KAAK,GAChB;GACT;GACA,yBAAyB,KAAK,wBAAwB;EACxD,GAK4B,KAAA,WAAA,EAAkB,CAAC,CAAC,KAAK;GACnD,oBAAoB,KAAK,kBAAkB,kBAAkB;GAC7D,mBAAmB,KAAK;EAC1B,CAAC,GACmC,KAAA,mCAAA,IAAI,IAA0B,GACnC,KAAA,cAAA,EAAsB,CAAC,CAAC,KAAK,GACzB,KAAA,kBAAA,IAAI,EAAqB,GAC7B,KAAA,uBAAA,IACQ,KAAA,+BAAA,IACX,KAAA,oBAAA,IAGe,KAAA,0BAAA,EAAmC,CAAC,CAAC,KAAK,GAGjD,KAAA,mBAAA,IAAI,uBAAuB;GAC7D,AAAI,KAAK,WAAS,KAAK,iBAAiB;EAC1C,CAAC,GAsNkC,KAAA,kBAAA,IAAsB,cAAc;GAChE,KAAK,YACV,KAAK,gBAAgB,KAAK,GAC1B,KAAK,eAAe,GACpB,KAAK,MAAM;EACb,GA0UyB,KAAA,iBAAA,MAAkB;GACrC,EAAE,WAAW,KAAK,WAElB,KAAK,qBAAqB,KAC9B,KAAK,eAAe;EACtB,GAU0B,KAAA,kBAAA,MAAa;GACrC,EAAE,eAAe;EACnB,GAuL0B,KAAA,kBAAA,MAAoB;GAI5C,KAAK,qBAAqB,MAErB,KAAK,aAAa,MAAK,YAAY,EAAE,gBAAgB,YAC1D,KAAK,iBAAiB,GACtB,KAAK,mBAAmB;IAAE,GAAG,EAAE;IAAS,GAAG,EAAE;GAAQ,GACrD,KAAK,kBAAkB,iBACf;IACJ,KAAK,kBAAkB,KAAA;IACvB,IAAM,IAAS,KAAK;IACf,MACL,KAAK,mBAAmB,KAAA,GACpB,MAAK,YACT,KAAK,qBAAqB,YAAY,IAAI,GAEtC,KAAK,QAAQ,EAAO,GAAG,EAAO,GAAG,IAAO,IAAM,EAAI,KAAG,KAAK,gBAAgB,KAAK;GACrF,GAEA,KAAK,cACP;EACF,GAE0B,KAAA,kBAAA,MAAoB;GAC5C,IAAI,EAAE,gBAAgB,SAAS;GAC/B,IAAM,IAAS,KAAK;GACf,KAGY,KAAK,MAAM,EAAE,UAAU,EAAO,GAAG,EAAE,UAAU,EAAO,CACjE,IAAW,KAA2B,KAAK,iBAAiB;EAClE,GAEwB,KAAA,gBAAA,MAAoB;GAC1C,AAAI,EAAE,gBAAgB,WAAS,KAAK,iBAAiB;EACvD,GAEiC,KAAA,yBAAA;GAC/B,KAAK,iBAAiB;EACxB,GAsB0B,KAAA,kBAAA,MAAkB;GACtC,UAAK,UACT;QAAI,KAAK,qBAAqB,GAAG;KAC/B,EAAE,eAAe;KACjB;IACF;IAEA,AADA,EAAE,eAAe,GACb,KAAK,QAAQ,EAAE,SAAS,EAAE,SAAS,IAAO,EAAI,KAAG,KAAK,gBAAgB,KAAK;GAF/E;EAGF,GAEiC,KAAA,yBAAA,MAAkB;GACjD,AAAI,KAAK,WAAW,CAAC,KAAK,mBAAmB,SAAS,CAAC,KACrD,KAAK,eAAe;EAExB,GAEsB,KAAA,cAAA,MAAqB;GACrC,UAAK,aAEL,EAAE,QAAQ,iBAAkB,EAAE,YAAY,EAAE,QAAQ,QAAQ;IAC9D,EAAE,eAAe;IACjB,IAAM,IAAU,SAAS;IACzB,IAAI,KAAW,MAAY,SAAS,MAAM;KACxC,IAAM,IAAO,EAAQ,sBAAsB;KAC3C,AAAI,KAAK,QAAQ,EAAK,MAAM,EAAK,QAAQ,IAAM,EAAK,KAAG,KAAK,gBAAgB,KAAK;IACnF,OACE,AAAI,KAAK,QAAQ,OAAO,aAAa,GAAG,OAAO,cAAc,GAAG,IAAM,EAAK,KAAG,KAAK,gBAAgB,KAAK;IAE1G;GACF;EACF,GAEwB,KAAA,gBAAA,MAAkB;GACxC,IAAM,IAAO,EAAqB,CAAC;GAC/B,IAAC,KAAQ,GAAK,aAAa,UAAU,GACzC;QAAI,EAAK,aAAa,SAAS,GAAG;KAChC,KAAK,aAAa,CAAI;KACtB;IACF;IACA,KAAK,eAAe,WAAW;GAD/B;EAEF,GAYoB,KAAA,YAAA,MAAkB;GACpC,KAAK,yBAAyB,CAAC;EACjC,GAEwB,KAAA,gBAAA,MAAkB;GACxC,KAAK,yBAAyB,CAAC;EACjC,GAE8B,KAAA,sBAAA,MAAqB;GAC5C,KAAK,WAGV,EAAmB,KAAK,mBAAmB,CAAC;EAC9C;;;EA9gCyB,KAAA,SAAA,EAAU,CAAK;;CAexC,IACI,iBAAyB;EAC3B,OAAO,KAAK;CACd;CACA,IAAI,eAAe,GAAW;EAC5B,IAAM,IAAM,KAAK;EAEjB,AADA,KAAK,kBAAkB,EAAgB,GAAG,GAAG,GAAsB,CAAwB,GAC3F,KAAK,cAAc,kBAAkB,CAAG;CAC1C;CA4IA,IAAI,SAAkB;EACpB,OAAO,KAAK;CACd;CAEA,oBAA6B;EAa3B,AAZA,MAAM,kBAAkB,GACxB,KAAK,eAAe,GACpB,KAAK,iBAAiB,eAAe,KAAK,cAAc,GACxD,KAAK,iBAAiB,WAAW,KAAK,UAAU,GAChD,KAAK,iBAAiB,SAAS,KAAK,YAAY,GAChD,KAAK,iBAAiB,eAAe,KAAK,cAAc,GACxD,KAAK,iBAAiB,eAAe,KAAK,cAAc,GACxD,KAAK,iBAAiB,aAAa,KAAK,YAAY,GACpD,KAAK,iBAAiB,iBAAiB,KAAK,gBAAgB,GAC5D,SAAS,iBAAiB,eAAe,KAAK,qBAAqB,GACnE,SAAS,iBAAiB,SAAS,KAAK,UAAU;GAAE,SAAS;GAAM,SAAS;EAAM,CAAC,GACnF,SAAS,iBAAiB,aAAa,KAAK,cAAc;GAAE,SAAS;GAAM,SAAS;EAAM,CAAC,GAC3F,SAAS,iBAAiB,WAAW,KAAK,kBAAkB;CAC9D;CAEA,uBAAgC;EAmC9B,AAlCA,MAAM,qBAAqB,GAC3B,KAAK,oBAAoB,eAAe,KAAK,cAAc,GAC3D,KAAK,oBAAoB,WAAW,KAAK,UAAU,GACnD,KAAK,oBAAoB,SAAS,KAAK,YAAY,GACnD,KAAK,oBAAoB,eAAe,KAAK,cAAc,GAC3D,KAAK,oBAAoB,eAAe,KAAK,cAAc,GAC3D,KAAK,oBAAoB,aAAa,KAAK,YAAY,GACvD,KAAK,oBAAoB,iBAAiB,KAAK,gBAAgB,GAC/D,SAAS,oBAAoB,eAAe,KAAK,qBAAqB,GACtE,SAAS,oBAAoB,SAAS,KAAK,UAAU,EAAI,GACzD,SAAS,oBAAoB,aAAa,KAAK,cAAc,EAAI,GACjE,SAAS,oBAAoB,WAAW,KAAK,kBAAkB,GAC/D,KAAK,iBAAiB,WAAW,GACjC,KAAK,wBAAwB,WAAW,GACxC,KAAK,mBAAmB,QAAQ,GAChC,KAAK,aAAa,QAAQ,GAC1B,KAAK,YAAY,QAAQ,GACzB,KAAK,aAAa,QAAQ,GAC1B,KAAK,cAAc,MACnB,KAAK,mBAAmB,GACxB,KAAK,OAAO,MAAM,OAAO,GACzB,KAAK,QAAQ,KAAA,GACb,KAAK,mBAAmB,GAAG,EAAI,GAC/B,KAAK,iBAAiB,WAAW,GAMjC,KAAK,YAAY,GAGjB,KAAK,UAAU,IACf,KAAK,iBAAiB,GACtB,KAAK,qBAAqB;CAC5B;CAEA,QAA2B,GAA+B;EAExD,IAAI,CAAC,KAAK,aAAa;GACrB,KAAK,gBAAgB,EAAK;GAC1B;EACF;EAEA,IAAI,EAAQ,IAAI,SAAS,GAAG;GAC1B,IAAI,KAAK,SAAS;IAEhB,IADA,KAAK,gBAAgB,GACjB,CAAC,KAAK,OAAO;KAUf,IAAM,IAAQ,KAAK,WAAW;KAI9B,AAHA,KAAK,QAAQ,EAAwB,gBAAgB,MAAM,CAAK,GAChE,KAAK,MAAM,MAAM,aAAa,QAAQ,MAAM,GAC5C,KAAK,MAAM,MAAM,aAAa,cAAc,OAAO,GACnD,KAAK,MAAM,MAAM,iBAAiB,SAAS,KAAK,YAAY;IAC9D;IAQA,IAAI,CAAC,KAAK,aAAa;KACrB,KAAK,cAAc,KAAK,SAAS,MAAM,KAAK,MAAM,KAAK;KAGvD,KAAK,IAAM,KAAW,KAAK,iBAEzB,AADA,KAAK,gBAAgB,OAAO,EAAQ,KAAK,GACzC,KAAK,gBAAgB,IAAI,EAAQ,OAAO,KAAK,YAAY,MAAM,EAAQ,KAAK,CAAC;KAQ/E,KAAK,IAAM,KAAa,KAAK,iBAAiB,QAAQ,GACpD,KAAK,gBAAgB,IAAI,EAAU,OAAO,KAAK,YAAY,MAAM,EAAU,KAAK,CAAC;IAErF;IAGA,AADA,KAAK,iBAAiB,QAAQ,KAAK,MAAM,SAAS;KAAE,WAAW;KAAM,SAAS;IAAK,CAAC,GACpF,4BAA4B;KACtB,AAAC,KAAK,WAAY,KAAK,UAC3B,KAAK,aAAa,GAClB,EAAoB,KAAK,MAAM,OAAO,EAAE,WAAW,KAAK,qBAAqB,CAAC,GAC9E,KAAK,uBAAuB,IAc5B,4BAA4B;MACtB,AAAC,KAAK,WAAY,KAAK,SAC3B,KAAK,gBAAgB;KACvB,CAAC;IACH,CAAC;GACH,OAWE,AAVA,KAAK,gBAAgB,EAAK,GAG1B,KAAK,qBAAqB,MAE1B,KAAK,aAAa,QAAQ,GAC1B,KAAK,cAAc,MACnB,KAAK,iBAAiB,WAAW,GACjC,KAAK,wBAAwB,WAAW,GACxC,KAAK,oBAAoB,IACzB,KAAU,wBAAwB;GAEpC,AAAI,KAAK,gBAAgB,QAAQ,KAAG,KAAK,gBAAgB,KAAK,OAAO;EACvE;EACA,AAAI,EAAQ,IAAI,cAAc,KAAG,KAAK,gBAAgB;CACxD;CAQA,OAAO,GAAW,GAAW;EAC3B,KAAK,QAAQ,GAAG,GAAG,IAAM,EAAI;CAC/B;CAEA,QACE,GACA,GACA,GACA,GACA,IAAqB,IACZ;EAiBT,OAhBA,CAAI,KAAK,aACT,KAAK,KAAK,GACV,KAAK,KAAK,GACV,KAAK,uBAAuB,GAC5B,KAAK,+BAA+B,GACpC,KAAK,sBAAsB,GAC3B,KAAK,mBAAmB,IAAI,GACxB,KAAK,WACP,KAAK,mBAAmB,GAAG,EAAI,GAC/B,KAAK,iBAAiB,WAAW,GAEjC,KAAK,wBAAwB,WAAW,GACxC,KAAK,iBAAiB,GACf,OAET,KAAK,UAAU,IACR;CACT;CAMA,QAAQ;EACD,KAAK,YACV,KAAK,UAAU,IACf,KAAK,wBAAwB,WAAW,GAapC,KAAK,iBAAiB,eAAa,KAAK,sBAAsB,GAClE,KAAK,eAAe;CACtB;CAUA,mBAA2B;EACrB,MAAK,qBAAsB,KAAK,YACpC,KAAK,oBAAoB,IACzB,4BAA4B;GAC1B,KAAK,oBAAoB,IACpB,KAAK,WAAY,KAAK,SAC3B,KAAK,aAAa;EACpB,CAAC;CACH;CAGA,eAAuB;EAChB,KAAK,UACV,KAAK,gBAAgB,GACrB,KAAK,cAAc,GACnB,KAAK,aAAa,KAAK;CACzB;CAuBA,gBAAwB;EACtB,IAAM,IAAQ,KAAK,OAAO;EAC1B,IAAI,CAAC,GAAO;EAWZ,IAAM,IAAiB,KAAK,gBAAgB;EAG5C,AADA,EAAM,MAAM,aAAa,UACzB,EAAM,MAAM,UAAU;EAEtB,IAAM,IAAa,KAAK,wBAAwB,KAAK,GAa/C,IAAkB;GACtB,KAAK;GACL,QAAQ,IAAmB;GAC3B,MAAM;GACN,OAAO;EACT,GAmBM,IAAa,KAAK,sBACnB,CAAC,UAAU,KAAK,IAChB,CAAC,gBAAgB,WAAW;EAEjC,EAAqB,EAAE,6BAA6B,IAAI,QAAQ,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,EAAE,GAAG,GAAO;GAChG,UAAU;GAUV,WAAW,EAAW;GACtB,YAAY,CAMV,EAAK;IAAE,oBAAoB,CAAC,GAAG,CAAU;IAAG,SAAS;GAAgB,CAAC,GACtE,EAAM;IAAE,SAAS;IAAiB,WAAW;GAAK,CAAC,CACrD;EACF,CAAC,CAAC,CAAC,MAAM,EAAE,MAAG,MAAG,cAAW,wBAAqB;GAC/C,IAAI,CAAC,KAAK,WAAW,KAAK,OAAO,UAAU,KAAS,CAAC,KAAK,wBAAwB,UAAU,CAAU,GAAG;GACzG,IAAM,IAAS,EAAe,OAAO,KAAK,GASpC,IAAoB,EAAU,SAAS,GAAG,IAM5C,IAAS,IACP,UACA,SAPF,MAAW,IACT,WACA,IAAS,IACP,UACA;GAIR,KAAK,mBAAmB,GAAO,GAAG,GAAG,GAAkB,EAAU,WAAW,QAAQ,IAAI,QAAQ,QAAQ;EAC1G,CAAC;CACH;CAaA,kBAAkC;EAChC,IAAM,IAAQ,OAAO,WAAW,iBAAiB,IAAI,CAAC,CAAC,iBAAiB,qCAAqC,CAAC;EAC9G,OAAO,OAAO,SAAS,CAAK,IAAI,IAAQ;CAC1C;CASA,mBACE,GACA,GACA,GACA,GACA,GACA;EAIA,AAHA,EAAM,MAAM,OAAO,GAAG,EAAE,KACxB,EAAM,MAAM,MAAM,GAAG,EAAE,KACvB,EAAM,MAAM,YAAY,2CAA2C,GAAG,EAAe,GAAG,GAAkB,GAC1G,EAAM,MAAM,aAAa;CAC3B;CAcA,gBAAwB,IAAU,GAAG;EAEnC,IAAM,IADQ,KAAK,OAAO,QAAQ,iBAA8B,sCAAsC,CAAA,GAC5E;EACrB,MACL,EAAc,GAAW,EAAE,sBAAsB,KAAK,6BAA6B,CAAC,GAChF,MAAU,KAAK,OACf,EAAoB,CAAC,KAAK,OAAO,KAAK,CAAC,KAC3C,4BAA4B;GACtB,AAAC,KAAK,WAAY,KAAK,SAC3B,KAAK,gBAAgB,IAAU,CAAC;EAClC,CAAC;CACH;CAEA,kBAA0B;EACxB,IAAM,IAAO,KAAK;EAClB,IAAI,CAAC,GAAM;EACX,IAAM,IAAU,EAAK;EAOrB,AALA,EAA0B,MAAM,GAAS,KAAK,kBAAkB,CAAW,GAC3E,KAAK,eAAe,GAIpB,EAAuB,GAAS,qBAAqB;EAKrD,IAAM,IAAU,EAAwB,GAAS,CAAW;EAE5D,AADA,EAAsB,MAAM,GAAS,KAAK,kBAAkB,CAAW,GACvE,EAAwB,GAAS,CAAO;CAC1C;CAEA,qBAA6B;EAC3B,IAAM,IAAO,KAAK;EACb,MAEL,KAAK,mBAAmB,GAAG,EAAI,GAC/B,KAAK,iBAAiB,WAAW,GAEjC,EAA6B,MAAM,EAAK,SAAS,KAAK,gBAAgB;CACxE;CASA,aAAwC;EACtC,IAAM,IAAQ,SAAS,cAAc,QAAQ;EAC7C,EAAM,YAAY;EAalB,IAAM,IAAc,SAAS,cAAc,OAAO;EAUlD,OATA,EAAY,cAAc,GAC1B,EAAM,OAAO,CAAW,GACxB,EAAM,aAAa,GAAY,EAAE,GACjC,EAAM,iBAAiB,SAAS,KAAK,aAAa,GAClD,EAAM,iBAAiB,UAAU,KAAK,cAAc,GAEpD,KAAK,aAAa,CAAC,CAAC,OAAO,CAAK,GAChC,EAAM,UAAU,GAChB,KAAK,SAAS,GACP;CACT;CAgBA,eAAoC;EAClC,OAAO,EAAiB,IAAI,CAAC,EAAE,eAAe,KAAK,SAAS;CAC9D;CAOA,cAAsB;EACpB,IAAM,IAAQ,KAAK;EACd,MACL,KAAK,SAAS,KAAA,GACV,EAAM,QAAM,EAAM,MAAM,GAC5B,EAAM,OAAO;CACf;CAYA,wBAAgC;EAC9B,IAAM,IAAQ,KAAK;EACnB,AAAI,GAAO,QAAM,EAAM,MAAM;CAC/B;CAiCA,MAAc,0BAA0B;EACtC,IAAM,IAAO,KAAK;EACd,OAAU,MAAM,EAAoB,EAAK,KAAK,MAC9C,MAAK,WAAY,KAAK,eAAe,KAAK,UAAU,MAwBxD,KAAK,YAAY,GAGjB,KAAK,mBAAmB,GACxB,GAAM,MAAM,OAAO,GACnB,KAAK,QAAQ,KAAA;CACf;CAEA,iBAAyB;EAEvB,AADA,EAAgB,IAAI,CAAC,CAAC,SAAQ,MAAS,EAAM,aAAa,QAAQ,qBAAqB,CAAC,GACxF,EAAuB,MAAM,qBAAqB;CACpD;CAEA,aAAqB,GAAmB,IAAY,IAAO;EACzD,IAAI,CAAC,KAAK,WAAW,CAAC,EAAK,aAAa,SAAS,KAAK,EAAK,aAAa,UAAU,GAAG;EAErF,IAAM,IAAQ,KAAK,cAAc,CAAI;EACrC,IAAI,MAAU,MAAM,KAAK,oBAAoB,OAAW,GAAM;EAE9D,KAAK,mBAAmB,CAAK;EAC7B,IAAM,IAAiB,KAAK,iBAAiB,KAAK,CAAI;EACtD,IAAI,GAAgB;GAmBlB,AAlBA,KAAK,gBAAgB,KAAS,GAC9B,KAAK,oBAAoB,KAAS,GAa9B,KAAK,eAAa,KAAK,gBAAgB,IAAI,EAAe,OAAO,KAAK,YAAY,MAAM,EAAe,KAAK,CAAC,GACjH,EAAK,aAAa,UAAU,EAAE,GAC9B,KAAK,iBAAiB,GAAM,CAAc,GAC1C,EAAoB,EAAe,OAAO,EAAE,aAAU,CAAC,GACvD,KAAK,aAAa,KAAK;GACvB;EACF;EACA,IAAI,EAAgB,CAAI,CAAC,CAAC,WAAW,GAAG;EAOxC,IAAM,IAAU,EAAwB,mBAAmB,MAAM,KAAK,MAAM;EAmB5E,AAlBA,EAAQ,MAAM,QAAQ,QAAQ,OAAO,CAAK,GAC1C,EAAQ,MAAM,aAAa,QAAQ,MAAM,GACzC,EAAQ,MAAM,aAAa,cAAc,KAAK,GAC9C,EAAQ,MAAM,MAAM,aAAa,UACjC,EAAQ,MAAM,iBAAiB,SAAS,KAAK,YAAY,GACzD,EAAiB,GAAM,EAAQ,OAAO,GAEtC,KAAK,gBAAgB,KAAS,GAC9B,KAAK,oBAAoB,KAAS,GAM9B,KAAK,eAAa,KAAK,gBAAgB,IAAI,EAAQ,OAAO,KAAK,YAAY,MAAM,EAAQ,KAAK,CAAC,GACnG,EAAK,aAAa,UAAU,EAAE,GAC9B,KAAK,iBAAiB,GAAM,CAAO,GACnC,EAAoB,EAAQ,OAAO,EAAE,aAAU,CAAC,GAChD,KAAK,aAAa,KAAK;CACzB;CAEA,mBAA2B,GAAe,IAAY,IAAO;EAC3D,KAAK,IAAI,IAAQ,KAAK,gBAAgB,SAAS,GAAG,KAAS,GAAO,KAAS;GACzE,IAAM,IAAU,KAAK,gBAAgB,IAC/B,IAAO,KAAK,oBAAoB;GAEtC,AADA,GAAM,gBAAgB,QAAQ,GAC1B,CAAC,KAAQ,KACP,KAAS,KAAK,gBAAgB,EAAQ,KAAK,GAC/C,KAAK,qBAAqB,GAAS,CAAI,GACvC,EAAQ,MAAM,OAAO,KAErB,KAAK,iBAAiB,WAAW,GAAM,CAAO;EAElD;EAEA,AADA,KAAK,gBAAgB,SAAS,GAC9B,KAAK,oBAAoB,SAAS;CACpC;CAEA,qBAA6B,GAA4B,GAAoB;EACtE,MACL,EAAiB,EAAQ,SAAS,CAAI,GAEtC,EAAuB,GAAM,qBAAqB;CACpD;CAEA,cAAsB,GAA2B;EAE/C,IADa,KAAK,OACR,MAAM,SAAS,CAAI,GAAG,OAAO;EACvC,IAAM,IAAc,KAAK,gBAAgB,WAAU,MAAW,EAAQ,MAAM,SAAS,CAAI,CAAC;EAC1F,OAAO,MAAgB,KAAK,KAAK,IAAc;CACjD;CAEA,iBAAyB,GAAmB,GAA4B;EAItE,IAAI,EAAQ,MAAM,yBAAyB,qBAAqB,EAAQ,MAAM,cAAc,MAAM;GAChG,IAEM,IAFW,EAAK,sBAED,CAAA,CAAS,QAAQ,EAAQ,MAAM,sBAAsB,CAAC,CAAC,QAAQ,KAAW,OAAO,YAGhG,KAAS,KAAK,uBAAuB,IAAI,EAAQ,KAAK,KAAK,KAAK;GAEtE,AADA,KAAK,uBAAuB,IAAI,EAAQ,OAAO,CAAK,GACpD,EAAqB,GAAM,EAAQ,OAAO;IACxC,UAAU;IACV,WAAW,IAAe,gBAAgB;IAC1C,YAAY,CAAC,EAAM;KAAE;KAAS,WAAW;IAAK,CAAC,CAAC;GAClD,CAAC,CAAC,CAAC,MAAM,EAAE,MAAG,MAAG,wBAAqB;IACpC,IAAI,CAAC,KAAK,gBAAgB,SAAS,CAAO,KAAK,MAAU,KAAK,uBAAuB,IAAI,EAAQ,KAAK,GAAG;IAEzG,IAAM,IAAS,EAAe,OAAO,KAAK,GACpC,IAAS,EAAe,OAAO,KAAK,GACpC,IAAmB,IAAS,IAAI,UAAU,QAC1C,IAAiB,IAAS,IAAI,WAAW;IAO/C,AANA,EAAQ,MAAM,MAAM,OAAO,GAAG,EAAE,KAChC,EAAQ,MAAM,MAAM,MAAM,GAAG,EAAE,KAC/B,EAAQ,MAAM,MAAM,YAClB,2CACA,GAAG,EAAe,GAAG,GACvB,GACA,EAAQ,MAAM,MAAM,aAAa;GACnC,CAAC;GACD;EACF;EAEA,IAAM,IAAW,EAAK,sBAAsB,GACtC,IAAc,EAAQ,MAAM,sBAAsB,GAElD,IAAe,EAAS,QAAQ,EAAY,QAAQ,KAAW,OAAO,YACtE,IAAO,IAAe,EAAS,QAAQ,KAAK,IAAI,GAAS,EAAS,OAAO,EAAY,KAAK,GAC1F,IAAM,KAAK,IAAI,KAAK,IAAI,GAAS,EAAS,GAAG,GAAG,OAAO,cAAc,EAAY,SAAS,CAAO;EAKvG,AAHA,EAAQ,MAAM,MAAM,OAAO,GAAG,EAAK,KACnC,EAAQ,MAAM,MAAM,MAAM,GAAG,EAAI,KACjC,EAAQ,MAAM,MAAM,YAAY,2CAA2C,IAAe,aAAa,WAAW,GAClH,EAAQ,MAAM,MAAM,aAAa;CACnC;CAEA,gBAAwB,GAAe;EACrC,EAAwB,MAAM,CAAI;CACpC;CA6CA,mBAA2B;EAKzB,AAJI,KAAK,oBAAoB,KAAA,MAC3B,aAAa,KAAK,eAAe,GACjC,KAAK,kBAAkB,KAAA,IAEzB,KAAK,mBAAmB,KAAA;CAC1B;CAGA,uBAAwC;EACtC,IAAM,IAAW,KAAK;EAOtB,OANI,MAAa,OAAa,KAE1B,YAAY,IAAI,IAAI,IAAW,KACjC,KAAK,qBAAqB,MACnB,MAEF;CACT;CA4CA,mBAA2B,GAAe;EACxC,OAAO,MAAU,IAAI,KAAK,QAAQ,KAAK,gBAAgB,IAAQ;CACjE;CAEA,eAAuB,GAA8B;EACnD,IAAM,IAAY,KAAK,mBAAmB,CAAK;EAE/C,OADK,IACE,EAAgB,EAAU,OAAO,IADjB,CAAC;CAE1B;CAiBA,0BAAkC;EAChC,IAAI,KAAK,gBAAgB,SAAS,GAAG;GACnC,IAAM,IAAQ,KAAK,gBAAgB,SAAS,GACtC,IAAS,KAAK,oBAAoB;GAGxC,AAFA,KAAK,mBAAmB,CAAK,GAC7B,EAAc,CAAM,GACpB,KAAK,aAAa,KAAK;EACzB,OACE,KAAK,eAAe;CAExB;CAEA,yBAAiC,GAAU;EACrC,CAAC,KAAK,WAAW,KAAK,gBAAgB,KAAK,kBAAkB,CAAC,KAClE,EAAE,eAAe;CACnB;CAEA,gBAAwB,IAAS,KAAK,SAAS;EAC7C,KAAK,YAAY,KAAK,KAAU,CAAC,KAAK,YAAY;CACpD;CAGA,gBAAwB,GAAoB;EAE1C,AADA,KAAK,gBAAgB,IAAI,CAAK,CAAC,EAAE,QAAQ,GACzC,KAAK,gBAAgB,OAAO,CAAK;CACnC;CAEA,kBAA0B,GAAmB;EAE3C,OAAO,KAAK,aAAa,cAAc,CAAC,KAAK;CAC/C;CAEA,SAAkB;EAChB,OAAO,CAAI;;4BAEa,KAAK,cAAc;;;CAG7C;CAEA,gBAAwB;EAItB,AAHA,KAAK,eAAe,GAGhB,KAAK,WAAS,KAAK,iBAAiB;CAC1C;AAKF;AAhkCG,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,YAAA,KAAA,CAAA,GACzC,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;CAAM,WAAW;AAAiB,CAAC,CAAA,GAAA,EAAA,WAAA,gBAAA,KAAA,CAAA,GAEtE,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;CAAM,WAAW;AAAa,CAAC,CAAA,GAAA,EAAA,WAAA,aAAA,KAAA,CAAA,GAUlE,EAAA,CAAA,EAAS;CAAE,MAAM;CAAQ,WAAW;AAAmB,CAAC,CAAA,GAAA,EAAA,WAAA,kBAAA,IAAA,GAUxD,EAAA,CAAA,EAAM,CAAA,GAAA,EAAA,WAAA,WAAA,KAAA,CAAA,GACN,EAAA,CAAA,EAAM,CAAA,GAAA,EAAA,WAAA,MAAA,KAAA,CAAA,GACN,EAAA,CAAA,EAAM,CAAA,GAAA,EAAA,WAAA,MAAA,KAAA,CAAA,GA7BR,IAAA,EAAA,CAAA,EAAc,qBAAqB,CAAA,GAAA,CAAA"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/context-menu/index.ts"],"sourcesContent":["import { computePosition, flip, shift } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { UserChangeController } from '@/shared/events/user-change'\nimport {\n createClosingSubmenuStack,\n createMenuHoverBinder,\n createMenuOutsideClickGuard,\n getEnabledMenuLevelItems,\n getFocusedMenuItemFromPanels,\n handleMenuKeyboard,\n type MenuHoverDelegate,\n type MenuKeyboardDelegate\n} from '@/shared/menu-behavior'\nimport { createMenuPortalOverlay, type MenuPortalOverlay } from '@/shared/menu-portal/menu-portal'\nimport {\n captureFrameworkAnchors,\n findFocusedMenuItem,\n focusMenuItem,\n getMenuChildren,\n getMenuItemFromEvent,\n hideNestedMenuChildren,\n moveMenuChildren,\n orderManagedMenuItems,\n reconcileManagedMenuItems,\n restoreFrameworkAnchors,\n returnManagedMenuItemsToSlot\n} from '@/shared/menu-portal/menu-tree'\nimport { normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { defineOverlayPositioningGeneration } from '@/shared/overlay/positioning-generation'\nimport { hideOverlayPresence, showOverlayPresence } from '@/shared/overlay/presence'\nimport { findNearestTheme } from '@/shared/overlay/theme-overlay-scope'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\n\nimport scrimStyle from './scrim.css?inline'\nimport style from './style.css?inline'\n\nconst MARKER_TEXT = 'wui-context-menu-item'\n\n/** 与 iOS / Android 原生长按一致的默认时长。 */\nconst DEFAULT_LONG_PRESS_DELAY = 500\n/** long-press-delay 的上界:超过 5s 的长按不再是「长按」,等同没有该属性。 */\nconst MAX_LONG_PRESS_DELAY = 5000\n/** 按住期间的容差位移;超出即视为滚动/拖拽意图,取消长按。 */\nconst LONG_PRESS_MOVE_TOLERANCE = 10\n/**\n * 长按开菜单后吸收浏览器补发事件的窗口上限。\n *\n * 这不是时序保证:实测补发几乎紧跟抬手(touchend(1019ms) → click(1022ms),间隔 3ms),\n * 1000ms 远大于实际需要,只是一个陈旧兜底上限。真正的边界是下一次 `pointerdown`\n * (`_onPointerDown` 无条件清零,含鼠标)与菜单关闭,两条硬边界都在窗口判定之外,\n * 所以窗口只负责兜住「补发事件既没等到下一次按下、菜单也没关闭」这种不该发生的路径。\n */\nconst LONG_PRESS_FOLLOW_UP_WINDOW_MS = 1000\n\n/**\n * 菜单打开期间铺在整屏上的 scrim `<dialog>`。\n *\n * 用 `<dialog>` + `showModal()` 而不是 plain `div`:模态 dialog 进 top layer,\n * 于是「z-index 谁高谁低」这道约束整体消失 —— 面板与 scrim 同在 top layer,\n * 而 top layer 之下的任何 z-index 都碰不到它们。plain `div` 也能修好 light-dismiss\n * 穿透,但那样 scrim 必须靠 z-index 压过 `--wui-layer-menu`(100),是另一套易碎的约定。\n */\nconst SCRIM_ATTR = 'data-wui-menu-scrim'\n\ntype CloseSource = 'menu-item' | 'default'\n\n/** 首项聚焦的最大尝试次数(含首次)。见 `_focusFirstItem`。 */\nconst FOCUS_FIRST_ITEM_ATTEMPTS = 5\n\n/** 面板与视口边缘的最小间距,与既有夹取算术同源。 */\nconst VIEWPORT_PADDING = 8\n\n@customElement('web-ui-context-menu')\nexport class WebUiContextMenu extends LitElement {\n static override styles = unsafeCSS(style)\n\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true, attribute: 'no-scroll-lock' }) noScrollLock = false\n /** opt-in:触屏长按打开菜单。默认关闭,鼠标路径靠原生 contextmenu,无需此属性。 */\n @property({ type: Boolean, reflect: true, attribute: 'long-press' }) longPress = false\n\n private _longPressDelay = DEFAULT_LONG_PRESS_DELAY\n\n /**\n * 走包内既有的 `normalizeNumber` 通道(与 `dropdown.offset`、`popover.offset` 同形)。\n * 不能只靠 `Math.max(0, …)` 挡负数:`Math.max(0, NaN) === NaN`,而属性写错\n * (`long-press-delay=\"abc\"`)时 Lit 的 Number converter 给的正是 NaN,\n * `setTimeout(fn, NaN)` 会立刻触发,长按就在 pointerdown 同一帧开菜单、绕过全部时长语义。\n */\n @property({ type: Number, attribute: 'long-press-delay' })\n get longPressDelay(): number {\n return this._longPressDelay\n }\n set longPressDelay(v: number) {\n const old = this._longPressDelay\n this._longPressDelay = normalizeNumber(v, 0, MAX_LONG_PRESS_DELAY, DEFAULT_LONG_PRESS_DELAY)\n this.requestUpdate('longPressDelay', old)\n }\n\n @state() private _isOpen = false\n @state() private _x = 0\n @state() private _y = 0\n\n private _activeSubmenus: MenuPortalOverlay[] = []\n private _activeSubmenuItems: HTMLElement[] = []\n private _longPressTimer: ReturnType<typeof setTimeout> | undefined\n private _longPressOrigin: { x: number; y: number } | undefined\n /**\n * 长按自行开菜单的时刻。浏览器会为**同一次触摸**补发两个事件,二者都属于\n * 那一次意图,不是两次独立操作:\n *\n * - 原生 `contextmenu`:长按被识别时立刻派发,会重复打开(`_openAt` 走已开分支)。\n * - 合成 `click`:`touchend` 之后由引擎补发,会把刚打开的菜单立刻 light-dismiss。\n * 实测真实触控管线下的顺序为 pointerdown(9ms) → 长按开菜单(≈509ms) →\n * touchend(1019ms) → click(1022ms),缺这一步菜单开了就被自己的补发事件关掉。\n *\n * 因此在窗口内吸收这两个补发事件;窗口由下一次 `pointerdown` 关闭。\n */\n private _longPressOpenedAt: number | null = null\n // 子菜单 dialog 定位的唯一写者代数(按 panel 键控):同帧关闭→重开复用同一 panel\n // 时只允许最新一次定位写入;不同层级子菜单各有 panel,互不作废。\n private readonly _submenuPositionEpochs = new WeakMap<HTMLElement, number>()\n private _menu?: MenuPortalOverlay\n /**\n * 模态 scrim。菜单打开期间存在并处于 `showModal()` 状态,关闭时随面板一起移除。\n *\n * 面板必须**显式**挂进它(`createMenuPortalOverlay` 的第三参):`resolveOverlayContainer`\n * 查的是 target 的**祖先**里有无已打开的 dialog,而 scrim 是面板的反方向,祖先发现查不到。\n */\n private _scrim?: HTMLDialogElement\n /**\n * 本次开启是否**水平居中于触发点**(触屏长按路径)。鼠标 / 键盘路径仍左上角对齐光标。\n *\n * 只切换 placement 的对齐量,不切换定位算法 —— 两条入口共用 `_positionMenu` 里的\n * 同一套 `computePosition` + `flip` / `shift`,垂直契约(上缘对齐触发点、向下展开、\n * 放不下翻到上方)完全一致。\n *\n * 触屏要居中:手指按下去的位置是「这块内容在这里」的意思,菜单在它正下方居中展开,\n * 正是 iOS / Android 原生长按菜单的形态;左对齐会让菜单整体偏到手指右侧,看起来像\n * 弹在了别的东西上面。\n *\n * 鼠标不居中:桌面右键的肌肉记忆是菜单**左上角**出现在光标处。更要紧的是居中会让光标\n * 落在面板上缘正中 —— 也就是落在第一项的命中区里。后果是菜单一开就有项处于 hover 高亮,\n * 并且紧接着再右键一次(想开子菜单)会打在菜单项上而不是空白处。`overlay-in-dialog`\n * 里 `openAt(100, 400)` 那条用例钉的 `'top left'` 正是这条契约。\n *\n * 判据是**开启来源**(调用点显式传入),不靠运行时嗅探 pointerType 或视口宽度:\n * 后两者会让同一块屏幕上的两种输入拿到两套布局,且与本组件 opt-in 的 `long-press`\n * 属性不同源。\n */\n private _centerOnPressPoint = false\n\n /**\n * 本次关闭的来源,决定焦点归还走哪条路。语义见 `close()` 里的说明。\n *\n * 只有菜单项激活这一条需要抢在退场之前出顶层;点外面 / Escape / 程序式关闭一律\n * 保留 dialog 默认效果 —— 那正是用户「回到原处」的期望。\n */\n private _closeSource: CloseSource = 'default'\n /**\n * 「可激活菜单项被点了」这件事是否发生在**当前这一次事件派发里**,且关闭尚未被组件接管。\n *\n * 由面板上的**捕获阶段**监听器置位,早于菜单项自己的监听器;作用域与理由(尤其是为什么\n * 不能留到会话结束)见 `_onMenuItemActivation`。\n *\n * 存在的理由是 `close()` 的出顶层判定不能只看 `_closeSource`:消费方完全可以在自己的\n * 菜单项回调里直接调 `close()`(interweave 的右键重命名就是 `emit` 之后自己关),\n * 那时 `_closeSource` 还是 `'default'`,scrim 就一直留在 top layer 里没退出,\n * 消费方随后把焦点送进编辑器的动作落在 inert 文档上被浏览器静默丢弃。\n *\n * 消费方先关、组件后关的路径(`_onMenuClick` 的 `_closeFromUser('menu-item')`)随后看到的是\n * `'menu-item'`,两条判定同向,不会互相抵消。\n */\n private _menuItemActivated = false\n /** 根面板的开启会话句柄;未开启时为 null。查询走它。 */\n private _menuHandle: OpenOverlayHandle | null = null\n /**\n * 子菜单 panel → 会话句柄。子层经 `adopt` 进根句柄子树(默认不成为独立候选),\n * 但仍各自持有句柄:整层关闭或收尾动画结束时按 panel 精确撤销。\n */\n private readonly _submenuHandles = new Map<HTMLElement, OpenOverlayHandle>()\n // 行为层(hover / outside-click / 键盘 / submenu 收尾)由 shared/menu-behavior 驱动\n private readonly _outsideClickGuard = createMenuOutsideClickGuard(\n this,\n // 根句柄的 contains 递归覆盖全部已 adopt 的子层,一次查询替代逐 panel 判定。\n node => this._menuHandle?.contains(node) ?? false\n )\n private readonly _closingSubmenus = createClosingSubmenuStack<MenuPortalOverlay>({\n getPanel: container => container.panel,\n restoreItems: (container, parentItem) => this._restoreSubmenuItems(container, parentItem),\n dispose: container => {\n this._releaseSubmenu(container.panel)\n container.panel.remove()\n }\n })\n private readonly _hoverDelegate: MenuHoverDelegate = {\n getOpenDepth: () => this._activeSubmenus.length,\n getLevelItems: level => this._getLevelItems(level),\n getActiveItem: level => this._activeSubmenuItems[level],\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenu: item => this._openSubmenu(item),\n closeFrom: level => {\n this._closeSubmenusFrom(level)\n this._hoverBinder.bind()\n }\n }\n private readonly _hoverBinder = createMenuHoverBinder(this._hoverDelegate, 'web-ui-dropdown-item')\n private readonly _keyboardDelegate: MenuKeyboardDelegate = {\n getFocusedItem: event =>\n getFocusedMenuItemFromPanels(\n event,\n [this._menu?.content, ...this._activeSubmenus.map(submenu => submenu.content)],\n 'web-ui-dropdown-item'\n ),\n getLevelOf: item => {\n const level = this._getItemLevel(item)\n return level === -1 ? undefined : level\n },\n getEnabledItems: level => getEnabledMenuLevelItems(this._getLevelContainer(level)?.content),\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenuInstant: item => this._openSubmenu(item, true),\n closeToParent: level => {\n const parent = this._activeSubmenuItems[level - 1]\n this._closeSubmenusFrom(level - 1)\n this._hoverBinder.bind()\n return parent\n },\n closeDeepestOrAll: () => this._closeLastSubmenuOrMenu()\n }\n /*\n * 开启态浮层(issue #120 Block 1)。根面板是登记对象:子菜单经 adopt 成为它的逻辑\n * 后代;关闭动作仍走 closeDeepestOrAll(最深子菜单优先)。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._keyboardDelegate.closeDeepestOrAll(),\n isConnected: () => this.isConnected\n })\n private readonly _menuItemAnchors = new Map<HTMLElement, Comment>()\n private readonly _scrollLock = defineScrollLockLease().make()\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n private _suppressInitialFocusVisible = true\n private _refreshScheduled = false\n // 主菜单 dialog 路径与子菜单一样使用代数令牌;快速 openAt/refresh 或 close 后,\n // 迟到的 positioning promise 只能被丢弃,不能覆盖最新坐标。\n private readonly _menuPositionGeneration = defineOverlayPositioningGeneration().make()\n // 菜单打开期间宿主可能不经重定位直接改写子内容(网络推送、定时器等),\n // 新节点缺隐藏 slot 会可见叠加到菜单上;观察 portal 内容并在下一帧重新同步。\n private readonly _contentObserver = new MutationObserver(() => {\n if (this._isOpen) this._scheduleRefresh()\n })\n\n get isOpen(): boolean {\n return this._isOpen\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._hideMenuItems()\n this.addEventListener('contextmenu', this._onContextMenu)\n this.addEventListener('keydown', this._onKeydown)\n this.addEventListener('click', this._onMenuClick)\n this.addEventListener('pointerdown', this._onPointerDown)\n this.addEventListener('pointermove', this._onPointerMove)\n this.addEventListener('pointerup', this._onPointerUp)\n this.addEventListener('pointercancel', this._onPointerCancel)\n document.addEventListener('contextmenu', this._onContextMenuOutside)\n document.addEventListener('wheel', this._onWheel, { capture: true, passive: false })\n document.addEventListener('touchmove', this._onTouchMove, { capture: true, passive: false })\n document.addEventListener('keydown', this._onDocumentKeydown)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('contextmenu', this._onContextMenu)\n this.removeEventListener('keydown', this._onKeydown)\n this.removeEventListener('click', this._onMenuClick)\n this.removeEventListener('pointerdown', this._onPointerDown)\n this.removeEventListener('pointermove', this._onPointerMove)\n this.removeEventListener('pointerup', this._onPointerUp)\n this.removeEventListener('pointercancel', this._onPointerCancel)\n document.removeEventListener('contextmenu', this._onContextMenuOutside)\n document.removeEventListener('wheel', this._onWheel, true)\n document.removeEventListener('touchmove', this._onTouchMove, true)\n document.removeEventListener('keydown', this._onDocumentKeydown)\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._outsideClickGuard.dispose()\n this._hoverBinder.dispose()\n this._scrollLock.release()\n this._menuHandle?.release()\n this._menuHandle = null\n this._returnItemsToSlot()\n this._menu?.panel.remove()\n this._menu = undefined\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n /*\n * scrim 挂在宿主以外的容器里(theme overlay root,无主题时 document.body),不随宿主一起\n * 脱离文档。不在这里收掉就会留下一张仍然 `showModal()` 的全屏 dialog:top layer 不受宿主\n * 移除影响,整个页面就此锁死。\n */\n this._closeScrim()\n // 脱离文档即视为关闭:否则重连后 _isOpen 仍为 true 而 _menu 已清空,\n // 下次 openAt 会走已打开分支静默失败,菜单无法再打开。\n this._isOpen = false\n this._cancelLongPress()\n this._longPressOpenedAt = null\n }\n\n protected override updated(changed: Map<string, unknown>) {\n // Lit 的已排队更新可在卸载后执行,不能让已失效实例重新获取全局滚动锁。\n if (!this.isConnected) {\n this._syncScrollLock(false)\n return\n }\n\n if (changed.has('_isOpen')) {\n if (this._isOpen) {\n this._syncScrollLock()\n if (!this._menu) {\n /*\n * 顺序:先建 scrim 并 `showModal()`,再把面板挂进去。\n *\n * `showModal()` 会把此前聚焦的元素记为还原目标;此刻焦点仍在 opener 上,\n * 正是归还目标。面板随后 `_focusFirstItem()` 把焦点带进菜单,关闭时 UA 还回去。\n *\n * 面板挂进 scrim 是承重的,不是「顺手放一起」:top layer 只对**自身子树内的\n * 元素**做命中拦截,面板若留在 overlay root(scrim 之外),模态期间它自己就不可点。\n */\n const scrim = this._openScrim()\n this._menu = createMenuPortalOverlay('context-menu', this, scrim)\n this._menu.panel.setAttribute('role', 'menu')\n this._menu.panel.setAttribute('aria-label', '上下文菜单')\n this._menu.panel.addEventListener('click', this._onMenuClick)\n this._menu.panel.addEventListener('click', this._onMenuItemActivation, true)\n }\n /*\n * 登记与面板建立解耦。退场动画被打断时(`_closeMenuAfterPresence` 在\n * `hideOverlayPresence` 返回 false 后提前退出)`_menu` 还在,而关闭分支已经把句柄\n * 撤了;此时重开必须补 claim。否则菜单开着且可见却没有登记:Escape 关不掉它\n * (仲裁看不到它),而 `_menuHandle === null` 会让每次 document click——包括面板\n * 内部与子菜单父项——都被 `_outsideClickGuard` 判成外部点击而关闭菜单。\n */\n if (!this._menuHandle) {\n this._menuHandle = this._overlay.claim(this._menu.panel)\n // 重新 claim = 新会话:根层的子层随之作废,仍在场的子菜单面板要重新 adopt 回\n // 新句柄的子树,否则根句柄的 contains 看不到它们。\n for (const submenu of this._activeSubmenus) {\n this._submenuHandles.delete(submenu.panel)\n this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n }\n // 收尾栈里的子菜单面板同样「可见但已脱离快照」:`_closeSubmenusFrom` 只把它们\n // 移出 `_activeSubmenus`,面板仍在 DOM 里退场。**这个循环承重**:不补 adopt,\n // 它们就会落在句柄的登记树之外,被判成「面板外」,点它内部会关掉整张菜单。\n // 用例 `退场窗口内重开后,收尾中的子菜单面板在取回前后都不可被当成面板外` 守着\n // 这一条 —— 把循环体整段删掉,那条会红(实测 1 failed | 95 passed)。\n // 写进 `_submenuHandles` 还因为收尾结束时 `_releaseSubmenu` 按 panel 取句柄释放。\n for (const container of this._closingSubmenus.closing()) {\n this._submenuHandles.set(container.panel, this._menuHandle.adopt(container.panel))\n }\n }\n // 父项始终留在 menu.content 内,观察它即可覆盖各级子菜单在打开期间的内容重建。\n this._contentObserver.observe(this._menu.content, { childList: true, subtree: true })\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n showOverlayPresence(this._menu.panel, { isInstant: this._shouldOpenInstantly })\n this._shouldOpenInstantly = true\n /*\n * 首项聚焦必须再等一帧,不能和 `_refreshMenu()` 同帧。\n *\n * 两个原因都指向「太早」:\n *\n * 1. `showModal()` 的 dialog focusing steps 由 UA 排队执行,会在我们已经聚焦首项\n * **之后**再把焦点挪到 dialog 上 —— 模态化之前没有这一步,也就没有这场竞争。\n * 2. `_setupMenuItems()` 刚创建的 `web-ui-dropdown-item` 尚未完成 Lit 的 shadow\n * 渲染,`focusItem()` 此刻还找不到 `.item-inner`,静默无操作。\n *\n * 实测:同帧聚焦后 `document.activeElement` 是 `<dialog>` 而非首项;再等一帧聚焦,\n * 焦点稳定落在菜单项内。\n */\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem()\n })\n })\n } else {\n this._syncScrollLock(false)\n // 吸收窗口只覆盖「长按之后浏览器补发的那几个事件」。菜单已关,\n // 之后任何一次真实右键或点击都必须走正常路径,不能被这个窗口吞掉。\n this._longPressOpenedAt = null\n // release ⟺ 关闭:登记与仲裁归属同时撤销,退场动画只是视觉收尾。\n this._menuHandle?.release()\n this._menuHandle = null\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._refreshScheduled = false\n void this._closeMenuAfterPresence()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this._isOpen)\n }\n if (changed.has('noScrollLock')) this._syncScrollLock()\n }\n\n /**\n * 在指定视口坐标打开菜单。\n * @param x 水平坐标(px)。\n * @param y 垂直坐标(px)。\n * @returns 无返回值;命令式打开不派发 `open-change` 事件。\n */\n openAt(x: number, y: number) {\n this._openAt(x, y, true, true)\n }\n\n private _openAt(\n x: number,\n y: number,\n isInstant: boolean,\n suppressFocusVisible: boolean,\n centerOnPressPoint = false\n ): boolean {\n if (this.disabled) return false\n this._x = x\n this._y = y\n this._shouldOpenInstantly = isInstant\n this._suppressInitialFocusVisible = suppressFocusVisible\n this._centerOnPressPoint = centerOnPressPoint\n this._outsideClickGuard.arm()\n if (this._isOpen) {\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n // 先失效尚未完成的旧定位,再调度下一帧新代;避免新请求前的旧 promise 胜出。\n this._menuPositionGeneration.invalidate()\n this._scheduleRefresh()\n return false\n }\n this._isOpen = true\n return true\n }\n\n /**\n * 关闭菜单。\n * @returns 无返回值;命令式关闭不派发 `open-change` 事件。\n */\n close() {\n if (!this._isOpen) return\n this._isOpen = false\n this._menuPositionGeneration.invalidate()\n /*\n * 菜单项激活触发的关闭要**现在**就让 scrim 出顶层,不能等退场结束。\n *\n * 焦点归还由 UA 负责(`close()` 把焦点还给 `showModal()` 之前的 activeElement),\n * 而菜单项的回调常紧接着把焦点送到别处 —— 典型是右键重命名:回调把行换成编辑态、\n * 下一帧 `select()` 进 textarea。等到退场结束才关,UA 的归还会把焦点从 textarea\n * 拽回那一行,blur 掉编辑器,而 editable-text 的 blur 契约会**提交未改动的标题**。\n * 那是 interweave 上真实存在的 bug,不能放回来。\n *\n * **判定不能只看 `_closeSource`。** 消费方可以在自己的菜单项回调里直接调 `close()`:\n * 那时 `_onMenuClick` 还没跑到(它在面板上是冒泡阶段,而回调挂在条目上、先跑),\n * `_closeSource` 仍是 `'default'`,scrim 就留在 top layer 里不退,模态期间整份文档\n * 是 inert 的 —— 消费方紧接着送进编辑器的焦点被浏览器静默丢弃,症状是「重命名后焦点\n * 停在菜单项上、编辑器根本没进编辑态」,与本段要防的 blur 是同一个根因的两种落点。\n * `_menuItemActivated`(捕获阶段置位)把那一条路径也纳入判定,理由见它的注释。\n *\n * 此刻焦点还在菜单里,UA 的归还正好落在 opener 上——那本来就是用户期望的落点,\n * 应用随后的焦点转移不会被覆盖。元素本身要留到面板真正移除(见 `_releaseScrimModality`)。\n */\n if (this._closeSource === 'menu-item' || this._menuItemActivated) this._releaseScrimModality()\n this._closeSource = 'default'\n this._menuItemActivated = false\n }\n\n private readonly _closeFromUser = (source: CloseSource = 'default') => {\n if (!this._isOpen) return\n this._userOpenChange.mark()\n this._closeSource = source\n this.close()\n }\n\n // 同一帧内多次内容变化只刷新一次,避免观察者与刷新自身 append 形成循环。\n private _scheduleRefresh() {\n if (this._refreshScheduled || !this._isOpen) return\n this._refreshScheduled = true\n requestAnimationFrame(() => {\n this._refreshScheduled = false\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n })\n }\n\n // 重新同步 portal 内容并重定位;fresh-open、重定位与观察者触发的刷新共用。\n private _refreshMenu() {\n if (!this._menu) return\n this._setupMenuItems()\n this._positionMenu()\n this._hoverBinder.bind()\n }\n\n /**\n * 定位:**触屏长按与鼠标右键共用这一条 Floating UI 路径**。\n *\n * 两条入口现在只差「触发点的 x/y 从哪来」,布局契约完全一致:\n * `bottom-start` 表达「面板上缘 == 触发点 y、向下展开」,水平以触发点 x 为基准。\n * 之前有一条手写的贴底分支(先按点、再按视口下缘算 `top`),那是把「贴视口底」硬编码\n * 成算术;真机反馈证明它错(菜单跑到屏幕最底部,与手指按下的位置完全脱节)。现在\n * 落点交给 `flip` / `shift` 按**实际空间**决定,`placement` 只表达意图。\n *\n * 不再按「是否触屏」分流:窄视口的鼠标右键同样期望菜单贴着光标出现,按输入类型或视口\n * 宽度分流会让同一块屏幕上的两种输入拿到两套布局。分流所需的 `_anchorAtPressPoint`\n * 连同 `_positionMenuInViewport` / `_positionMenuAtPressPoint` 两个方法一起删掉 ——\n * 面板恒为 open scrim `<dialog>` 的直接子节点(`createMenuPortalOverlay` 第三参传入\n * scrim,`menu-portal.ts` 里 `container = scrim ?? …`),所以旧代码那条\n * 「非 dialog 容器」的分支对 context-menu 本来就不可达。\n *\n * 算出来的 left/top 是**视口坐标**。面板是 `position: fixed`,而 scrim 刻意不带\n * transform / filter / contain(见 style.css),因此不构成 fixed 的包含块 ——\n * 坐标直接相对视口解析。这条等价关系由 style.css 的满屏声明保证,并由 R6 的\n * 「scrim 无 transform/filter/contain」+ viewport-fit 的几何断言兜底。\n */\n private _positionMenu() {\n const panel = this._menu?.panel\n if (!panel) return\n\n /*\n * 先读安全区,再碰面板:`getComputedStyle` 会强制一次同步布局,让它发生在面板进入\n * 「可测」状态之前,而不是夹在「面板刚可测」与「Floating UI 量它」之间。\n *\n * 需要说清楚的是,**这不是** viewport-fit 那条横向断言里 2px 抖动的成因。把这一行移到\n * 最前面实测并没有让那个偏差消失(改前改后都是 204 对期望 206)。那个 2px 来自 flip 的\n * reset 会多跑一轮测量:那一刻面板宽 202px、落定后 200px。相关用例已经不再钉与视口\n * 边界的精确距离。\n */\n const safeAreaBottom = this._safeAreaBottom()\n\n panel.style.visibility = 'hidden'\n panel.style.display = ''\n\n const generation = this._menuPositionGeneration.next()\n /*\n * 可用矩形 = 视口 − 安全区 − 最小间距。flip 与 shift 必须吃**同一份** padding:\n *\n * - flip 用它判断「这一侧放不放得下」,决定翻不翻;\n * - shift 用它做实际钳制,且 `crossAxis: true` 会**同时**钳主轴与交叉轴\n * (core 的 shift 默认 mainAxis=true / crossAxis=false,开了 crossAxis 才两边都管)。\n *\n * 安全区必须进 shift 而不只是 flip:flip 只挑「哪一侧」,挑完就把面板贴在触发点上,\n * 不再管这一侧内部的位置。触发点本身落在安全区里时(按在屏幕最底部),挑出来的\n * 「上方」那一侧仍然把面板下缘按在安全区里 —— 只有 shift 的主轴钳制会把它推上来。\n * 这条正是 R7 用例在钉的行为。\n */\n const boundaryPadding = {\n top: VIEWPORT_PADDING,\n bottom: VIEWPORT_PADDING + safeAreaBottom,\n left: VIEWPORT_PADDING,\n right: VIEWPORT_PADDING\n }\n /*\n * 限制 flip 的候选集为「下方 / 上方」两个,**刻意不含 `-start` / `-end` 的另一侧**。\n *\n * 为什么必须限制:`flip` 对对齐过的 placement(`bottom-start`)默认展开成\n * `getExpandedPlacements()`,候选里带着 `bottom-end` / `top-end`。于是鼠标贴视口右缘、\n * `bottom-start` 的对齐侧溢出时,flip 会认定「`bottom-end` 才放得下」并把面板整个挪到\n * 光标左侧。落点仍在视口内,但那是一次没人要求的契约变更 —— 水平夹取本来就归 shift,\n * 而且对齐量一变,transform-origin 的推导前提(对齐量不变 ⇒ 水平 origin 只由 shift 位移\n * 决定)就不成立了。\n *\n * 用的是 `fallbackPlacements` 而不是 `allowedPlacements`:后者是 `autoPlacement` 的选项,\n * `flip` 根本不读,传进去只会被静默忽略(TS 会报错,运行时会当成 padding 之外的字段丢掉)。\n * `fallbackPlacements` 正是 flip 用来**替换**上面那个默认展开列表的字段,指定后\n * `placements = [initialPlacement, ...fallbackPlacements]`,连 bestFit 兜底也只在这份\n * 候选里挑,`-end` 因此永远不可能出现。\n *\n * 用例「鼠标贴视口右缘时对齐量不被 flip 改成 -end」钉的就是这一条。\n */\n const placements = this._centerOnPressPoint\n ? (['bottom', 'top'] as const)\n : (['bottom-start', 'top-start'] as const)\n\n void computePosition({ getBoundingClientRect: () => new DOMRect(this._x, this._y, 0, 0) }, panel, {\n strategy: 'fixed',\n /*\n * `bottom`(触屏):面板上缘对齐触发点 y、水平居中于触发点 x。\n * `bottom-start`(鼠标):同样上缘对齐 y,但左缘对齐光标 x。理由见\n * `_centerOnPressPoint` —— 居中会把光标落进面板上缘的命中区。\n *\n * 居中在贴边时**必然**被 shift 的交叉轴钳制打破(按点 x 太靠边,居中的面板会溢出视口)。\n * 这不是缺陷而是取舍:完整可见优先于几何居中。shift 推完面板仍在视口内,\n * 只是不再居中 —— 用例「按点贴视口右缘时仍完整可见」钉的就是这一条。\n */\n placement: placements[0],\n middleware: [\n /*\n * flip 负责「向下放不下就翻到按点上方」—— 面板下缘对齐按点、向上长。\n * 两侧都放不下(按点极低且面板极高)时它按 bestFit 选空间多的一侧并夹住主轴,\n * 所以不需要自己再写一遍兜底夹取。\n */\n flip({ fallbackPlacements: [...placements], padding: boundaryPadding }),\n shift({ padding: boundaryPadding, crossAxis: true })\n ]\n }).then(({ x, y, placement, middlewareData }) => {\n if (!this._isOpen || this._menu?.panel !== panel || !this._menuPositionGeneration.isCurrent(generation)) return\n const shiftX = middlewareData.shift?.x ?? 0\n /*\n * 垂直 origin 取 flip 后的实际 placement,不看 shift 位移:placement 直接说明面板\n * 落在触发点的哪一侧,而「离触发点最近的那条边」就是 scale 应该长出来的地方。\n *\n * 水平:默认跟 placement 的对齐量;被 shift 推动时改用推动方向那一侧,菜单才是朝\n * 光标「长过来」而不是从视口边缘滑进去。居中(placement 不带 `-` 对齐后缀)且未被\n * 推动时保持 `center`。\n */\n const horizontalOrigin = !placement.includes('-')\n ? shiftX === 0\n ? 'center'\n : shiftX < 0\n ? 'right'\n : 'left'\n : shiftX < 0\n ? 'right'\n : 'left'\n this._applyMenuPosition(panel, x, y, horizontalOrigin, placement.startsWith('bottom') ? 'top' : 'bottom')\n })\n }\n\n /**\n * 视口下缘的安全区高度(px)。\n *\n * 改按压点展开之后它**仍然必需**,而且不再只服务触屏:菜单默认从触发点向下长,触发点\n * 落在屏幕下半部时面板下缘会进入 home indicator 区域。它现在折进 flip 与 shift 共用的\n * 边界 padding(见 `_positionMenu`),不再需要单独一条 `maxBottom` 算术。\n *\n * 读宿主的自定义属性而不是 `env()`:自定义属性读回的是**计算后**的解析值,\n * 而 `env()` 在 JS 里没有对应的求值入口,只能间接经由声明它的属性取。\n * 属性未解析时 `parseFloat` 得 NaN,落到 0 —— 与 `env(..., 0px)` 的缺省一致。\n */\n private _safeAreaBottom(): number {\n const value = Number.parseFloat(getComputedStyle(this).getPropertyValue('--wui-context-menu-safe-area-bottom'))\n return Number.isFinite(value) ? value : 0\n }\n\n /**\n * 写下面板坐标与进场 transform 的原点。\n *\n * 两个 origin 必传且**不**再回退成坐标比较:flip 会改变垂直方向,回退表达式\n * `y < this._y ? 'bottom' : 'top'` 在「面板顶边与触发点重合」时两边都成立,等于随机取。\n * 唯一调用方从 flip 后的 placement 与 shift 位移推出真实侧边,见 `_positionMenu`。\n */\n private _applyMenuPosition(\n panel: HTMLElement,\n x: number,\n y: number,\n horizontalOrigin: 'left' | 'right' | 'center',\n verticalOrigin: 'top' | 'bottom'\n ) {\n panel.style.left = `${x}px`\n panel.style.top = `${y}px`\n panel.style.setProperty('--wui-internal-overlay-transform-origin', `${verticalOrigin} ${horizontalOrigin}`)\n panel.style.visibility = ''\n }\n\n /**\n * 把焦点带进菜单的第一项。\n *\n * `attempt` 是重试计数:菜单项是刚被 `reconcileManagedMenuItems` 搬进来的自定义元素,\n * 它们的 shadow(`.item-inner`)由各自的 Lit 更新**异步**渲染。`focusMenuItem()` 委托给\n * 项的 `focusItem()`,那一刻 `.item-inner` 还不存在,于是**静默无操作** —— 不抛错、\n * 也不留痕,只表现为「菜单开了但焦点没进去」。\n *\n * 所以这里不假设一次成功:调用后用 `findFocusedMenuItem` 复核,没落上就在下一帧再试,\n * 最多 `FOCUS_FIRST_ITEM_ATTEMPTS` 次。上界是必要的 —— 菜单项若始终渲染不出来\n * (空菜单、内容被外部换掉),不能让重试链一直跑下去。\n */\n private _focusFirstItem(attempt = 0) {\n const items = this._menu?.content.querySelectorAll<HTMLElement>('web-ui-dropdown-item:not([disabled])')\n const firstItem = items?.[0]\n if (!firstItem) return\n focusMenuItem(firstItem, { suppressFocusVisible: this._suppressInitialFocusVisible })\n if (attempt + 1 >= FOCUS_FIRST_ITEM_ATTEMPTS) return\n if (findFocusedMenuItem([this._menu?.panel])) return\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem(attempt + 1)\n })\n }\n\n private _setupMenuItems() {\n const menu = this._menu\n if (!menu) return\n const content = menu.content\n\n reconcileManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n this._hideMenuItems()\n // 宿主可能在菜单打开期间动态改写 slot 子内容(如切换上下文后重建嵌套子项),\n // 新节点没有隐藏 slot 会落入父项默认 slot 可见渲染并叠到一级菜单上;\n // 因此对已移入 content 的嵌套子项也要重新隐藏。\n hideNestedMenuChildren(content, 'context-menu-hidden')\n\n // 框架 v-if 注释锚点的复位独立于元素重排:无论元素序是否已变都要执行。\n // 若挂在「元素序已变才重排」之下,顺序恰好正确时会跳过复位,锚点漂移无法收敛,\n // 而 Vue 下次翻转正依赖锚点引导新分支项的插入点。\n const anchors = captureFrameworkAnchors(content, MARKER_TEXT)\n orderManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n restoreFrameworkAnchors(content, anchors)\n }\n\n private _returnItemsToSlot() {\n const menu = this._menu\n if (!menu) return\n\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n\n returnManagedMenuItemsToSlot(this, menu.content, this._menuItemAnchors)\n }\n\n /**\n * 建 scrim 并让它进入模态态,返回容器供面板挂载。\n *\n * 每次开启都新建而不是复用:scrim 的生命周期必须与「这一次开启」严格同进同出。\n * 复用一个 dialog 意味着上一轮的 `close()` 事件、`open` 属性与 UA 记下的焦点还原目标\n * 会跨会话残留,而这些都不是本组件能重置的。\n */\n private _openScrim(): HTMLDialogElement {\n const scrim = document.createElement('dialog')\n scrim.className = 'wui-context-menu-scrim'\n /*\n * scrim 的样式必须**显式挂到 scrim 元素上**。\n *\n * `static styles`(style.css)只进宿主的 shadow root,scrim 又是本组件自己造的节点、不进\n * 宿主 shadow root,拿不到那份样式;`scrim.css` 若只躺在文件里而不注入,就是死代码。\n * 实测未注入时 scrim 完全是 UA 默认 `dialog:modal` 的样子:`margin: auto` +\n * `width/height: fit-content` 让它在 414x896 视口里缩成 **38x38 的白盒子**正中,\n * `background` 还是不透明白 —— 菜单一开就在屏幕中间糊一块白色方块。\n *\n * 用子 `<style>` 而不是行内 `cssText`:与 `menu-portal.ts` 对 dialog 容器注入共享样式\n * 是同一手法(那边已在 dialog 容器上验证过),随元素一起移除,不留全局残留。\n */\n const scrimStyles = document.createElement('style')\n scrimStyles.textContent = scrimStyle\n scrim.append(scrimStyles)\n scrim.setAttribute(SCRIM_ATTR, '')\n scrim.addEventListener('click', this._onScrimClick)\n scrim.addEventListener('cancel', this._onScrimCancel)\n // showModal() 要求元素已在文档中。\n this._scrimParent().append(scrim)\n scrim.showModal()\n this._scrim = scrim\n return scrim\n }\n\n /**\n * scrim 的挂载点:**与面板在没有 scrim 时会落到的容器同源** —— 最近主题的 theme-owned\n * overlay root。scrim 就是那个容器的替身(面板必须挂进 scrim,见 `updated()` 的顺序注释),\n * 所以它得站在容器该站的位置上。\n *\n * 为什么不能在整棵 DOM 里随便挑位置:`--wui-color-*` 由 `<web-ui-theme>` 写在自己的 `:host`\n * 上,只沿 DOM 树向下继承。scrim 若挂到 `document.body`,而 `<web-ui-theme>` 是 body 的**子节点**,\n * 这条继承链就断了 —— 面板与 scrim 双双落回 `var(--wui-color-*, <浅色字面量>)` 的 fallback,\n * 深色外观下菜单是一块浅色玻璃(app 侧的 `dark:` 变体也够不到它)。挂进 theme overlay root\n * 之后,面板与 scrim 跟 dropdown / select 一样吃同一份 token。\n *\n * 无主题时保持历史行为挂在 `document.body`:那条路径本来就没有 token 作用域可继承,改用\n * fallback overlay root 只会改变既有测试与消费者的观察面,不换来任何东西。\n */\n private _scrimParent(): HTMLElement {\n return findNearestTheme(this)?.getOverlayRoot() ?? document.body\n }\n\n /**\n * 关掉并摘除 scrim。幂等:关闭分支与 `disconnectedCallback` 都会调。\n *\n * `close()` 触发 UA 的焦点归还,**必须先于面板 `remove()`** —— 理由见 `_closeMenuAfterPresence`。\n */\n private _closeScrim() {\n const scrim = this._scrim\n if (!scrim) return\n this._scrim = undefined\n if (scrim.open) scrim.close()\n scrim.remove()\n }\n\n /**\n * 只让 scrim 出顶层,不摘除元素。面板挂在 scrim 里,摘了就把退场动画一起摘掉。\n *\n * 提前出顶层是为了焦点(理由见 `close()`)。代价是这个尾巴上 scrim 已经在 top layer\n * 之外:下层恢复可命中,且面板的层级退回普通流 —— 在「菜单开在 drawer/dialog 之上」\n * 的场景里,退场动画会被下层的 top layer 盖住。\n *\n * 这条尾巴只有一次退场过渡的时长(默认 160ms),且只发生在菜单项激活时。\n * 摘除仍在 `_closeScrim()`,那时面板已经走完。\n */\n private _releaseScrimModality() {\n const scrim = this._scrim\n if (scrim?.open) scrim.close()\n }\n\n /**\n * 点 scrim = 关菜单(Q12:必关,不给 prop 选项),与 dialog / drawer 的实现同形:\n * 只有落点就是 scrim 本身才算「点外面」,落在面板(scrim 的后代)上不算。\n *\n * 落点判据之所以只能是这两个,是模态给的:菜单打开期间下层收不到命中,事件只可能\n * 落在 scrim 或面板上,两者都由本组件判定。\n *\n * 更早的实现在 document 捕获阶段挂 click 监听,把「点外面」理解成「命中测试落在菜单\n * 面板之外」—— 宿主 light DOM 的每一行都满足这个条件,于是判定与行激活成了同一个\n * click 的两个后果,中间没有任何仲裁,下层目标先被激活。那条监听器连同它的实现已一并\n * 删除:模态化之后它没有任何还能生效的场景,别再挂回来。\n */\n private _onScrimClick = (e: MouseEvent) => {\n if (e.target !== this._scrim) return\n // 长按抬手后浏览器补发的 click 属于那一次触摸意图,不能把刚开的菜单关掉。\n if (this._isLongPressFollowUp()) return\n this._closeFromUser()\n }\n\n /**\n * Escape 的**唯一**关闭路径仍是共享仲裁者(`_overlay.requestClose` → `closeDeepestOrAll`),\n * 它按层深决定是关最深子菜单还是关整张菜单。原生 dialog 自己也监听 Escape 并派发\n * `cancel`,若放任默认行为,UA 会直接关掉 scrim —— 那条路径不经过组件状态机,\n * 既可能漏派 `open-change`,也会把「关最深一层」的语义降级成「整张菜单没了」。\n *\n * 因此这里只吞掉默认行为,不重复关闭。仲裁者那条路径已经会走 `_closeFromUser()`。\n */\n private _onScrimCancel = (e: Event) => {\n e.preventDefault()\n }\n\n private async _closeMenuAfterPresence() {\n const menu = this._menu\n if (menu && !(await hideOverlayPresence(menu.panel))) return\n if (this._isOpen || !this.isConnected || this._menu !== menu) return\n\n /*\n * 归还判定必须在移除面板之前做完:面板一脱离文档,「焦点仍在菜单内」就再也读不到了。\n * 子菜单层此刻仍可能持有焦点,一并计入。\n */\n /*\n * 先关 scrim 再摘面板,顺序不能反。\n *\n * 焦点归还是 `showModal()` / `close()` 的 UA 行为,不再由组件自己实现:UA 记的是\n * `showModal()` 之前的 `document.activeElement`,`close()` 时还回去。反过来做就成了\n * 实测里那条「open 中直接移除 dialog → 焦点丢到 body,不归还」——面板先脱离文档,\n * 焦点链断了,UA 的还原目标也就落空。\n *\n * 菜单项激活那条路径的 scrim 早在 `close()` 里就出了顶层(`_releaseScrimModality`),\n * 这里的 `close()` 对它是幂等空转,摘除照常发生。\n *\n * 「关闭期间焦点被外部接管时不抢回」那道防护不是被删除,是被**移到了更早的位置**:\n * UA 自己没有这个分支(实测:外部接管后 close,焦点必被抢回 opener,`close()` /\n * `remove()` / `open = false` / 摘 `open` / 置 `display:none` / opener 移除或不可聚焦,\n * 十种写法全都照旧夺焦点)。所以只能在焦点**还没被送走**的时候就把顶层让掉 ——\n * 也就是菜单项激活那一刻。点外面 / Escape / 程序式关闭不抢焦点是用户期望的语义,\n * 那三条保留 dialog 默认效果。\n */\n this._closeScrim()\n\n // 登记已在关闭分支撤销(release ⟺ 关闭),这里只做内容归还与 DOM 收尾。\n this._returnItemsToSlot()\n menu?.panel.remove()\n this._menu = undefined\n }\n\n private _hideMenuItems() {\n getMenuChildren(this).forEach(child => child.setAttribute('slot', 'context-menu-hidden'))\n hideNestedMenuChildren(this, 'context-menu-hidden')\n }\n\n private _openSubmenu(item: HTMLElement, isInstant = false) {\n if (!this._isOpen || !item.hasAttribute('submenu') || item.hasAttribute('disabled')) return\n\n const level = this._getItemLevel(item)\n if (level === -1 || this._activeSubmenuItems[level] === item) return\n\n this._closeSubmenusFrom(level)\n const closingSubmenu = this._closingSubmenus.take(item)\n if (closingSubmenu) {\n this._activeSubmenus[level] = closingSubmenu\n this._activeSubmenuItems[level] = item\n /*\n * 取回的面板写回 `_submenuHandles`,让收尾结束时的 `_releaseSubmenu` 释放到正确的句柄。\n * 注:「面板在根句柄子树里」这一条已由上面的 claim 分支保证 —— 该分支会把仍在\n * closing 栈里的面板一并重挂(`_closingSubmenus.closing()`)。实测单独摘掉这一行\n * 全部用例仍绿,所以它维护的是释放账本,不是树的成员关系。\n *\n * **这一行没有任何测试守护**:把 `adopt` 与 `set` 一起摘掉,context-menu 的 96 条\n * 用例仍然全绿(实测 96 passed)。所以下面写的是「当前如此」—— 这一行维护的是\n * 释放账本(收尾结束时 `_releaseSubmenu` 按 panel 取句柄),不是树的成员关系;\n * 成员关系由上面 claim 分支的 `_closingSubmenus.closing()` 循环负责,那一条**是**\n * 有测试的。改动这一行前请自己判断要不要补一条能红的用例,别把这里的沉默当成覆盖。\n */\n if (this._menuHandle) this._submenuHandles.set(closingSubmenu.panel, this._menuHandle.adopt(closingSubmenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, closingSubmenu)\n showOverlayPresence(closingSubmenu.panel, { isInstant })\n this._hoverBinder.bind()\n return\n }\n if (getMenuChildren(item).length === 0) return\n\n /*\n * 子菜单与根菜单挂进**同一个** scrim。只改根菜单会让两层行为分裂:根层被 scrim\n * 罩住时,子菜单若仍留在 overlay root,它自己就成了 scrim 子树外的元素 —— 模态期间\n * 不可点,且点它内部会被判成「点外面」而关掉整张菜单。\n */\n const submenu = createMenuPortalOverlay('context-submenu', this, this._scrim)\n submenu.panel.dataset.level = String(level)\n submenu.panel.setAttribute('role', 'menu')\n submenu.panel.setAttribute('aria-label', '子菜单')\n submenu.panel.style.visibility = 'hidden'\n submenu.panel.addEventListener('click', this._onMenuClick)\n submenu.panel.addEventListener('click', this._onMenuItemActivation, true)\n moveMenuChildren(item, submenu.content)\n\n this._activeSubmenus[level] = submenu\n this._activeSubmenuItems[level] = item\n /*\n * 子层进根句柄的子树,默认不是独立候选 —— 「它在树里、但不参与这一层裁决」由此\n * 表达。原先按 level===0 选择根面板还是上一层子面板的父级判断随之消失:contains\n * 沿子树递归,拍平与嵌套对任何现有查询结果等价。\n */\n if (this._menuHandle) this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, submenu)\n showOverlayPresence(submenu.panel, { isInstant })\n this._hoverBinder.bind()\n }\n\n private _closeSubmenusFrom(level: number, isInstant = false) {\n for (let index = this._activeSubmenus.length - 1; index >= level; index--) {\n const submenu = this._activeSubmenus[index]\n const item = this._activeSubmenuItems[index]\n item?.removeAttribute('active')\n if (!item || isInstant) {\n if (submenu) this._releaseSubmenu(submenu.panel)\n this._restoreSubmenuItems(submenu, item)\n submenu.panel.remove()\n } else {\n this._closingSubmenus.closeAsync(item, submenu)\n }\n }\n this._activeSubmenus.length = level\n this._activeSubmenuItems.length = level\n }\n\n private _restoreSubmenuItems(submenu: MenuPortalOverlay, item?: HTMLElement) {\n if (!item) return\n moveMenuChildren(submenu.content, item)\n // 子菜单打开期间宿主可能重建了嵌套子项,归还时补隐藏,避免可见叠加。\n hideNestedMenuChildren(item, 'context-menu-hidden')\n }\n\n private _getItemLevel(item: HTMLElement): number {\n const menu = this._menu\n if (menu?.panel.contains(item)) return 0\n const parentLevel = this._activeSubmenus.findIndex(submenu => submenu.panel.contains(item))\n return parentLevel === -1 ? -1 : parentLevel + 1\n }\n\n private _positionSubmenu(item: HTMLElement, submenu: MenuPortalOverlay) {\n // 与主菜单同因:panel 进入 open native dialog 后处于 transformed containing\n // block,viewport 坐标的 left/top 会相对 dialog padding box 解析而整体偏移,\n // 需改走 Floating UI 换算为 dialog 相对坐标;开合方向仍按视口坐标度量预判。\n if (submenu.panel.parentElement instanceof HTMLDialogElement && submenu.panel.parentElement.open) {\n const itemRect = item.getBoundingClientRect()\n const padding = 8\n const canOpenRight = itemRect.right + submenu.panel.getBoundingClientRect().width + padding <= window.innerWidth\n // 同帧关闭→重开会复用同一 panel 产生两个 in-flight 定位 promise,完成序不保证\n // 后者胜出;按 panel 键控的代数 token 只允许最新一次调用写入,迟到旧 promise 丢弃。\n const epoch = (this._submenuPositionEpochs.get(submenu.panel) ?? 0) + 1\n this._submenuPositionEpochs.set(submenu.panel, epoch)\n void computePosition(item, submenu.panel, {\n strategy: 'fixed',\n placement: canOpenRight ? 'right-start' : 'left-start',\n middleware: [shift({ padding, crossAxis: true })]\n }).then(({ x, y, middlewareData }) => {\n if (!this._activeSubmenus.includes(submenu) || epoch !== this._submenuPositionEpochs.get(submenu.panel)) return\n // 与主菜单 dialog 路径一致:origin 由 shift 增量推导,视口预判只决定 placement。\n const shiftX = middlewareData.shift?.x ?? 0\n const shiftY = middlewareData.shift?.y ?? 0\n const horizontalOrigin = shiftX < 0 ? 'right' : 'left'\n const verticalOrigin = shiftY < 0 ? 'bottom' : 'top'\n submenu.panel.style.left = `${x}px`\n submenu.panel.style.top = `${y}px`\n submenu.panel.style.setProperty(\n '--wui-internal-overlay-transform-origin',\n `${verticalOrigin} ${horizontalOrigin}`\n )\n submenu.panel.style.visibility = ''\n })\n return\n }\n\n const itemRect = item.getBoundingClientRect()\n const submenuRect = submenu.panel.getBoundingClientRect()\n const padding = 8\n const canOpenRight = itemRect.right + submenuRect.width + padding <= window.innerWidth\n const left = canOpenRight ? itemRect.right : Math.max(padding, itemRect.left - submenuRect.width)\n const top = Math.min(Math.max(padding, itemRect.top), window.innerHeight - submenuRect.height - padding)\n\n submenu.panel.style.left = `${left}px`\n submenu.panel.style.top = `${top}px`\n submenu.panel.style.setProperty('--wui-internal-overlay-transform-origin', canOpenRight ? 'top left' : 'top right')\n submenu.panel.style.visibility = ''\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _onPointerDown = (e: PointerEvent) => {\n // 任何一次新的按下都结束「上一次长按的补发事件」窗口:抬手后浏览器合成的\n // click 一定早于下一次 pointerdown,所以这个边界既能吸收补发,又不会误吞\n // 用户之后真正的点击。\n this._longPressOpenedAt = null\n // 鼠标走原生 contextmenu,无需长按;只认触屏指针。\n if (!this.longPress || this.disabled || e.pointerType !== 'touch') return\n this._cancelLongPress()\n this._longPressOrigin = { x: e.clientX, y: e.clientY }\n this._longPressTimer = setTimeout(\n () => {\n this._longPressTimer = undefined\n const origin = this._longPressOrigin\n if (!origin) return\n this._longPressOrigin = undefined\n if (this._isOpen) return\n this._longPressOpenedAt = performance.now()\n // 触屏长按:面板上缘对齐按点、向下展开,水平居中于按点(判据见 `_centerOnPressPoint`)。\n if (this._openAt(origin.x, origin.y, false, true, true)) this._userOpenChange.mark()\n },\n // 属性已被 normalizeNumber 钳到 [0, 5000],这里直接用;不再二次 Math.max。\n this.longPressDelay\n )\n }\n\n private _onPointerMove = (e: PointerEvent) => {\n if (e.pointerType !== 'touch') return\n const origin = this._longPressOrigin\n if (!origin) return\n // 触屏有隐式指针捕获,pointermove 会持续派发到 pointerdown 的落点,\n // 因此位移超阈值即判定为滚动意图,取消长按。\n const distance = Math.hypot(e.clientX - origin.x, e.clientY - origin.y)\n if (distance > LONG_PRESS_MOVE_TOLERANCE) this._cancelLongPress()\n }\n\n private _onPointerUp = (e: PointerEvent) => {\n if (e.pointerType === 'touch') this._cancelLongPress()\n }\n\n private _onPointerCancel = () => {\n this._cancelLongPress()\n }\n\n private _cancelLongPress() {\n if (this._longPressTimer !== undefined) {\n clearTimeout(this._longPressTimer)\n this._longPressTimer = undefined\n }\n this._longPressOrigin = undefined\n }\n\n /** 该事件是否是长按开菜单之后、浏览器为同一次触摸补发的事件。 */\n private _isLongPressFollowUp(): boolean {\n const openedAt = this._longPressOpenedAt\n if (openedAt === null) return false\n // 超期只说明「这次长按的补发没来」,不代表窗口承担时序保证;见常量注释。\n if (performance.now() - openedAt > LONG_PRESS_FOLLOW_UP_WINDOW_MS) {\n this._longPressOpenedAt = null\n return false\n }\n return true\n }\n\n private _onContextMenu = (e: MouseEvent) => {\n if (this.disabled) return\n if (this._isLongPressFollowUp()) {\n e.preventDefault()\n return\n }\n e.preventDefault()\n if (this._openAt(e.clientX, e.clientY, false, true)) this._userOpenChange.mark()\n }\n\n private _onContextMenuOutside = (e: MouseEvent) => {\n if (this._isOpen && !this._outsideClickGuard.isInside(e)) {\n this._closeFromUser()\n }\n }\n\n private _onKeydown = (e: KeyboardEvent) => {\n if (this.disabled) return\n\n if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {\n e.preventDefault()\n const focused = document.activeElement\n if (focused && focused !== document.body) {\n const rect = focused.getBoundingClientRect()\n if (this._openAt(rect.left, rect.bottom, true, false)) this._userOpenChange.mark()\n } else {\n if (this._openAt(window.innerWidth / 2, window.innerHeight / 2, true, false)) this._userOpenChange.mark()\n }\n return\n }\n }\n\n /*\n * 捕获阶段记「可激活的菜单项被点了」,早于条目自己的监听器。\n *\n * 判据与 `_onMenuClick` 同源(同一个 `getMenuItemFromEvent` + 同样的 disabled /\n * submenu 排除),但只置标志、不做关闭。子菜单父项与禁用项在这一层就被排除,\n * 它们不触发关闭,不该让后续关闭提前出顶层。\n *\n * 挂在面板上而不是条目上:条目在开菜单期间被搬进面板,面板是它们此刻共同的祖先。\n *\n * **标志的作用域是这一次点击,不是整个会话。** 菜单项被点并不必然导致关闭:消费方在\n * 自己的条目回调里 `stopPropagation()` 是常见写法(勾选型条目要让菜单留着),此时面板上\n * 的冒泡监听收不到,菜单不关。若标志一直留到会话结束,那一次点击之后的**每一次**关闭——\n * Escape、点外面、程序式 `close()`——都会被误判成菜单项激活而提前出顶层,把 README 承诺的\n * 「点外面 / Escape / 程序式关闭不抢焦点」整个破坏掉。\n *\n * 复位用微任务而不是「会话结束」:捕获阶段(含本监听器)与冒泡阶段(`_onMenuClick`\n * 所在的阶段,关闭动作就发生在这里)都在同一个任务里跑完,微任务检查点在它们之后。\n * 于是需要这个标志的两条路径都赶得上——消费方同步调 `close()`、组件自己在冒泡阶段关——\n * 而「点了但没关」的那一条必然跨过微任务检查点,标志已被清掉。\n */\n private _onMenuItemActivation = (e: Event) => {\n const item = getMenuItemFromEvent(e)\n if (!item || item.hasAttribute('disabled') || item.hasAttribute('submenu')) return\n this._menuItemActivated = true\n queueMicrotask(() => {\n this._menuItemActivated = false\n })\n }\n\n private _onMenuClick = (e: MouseEvent) => {\n const item = getMenuItemFromEvent(e)\n if (!item || item.hasAttribute('disabled')) return\n if (item.hasAttribute('submenu')) {\n this._openSubmenu(item)\n return\n }\n this._closeFromUser('menu-item')\n }\n\n private _getLevelContainer(level: number) {\n return level === 0 ? this._menu : this._activeSubmenus[level - 1]\n }\n\n private _getLevelItems(level: number): HTMLElement[] {\n const container = this._getLevelContainer(level)\n if (!container) return []\n return getMenuChildren(container.content)\n }\n\n private _onWheel = (e: WheelEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onTouchMove = (e: TouchEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onDocumentKeydown = (e: KeyboardEvent) => {\n if (!this._isOpen) return\n // 鼠标右键打开的菜单焦点不在菜单内,e.target !== this 时也必须能 Escape 关闭;\n // 菜单为模态浮层,按 Escape 即关闭,无需限定焦点位置\n handleMenuKeyboard(this._keyboardDelegate, e)\n }\n\n private _closeLastSubmenuOrMenu() {\n if (this._activeSubmenus.length > 0) {\n const level = this._activeSubmenus.length - 1\n const parent = this._activeSubmenuItems[level]\n this._closeSubmenusFrom(level)\n focusMenuItem(parent)\n this._hoverBinder.bind()\n } else {\n this._closeFromUser()\n }\n }\n\n private _preventBackgroundScroll(e: Event) {\n if (!this._isOpen || this.noScrollLock || this._isMenuPanelEvent(e)) return\n e.preventDefault()\n }\n\n private _syncScrollLock(isOpen = this._isOpen) {\n this._scrollLock.sync(isOpen && !this.noScrollLock)\n }\n\n /** 按 panel 撤销一层子菜单的登记;句柄已随根层递归撤销时幂等无操作。 */\n private _releaseSubmenu(panel: HTMLElement) {\n this._submenuHandles.get(panel)?.release()\n this._submenuHandles.delete(panel)\n }\n\n private _isMenuPanelEvent(e: Event): boolean {\n // 在监听器内部判定:composedPath() 在派发结束后会被清空。\n return this._menuHandle?.containsEvent(e) ?? false\n }\n\n override render() {\n return html`\n <div class=\"context-menu-anchor\">\n <slot @slotchange=${this._onSlotChange}></slot>\n </div>\n `\n }\n\n private _onSlotChange() {\n this._hideMenuItems()\n // 打开期间宿主重建顶层项时,新成员要移入 portal 才对用户可见;\n // 关闭状态下无需移动,等下次打开时由 _setupMenuItems 统一处理。\n if (this._isOpen) this._scheduleRefresh()\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-context-menu': WebUiContextMenu\n }\n}\n"],"mappings":"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"}
@@ -3,22 +3,23 @@ import "../icon/index.js";
3
3
  import t from "../../assets/glass.js";
4
4
  import n from "../../icons/generated/ooui-close.js";
5
5
  import "../button/index.js";
6
- import { dispatchOpenChangeEvent as r } from "../../shared/open-state/index.js";
7
- import { defineOpenOverlay as i } from "../../shared/overlay/open-overlay.js";
8
- import { defineScrollLockLease as a } from "../../shared/scroll-lock/scroll-lock.js";
9
- import { UserChangeController as o } from "../../shared/events/user-change.js";
10
- import s from "../../assets/modal-surface.js";
11
- import { defineNativeDialogPresence as c } from "../../shared/overlay/native-dialog-presence.js";
12
- import l from "./style.js";
13
- import { LitElement as u, html as d, nothing as f, unsafeCSS as p } from "lit";
14
- import { customElement as m, property as h, state as g } from "lit/decorators.js";
6
+ import r from "../../scrollbars/internal.js";
7
+ import { dispatchOpenChangeEvent as i } from "../../shared/open-state/index.js";
8
+ import { defineOpenOverlay as a } from "../../shared/overlay/open-overlay.js";
9
+ import { defineScrollLockLease as o } from "../../shared/scroll-lock/scroll-lock.js";
10
+ import { UserChangeController as s } from "../../shared/events/user-change.js";
11
+ import c from "../../assets/modal-surface.js";
12
+ import { defineNativeDialogPresence as l } from "../../shared/overlay/native-dialog-presence.js";
13
+ import u from "./style.js";
14
+ import { LitElement as d, html as f, nothing as p, unsafeCSS as m } from "lit";
15
+ import { customElement as h, property as g, state as _ } from "lit/decorators.js";
15
16
  //#region src/components/dialog/index.ts
16
- var _ = class extends u {
17
+ var v = class extends d {
17
18
  constructor(...e) {
18
- super(...e), this.open = !1, this.closable = !1, this.noScrollLock = !1, this.noBackdropClose = !1, this.noEscapeClose = !1, this.controlled = !1, this.heading = "", this.headless = !1, this.dialogLabel = "", this._hasHeader = !1, this._userOpenChange = new o(), this._overlay = i().make({
19
+ super(...e), this.open = !1, this.closable = !1, this.noScrollLock = !1, this.noBackdropClose = !1, this.noEscapeClose = !1, this.controlled = !1, this.heading = "", this.headless = !1, this.dialogLabel = "", this._hasHeader = !1, this._userOpenChange = new s(), this._overlay = a().make({
19
20
  requestClose: () => this._closeFromUser(),
20
21
  isConnected: () => this.isConnected
21
- }), this._handle = null, this._scrollLock = a().make(), this._presence = c().make({
22
+ }), this._handle = null, this._scrollLock = o().make(), this._presence = l().make({
22
23
  getDialog: () => this.dialog,
23
24
  isConnected: () => this.isConnected,
24
25
  isOpen: () => this.open
@@ -46,9 +47,10 @@ var _ = class extends u {
46
47
  }
47
48
  static {
48
49
  this.styles = [
49
- p(t),
50
- p(s),
51
- p(l)
50
+ m(t),
51
+ m(c),
52
+ m(r),
53
+ m(u)
52
54
  ];
53
55
  }
54
56
  get dialog() {
@@ -91,7 +93,7 @@ var _ = class extends u {
91
93
  e.target === e.currentTarget && (this.noBackdropClose || this._closeFromUser());
92
94
  }
93
95
  emitOpenChange(e = this.open) {
94
- r(this, e);
96
+ i(this, e);
95
97
  }
96
98
  _syncScrollLock(e = this.open) {
97
99
  this._scrollLock.sync(e && !this.noScrollLock);
@@ -105,7 +107,7 @@ var _ = class extends u {
105
107
  return this._hasHeader || this.heading !== "";
106
108
  }
107
109
  _renderCloseButton() {
108
- return d`
110
+ return f`
109
111
  <web-ui-button
110
112
  class="wui-dialog-close"
111
113
  @click=${this._closeFromUser}
@@ -119,10 +121,10 @@ var _ = class extends u {
119
121
  `;
120
122
  }
121
123
  render() {
122
- let e = this._showHeader, t = this.dialogLabel.trim(), n = !t && !this.headless && e ? "wui-dialog-heading" : f;
123
- return d`
124
+ let e = this._showHeader, t = this.dialogLabel.trim(), n = !t && !this.headless && e ? "wui-dialog-heading" : p;
125
+ return f`
124
126
  <dialog
125
- aria-label=${t || f}
127
+ aria-label=${t || p}
126
128
  aria-labelledby=${n}
127
129
  @cancel=${this.handleCancel}
128
130
  @close=${this._onNativeClose}
@@ -130,55 +132,55 @@ var _ = class extends u {
130
132
  @transitionend=${this._onTransitionEnd}
131
133
  >
132
134
  <div class="wui-dialog-body wui-glass">
133
- ${this.headless ? d`
134
- <div class="wui-dialog-content">
135
+ ${this.headless ? f`
136
+ <div class="wui-dialog-content wui-scroll">
135
137
  <slot></slot>
136
138
  </div>
137
- ${this.closable ? this._renderCloseButton() : f}
138
- ` : d`
139
+ ${this.closable ? this._renderCloseButton() : p}
140
+ ` : f`
139
141
  <div class="header" id="wui-dialog-heading" ?hidden=${!e}>
140
142
  <slot name="header" @slotchange=${this._onHeaderSlotChange}>
141
- ${this.heading ? d`<span class="wui-dialog-heading">${this.heading}</span>` : f}
143
+ ${this.heading ? f`<span class="wui-dialog-heading">${this.heading}</span>` : p}
142
144
  </slot>
143
145
  </div>
144
- <div class="desc"><slot></slot></div>
146
+ <div class="desc wui-scroll"><slot></slot></div>
145
147
  <div class="wui-dialog-footer"><slot name="footer"></slot></div>
146
- ${this.closable ? this._renderCloseButton() : f}
148
+ ${this.closable ? this._renderCloseButton() : p}
147
149
  `}
148
150
  </div>
149
151
  </dialog>
150
152
  `;
151
153
  }
152
154
  };
153
- e([h({
155
+ e([g({
154
156
  type: Boolean,
155
157
  reflect: !0
156
- })], _.prototype, "open", void 0), e([h({
158
+ })], v.prototype, "open", void 0), e([g({
157
159
  type: Boolean,
158
160
  reflect: !0
159
- })], _.prototype, "closable", void 0), e([h({
161
+ })], v.prototype, "closable", void 0), e([g({
160
162
  type: Boolean,
161
163
  reflect: !0,
162
164
  attribute: "no-scroll-lock"
163
- })], _.prototype, "noScrollLock", void 0), e([h({
165
+ })], v.prototype, "noScrollLock", void 0), e([g({
164
166
  type: Boolean,
165
167
  reflect: !0,
166
168
  attribute: "no-backdrop-close"
167
- })], _.prototype, "noBackdropClose", void 0), e([h({
169
+ })], v.prototype, "noBackdropClose", void 0), e([g({
168
170
  type: Boolean,
169
171
  reflect: !0,
170
172
  attribute: "no-escape-close"
171
- })], _.prototype, "noEscapeClose", void 0), e([h({
173
+ })], v.prototype, "noEscapeClose", void 0), e([g({
172
174
  type: Boolean,
173
175
  reflect: !0
174
- })], _.prototype, "controlled", void 0), e([h({ type: String })], _.prototype, "heading", void 0), e([h({
176
+ })], v.prototype, "controlled", void 0), e([g({ type: String })], v.prototype, "heading", void 0), e([g({
175
177
  type: Boolean,
176
178
  reflect: !0
177
- })], _.prototype, "headless", void 0), e([h({
179
+ })], v.prototype, "headless", void 0), e([g({
178
180
  type: String,
179
181
  attribute: "dialog-label"
180
- })], _.prototype, "dialogLabel", void 0), e([g()], _.prototype, "_hasHeader", void 0), _ = e([m("web-ui-dialog")], _);
182
+ })], v.prototype, "dialogLabel", void 0), e([_()], v.prototype, "_hasHeader", void 0), v = e([h("web-ui-dialog")], v);
181
183
  //#endregion
182
- export { _ as WebUiDialog };
184
+ export { v as WebUiDialog };
183
185
 
184
186
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/dialog/index.ts"],"sourcesContent":["import { html, LitElement, nothing, type PropertyValues, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport '@/components/icon'\nimport '@/components/button'\nimport glass from '@/assets/glass.css?inline'\nimport modalSurface from '@/assets/modal-surface.css?inline'\nimport { oouiClose } from '@/icons'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineNativeDialogPresence } from '@/shared/overlay/native-dialog-presence'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\n\nimport style from './style.css?inline'\n\n@customElement('web-ui-dialog')\nexport class WebUiDialog extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(modalSurface), unsafeCSS(style)]\n\n @property({ type: Boolean, reflect: true }) open = false\n @property({ type: Boolean, reflect: true }) closable = false\n @property({ type: Boolean, reflect: true, attribute: 'no-scroll-lock' }) noScrollLock = false\n @property({ type: Boolean, reflect: true, attribute: 'no-backdrop-close' }) noBackdropClose = false\n @property({ type: Boolean, reflect: true, attribute: 'no-escape-close' }) noEscapeClose = false\n\n /**\n * Controlled 模式下,Escape 和遮罩点击只派发 `open-change` 请求,\n * 不会自行修改 `open`。Consumer 回写 `open` 后才执行关闭动画。\n * 程序化 API(showModal/close/直接赋值 open)不受此模式影响,始终直通。\n */\n @property({ type: Boolean, reflect: true }) controlled = false\n\n /**\n * `header` 槽为空时的标题文案。槽优先:一旦有元素分配到 `header`,槽内容整条接管,\n * 属性被忽略。默认空串且槽也为空时不渲染 chrome 带。`headless` 模式不渲染 chrome 带,\n * `heading` 一并失效。\n */\n @property({ type: String }) heading = ''\n\n /**\n * Headless 模式:只保留 overlay 基础设施(backdrop、动画、scroll lock、`<dialog>` 语义),\n * 移除内置 UI(chrome 带、正文区、footer),默认槽的内容渲染进卡片内的滚动层\n * `.wui-dialog-content`,视觉交给 Consumer。形态与 `<web-ui-drawer>` 的 `headless` 一致。\n *\n * 该模式没有可自动关联的内置标题,内部原生 dialog 的 accessible name 必须由 Consumer\n * 用 `dialog-label`(或自绘标题 + 自己的 `aria-labelledby`)提供。\n */\n @property({ type: Boolean, reflect: true }) headless = false\n\n /**\n * 内部原生 dialog 的 accessible name。`headless` 模式必须由 Consumer 提供,\n * 因为该模式不会渲染可自动关联的内置 chrome 带。\n */\n @property({ type: String, attribute: 'dialog-label' }) dialogLabel = ''\n\n @state() private _hasHeader = false\n private readonly _userOpenChange = new UserChangeController()\n\n /*\n * 开启态浮层(issue #120 Block 1)。dialog 的 Escape 原本只走原生 cancel 事件:仲裁者\n * 会在 keydown 上 preventDefault 压掉原生关闭请求并 stopPropagation,因此这里必须\n * 自己表达关闭语义(controlled 只派发请求)。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._closeFromUser(),\n isConnected: () => this.isConnected\n })\n /** 当前开启会话的句柄;未开启时为 null。查询与惰性同步走它。 */\n private _handle: OpenOverlayHandle | null = null\n private readonly _scrollLock = defineScrollLockLease().make()\n private readonly _presence = defineNativeDialogPresence().make({\n getDialog: () => this.dialog,\n isConnected: () => this.isConnected,\n isOpen: () => this.open\n })\n\n private get dialog() {\n return this.shadowRoot?.querySelector('dialog') ?? null\n }\n\n protected override updated(props: PropertyValues) {\n super.updated(props)\n if (!this.isConnected) return\n\n if (props.has('open')) {\n if (this._userOpenChange.consume()) this.emitOpenChange()\n this._presence.sync(this.open)\n if (this.open) {\n // presence.sync 内的 showModal() 把 dialog 提升进 top layer。双击手势本身\n // 留下的活选区会被浏览器拿去和提升后的新布局重新解析,结果选区落到刚挂载的\n // dialog 正文上。脆弱的是「手势来源的活选区」,不是 dblclick 的判定窗口:把\n // 打开推迟 250ms(早过双击判定)仍然复现。选区由浏览器在 showModal() 调用\n // 内部生成:脚本没有任何 Selection API 调用,在 dblclick 上 preventDefault\n // 也拦不住。\n //\n // 实测(Chromium)showModal() 不会动打开前已经存在的选区,所以这一次清理\n // 同时也覆盖「先选中页面文本、再打开」的情况——modal 打开后被选中的内容挡在\n // 遮罩后面本来也已不可用。这里只清文档选区,不动 user-select,浮层内正文\n // 仍可正常拖选。\n window.getSelection()?.removeAllRanges()\n }\n // 原生 dialog 登记为开启态浮层:挂在它上面的 portal 面板成为本层后代,\n // Escape 仲裁据此判出最内层(issue #120 Block 1)。\n const dialog = this.dialog\n if (dialog) {\n if (this.open) {\n // 同一面板重新 claim = 新的一次开启:旧会话(含其子层)整体作废。\n this._handle?.release()\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n } else {\n this._handle?.release()\n this._handle = null\n }\n }\n }\n if (props.has('open') || props.has('noScrollLock')) this._syncScrollLock()\n this._syncOverlayInert()\n }\n\n override connectedCallback() {\n super.connectedCallback()\n // 重挂载对账:断连时 presence、滚动锁与登记都已被撤销,而 `open` 未变化时\n // `updated()` 不会补跑任何 sync 分支。首次连接时 shadow 尚未渲染、`this.dialog`\n // 为 null,三种情况都直接跳过;打开态的首次进入仍由 updated() 的\n // `props.has('open')` 分支处理。\n this._presence.reconcile()\n this._syncScrollLock()\n this._reclaimIfOpen()\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._presence.dispose()\n this._scrollLock.release()\n // 断连即撤销登记:层不留在全局注册表里,重连后由 _reclaimIfOpen 显式重新声明。\n this._handle?.release()\n this._handle = null\n }\n\n /*\n * 「重挂载恢复」的调用方一半:模块刻意不观察 DOM 连接状态,所以断连撤销登记之后,\n * 重连时仍有开启态就必须由组件重新声明。\n */\n private _reclaimIfOpen() {\n if (!this.open || this._handle) return\n const dialog = this.dialog\n if (!dialog) return\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n this._syncOverlayInert()\n }\n\n /*\n * `no-escape-close` 时 dialog 仍是候选(Escape 被吞掉、原生 cancel 被压掉),只是\n * 不走关闭入口。与旧的「跳过候选」语义不同:旧语义放任事件落到下层浮层,把外层\n * 一起关掉。该属性可在开启期间改写,所以每次渲染后读它,而不是在 claim 时固定。\n */\n private _syncOverlayInert() {\n this._handle?.setInert(this.noEscapeClose)\n }\n\n showModal() {\n if (this.open) return\n this.open = true\n }\n\n close() {\n this.open = false\n }\n\n /*\n * 仲裁者在 capture 阶段 preventDefault 了 Escape 的 keydown,UA 因此不再派发原生\n * cancel,但本 handler 仍有两个仲裁者不负责的职责,**不可删**:\n * ① 任何到达 dialog 的 cancel 都要 preventDefault,把 top layer 保留到视觉退场结束\n * (否则原生关闭会跳过退出动画);\n * ② 忽略子控件(例如 file input)冒泡上来的 cancel。\n * 实测(overlay-open-owner-260918,2026-09-18)摘掉 `@cancel` 后 dialog.browser.spec.ts\n * 的「no-escape-close 存在时 Escape/cancel 不关闭对话框」与 remount-reconcile 的\n * 「打开态被移出文档再接回」两例立刻转红,断言正是 `dispatchEvent(cancel) === false`。\n */\n private handleCancel(e: Event) {\n if (e.target !== e.currentTarget) return\n\n e.preventDefault()\n if (this.noEscapeClose) return\n this._closeFromUser()\n }\n\n private handleBackdropClick(e: MouseEvent) {\n if (e.target !== (e.currentTarget as HTMLDialogElement)) return\n if (this.noBackdropClose) return\n this._closeFromUser()\n }\n\n /** 用户发起的关闭入口(Escape、遮罩点击、关闭按钮)共用一条路径。 */\n private readonly _closeFromUser = () => {\n if (this.controlled) {\n this.emitOpenChange(false)\n return\n }\n this._userOpenChange.mark()\n this.close()\n }\n\n // controlled 下派发关闭请求:detail 固定 false(用户只能请求关闭,打开永远由 Consumer 写)。\n private emitOpenChange(open = this.open) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _onTransitionEnd = (event: TransitionEvent) => {\n this._presence.handleTransitionEnd(event)\n }\n\n private _onNativeClose = () => {\n // 关闭态下的 close 事件只可能是我们自己 finishClosing 排队的异步事件(或冗余的\n // 外部关闭):交给 presence 消费 self-close 标志,避免标志泄漏到下一次真实关闭。\n if (!this.open) {\n this._presence.handleNativeClose()\n return\n }\n\n // 我们自己 dialog.close() 排队的 close 事件是异步任务:正常时序在本关闭会话内\n // 到达,快速「关闭→重开」时它在重新打开之后才到达(过期事件)。两种情况都已\n // 完成清理,由 presence 消费并返回 true,不能当成外部关闭把刚重开的 dialog 关掉。\n if (this._presence.handleNativeClose()) return\n\n // controlled 下原生关闭(如表单 method=\"dialog\")视为未经 Consumer 批准的状态丢失:\n // 恢复受控状态并派发关闭请求,由 Consumer 决定是否关闭。\n if (this.controlled) {\n this._presence.sync(true)\n this.emitOpenChange(false)\n return\n }\n\n this._userOpenChange.mark()\n this.open = false\n }\n\n private _syncScrollLock(isOpen = this.open) {\n this._scrollLock.sync(isOpen && !this.noScrollLock)\n }\n\n /**\n * 只在「有/无」翻转时写 state:slotchange 在每次分配变化时都会触发,逐次赋值会让\n * Lit 无谓重渲染。`heading` 与槽共同决定 chrome 带的存在,见 `_showHeader`。\n */\n private _onHeaderSlotChange(e: Event) {\n if (!(e.target instanceof HTMLSlotElement)) return\n const has = e.target.assignedElements().length > 0\n if (has !== this._hasHeader) this._hasHeader = has\n }\n\n private get _showHeader(): boolean {\n return this._hasHeader || this.heading !== ''\n }\n\n /**\n * 默认模式与 `headless` 模式共用同一枚关闭按钮:都绝对定位到卡片右上角,偏移由\n * `--wui-dialog-close-top` / `--wui-dialog-close-right` 控制(见 style.css)。\n * 它在两种模式下都是卡片的直接子元素,定位参照是卡片本身(.wui-dialog-body)。\n */\n private _renderCloseButton() {\n return html`\n <web-ui-button\n class=\"wui-dialog-close\"\n @click=${this._closeFromUser}\n aria-label=\"关闭\"\n variant=\"secondary\"\n icon\n size=\"26\"\n >\n <web-ui-icon size=\"14\" .icon=${oouiClose}></web-ui-icon>\n </web-ui-button>\n `\n }\n\n override render() {\n const showHeader = this._showHeader\n /*\n * accessible name:显式 `dialog-label` 优先;否则(非 headless 且带可见时)把原生 dialog\n * 的 `aria-labelledby` 指向 chrome 带——与 drawer 同形。headless 模式没有可自动关联的\n * 内置标题,名字留给 Consumer。\n */\n const dialogLabel = this.dialogLabel.trim()\n const dialogLabelledBy = !dialogLabel && !this.headless && showHeader ? 'wui-dialog-heading' : nothing\n\n return html`\n <dialog\n aria-label=${dialogLabel || nothing}\n aria-labelledby=${dialogLabelledBy}\n @cancel=${this.handleCancel}\n @close=${this._onNativeClose}\n @click=${this.handleBackdropClick}\n @transitionend=${this._onTransitionEnd}\n >\n <div class=\"wui-dialog-body wui-glass\">\n ${\n this.headless\n ? html`\n <div class=\"wui-dialog-content\">\n <slot></slot>\n </div>\n ${this.closable ? this._renderCloseButton() : nothing}\n `\n : html`\n <div class=\"header\" id=\"wui-dialog-heading\" ?hidden=${!showHeader}>\n <slot name=\"header\" @slotchange=${this._onHeaderSlotChange}>\n ${this.heading ? html`<span class=\"wui-dialog-heading\">${this.heading}</span>` : nothing}\n </slot>\n </div>\n <div class=\"desc\"><slot></slot></div>\n <div class=\"wui-dialog-footer\"><slot name=\"footer\"></slot></div>\n ${this.closable ? this._renderCloseButton() : nothing}\n `\n }\n </div>\n </dialog>\n `\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-dialog': WebUiDialog\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/dialog/index.ts"],"sourcesContent":["import '@/components/button'\nimport '@/components/icon'\n\nimport { html, LitElement, nothing, type PropertyValues, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport modalSurface from '@/assets/modal-surface.css?inline'\nimport { oouiClose } from '@/icons'\nimport internalScrollbars from '@/scrollbars/internal.css?inline'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineNativeDialogPresence } from '@/shared/overlay/native-dialog-presence'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\n\nimport style from './style.css?inline'\n\n@customElement('web-ui-dialog')\nexport class WebUiDialog extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(modalSurface), unsafeCSS(internalScrollbars), unsafeCSS(style)]\n\n @property({ type: Boolean, reflect: true }) open = false\n @property({ type: Boolean, reflect: true }) closable = false\n @property({ type: Boolean, reflect: true, attribute: 'no-scroll-lock' }) noScrollLock = false\n @property({ type: Boolean, reflect: true, attribute: 'no-backdrop-close' }) noBackdropClose = false\n @property({ type: Boolean, reflect: true, attribute: 'no-escape-close' }) noEscapeClose = false\n\n /**\n * Controlled 模式下,Escape 和遮罩点击只派发 `open-change` 请求,\n * 不会自行修改 `open`。Consumer 回写 `open` 后才执行关闭动画。\n * 程序化 API(showModal/close/直接赋值 open)不受此模式影响,始终直通。\n */\n @property({ type: Boolean, reflect: true }) controlled = false\n\n /**\n * `header` 槽为空时的标题文案。槽优先:一旦有元素分配到 `header`,槽内容整条接管,\n * 属性被忽略。默认空串且槽也为空时不渲染 chrome 带。`headless` 模式不渲染 chrome 带,\n * `heading` 一并失效。\n */\n @property({ type: String }) heading = ''\n\n /**\n * Headless 模式:只保留 overlay 基础设施(backdrop、动画、scroll lock、`<dialog>` 语义),\n * 移除内置 UI(chrome 带、正文区、footer),默认槽的内容渲染进卡片内的滚动层\n * `.wui-dialog-content`,视觉交给 Consumer。形态与 `<web-ui-drawer>` 的 `headless` 一致。\n *\n * 该模式没有可自动关联的内置标题,内部原生 dialog 的 accessible name 必须由 Consumer\n * 用 `dialog-label`(或自绘标题 + 自己的 `aria-labelledby`)提供。\n */\n @property({ type: Boolean, reflect: true }) headless = false\n\n /**\n * 内部原生 dialog 的 accessible name。`headless` 模式必须由 Consumer 提供,\n * 因为该模式不会渲染可自动关联的内置 chrome 带。\n */\n @property({ type: String, attribute: 'dialog-label' }) dialogLabel = ''\n\n @state() private _hasHeader = false\n private readonly _userOpenChange = new UserChangeController()\n\n /*\n * 开启态浮层(issue #120 Block 1)。dialog 的 Escape 原本只走原生 cancel 事件:仲裁者\n * 会在 keydown 上 preventDefault 压掉原生关闭请求并 stopPropagation,因此这里必须\n * 自己表达关闭语义(controlled 只派发请求)。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._closeFromUser(),\n isConnected: () => this.isConnected\n })\n /** 当前开启会话的句柄;未开启时为 null。查询与惰性同步走它。 */\n private _handle: OpenOverlayHandle | null = null\n private readonly _scrollLock = defineScrollLockLease().make()\n private readonly _presence = defineNativeDialogPresence().make({\n getDialog: () => this.dialog,\n isConnected: () => this.isConnected,\n isOpen: () => this.open\n })\n\n private get dialog() {\n return this.shadowRoot?.querySelector('dialog') ?? null\n }\n\n protected override updated(props: PropertyValues) {\n super.updated(props)\n if (!this.isConnected) return\n\n if (props.has('open')) {\n if (this._userOpenChange.consume()) this.emitOpenChange()\n this._presence.sync(this.open)\n if (this.open) {\n // presence.sync 内的 showModal() 把 dialog 提升进 top layer。双击手势本身\n // 留下的活选区会被浏览器拿去和提升后的新布局重新解析,结果选区落到刚挂载的\n // dialog 正文上。脆弱的是「手势来源的活选区」,不是 dblclick 的判定窗口:把\n // 打开推迟 250ms(早过双击判定)仍然复现。选区由浏览器在 showModal() 调用\n // 内部生成:脚本没有任何 Selection API 调用,在 dblclick 上 preventDefault\n // 也拦不住。\n //\n // 实测(Chromium)showModal() 不会动打开前已经存在的选区,所以这一次清理\n // 同时也覆盖「先选中页面文本、再打开」的情况——modal 打开后被选中的内容挡在\n // 遮罩后面本来也已不可用。这里只清文档选区,不动 user-select,浮层内正文\n // 仍可正常拖选。\n window.getSelection()?.removeAllRanges()\n }\n // 原生 dialog 登记为开启态浮层:挂在它上面的 portal 面板成为本层后代,\n // Escape 仲裁据此判出最内层(issue #120 Block 1)。\n const dialog = this.dialog\n if (dialog) {\n if (this.open) {\n // 同一面板重新 claim = 新的一次开启:旧会话(含其子层)整体作废。\n this._handle?.release()\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n } else {\n this._handle?.release()\n this._handle = null\n }\n }\n }\n if (props.has('open') || props.has('noScrollLock')) this._syncScrollLock()\n this._syncOverlayInert()\n }\n\n override connectedCallback() {\n super.connectedCallback()\n // 重挂载对账:断连时 presence、滚动锁与登记都已被撤销,而 `open` 未变化时\n // `updated()` 不会补跑任何 sync 分支。首次连接时 shadow 尚未渲染、`this.dialog`\n // 为 null,三种情况都直接跳过;打开态的首次进入仍由 updated() 的\n // `props.has('open')` 分支处理。\n this._presence.reconcile()\n this._syncScrollLock()\n this._reclaimIfOpen()\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._presence.dispose()\n this._scrollLock.release()\n // 断连即撤销登记:层不留在全局注册表里,重连后由 _reclaimIfOpen 显式重新声明。\n this._handle?.release()\n this._handle = null\n }\n\n /*\n * 「重挂载恢复」的调用方一半:模块刻意不观察 DOM 连接状态,所以断连撤销登记之后,\n * 重连时仍有开启态就必须由组件重新声明。\n */\n private _reclaimIfOpen() {\n if (!this.open || this._handle) return\n const dialog = this.dialog\n if (!dialog) return\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n this._syncOverlayInert()\n }\n\n /*\n * `no-escape-close` 时 dialog 仍是候选(Escape 被吞掉、原生 cancel 被压掉),只是\n * 不走关闭入口。与旧的「跳过候选」语义不同:旧语义放任事件落到下层浮层,把外层\n * 一起关掉。该属性可在开启期间改写,所以每次渲染后读它,而不是在 claim 时固定。\n */\n private _syncOverlayInert() {\n this._handle?.setInert(this.noEscapeClose)\n }\n\n showModal() {\n if (this.open) return\n this.open = true\n }\n\n close() {\n this.open = false\n }\n\n /*\n * 仲裁者在 capture 阶段 preventDefault 了 Escape 的 keydown,UA 因此不再派发原生\n * cancel,但本 handler 仍有两个仲裁者不负责的职责,**不可删**:\n * ① 任何到达 dialog 的 cancel 都要 preventDefault,把 top layer 保留到视觉退场结束\n * (否则原生关闭会跳过退出动画);\n * ② 忽略子控件(例如 file input)冒泡上来的 cancel。\n * 实测(overlay-open-owner-260918,2026-09-18)摘掉 `@cancel` 后 dialog.browser.spec.ts\n * 的「no-escape-close 存在时 Escape/cancel 不关闭对话框」与 remount-reconcile 的\n * 「打开态被移出文档再接回」两例立刻转红,断言正是 `dispatchEvent(cancel) === false`。\n */\n private handleCancel(e: Event) {\n if (e.target !== e.currentTarget) return\n\n e.preventDefault()\n if (this.noEscapeClose) return\n this._closeFromUser()\n }\n\n private handleBackdropClick(e: MouseEvent) {\n if (e.target !== (e.currentTarget as HTMLDialogElement)) return\n if (this.noBackdropClose) return\n this._closeFromUser()\n }\n\n /** 用户发起的关闭入口(Escape、遮罩点击、关闭按钮)共用一条路径。 */\n private readonly _closeFromUser = () => {\n if (this.controlled) {\n this.emitOpenChange(false)\n return\n }\n this._userOpenChange.mark()\n this.close()\n }\n\n // controlled 下派发关闭请求:detail 固定 false(用户只能请求关闭,打开永远由 Consumer 写)。\n private emitOpenChange(open = this.open) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _onTransitionEnd = (event: TransitionEvent) => {\n this._presence.handleTransitionEnd(event)\n }\n\n private _onNativeClose = () => {\n // 关闭态下的 close 事件只可能是我们自己 finishClosing 排队的异步事件(或冗余的\n // 外部关闭):交给 presence 消费 self-close 标志,避免标志泄漏到下一次真实关闭。\n if (!this.open) {\n this._presence.handleNativeClose()\n return\n }\n\n // 我们自己 dialog.close() 排队的 close 事件是异步任务:正常时序在本关闭会话内\n // 到达,快速「关闭→重开」时它在重新打开之后才到达(过期事件)。两种情况都已\n // 完成清理,由 presence 消费并返回 true,不能当成外部关闭把刚重开的 dialog 关掉。\n if (this._presence.handleNativeClose()) return\n\n // controlled 下原生关闭(如表单 method=\"dialog\")视为未经 Consumer 批准的状态丢失:\n // 恢复受控状态并派发关闭请求,由 Consumer 决定是否关闭。\n if (this.controlled) {\n this._presence.sync(true)\n this.emitOpenChange(false)\n return\n }\n\n this._userOpenChange.mark()\n this.open = false\n }\n\n private _syncScrollLock(isOpen = this.open) {\n this._scrollLock.sync(isOpen && !this.noScrollLock)\n }\n\n /**\n * 只在「有/无」翻转时写 state:slotchange 在每次分配变化时都会触发,逐次赋值会让\n * Lit 无谓重渲染。`heading` 与槽共同决定 chrome 带的存在,见 `_showHeader`。\n */\n private _onHeaderSlotChange(e: Event) {\n if (!(e.target instanceof HTMLSlotElement)) return\n const has = e.target.assignedElements().length > 0\n if (has !== this._hasHeader) this._hasHeader = has\n }\n\n private get _showHeader(): boolean {\n return this._hasHeader || this.heading !== ''\n }\n\n /**\n * 默认模式与 `headless` 模式共用同一枚关闭按钮:都绝对定位到卡片右上角,偏移由\n * `--wui-dialog-close-top` / `--wui-dialog-close-right` 控制(见 style.css)。\n * 它在两种模式下都是卡片的直接子元素,定位参照是卡片本身(.wui-dialog-body)。\n */\n private _renderCloseButton() {\n return html`\n <web-ui-button\n class=\"wui-dialog-close\"\n @click=${this._closeFromUser}\n aria-label=\"关闭\"\n variant=\"secondary\"\n icon\n size=\"26\"\n >\n <web-ui-icon size=\"14\" .icon=${oouiClose}></web-ui-icon>\n </web-ui-button>\n `\n }\n\n override render() {\n const showHeader = this._showHeader\n /*\n * accessible name:显式 `dialog-label` 优先;否则(非 headless 且带可见时)把原生 dialog\n * 的 `aria-labelledby` 指向 chrome 带——与 drawer 同形。headless 模式没有可自动关联的\n * 内置标题,名字留给 Consumer。\n */\n const dialogLabel = this.dialogLabel.trim()\n const dialogLabelledBy = !dialogLabel && !this.headless && showHeader ? 'wui-dialog-heading' : nothing\n\n return html`\n <dialog\n aria-label=${dialogLabel || nothing}\n aria-labelledby=${dialogLabelledBy}\n @cancel=${this.handleCancel}\n @close=${this._onNativeClose}\n @click=${this.handleBackdropClick}\n @transitionend=${this._onTransitionEnd}\n >\n <div class=\"wui-dialog-body wui-glass\">\n ${\n this.headless\n ? html`\n <div class=\"wui-dialog-content wui-scroll\">\n <slot></slot>\n </div>\n ${this.closable ? this._renderCloseButton() : nothing}\n `\n : html`\n <div class=\"header\" id=\"wui-dialog-heading\" ?hidden=${!showHeader}>\n <slot name=\"header\" @slotchange=${this._onHeaderSlotChange}>\n ${this.heading ? html`<span class=\"wui-dialog-heading\">${this.heading}</span>` : nothing}\n </slot>\n </div>\n <div class=\"desc wui-scroll\"><slot></slot></div>\n <div class=\"wui-dialog-footer\"><slot name=\"footer\"></slot></div>\n ${this.closable ? this._renderCloseButton() : nothing}\n `\n }\n </div>\n </dialog>\n `\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-dialog': WebUiDialog\n }\n}\n"],"mappings":"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"}