@greypan/web-ui 5.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.CN.md +97 -23
  2. package/README.md +112 -21
  3. package/dist/assets/glass.js +1 -1
  4. package/dist/assets/glass.js.map +1 -1
  5. package/dist/assets/menu-portal.js +1 -1
  6. package/dist/assets/menu-portal.js.map +1 -1
  7. package/dist/components/autocomplete/index.d.ts +367 -4
  8. package/dist/components/autocomplete/index.js +83 -77
  9. package/dist/components/autocomplete/index.js.map +1 -1
  10. package/dist/components/autocomplete/style.js +1 -1
  11. package/dist/components/autocomplete/style.js.map +1 -1
  12. package/dist/components/avatar/style.js +1 -1
  13. package/dist/components/avatar/style.js.map +1 -1
  14. package/dist/components/back-top/style.js +1 -1
  15. package/dist/components/back-top/style.js.map +1 -1
  16. package/dist/components/badge/style.js +1 -1
  17. package/dist/components/badge/style.js.map +1 -1
  18. package/dist/components/button/style.js +1 -1
  19. package/dist/components/button/style.js.map +1 -1
  20. package/dist/components/button-group/style.js +1 -1
  21. package/dist/components/button-group/style.js.map +1 -1
  22. package/dist/components/checkbox/index.d.ts +366 -5
  23. package/dist/components/checkbox/index.js +19 -31
  24. package/dist/components/checkbox/index.js.map +1 -1
  25. package/dist/components/checkbox/style.js +1 -1
  26. package/dist/components/checkbox/style.js.map +1 -1
  27. package/dist/components/checkbox-group/index.d.ts +366 -4
  28. package/dist/components/checkbox-group/index.js +16 -25
  29. package/dist/components/checkbox-group/index.js.map +1 -1
  30. package/dist/components/collapse/index.js +24 -27
  31. package/dist/components/collapse/index.js.map +1 -1
  32. package/dist/components/context-menu/index.d.ts +6 -34
  33. package/dist/components/context-menu/index.js +88 -201
  34. package/dist/components/context-menu/index.js.map +1 -1
  35. package/dist/components/dialog/index.js +20 -23
  36. package/dist/components/dialog/index.js.map +1 -1
  37. package/dist/components/dialog/style.js +1 -1
  38. package/dist/components/dialog/style.js.map +1 -1
  39. package/dist/components/drawer/index.js +54 -57
  40. package/dist/components/drawer/index.js.map +1 -1
  41. package/dist/components/drawer/style.js +1 -1
  42. package/dist/components/drawer/style.js.map +1 -1
  43. package/dist/components/dropdown/index.d.ts +11 -14
  44. package/dist/components/dropdown/index.js +128 -155
  45. package/dist/components/dropdown/index.js.map +1 -1
  46. package/dist/components/dropdown-header/style.js +1 -1
  47. package/dist/components/dropdown-header/style.js.map +1 -1
  48. package/dist/components/dropdown-item/style.js +1 -1
  49. package/dist/components/dropdown-item/style.js.map +1 -1
  50. package/dist/components/empty/style.js +1 -1
  51. package/dist/components/empty/style.js.map +1 -1
  52. package/dist/components/input/index.d.ts +366 -5
  53. package/dist/components/input/index.js +35 -47
  54. package/dist/components/input/index.js.map +1 -1
  55. package/dist/components/input/style.js +1 -1
  56. package/dist/components/input/style.js.map +1 -1
  57. package/dist/components/input-number/index.d.ts +366 -5
  58. package/dist/components/input-number/index.js +31 -44
  59. package/dist/components/input-number/index.js.map +1 -1
  60. package/dist/components/input-number/style.js +1 -1
  61. package/dist/components/input-number/style.js.map +1 -1
  62. package/dist/components/layout/style.js +1 -1
  63. package/dist/components/layout/style.js.map +1 -1
  64. package/dist/components/option/style.js +1 -1
  65. package/dist/components/option/style.js.map +1 -1
  66. package/dist/components/popover/index.d.ts +2 -0
  67. package/dist/components/popover/index.js +33 -44
  68. package/dist/components/popover/index.js.map +1 -1
  69. package/dist/components/popover/style.js +1 -1
  70. package/dist/components/popover/style.js.map +1 -1
  71. package/dist/components/radio/index.d.ts +366 -5
  72. package/dist/components/radio/index.js +19 -31
  73. package/dist/components/radio/index.js.map +1 -1
  74. package/dist/components/radio/style.js +1 -1
  75. package/dist/components/radio/style.js.map +1 -1
  76. package/dist/components/radio-group/index.d.ts +366 -4
  77. package/dist/components/radio-group/index.js +16 -25
  78. package/dist/components/radio-group/index.js.map +1 -1
  79. package/dist/components/segmented/index.d.ts +366 -4
  80. package/dist/components/segmented/index.js +26 -35
  81. package/dist/components/segmented/index.js.map +1 -1
  82. package/dist/components/segmented/style.js +1 -1
  83. package/dist/components/segmented/style.js.map +1 -1
  84. package/dist/components/segmented-trigger/style.js +1 -1
  85. package/dist/components/segmented-trigger/style.js.map +1 -1
  86. package/dist/components/select/index.d.ts +368 -5
  87. package/dist/components/select/index.js +65 -67
  88. package/dist/components/select/index.js.map +1 -1
  89. package/dist/components/select/style.js +1 -1
  90. package/dist/components/select/style.js.map +1 -1
  91. package/dist/components/slider/index.d.ts +366 -5
  92. package/dist/components/slider/index.js +38 -50
  93. package/dist/components/slider/index.js.map +1 -1
  94. package/dist/components/slider/style.js +1 -1
  95. package/dist/components/slider/style.js.map +1 -1
  96. package/dist/components/spinner/style.js +1 -1
  97. package/dist/components/spinner/style.js.map +1 -1
  98. package/dist/components/switch/index.d.ts +366 -5
  99. package/dist/components/switch/index.js +26 -38
  100. package/dist/components/switch/index.js.map +1 -1
  101. package/dist/components/switch/style.js +1 -1
  102. package/dist/components/switch/style.js.map +1 -1
  103. package/dist/components/textarea/index.d.ts +366 -5
  104. package/dist/components/textarea/index.js +43 -57
  105. package/dist/components/textarea/index.js.map +1 -1
  106. package/dist/components/textarea/style.js +1 -1
  107. package/dist/components/textarea/style.js.map +1 -1
  108. package/dist/components/theme/style.js +1 -1
  109. package/dist/components/theme/style.js.map +1 -1
  110. package/dist/components/toast/style.js +1 -1
  111. package/dist/components/toast/style.js.map +1 -1
  112. package/dist/components/tooltip/index.d.ts +1 -0
  113. package/dist/components/tooltip/index.js +32 -46
  114. package/dist/components/tooltip/index.js.map +1 -1
  115. package/dist/components/tooltip/style.js +1 -1
  116. package/dist/components/tooltip/style.js.map +1 -1
  117. package/dist/shared/form-association/index.js +31 -4
  118. package/dist/shared/form-association/index.js.map +1 -1
  119. package/dist/shared/menu-behavior/index.js +112 -0
  120. package/dist/shared/menu-behavior/index.js.map +1 -0
  121. package/dist/shared/menu-portal/menu-tree.js +60 -1
  122. package/dist/shared/menu-portal/menu-tree.js.map +1 -1
  123. package/dist/shared/open-state/index.js +12 -0
  124. package/dist/shared/open-state/index.js.map +1 -0
  125. package/dist/shared/option-portal/combobox-shell.js +41 -0
  126. package/dist/shared/option-portal/combobox-shell.js.map +1 -0
  127. package/dist/shared/option-portal/index.js +23 -22
  128. package/dist/shared/option-portal/index.js.map +1 -1
  129. package/dist/shared/overlay/overlay.js.map +1 -1
  130. package/dist/shared/overlay/placement-props.js +19 -0
  131. package/dist/shared/overlay/placement-props.js.map +1 -0
  132. package/dist/shared/overlay/portal.js +94 -20
  133. package/dist/shared/overlay/portal.js.map +1 -1
  134. package/package.json +6 -7
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/collapse/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\n\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { getTransitionDuration } from '@/shared/overlay/presence'\n\nimport style from './style.css?inline'\n\nconst EXIT_FALLBACK_BUFFER = 80\n\nlet collapseIdCounter = 0\n\n/**\n * 文档流内折叠面板:trigger 经默认 slot 提供,内容经 `slot=\"content\"` 提供。\n *\n * trigger 语义完全来自 slot 进来的交互元素(原生 button / web-ui-button 等):\n * Enter/Space 激活走该元素的原生 click,不做 role=\"button\" 补齐;组件把\n * `aria-expanded` / `aria-controls` / `aria-disabled` 回写到首个 assigned\n * element(与 popover trigger 的既定回写模式一致)。wrapper 仅负责点击代理,\n * 不可聚焦,不承载 ARIA。\n *\n * 内容区在 shadow 内以 grid `0fr ↔ 1fr` track 过渡驱动高度/宽度动画(ADR-0038\n * 选型保留)。关闭稳态三态(消费者 light DOM 永不移动,hidden 全部命令式管理,\n * 避免渲染绑定在关闭动画起点抢先 display:none):\n * - 默认:content 容器 `hidden`(display:none,脱离渲染与可访问性树);\n * - `keep-mounted`:inner `inert`(保留在 0fr 轨道内,滚动位置与布局可测量);\n * - 动画进行中:inner `inert` + track pointer-events 禁用,结束后进入对应稳态。\n */\n@customElement('web-ui-collapse')\nexport class WebUiCollapse extends LitElement {\n static override styles = unsafeCSS(style)\n\n /** 展开状态;严格受控,唯一状态源。 */\n @property({ type: Boolean, reflect: true }) open = false\n\n /** 禁用触发:点击忽略、aria-disabled 写入 trigger 元素;已展开内容保持现状。 */\n @property({ type: Boolean, reflect: true }) disabled = false\n\n /** 水平方向:true 时沿宽度展开,默认沿高度。 */\n @property({ type: Boolean, reflect: true }) horizontal = false\n\n /** 关闭稳态保持内容 `inert` 而非 `hidden`(保留滚动位置,可测量)。 */\n @property({ type: Boolean, reflect: true, attribute: 'keep-mounted' }) keepMounted = false\n\n @query('.wui-collapse-trigger-wrapper') private _triggerWrapper!: HTMLElement\n\n private _contentId = `wui-collapse-content-${++collapseIdCounter}`\n\n private readonly _userOpenChange = new UserChangeController()\n\n private _exitTimer?: ReturnType<typeof setTimeout>\n // 动画代际:每次管线启动自增,旧管线的 transitionend/兜底定时器/rAF 全部失效。\n private _generation = 0\n private _lastOpen?: boolean\n\n override connectedCallback() {\n super.connectedCallback()\n // 点击监听与生命周期同寿(宿主移除即整体销毁),无需 disconnected 清理。\n this.addEventListener('click', this._onTriggerClick)\n // 断连可能打断动画管线(瞬态残留:presence='closing'、容器未 hidden)。\n // 首次连接不必处理(初始 render 走 _lastOpen===undefined 落稳态,请求多余\n // 渲染会触发「scheduled an update after update completed」告警);重连时\n // 重置同步状态并强制一轮 update,由首次同步分支重新落稳态、丢弃在途管线。\n if (this.hasUpdated) {\n this._generation++\n this._clearExitTimer()\n this._lastOpen = undefined\n this.requestUpdate()\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._clearExitTimer()\n }\n\n protected override updated(changed: Map<string, unknown>) {\n // ARIA 回写不依赖 open 分支,任何渲染后都保持与宿主状态同步。\n this._syncTriggerAria()\n\n // 首次同步(含 open 为默认值的场景,changed map 不含默认值属性):\n // 直接落到正确稳态,不播动画(popover 初始 open 同款)。\n if (this._lastOpen === undefined) {\n this._lastOpen = this.open\n if (this.open) this._applyOpenSteadyState()\n else this._applyClosedSteadyState()\n return\n }\n\n if (changed.has('keepMounted') && !this.open) {\n // 运行态切换 keep-mounted:关闭稳态需在 hidden/inert 间重新落地。\n this._applyClosedSteadyState()\n }\n if (!changed.has('open') || this.open === this._lastOpen) return\n\n this._lastOpen = this.open\n if (this._userOpenChange.consume()) this._dispatchChange(this.open)\n if (this.open) this._beginExpand()\n else this._beginCollapse()\n }\n\n show() {\n if (this.open) return\n this.open = true\n }\n\n close() {\n if (!this.open) return\n this.open = false\n }\n\n toggle() {\n if (this.open) this.close()\n else this.show()\n }\n\n // ===== trigger slot =====\n\n private _dispatchChange(open: boolean) {\n this.dispatchEvent(\n new CustomEvent('open-change', {\n detail: { open },\n bubbles: true,\n composed: true\n })\n )\n }\n\n /*\n * 点击代理:冒泡路径穿过 trigger wrapper 的 click 一律切换。语义由 slot 内\n * 的交互元素(原生 button / web-ui-button)提供;slot 放纯文本时无激活语义\n * (README 约定)。内容区与嵌套内层 collapse 的 click 不经过本 wrapper,天然隔离。\n */\n private _onTriggerClick = (event: MouseEvent) => {\n if (this.disabled) return\n if (!event.composedPath().includes(this._triggerWrapper)) return\n this._userOpenChange.mark()\n this.toggle()\n }\n\n /*\n * Trigger 元素取首个 assigned element(与 popover `_queryTrigger` 同款惰性查询,\n * 不依赖 slotchange——jsdom 不派发该事件,无法用状态缓存)。slot 为空时无回写目标。\n */\n private _queryTrigger(): HTMLElement | null {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('.wui-collapse-trigger-wrapper slot')\n const el = slot?.assignedElements()[0]\n return el instanceof HTMLElement ? el : null\n }\n\n /*\n * slot 内容增删不触发宿主响应式更新:显式请求一轮渲染,由 updated() 的\n * ARIA 回写覆盖晚到的 trigger 元素(slotchange 在 jsdom 不派发,无法监听)。\n */\n private readonly _onTriggerSlotChange = () => {\n this.requestUpdate()\n }\n\n // ARIA 状态回写到 trigger 元素本身(wrapper 不可聚焦,AT 读不到)。\n private _syncTriggerAria() {\n const trigger = this._queryTrigger()\n if (!trigger) return\n trigger.setAttribute('aria-expanded', String(this.open))\n trigger.setAttribute('aria-controls', this._contentId)\n if (this.disabled) trigger.setAttribute('aria-disabled', 'true')\n else trigger.removeAttribute('aria-disabled')\n }\n\n // ===== 内容区动画管线(三态语义自 ADR-0038 迁移) =====\n\n private _applyOpenSteadyState() {\n this._generation++\n this._clearExitTimer()\n const container = this._contentContainer()\n const track = this._contentTrack()\n if (container) container.hidden = false\n if (track) {\n track.dataset.wuiPresence = 'open'\n this._setInnerInert(false)\n }\n }\n\n private _applyClosedSteadyState() {\n this._generation++\n this._clearExitTimer()\n const container = this._contentContainer()\n const track = this._contentTrack()\n if (track) delete track.dataset.wuiPresence\n if (this.keepMounted) {\n if (container) container.hidden = false\n this._setInnerInert(true)\n return\n }\n this._setInnerInert(false)\n if (container) container.hidden = true\n }\n\n private _beginExpand() {\n this._generation++\n this._clearExitTimer()\n const container = this._contentContainer()\n const track = this._contentTrack()\n if (!container || !track) return\n\n const generation = this._generation\n // 中断关闭动画时 inner 已 inert:先恢复可交互,内容随展开重新可见。\n this._setInnerInert(false)\n // 从 display:none 进入时无先前计算样式可过渡:提交 0fr 起点后再翻转。\n container.hidden = false\n void track.offsetWidth\n\n requestAnimationFrame(() => {\n if (generation !== this._generation || !this.open) return\n track.dataset.wuiPresence = 'open'\n this._awaitTransition(track, generation, () => this._settle(true))\n })\n }\n\n private _beginCollapse() {\n this._generation++\n this._clearExitTimer()\n const track = this._contentTrack()\n if (!track) return\n\n // 收起期间立即阻断交互,防止焦点落入正在消失的区域。\n this._setInnerInert(true)\n track.dataset.wuiPresence = 'closing'\n const generation = this._generation\n this._awaitTransition(track, generation, () => this._settle(false))\n }\n\n // 关闭稳态:keep-mounted → 内容保留在 0fr 轨道内并 inert;默认 → 容器 hidden。\n private _settle(open: boolean) {\n this._clearExitTimer()\n if (open) {\n this._setInnerInert(false)\n return\n }\n // 落稳态必须清除 closing 瞬态标记:否则 track 永久保留 pointer-events:none\n // 与 exit 过渡配置(keep-mounted 分支同样清除,避免残留)。\n const track = this._contentTrack()\n if (track) delete track.dataset.wuiPresence\n if (this.keepMounted) return\n const container = this._contentContainer()\n this._setInnerInert(false)\n if (container) container.hidden = true\n }\n\n // 等待 grid-template-rows/columns 过渡结束;时长为 0(reduced motion、jsdom)\n // 直接稳态,transitionend 不触发时按时长 + 缓冲兜底。\n private _awaitTransition(track: HTMLElement, generation: number, onDone: () => void) {\n const duration = getTransitionDuration(track, 0)\n if (duration === 0) {\n if (generation === this._generation) onDone()\n return\n }\n\n let settled = false\n const finish = () => {\n track.removeEventListener('transitionend', onTransitionEnd)\n if (this._exitTimer === timer) this._exitTimer = undefined\n clearTimeout(timer)\n // 过渡可能被属性匹配的后续动画触发(如中断重开):代际不符时静默退场。\n if (settled || generation !== this._generation) return\n settled = true\n onDone()\n }\n const timer = setTimeout(finish, duration + EXIT_FALLBACK_BUFFER)\n this._exitTimer = timer\n\n const onTransitionEnd = (event: TransitionEvent) => {\n if (\n event.target === track &&\n (event.propertyName === 'grid-template-rows' || event.propertyName === 'grid-template-columns')\n )\n finish()\n }\n track.addEventListener('transitionend', onTransitionEnd)\n }\n\n private _clearExitTimer() {\n if (this._exitTimer !== undefined) {\n clearTimeout(this._exitTimer)\n this._exitTimer = undefined\n }\n }\n\n private _setInnerInert(inert: boolean) {\n this.shadowRoot?.querySelector<HTMLElement>('.wui-collapse-inner')?.toggleAttribute('inert', inert)\n }\n\n private _contentContainer(): HTMLElement | null {\n return this.shadowRoot?.querySelector<HTMLElement>('.wui-collapse-content') ?? null\n }\n\n private _contentTrack(): HTMLElement | null {\n return this.shadowRoot?.querySelector<HTMLElement>('.wui-collapse-track') ?? null\n }\n\n override render() {\n // 三态由 data-wui-presence 驱动(open/closing),缺省即关闭稳态。\n const trackClass = {\n 'wui-collapse-track': true,\n 'is-horizontal': this.horizontal\n }\n\n return html`\n <div class=\"wui-collapse-trigger-wrapper\" @slotchange=${this._onTriggerSlotChange}><slot></slot></div>\n <div class=\"wui-collapse-content\">\n <div class=${classMap(trackClass)} id=${this._contentId}>\n <div class=\"wui-collapse-inner\">\n <slot name=\"content\"></slot>\n </div>\n </div>\n </div>\n `\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-collapse': WebUiCollapse\n }\n}\n"],"mappings":";;;;;;;;AASA,IAAM,IAAuB,IAEzB,IAAoB,GAmBX,IAAN,cAA4B,EAAW;;EA6HE,aAzHK,KAAA,OAAA,IAGI,KAAA,WAAA,IAGE,KAAA,aAAA,IAG4B,KAAA,cAAA,IAIhE,KAAA,aAAA,wBAAwB,EAAE,KAEZ,KAAA,kBAAA,IAAI,EAAqB,GAItC,KAAA,cAAA,GAiFK,KAAA,mBAAA,MAAsB;GAC3C,KAAK,YACJ,EAAM,aAAa,CAAC,CAAC,SAAS,KAAK,eAAe,MACvD,KAAK,gBAAgB,KAAK,GAC1B,KAAK,OAAO;EACd,GAgB8C,KAAA,6BAAA;GAC5C,KAAK,cAAc;EACrB;;;EA9HyB,KAAA,SAAA,EAAU,CAAK;;CAyBxC,oBAA6B;EAQ3B,AAPA,MAAM,kBAAkB,GAExB,KAAK,iBAAiB,SAAS,KAAK,eAAe,GAK/C,KAAK,eACP,KAAK,eACL,KAAK,gBAAgB,GACrB,KAAK,YAAY,KAAA,GACjB,KAAK,cAAc;CAEvB;CAEA,uBAAgC;EAE9B,AADA,MAAM,qBAAqB,GAC3B,KAAK,gBAAgB;CACvB;CAEA,QAA2B,GAA+B;EAMxD,IAJA,KAAK,iBAAiB,GAIlB,KAAK,cAAc,KAAA,GAAW;GAEhC,AADA,KAAK,YAAY,KAAK,MAClB,KAAK,OAAM,KAAK,sBAAsB,IACrC,KAAK,wBAAwB;GAClC;EACF;EAEA,AAAI,EAAQ,IAAI,aAAa,KAAK,CAAC,KAAK,QAEtC,KAAK,wBAAwB,GAE3B,GAAC,EAAQ,IAAI,MAAM,KAAK,KAAK,SAAS,KAAK,eAE/C,KAAK,YAAY,KAAK,MAClB,KAAK,gBAAgB,QAAQ,KAAG,KAAK,gBAAgB,KAAK,IAAI,GAC9D,KAAK,OAAM,KAAK,aAAa,IAC5B,KAAK,eAAe;CAC3B;CAEA,OAAO;EACD,AACJ,KAAK,SAAO;CACd;CAEA,QAAQ;EACD,AACL,KAAK,SAAO;CACd;CAEA,SAAS;EACP,AAAI,KAAK,OAAM,KAAK,MAAM,IACrB,KAAK,KAAK;CACjB;CAIA,gBAAwB,GAAe;EACrC,KAAK,cACH,IAAI,YAAY,eAAe;GAC7B,QAAQ,EAAE,QAAK;GACf,SAAS;GACT,UAAU;EACZ,CAAC,CACH;CACF;CAkBA,gBAA4C;EAE1C,IAAM,KADO,KAAK,YAAY,cAA+B,oCAAoC,EAAA,EAChF,iBAAiB,CAAC,CAAC;EACpC,OAAO,aAAc,cAAc,IAAK;CAC1C;CAWA,mBAA2B;EACzB,IAAM,IAAU,KAAK,cAAc;EAC9B,MACL,EAAQ,aAAa,iBAAiB,OAAO,KAAK,IAAI,CAAC,GACvD,EAAQ,aAAa,iBAAiB,KAAK,UAAU,GACjD,KAAK,WAAU,EAAQ,aAAa,iBAAiB,MAAM,IAC1D,EAAQ,gBAAgB,eAAe;CAC9C;CAIA,wBAAgC;EAE9B,AADA,KAAK,eACL,KAAK,gBAAgB;EACrB,IAAM,IAAY,KAAK,kBAAkB,GACnC,IAAQ,KAAK,cAAc;EAEjC,AADI,MAAW,EAAU,SAAS,KAC9B,MACF,EAAM,QAAQ,cAAc,QAC5B,KAAK,eAAe,EAAK;CAE7B;CAEA,0BAAkC;EAEhC,AADA,KAAK,eACL,KAAK,gBAAgB;EACrB,IAAM,IAAY,KAAK,kBAAkB,GACnC,IAAQ,KAAK,cAAc;EAEjC,IADI,KAAO,OAAO,EAAM,QAAQ,aAC5B,KAAK,aAAa;GAEpB,AADI,MAAW,EAAU,SAAS,KAClC,KAAK,eAAe,EAAI;GACxB;EACF;EAEA,AADA,KAAK,eAAe,EAAK,GACrB,MAAW,EAAU,SAAS;CACpC;CAEA,eAAuB;EAErB,AADA,KAAK,eACL,KAAK,gBAAgB;EACrB,IAAM,IAAY,KAAK,kBAAkB,GACnC,IAAQ,KAAK,cAAc;EACjC,IAAI,CAAC,KAAa,CAAC,GAAO;EAE1B,IAAM,IAAa,KAAK;EAOxB,AALA,KAAK,eAAe,EAAK,GAEzB,EAAU,SAAS,IACnB,EAAW,aAEX,4BAA4B;GACtB,MAAe,KAAK,eAAe,CAAC,KAAK,SAC7C,EAAM,QAAQ,cAAc,QAC5B,KAAK,iBAAiB,GAAO,SAAkB,KAAK,QAAQ,EAAI,CAAC;EACnE,CAAC;CACH;CAEA,iBAAyB;EAEvB,AADA,KAAK,eACL,KAAK,gBAAgB;EACrB,IAAM,IAAQ,KAAK,cAAc;EACjC,IAAI,CAAC,GAAO;EAIZ,AADA,KAAK,eAAe,EAAI,GACxB,EAAM,QAAQ,cAAc;EAC5B,IAAM,IAAa,KAAK;EACxB,KAAK,iBAAiB,GAAO,SAAkB,KAAK,QAAQ,EAAK,CAAC;CACpE;CAGA,QAAgB,GAAe;EAE7B,IADA,KAAK,gBAAgB,GACjB,GAAM;GACR,KAAK,eAAe,EAAK;GACzB;EACF;EAGA,IAAM,IAAQ,KAAK,cAAc;EAEjC,IADI,KAAO,OAAO,EAAM,QAAQ,aAC5B,KAAK,aAAa;EACtB,IAAM,IAAY,KAAK,kBAAkB;EAEzC,AADA,KAAK,eAAe,EAAK,GACrB,MAAW,EAAU,SAAS;CACpC;CAIA,iBAAyB,GAAoB,GAAoB,GAAoB;EACnF,IAAM,IAAW,EAAsB,GAAO,CAAC;EAC/C,IAAI,MAAa,GAAG;GAClB,AAAI,MAAe,KAAK,eAAa,EAAO;GAC5C;EACF;EAEA,IAAI,IAAU,IACR,UAAe;GACnB,EAAM,oBAAoB,iBAAiB,CAAe,GACtD,KAAK,eAAe,MAAO,KAAK,aAAa,KAAA,IACjD,aAAa,CAAK,GAEd,OAAW,MAAe,KAAK,iBACnC,IAAU,IACV,EAAO;EACT,GACM,IAAQ,WAAW,GAAQ,IAAW,CAAoB;EAChE,KAAK,aAAa;EAElB,IAAM,KAAmB,MAA2B;GAClD,AACE,EAAM,WAAW,MAChB,EAAM,iBAAiB,wBAAwB,EAAM,iBAAiB,4BAEvE,EAAO;EACX;EACA,EAAM,iBAAiB,iBAAiB,CAAe;CACzD;CAEA,kBAA0B;EACxB,AAAI,KAAK,eAAe,KAAA,MACtB,aAAa,KAAK,UAAU,GAC5B,KAAK,aAAa,KAAA;CAEtB;CAEA,eAAuB,GAAgB;EACrC,KAAK,YAAY,cAA2B,qBAAqB,CAAC,EAAE,gBAAgB,SAAS,CAAK;CACpG;CAEA,oBAAgD;EAC9C,OAAO,KAAK,YAAY,cAA2B,uBAAuB,KAAK;CACjF;CAEA,gBAA4C;EAC1C,OAAO,KAAK,YAAY,cAA2B,qBAAqB,KAAK;CAC/E;CAEA,SAAkB;EAEhB,IAAM,IAAa;GACjB,sBAAsB;GACtB,iBAAiB,KAAK;EACxB;EAEA,OAAO,CAAI;8DAC+C,KAAK,qBAAqB;;qBAEnE,EAAS,CAAU,EAAE,MAAM,KAAK,WAAW;;;;;;;CAO9D;AAKF;AAhSG,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,QAAA,KAAA,CAAA,GAGzC,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,YAAA,KAAA,CAAA,GAGzC,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,cAAA,KAAA,CAAA,GAGzC,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;CAAM,WAAW;AAAe,CAAC,CAAA,GAAA,EAAA,WAAA,eAAA,KAAA,CAAA,GAEpE,EAAA,CAAA,EAAM,+BAA+B,CAAA,GAAA,EAAA,WAAA,mBAAA,KAAA,CAAA,GAhBvC,IAAA,EAAA,CAAA,EAAc,iBAAiB,CAAA,GAAA,CAAA"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/collapse/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\n\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { getTransitionDuration } from '@/shared/overlay/presence'\n\nimport style from './style.css?inline'\n\nconst EXIT_FALLBACK_BUFFER = 80\n\nlet collapseIdCounter = 0\n\n/**\n * 文档流内折叠面板:trigger 经默认 slot 提供,内容经 `slot=\"content\"` 提供。\n *\n * trigger 语义完全来自 slot 进来的交互元素(原生 button / web-ui-button 等):\n * Enter/Space 激活走该元素的原生 click,不做 role=\"button\" 补齐;组件把\n * `aria-expanded` / `aria-controls` / `aria-disabled` 回写到首个 assigned\n * element(与 popover trigger 的既定回写模式一致)。wrapper 仅负责点击代理,\n * 不可聚焦,不承载 ARIA。\n *\n * 内容区在 shadow 内以 grid `0fr ↔ 1fr` track 过渡驱动高度/宽度动画(ADR-0038\n * 选型保留)。关闭稳态三态(消费者 light DOM 永不移动,hidden 全部命令式管理,\n * 避免渲染绑定在关闭动画起点抢先 display:none):\n * - 默认:content 容器 `hidden`(display:none,脱离渲染与可访问性树);\n * - `keep-mounted`:inner `inert`(保留在 0fr 轨道内,滚动位置与布局可测量);\n * - 动画进行中:inner `inert` + track pointer-events 禁用,结束后进入对应稳态。\n */\n@customElement('web-ui-collapse')\nexport class WebUiCollapse extends LitElement {\n static override styles = unsafeCSS(style)\n\n /** 展开状态;严格受控,唯一状态源。 */\n @property({ type: Boolean, reflect: true }) open = false\n\n /** 禁用触发:点击忽略、aria-disabled 写入 trigger 元素;已展开内容保持现状。 */\n @property({ type: Boolean, reflect: true }) disabled = false\n\n /** 水平方向:true 时沿宽度展开,默认沿高度。 */\n @property({ type: Boolean, reflect: true }) horizontal = false\n\n /** 关闭稳态保持内容 `inert` 而非 `hidden`(保留滚动位置,可测量)。 */\n @property({ type: Boolean, reflect: true, attribute: 'keep-mounted' }) keepMounted = false\n\n @query('.wui-collapse-trigger-wrapper') private _triggerWrapper!: HTMLElement\n\n private _contentId = `wui-collapse-content-${++collapseIdCounter}`\n\n private readonly _userOpenChange = new UserChangeController()\n\n private _exitTimer?: ReturnType<typeof setTimeout>\n // 动画代际:每次管线启动自增,旧管线的 transitionend/兜底定时器/rAF 全部失效。\n private _generation = 0\n private _lastOpen?: boolean\n\n override connectedCallback() {\n super.connectedCallback()\n // 点击监听与生命周期同寿(宿主移除即整体销毁),无需 disconnected 清理。\n this.addEventListener('click', this._onTriggerClick)\n // 断连可能打断动画管线(瞬态残留:presence='closing'、容器未 hidden)。\n // 首次连接不必处理(初始 render 走 _lastOpen===undefined 落稳态,请求多余\n // 渲染会触发「scheduled an update after update completed」告警);重连时\n // 重置同步状态并强制一轮 update,由首次同步分支重新落稳态、丢弃在途管线。\n if (this.hasUpdated) {\n this._generation++\n this._clearExitTimer()\n this._lastOpen = undefined\n this.requestUpdate()\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._clearExitTimer()\n }\n\n protected override updated(changed: Map<string, unknown>) {\n // ARIA 回写不依赖 open 分支,任何渲染后都保持与宿主状态同步。\n this._syncTriggerAria()\n\n // 首次同步(含 open 为默认值的场景,changed map 不含默认值属性):\n // 直接落到正确稳态,不播动画(popover 初始 open 同款)。\n if (this._lastOpen === undefined) {\n this._lastOpen = this.open\n if (this.open) this._applyOpenSteadyState()\n else this._applyClosedSteadyState()\n return\n }\n\n if (changed.has('keepMounted') && !this.open) {\n // 运行态切换 keep-mounted:关闭稳态需在 hidden/inert 间重新落地。\n this._applyClosedSteadyState()\n }\n if (!changed.has('open') || this.open === this._lastOpen) return\n\n this._lastOpen = this.open\n if (this._userOpenChange.consume()) this._dispatchChange(this.open)\n if (this.open) this._beginExpand()\n else this._beginCollapse()\n }\n\n show() {\n if (this.open) return\n this.open = true\n }\n\n close() {\n if (!this.open) return\n this.open = false\n }\n\n toggle() {\n if (this.open) this.close()\n else this.show()\n }\n\n // ===== trigger slot =====\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n /*\n * 点击代理:冒泡路径穿过 trigger wrapper 的 click 一律切换。语义由 slot 内\n * 的交互元素(原生 button / web-ui-button)提供;slot 放纯文本时无激活语义\n * (README 约定)。内容区与嵌套内层 collapse 的 click 不经过本 wrapper,天然隔离。\n */\n private _onTriggerClick = (event: MouseEvent) => {\n if (this.disabled) return\n if (!event.composedPath().includes(this._triggerWrapper)) return\n this._userOpenChange.mark()\n this.toggle()\n }\n\n /*\n * Trigger 元素取首个 assigned element(与 popover `_queryTrigger` 同款惰性查询,\n * 不依赖 slotchange——jsdom 不派发该事件,无法用状态缓存)。slot 为空时无回写目标。\n */\n private _queryTrigger(): HTMLElement | null {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('.wui-collapse-trigger-wrapper slot')\n const el = slot?.assignedElements()[0]\n return el instanceof HTMLElement ? el : null\n }\n\n /*\n * slot 内容增删不触发宿主响应式更新:显式请求一轮渲染,由 updated() 的\n * ARIA 回写覆盖晚到的 trigger 元素(slotchange 在 jsdom 不派发,无法监听)。\n */\n private readonly _onTriggerSlotChange = () => {\n this.requestUpdate()\n }\n\n // ARIA 状态回写到 trigger 元素本身(wrapper 不可聚焦,AT 读不到)。\n private _syncTriggerAria() {\n const trigger = this._queryTrigger()\n if (!trigger) return\n trigger.setAttribute('aria-expanded', String(this.open))\n trigger.setAttribute('aria-controls', this._contentId)\n if (this.disabled) trigger.setAttribute('aria-disabled', 'true')\n else trigger.removeAttribute('aria-disabled')\n }\n\n // ===== 内容区动画管线(三态语义自 ADR-0038 迁移) =====\n\n private _applyOpenSteadyState() {\n this._generation++\n this._clearExitTimer()\n const container = this._contentContainer()\n const track = this._contentTrack()\n if (container) container.hidden = false\n if (track) {\n track.dataset.wuiPresence = 'open'\n this._setInnerInert(false)\n }\n }\n\n private _applyClosedSteadyState() {\n this._generation++\n this._clearExitTimer()\n const container = this._contentContainer()\n const track = this._contentTrack()\n if (track) delete track.dataset.wuiPresence\n if (this.keepMounted) {\n if (container) container.hidden = false\n this._setInnerInert(true)\n return\n }\n this._setInnerInert(false)\n if (container) container.hidden = true\n }\n\n private _beginExpand() {\n this._generation++\n this._clearExitTimer()\n const container = this._contentContainer()\n const track = this._contentTrack()\n if (!container || !track) return\n\n const generation = this._generation\n // 中断关闭动画时 inner 已 inert:先恢复可交互,内容随展开重新可见。\n this._setInnerInert(false)\n // 从 display:none 进入时无先前计算样式可过渡:提交 0fr 起点后再翻转。\n container.hidden = false\n void track.offsetWidth\n\n requestAnimationFrame(() => {\n if (generation !== this._generation || !this.open) return\n track.dataset.wuiPresence = 'open'\n this._awaitTransition(track, generation, () => this._settle(true))\n })\n }\n\n private _beginCollapse() {\n this._generation++\n this._clearExitTimer()\n const track = this._contentTrack()\n if (!track) return\n\n // 收起期间立即阻断交互,防止焦点落入正在消失的区域。\n this._setInnerInert(true)\n track.dataset.wuiPresence = 'closing'\n const generation = this._generation\n this._awaitTransition(track, generation, () => this._settle(false))\n }\n\n // 关闭稳态:keep-mounted → 内容保留在 0fr 轨道内并 inert;默认 → 容器 hidden。\n private _settle(open: boolean) {\n this._clearExitTimer()\n if (open) {\n this._setInnerInert(false)\n return\n }\n // 落稳态必须清除 closing 瞬态标记:否则 track 永久保留 pointer-events:none\n // 与 exit 过渡配置(keep-mounted 分支同样清除,避免残留)。\n const track = this._contentTrack()\n if (track) delete track.dataset.wuiPresence\n if (this.keepMounted) return\n const container = this._contentContainer()\n this._setInnerInert(false)\n if (container) container.hidden = true\n }\n\n // 等待 grid-template-rows/columns 过渡结束;时长为 0(reduced motion、jsdom)\n // 直接稳态,transitionend 不触发时按时长 + 缓冲兜底。\n private _awaitTransition(track: HTMLElement, generation: number, onDone: () => void) {\n const duration = getTransitionDuration(track, 0)\n if (duration === 0) {\n if (generation === this._generation) onDone()\n return\n }\n\n let settled = false\n const finish = () => {\n track.removeEventListener('transitionend', onTransitionEnd)\n if (this._exitTimer === timer) this._exitTimer = undefined\n clearTimeout(timer)\n // 过渡可能被属性匹配的后续动画触发(如中断重开):代际不符时静默退场。\n if (settled || generation !== this._generation) return\n settled = true\n onDone()\n }\n const timer = setTimeout(finish, duration + EXIT_FALLBACK_BUFFER)\n this._exitTimer = timer\n\n const onTransitionEnd = (event: TransitionEvent) => {\n if (\n event.target === track &&\n (event.propertyName === 'grid-template-rows' || event.propertyName === 'grid-template-columns')\n )\n finish()\n }\n track.addEventListener('transitionend', onTransitionEnd)\n }\n\n private _clearExitTimer() {\n if (this._exitTimer !== undefined) {\n clearTimeout(this._exitTimer)\n this._exitTimer = undefined\n }\n }\n\n private _setInnerInert(inert: boolean) {\n this.shadowRoot?.querySelector<HTMLElement>('.wui-collapse-inner')?.toggleAttribute('inert', inert)\n }\n\n private _contentContainer(): HTMLElement | null {\n return this.shadowRoot?.querySelector<HTMLElement>('.wui-collapse-content') ?? null\n }\n\n private _contentTrack(): HTMLElement | null {\n return this.shadowRoot?.querySelector<HTMLElement>('.wui-collapse-track') ?? null\n }\n\n override render() {\n // 三态由 data-wui-presence 驱动(open/closing),缺省即关闭稳态。\n const trackClass = {\n 'wui-collapse-track': true,\n 'is-horizontal': this.horizontal\n }\n\n return html`\n <div class=\"wui-collapse-trigger-wrapper\" @slotchange=${this._onTriggerSlotChange}><slot></slot></div>\n <div class=\"wui-collapse-content\">\n <div class=${classMap(trackClass)} id=${this._contentId}>\n <div class=\"wui-collapse-inner\">\n <slot name=\"content\"></slot>\n </div>\n </div>\n </div>\n `\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-collapse': WebUiCollapse\n }\n}\n"],"mappings":"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"}
@@ -9,12 +9,12 @@ export declare class WebUiContextMenu extends LitElement {
9
9
  private _activeSubmenus;
10
10
  private _activeSubmenuItems;
11
11
  private readonly _submenuPositionEpochs;
12
- private readonly _closingSubmenus;
13
- private _submenuTimer?;
14
- private _ignoreOutsideClick;
15
- private _ignoreOutsideClickTimer?;
16
- private _hoverCleanupFns;
17
12
  private _menu?;
13
+ private readonly _outsideClickGuard;
14
+ private readonly _closingSubmenus;
15
+ private readonly _hoverDelegate;
16
+ private readonly _hoverBinder;
17
+ private readonly _keyboardDelegate;
18
18
  private readonly _menuItemAnchors;
19
19
  private readonly _scrollLock;
20
20
  private readonly _userOpenChange;
@@ -47,56 +47,28 @@ export declare class WebUiContextMenu extends LitElement {
47
47
  private _applyMenuPosition;
48
48
  private _focusFirstItem;
49
49
  private _setupMenuItems;
50
- /**
51
- * 捕获框架 v-if 注释锚点相对其后继元素的绑定,供重排后复位。
52
- * 后继以 nextElementSibling 解析(跳过注释/文本,多个相邻锚点绑定同一后继)。
53
- * 锚点在 content 末尾、无后继元素时记为 null(尾部锚点,复位时保持末尾)。
54
- */
55
- private _captureFrameworkAnchors;
56
- /**
57
- * 把锚点复位到捕获时其后继元素之前(尾部锚点保持末尾)。
58
- * 元素重排用 insertBefore/appendChild 不会移动注释,锚点必须显式复位,
59
- * 否则脱离模板位置后 Vue 下次 v-if 翻转会把新分支项插到错误插入点。
60
- */
61
- private _restoreFrameworkAnchors;
62
- /**
63
- * 全集 reconcile:以「content 现有元素 ∪ 宿主元素」为托管全集。
64
- * 框架(如 Vue 的 v-if 翻转)可能不经宿主直接改写 portal 内节点——卸载旧项留下
65
- * 注释锚点、把新项直接插进 content——它们都会被这里收编(补 marker + 补隐藏),
66
- * 已被框架删除/移出两处的条目则被清理。
67
- */
68
- private _syncManagedItems;
69
- private _orderMenuItems;
70
50
  private _returnItemsToSlot;
71
51
  private _closeMenuAfterPresence;
72
52
  private _hideMenuItems;
73
53
  private _openSubmenu;
74
54
  private _closeSubmenusFrom;
75
- private _closeSubmenuAfterPresence;
76
55
  private _restoreSubmenuItems;
77
- private _restoreClosingSubmenus;
78
56
  private _getItemLevel;
79
57
  private _positionSubmenu;
80
58
  private _dispatchChange;
81
- private _isInsideShadowRoot;
82
59
  private _onContextMenu;
83
60
  private _onContextMenuOutside;
84
61
  private _onKeydown;
85
62
  private _onClickOutside;
86
63
  private _onMenuClick;
64
+ private _getLevelContainer;
87
65
  private _getLevelItems;
88
- private _bindLevelHovers;
89
- private _ignoreCurrentOutsideClick;
90
66
  private _onWheel;
91
67
  private _onTouchMove;
92
68
  private _onDocumentKeydown;
93
69
  private _closeLastSubmenuOrMenu;
94
70
  private _preventBackgroundScroll;
95
71
  private _syncScrollLock;
96
- private _getEnabledLevelItems;
97
- private _getFocusedItem;
98
- private _getFocusedLevel;
99
- private _focusMenuItem;
100
72
  private _isMenuPanelEvent;
101
73
  render(): import('lit').TemplateResult<1>;
102
74
  private _onSlotChange;
@@ -1,27 +1,56 @@
1
1
  import e from "../../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
2
- import { computePosition as t, shift as n } from "../../node_modules/.pnpm/@floating-ui_dom@1.8.0/node_modules/@floating-ui/dom/dist/floating-ui.dom.js";
3
- import { hideOverlayPresence as r, showOverlayPresence as i } from "../../shared/overlay/presence.js";
4
- import { defineScrollLockLease as a } from "../../shared/scroll-lock/scroll-lock.js";
5
- import { UserChangeController as o } from "../../shared/events/user-change.js";
2
+ import { dispatchOpenChangeEvent as t } from "../../shared/open-state/index.js";
3
+ import { computePosition as n, shift as r } from "../../node_modules/.pnpm/@floating-ui_dom@1.8.0/node_modules/@floating-ui/dom/dist/floating-ui.dom.js";
4
+ import { hideOverlayPresence as i, showOverlayPresence as a } from "../../shared/overlay/presence.js";
5
+ import { defineScrollLockLease as o } from "../../shared/scroll-lock/scroll-lock.js";
6
+ import { UserChangeController as s } from "../../shared/events/user-change.js";
6
7
  import "../dropdown-divider/index.js";
7
8
  import "../dropdown-header/index.js";
8
9
  import "../dropdown-item/index.js";
9
- import { createMenuPortalOverlay as s } from "../../shared/menu-portal/menu-portal.js";
10
- import { findFocusedMenuItem as c, focusMenuItem as l, getEnabledMenuItems as u, getMenuChildren as d, getMenuItemFromEvent as f, getMovableMenuSubtrees as p, hideNestedMenuChildren as m, moveMenuChildren as h } from "../../shared/menu-portal/menu-tree.js";
11
- import g from "./style.js";
12
- import { LitElement as _, html as v, unsafeCSS as y } from "lit";
13
- import { customElement as b, property as x, state as S } from "lit/decorators.js";
10
+ import { captureFrameworkAnchors as c, focusMenuItem as l, getMenuChildren as u, getMenuItemFromEvent as d, hideNestedMenuChildren as f, moveMenuChildren as p, orderManagedMenuItems as m, reconcileManagedMenuItems as h, restoreFrameworkAnchors as g, returnManagedMenuItemsToSlot as _ } from "../../shared/menu-portal/menu-tree.js";
11
+ import { createClosingSubmenuStack as v, createMenuHoverBinder as y, createMenuOutsideClickGuard as b, getEnabledMenuLevelItems as x, getFocusedMenuItemFromPanels as S, handleMenuKeyboard as C } from "../../shared/menu-behavior/index.js";
12
+ import { createMenuPortalOverlay as w } from "../../shared/menu-portal/menu-portal.js";
13
+ import T from "./style.js";
14
+ import { LitElement as E, html as D, unsafeCSS as O } from "lit";
15
+ import { customElement as k, property as A, state as j } from "lit/decorators.js";
14
16
  //#region src/components/context-menu/index.ts
15
- var C = "wui-context-menu-item", w = class extends _ {
17
+ var M = "wui-context-menu-item", N = class extends E {
16
18
  constructor(...e) {
17
- super(...e), this.disabled = !1, this.noScrollLock = !1, this._isOpen = !1, this._x = 0, this._y = 0, this._activeSubmenus = [], this._activeSubmenuItems = [], this._submenuPositionEpochs = /* @__PURE__ */ new WeakMap(), this._closingSubmenus = /* @__PURE__ */ new Map(), this._ignoreOutsideClick = !1, this._hoverCleanupFns = [], this._menuItemAnchors = /* @__PURE__ */ new Map(), this._scrollLock = a().make(), this._userOpenChange = new o(), this._shouldOpenInstantly = !0, this._refreshScheduled = !1, this._contentObserver = new MutationObserver(() => {
19
+ super(...e), this.disabled = !1, this.noScrollLock = !1, this._isOpen = !1, this._x = 0, this._y = 0, this._activeSubmenus = [], this._activeSubmenuItems = [], this._submenuPositionEpochs = /* @__PURE__ */ new WeakMap(), this._outsideClickGuard = b(this, (e) => this._menu?.panel.contains(e) === !0 || this._activeSubmenus.some((t) => t.panel.contains(e))), this._closingSubmenus = v({
20
+ getPanel: (e) => e.panel,
21
+ restoreItems: (e, t) => this._restoreSubmenuItems(e, t),
22
+ dispose: (e) => e.panel.remove()
23
+ }), this._hoverDelegate = {
24
+ getOpenDepth: () => this._activeSubmenus.length,
25
+ getLevelItems: (e) => this._getLevelItems(e),
26
+ getActiveItem: (e) => this._activeSubmenuItems[e],
27
+ isSubmenuItem: (e) => e.hasAttribute("submenu"),
28
+ openSubmenu: (e) => this._openSubmenu(e),
29
+ closeFrom: (e) => {
30
+ this._closeSubmenusFrom(e), this._hoverBinder.bind();
31
+ }
32
+ }, this._hoverBinder = y(this._hoverDelegate, "web-ui-dropdown-item"), this._keyboardDelegate = {
33
+ getFocusedItem: (e) => S(e, [this._menu?.content, ...this._activeSubmenus.map((e) => e.content)], "web-ui-dropdown-item"),
34
+ getLevelOf: (e) => {
35
+ let t = this._getItemLevel(e);
36
+ return t === -1 ? void 0 : t;
37
+ },
38
+ getEnabledItems: (e) => x(this._getLevelContainer(e)?.content),
39
+ isSubmenuItem: (e) => e.hasAttribute("submenu"),
40
+ openSubmenuInstant: (e) => this._openSubmenu(e, !0),
41
+ closeToParent: (e) => {
42
+ let t = this._activeSubmenuItems[e - 1];
43
+ return this._closeSubmenusFrom(e - 1), this._hoverBinder.bind(), t;
44
+ },
45
+ closeDeepestOrAll: () => this._closeLastSubmenuOrMenu()
46
+ }, this._menuItemAnchors = /* @__PURE__ */ new Map(), this._scrollLock = o().make(), this._userOpenChange = new s(), this._shouldOpenInstantly = !0, this._refreshScheduled = !1, this._contentObserver = new MutationObserver(() => {
18
47
  this._isOpen && this._scheduleRefresh();
19
48
  }), this._closeFromUser = () => {
20
49
  this._isOpen && (this._userOpenChange.mark(), this.close());
21
50
  }, this._onContextMenu = (e) => {
22
51
  this.disabled || (e.preventDefault(), this._restoreFocusTarget = e.target instanceof HTMLElement ? e.target : void 0, this._openAt(e.clientX, e.clientY, !1) && this._userOpenChange.mark());
23
52
  }, this._onContextMenuOutside = (e) => {
24
- this._isOpen && !this._isInsideShadowRoot(e) && this._closeFromUser();
53
+ this._isOpen && !this._outsideClickGuard.isInside(e) && this._closeFromUser();
25
54
  }, this._onKeydown = (e) => {
26
55
  if (!this.disabled && (e.key === "ContextMenu" || e.shiftKey && e.key === "F10")) {
27
56
  e.preventDefault();
@@ -33,9 +62,9 @@ var C = "wui-context-menu-item", w = class extends _ {
33
62
  return;
34
63
  }
35
64
  }, this._onClickOutside = (e) => {
36
- !this._isOpen || this._ignoreOutsideClick || this._isInsideShadowRoot(e) || this._closeFromUser();
65
+ !this._isOpen || this._outsideClickGuard.isArmed() || this._outsideClickGuard.isInside(e) || this._closeFromUser();
37
66
  }, this._onMenuClick = (e) => {
38
- let t = f(e);
67
+ let t = d(e);
39
68
  if (!(!t || t.hasAttribute("disabled"))) {
40
69
  if (t.hasAttribute("submenu")) {
41
70
  this._openSubmenu(t);
@@ -48,50 +77,11 @@ var C = "wui-context-menu-item", w = class extends _ {
48
77
  }, this._onTouchMove = (e) => {
49
78
  this._preventBackgroundScroll(e);
50
79
  }, this._onDocumentKeydown = (e) => {
51
- if (!this._isOpen) return;
52
- if (e.key === "Escape") {
53
- this._closeLastSubmenuOrMenu(), e.preventDefault();
54
- return;
55
- }
56
- let t = this._getFocusedLevel();
57
- if (t === void 0) return;
58
- let n = this._getEnabledLevelItems(t), r = this._getFocusedItem(), i = r ? n.indexOf(r) : -1;
59
- switch (e.key) {
60
- case "ArrowDown":
61
- this._focusMenuItem(n[(i + 1 + n.length) % n.length]);
62
- break;
63
- case "ArrowUp":
64
- this._focusMenuItem(n[(i - 1 + n.length) % n.length]);
65
- break;
66
- case "Home":
67
- this._focusMenuItem(n[0]);
68
- break;
69
- case "End":
70
- this._focusMenuItem(n.at(-1));
71
- break;
72
- case "ArrowRight":
73
- r?.hasAttribute("submenu") && (this._openSubmenu(r, !0), requestAnimationFrame(() => this._focusMenuItem(this._getEnabledLevelItems(t + 1)[0])));
74
- break;
75
- case "ArrowLeft":
76
- if (t > 0) {
77
- let e = this._activeSubmenuItems[t - 1];
78
- this._closeSubmenusFrom(t - 1), this._focusMenuItem(e), this._bindLevelHovers();
79
- }
80
- break;
81
- case "Enter":
82
- case " ":
83
- r?.click();
84
- break;
85
- case "Escape":
86
- this._closeLastSubmenuOrMenu();
87
- break;
88
- default: return;
89
- }
90
- e.preventDefault();
80
+ this._isOpen && C(this._keyboardDelegate, e);
91
81
  };
92
82
  }
93
83
  static {
94
- this.styles = y(g);
84
+ this.styles = O(T);
95
85
  }
96
86
  get isOpen() {
97
87
  return this._isOpen;
@@ -106,25 +96,25 @@ var C = "wui-context-menu-item", w = class extends _ {
106
96
  }), document.addEventListener("keydown", this._onDocumentKeydown);
107
97
  }
108
98
  disconnectedCallback() {
109
- super.disconnectedCallback(), this.removeEventListener("contextmenu", this._onContextMenu), this.removeEventListener("keydown", this._onKeydown), this.removeEventListener("click", this._onMenuClick), document.removeEventListener("click", this._onClickOutside), document.removeEventListener("contextmenu", this._onContextMenuOutside), document.removeEventListener("wheel", this._onWheel, !0), document.removeEventListener("touchmove", this._onTouchMove, !0), document.removeEventListener("keydown", this._onDocumentKeydown), this._contentObserver.disconnect(), clearTimeout(this._submenuTimer), clearTimeout(this._ignoreOutsideClickTimer), this._hoverCleanupFns.forEach((e) => e()), this._scrollLock.release(), this._returnItemsToSlot(), this._menu?.panel.remove(), this._menu = void 0, this._closeSubmenusFrom(0, !0), this._restoreClosingSubmenus(), this._isOpen = !1, this._restoreFocusTarget = void 0;
99
+ super.disconnectedCallback(), this.removeEventListener("contextmenu", this._onContextMenu), this.removeEventListener("keydown", this._onKeydown), this.removeEventListener("click", this._onMenuClick), document.removeEventListener("click", this._onClickOutside), document.removeEventListener("contextmenu", this._onContextMenuOutside), document.removeEventListener("wheel", this._onWheel, !0), document.removeEventListener("touchmove", this._onTouchMove, !0), document.removeEventListener("keydown", this._onDocumentKeydown), this._contentObserver.disconnect(), this._outsideClickGuard.dispose(), this._hoverBinder.dispose(), this._scrollLock.release(), this._returnItemsToSlot(), this._menu?.panel.remove(), this._menu = void 0, this._closeSubmenusFrom(0, !0), this._closingSubmenus.restoreAll(), this._isOpen = !1, this._restoreFocusTarget = void 0;
110
100
  }
111
101
  updated(e) {
112
102
  if (!this.isConnected) {
113
103
  this._syncScrollLock(!1);
114
104
  return;
115
105
  }
116
- e.has("_isOpen") && (this._isOpen ? (this._syncScrollLock(), this._menu || (this._menu = s("context-menu", this), this._menu.panel.setAttribute("role", "menu"), this._menu.panel.setAttribute("aria-label", "上下文菜单"), this._menu.panel.addEventListener("click", this._onMenuClick)), this._contentObserver.observe(this._menu.content, {
106
+ e.has("_isOpen") && (this._isOpen ? (this._syncScrollLock(), this._menu || (this._menu = w("context-menu", this), this._menu.panel.setAttribute("role", "menu"), this._menu.panel.setAttribute("aria-label", "上下文菜单"), this._menu.panel.addEventListener("click", this._onMenuClick)), this._contentObserver.observe(this._menu.content, {
117
107
  childList: !0,
118
108
  subtree: !0
119
109
  }), requestAnimationFrame(() => {
120
- !this._isOpen || !this._menu || (this._refreshMenu(), i(this._menu.panel, { isInstant: this._shouldOpenInstantly }), this._shouldOpenInstantly = !0, this._focusFirstItem());
110
+ !this._isOpen || !this._menu || (this._refreshMenu(), a(this._menu.panel, { isInstant: this._shouldOpenInstantly }), this._shouldOpenInstantly = !0, this._focusFirstItem());
121
111
  })) : (this._syncScrollLock(!1), this._contentObserver.disconnect(), this._refreshScheduled = !1, this._closeMenuAfterPresence()), this._userOpenChange.consume() && this._dispatchChange(this._isOpen)), e.has("noScrollLock") && this._syncScrollLock();
122
112
  }
123
113
  openAt(e, t) {
124
114
  this._openAt(e, t, !0);
125
115
  }
126
116
  _openAt(e, t, n) {
127
- return !this.disabled && (this._x = e, this._y = t, this._shouldOpenInstantly = n, this._ignoreCurrentOutsideClick(), this._isOpen ? (this._closeSubmenusFrom(0, !0), this._restoreClosingSubmenus(), this._scheduleRefresh(), !1) : (this._restoreFocusTarget ??= document.activeElement instanceof HTMLElement ? document.activeElement : void 0, this._isOpen = !0, !0));
117
+ return !this.disabled && (this._x = e, this._y = t, this._shouldOpenInstantly = n, this._outsideClickGuard.arm(), this._isOpen ? (this._closeSubmenusFrom(0, !0), this._closingSubmenus.restoreAll(), this._scheduleRefresh(), !1) : (this._restoreFocusTarget ??= document.activeElement instanceof HTMLElement ? document.activeElement : void 0, this._isOpen = !0, !0));
128
118
  }
129
119
  close() {
130
120
  this._isOpen &&= !1;
@@ -135,7 +125,7 @@ var C = "wui-context-menu-item", w = class extends _ {
135
125
  }));
136
126
  }
137
127
  _refreshMenu() {
138
- this._menu && (this._setupMenuItems(), this._positionMenu(), this._bindLevelHovers());
128
+ this._menu && (this._setupMenuItems(), this._positionMenu(), this._hoverBinder.bind());
139
129
  }
140
130
  _positionMenu() {
141
131
  let e = this._menu?.panel;
@@ -144,10 +134,10 @@ var C = "wui-context-menu-item", w = class extends _ {
144
134
  this._positionMenuInViewport(e);
145
135
  return;
146
136
  }
147
- t({ getBoundingClientRect: () => new DOMRect(this._x, this._y, 0, 0) }, e, {
137
+ n({ getBoundingClientRect: () => new DOMRect(this._x, this._y, 0, 0) }, e, {
148
138
  strategy: "fixed",
149
139
  placement: "bottom-start",
150
- middleware: [n({
140
+ middleware: [r({
151
141
  padding: 8,
152
142
  crossAxis: !0
153
143
  })]
@@ -169,176 +159,90 @@ var C = "wui-context-menu-item", w = class extends _ {
169
159
  }
170
160
  _focusFirstItem() {
171
161
  let e = this._menu?.content.querySelectorAll("web-ui-dropdown-item:not([disabled])")?.[0];
172
- e && this._focusMenuItem(e);
162
+ e && l(e);
173
163
  }
174
164
  _setupMenuItems() {
175
165
  let e = this._menu;
176
166
  if (!e) return;
177
167
  let t = e.content;
178
- this._syncManagedItems(t), this._hideMenuItems(), m(t, "context-menu-hidden");
179
- let n = this._captureFrameworkAnchors(t);
180
- this._orderMenuItems(t), this._restoreFrameworkAnchors(t, n);
181
- }
182
- _captureFrameworkAnchors(e) {
183
- let t = /* @__PURE__ */ new Map();
184
- for (let n of Array.from(e.childNodes)) if (n instanceof Comment && n.textContent !== C) {
185
- let e = n.nextElementSibling;
186
- t.set(n, e instanceof HTMLElement ? e : null);
187
- }
188
- return t;
189
- }
190
- _restoreFrameworkAnchors(e, t) {
191
- for (let [n, r] of t) n.parentNode === e && (r && r.parentNode === e ? n.nextElementSibling !== r && e.insertBefore(n, r) : e.appendChild(n));
192
- }
193
- _syncManagedItems(e) {
194
- let t = p(e), n = p(this), r = /* @__PURE__ */ new Set([...t, ...n]);
195
- this._menuItemAnchors.forEach((e, t) => {
196
- r.has(t) || (e.remove(), this._menuItemAnchors.delete(t));
197
- });
198
- let i = (e, t, n) => {
199
- let r = this._menuItemAnchors.get(e);
200
- if (r && r.parentNode === this) return r;
201
- r?.remove();
202
- let i = document.createComment(C);
203
- return t ? this.insertBefore(i, e) : n && n.parentNode === this ? this.insertBefore(i, n) : this.appendChild(i), this._menuItemAnchors.set(e, i), i;
204
- };
205
- for (let e = t.length - 1; e >= 0; e--) {
206
- let n = t[e], r = t[e + 1];
207
- i(n, !1, r ? this._menuItemAnchors.get(r) : void 0);
208
- }
209
- for (let e of n) i(e, !0);
210
- for (let t of n) e.appendChild(t);
211
- }
212
- _orderMenuItems(e) {
213
- let t = [];
214
- for (let n of Array.from(this.childNodes)) n.nodeType === Node.COMMENT_NODE && n.textContent === C && this._menuItemAnchors.forEach((r, i) => {
215
- r === n && i.parentNode === e && t.push(i);
216
- });
217
- if (t.length === 0) return;
218
- let n = Array.from(e.children);
219
- if (!(n.length === t.length && n.every((e, n) => e === t[n]))) for (let n = t.length - 1; n >= 0; n--) {
220
- let r = t[n], i = t[n + 1];
221
- i ? r.nextElementSibling !== i && e.insertBefore(r, i) : e.appendChild(r);
222
- }
168
+ h(this, t, this._menuItemAnchors, M), this._hideMenuItems(), f(t, "context-menu-hidden");
169
+ let n = c(t, M);
170
+ m(this, t, this._menuItemAnchors, M), g(t, n);
223
171
  }
224
172
  _returnItemsToSlot() {
225
173
  let e = this._menu;
226
- if (!e) return;
227
- this._closeSubmenusFrom(0, !0), this._restoreClosingSubmenus();
228
- let t = e.content, n = [];
229
- for (let e of Array.from(t.childNodes)) {
230
- if (e instanceof Comment) {
231
- n.push(e);
232
- continue;
233
- }
234
- if (!(e instanceof HTMLElement)) continue;
235
- let t = this._menuItemAnchors.get(e);
236
- if (t) {
237
- for (let e of n) t.parentNode === this ? this.insertBefore(e, t) : this.appendChild(e);
238
- n.length = 0, t.parentNode === this ? this.insertBefore(e, t) : this.appendChild(e), t.remove(), this._menuItemAnchors.delete(e);
239
- }
240
- }
241
- for (let e of n) this.appendChild(e);
242
- this._menuItemAnchors.forEach((e) => e.remove()), this._menuItemAnchors.clear();
174
+ e && (this._closeSubmenusFrom(0, !0), this._closingSubmenus.restoreAll(), _(this, e.content, this._menuItemAnchors));
243
175
  }
244
176
  async _closeMenuAfterPresence() {
245
177
  let e = this._menu;
246
- e && !await r(e.panel) || this._isOpen || !this.isConnected || this._menu !== e || (this._returnItemsToSlot(), e?.panel.remove(), this._menu = void 0, this._restoreFocusTarget?.focus(), this._restoreFocusTarget = void 0);
178
+ e && !await i(e.panel) || this._isOpen || !this.isConnected || this._menu !== e || (this._returnItemsToSlot(), e?.panel.remove(), this._menu = void 0, this._restoreFocusTarget?.focus(), this._restoreFocusTarget = void 0);
247
179
  }
248
180
  _hideMenuItems() {
249
- d(this).forEach((e) => e.setAttribute("slot", "context-menu-hidden")), m(this, "context-menu-hidden");
181
+ u(this).forEach((e) => e.setAttribute("slot", "context-menu-hidden")), f(this, "context-menu-hidden");
250
182
  }
251
183
  _openSubmenu(e, t = !1) {
252
184
  if (!this._isOpen || !e.hasAttribute("submenu") || e.hasAttribute("disabled")) return;
253
185
  let n = this._getItemLevel(e);
254
186
  if (n === -1 || this._activeSubmenuItems[n] === e) return;
255
187
  this._closeSubmenusFrom(n);
256
- let r = this._closingSubmenus.get(e);
188
+ let r = this._closingSubmenus.take(e);
257
189
  if (r) {
258
- this._closingSubmenus.delete(e), this._activeSubmenus[n] = r, this._activeSubmenuItems[n] = e, e.setAttribute("active", ""), this._positionSubmenu(e, r), i(r.panel, { isInstant: t }), this._bindLevelHovers();
190
+ this._activeSubmenus[n] = r, this._activeSubmenuItems[n] = e, e.setAttribute("active", ""), this._positionSubmenu(e, r), a(r.panel, { isInstant: t }), this._hoverBinder.bind();
259
191
  return;
260
192
  }
261
- if (d(e).length === 0) return;
262
- let a = s("context-submenu", this);
263
- a.panel.dataset.level = String(n), a.panel.setAttribute("role", "menu"), a.panel.setAttribute("aria-label", "子菜单"), a.panel.style.visibility = "hidden", a.panel.addEventListener("click", this._onMenuClick), h(e, a.content), this._activeSubmenus[n] = a, this._activeSubmenuItems[n] = e, e.setAttribute("active", ""), this._positionSubmenu(e, a), i(a.panel, { isInstant: t }), this._bindLevelHovers();
193
+ if (u(e).length === 0) return;
194
+ let i = w("context-submenu", this);
195
+ i.panel.dataset.level = String(n), i.panel.setAttribute("role", "menu"), i.panel.setAttribute("aria-label", "子菜单"), i.panel.style.visibility = "hidden", i.panel.addEventListener("click", this._onMenuClick), p(e, i.content), this._activeSubmenus[n] = i, this._activeSubmenuItems[n] = e, e.setAttribute("active", ""), this._positionSubmenu(e, i), a(i.panel, { isInstant: t }), this._hoverBinder.bind();
264
196
  }
265
197
  _closeSubmenusFrom(e, t = !1) {
266
198
  for (let n = this._activeSubmenus.length - 1; n >= e; n--) {
267
199
  let e = this._activeSubmenus[n], r = this._activeSubmenuItems[n];
268
- r?.removeAttribute("active"), !r || t ? (this._restoreSubmenuItems(e, r), e.panel.remove()) : (this._closingSubmenus.set(r, e), this._closeSubmenuAfterPresence(e, r));
200
+ r?.removeAttribute("active"), !r || t ? (this._restoreSubmenuItems(e, r), e.panel.remove()) : this._closingSubmenus.closeAsync(r, e);
269
201
  }
270
202
  this._activeSubmenus.length = e, this._activeSubmenuItems.length = e;
271
203
  }
272
- async _closeSubmenuAfterPresence(e, t) {
273
- await r(e.panel) && this._closingSubmenus.get(t) === e && (this._closingSubmenus.delete(t), this._restoreSubmenuItems(e, t), e.panel.remove());
274
- }
275
204
  _restoreSubmenuItems(e, t) {
276
- t && (h(e.content, t), m(t, "context-menu-hidden"));
277
- }
278
- _restoreClosingSubmenus() {
279
- this._closingSubmenus.forEach((e, t) => {
280
- this._restoreSubmenuItems(e, t), e.panel.remove();
281
- }), this._closingSubmenus.clear();
205
+ t && (p(e.content, t), f(t, "context-menu-hidden"));
282
206
  }
283
207
  _getItemLevel(e) {
284
208
  if (this._menu?.panel.contains(e)) return 0;
285
209
  let t = this._activeSubmenus.findIndex((t) => t.panel.contains(e));
286
210
  return t === -1 ? -1 : t + 1;
287
211
  }
288
- _positionSubmenu(e, r) {
289
- if (r.panel.parentElement instanceof HTMLDialogElement && r.panel.parentElement.open) {
290
- let i = e.getBoundingClientRect().right + r.panel.getBoundingClientRect().width + 8 <= window.innerWidth, a = (this._submenuPositionEpochs.get(r.panel) ?? 0) + 1;
291
- this._submenuPositionEpochs.set(r.panel, a), t(e, r.panel, {
212
+ _positionSubmenu(e, t) {
213
+ if (t.panel.parentElement instanceof HTMLDialogElement && t.panel.parentElement.open) {
214
+ let i = e.getBoundingClientRect().right + t.panel.getBoundingClientRect().width + 8 <= window.innerWidth, a = (this._submenuPositionEpochs.get(t.panel) ?? 0) + 1;
215
+ this._submenuPositionEpochs.set(t.panel, a), n(e, t.panel, {
292
216
  strategy: "fixed",
293
217
  placement: i ? "right-start" : "left-start",
294
- middleware: [n({
218
+ middleware: [r({
295
219
  padding: 8,
296
220
  crossAxis: !0
297
221
  })]
298
- }).then(({ x: e, y: t, middlewareData: n }) => {
299
- if (!this._activeSubmenus.includes(r) || a !== this._submenuPositionEpochs.get(r.panel)) return;
300
- let i = n.shift?.x ?? 0, o = n.shift?.y ?? 0, s = i < 0 ? "right" : "left", c = o < 0 ? "bottom" : "top";
301
- r.panel.style.left = `${e}px`, r.panel.style.top = `${t}px`, r.panel.style.setProperty("--wui-internal-overlay-transform-origin", `${c} ${s}`), r.panel.style.visibility = "";
222
+ }).then(({ x: e, y: n, middlewareData: r }) => {
223
+ if (!this._activeSubmenus.includes(t) || a !== this._submenuPositionEpochs.get(t.panel)) return;
224
+ let i = r.shift?.x ?? 0, o = r.shift?.y ?? 0, s = i < 0 ? "right" : "left", c = o < 0 ? "bottom" : "top";
225
+ t.panel.style.left = `${e}px`, t.panel.style.top = `${n}px`, t.panel.style.setProperty("--wui-internal-overlay-transform-origin", `${c} ${s}`), t.panel.style.visibility = "";
302
226
  });
303
227
  return;
304
228
  }
305
- let i = e.getBoundingClientRect(), a = r.panel.getBoundingClientRect(), o = i.right + a.width + 8 <= window.innerWidth, s = o ? i.right : Math.max(8, i.left - a.width), c = Math.min(Math.max(8, i.top), window.innerHeight - a.height - 8);
306
- r.panel.style.left = `${s}px`, r.panel.style.top = `${c}px`, r.panel.style.setProperty("--wui-internal-overlay-transform-origin", o ? "top left" : "top right"), r.panel.style.visibility = "";
229
+ let i = e.getBoundingClientRect(), a = t.panel.getBoundingClientRect(), o = i.right + a.width + 8 <= window.innerWidth, s = o ? i.right : Math.max(8, i.left - a.width), c = Math.min(Math.max(8, i.top), window.innerHeight - a.height - 8);
230
+ t.panel.style.left = `${s}px`, t.panel.style.top = `${c}px`, t.panel.style.setProperty("--wui-internal-overlay-transform-origin", o ? "top left" : "top right"), t.panel.style.visibility = "";
307
231
  }
308
232
  _dispatchChange(e) {
309
- this.dispatchEvent(new CustomEvent("open-change", {
310
- detail: { open: e },
311
- bubbles: !0,
312
- composed: !0
313
- }));
233
+ t(this, e);
314
234
  }
315
- _isInsideShadowRoot(e) {
316
- for (let t of e.composedPath()) if (t === this || t === this.shadowRoot || t instanceof Node && t.getRootNode() === this.shadowRoot || t instanceof Node && (this._menu?.panel.contains(t) || this._activeSubmenus.some((e) => e.panel.contains(t)))) return !0;
317
- return !1;
235
+ _getLevelContainer(e) {
236
+ return e === 0 ? this._menu : this._activeSubmenus[e - 1];
318
237
  }
319
238
  _getLevelItems(e) {
320
- let t = e === 0 ? this._menu?.content : this._activeSubmenus[e - 1]?.content;
321
- return t ? d(t) : [];
322
- }
323
- _bindLevelHovers() {
324
- this._hoverCleanupFns.forEach((e) => e()), this._hoverCleanupFns.length = 0;
325
- for (let e = 0; e <= this._activeSubmenus.length; e++) this._getLevelItems(e).forEach((t) => {
326
- if (!t.matches("web-ui-dropdown-item") || t.hasAttribute("disabled")) return;
327
- let n = (n) => {
328
- n.pointerType !== "touch" && (clearTimeout(this._submenuTimer), t.hasAttribute("submenu") ? this._activeSubmenuItems[e] !== t && (this._submenuTimer = setTimeout(() => this._openSubmenu(t), 200)) : this._activeSubmenus.length > e && (this._closeSubmenusFrom(e), this._bindLevelHovers()));
329
- };
330
- t.addEventListener("pointerenter", n, { passive: !0 }), this._hoverCleanupFns.push(() => t.removeEventListener("pointerenter", n));
331
- });
332
- }
333
- _ignoreCurrentOutsideClick() {
334
- this._ignoreOutsideClick = !0, clearTimeout(this._ignoreOutsideClickTimer), this._ignoreOutsideClickTimer = setTimeout(() => {
335
- this._ignoreOutsideClick = !1;
336
- });
239
+ let t = this._getLevelContainer(e);
240
+ return t ? u(t.content) : [];
337
241
  }
338
242
  _closeLastSubmenuOrMenu() {
339
243
  if (this._activeSubmenus.length > 0) {
340
244
  let e = this._activeSubmenus.length - 1, t = this._activeSubmenuItems[e];
341
- this._closeSubmenusFrom(e), this._focusMenuItem(t), this._bindLevelHovers();
245
+ this._closeSubmenusFrom(e), l(t), this._hoverBinder.bind();
342
246
  } else this._closeFromUser();
343
247
  }
344
248
  _preventBackgroundScroll(e) {
@@ -347,28 +251,11 @@ var C = "wui-context-menu-item", w = class extends _ {
347
251
  _syncScrollLock(e = this._isOpen) {
348
252
  this._scrollLock.sync(e && !this.noScrollLock);
349
253
  }
350
- _getEnabledLevelItems(e) {
351
- let t = e === 0 ? this._menu?.content : this._activeSubmenus[e - 1]?.content;
352
- return t ? u(t) : [];
353
- }
354
- _getFocusedItem() {
355
- return c([this._menu?.content, ...this._activeSubmenus.map((e) => e.content)]);
356
- }
357
- _getFocusedLevel() {
358
- let e = this._getFocusedItem();
359
- if (!e) return;
360
- if (this._menu?.panel.contains(e)) return 0;
361
- let t = this._activeSubmenus.findIndex((t) => t.panel.contains(e));
362
- return t < 0 ? void 0 : t + 1;
363
- }
364
- _focusMenuItem(e) {
365
- l(e);
366
- }
367
254
  _isMenuPanelEvent(e) {
368
255
  return e.composedPath().some((e) => e instanceof HTMLElement && (e.classList.contains("context-menu") || e.classList.contains("context-submenu")));
369
256
  }
370
257
  render() {
371
- return v`
258
+ return D`
372
259
  <div class="context-menu-anchor">
373
260
  <slot @slotchange=${this._onSlotChange}></slot>
374
261
  </div>
@@ -378,15 +265,15 @@ var C = "wui-context-menu-item", w = class extends _ {
378
265
  this._hideMenuItems(), this._isOpen && this._scheduleRefresh();
379
266
  }
380
267
  };
381
- e([x({
268
+ e([A({
382
269
  type: Boolean,
383
270
  reflect: !0
384
- })], w.prototype, "disabled", void 0), e([x({
271
+ })], N.prototype, "disabled", void 0), e([A({
385
272
  type: Boolean,
386
273
  reflect: !0,
387
274
  attribute: "no-scroll-lock"
388
- })], w.prototype, "noScrollLock", void 0), e([S()], w.prototype, "_isOpen", void 0), e([S()], w.prototype, "_x", void 0), e([S()], w.prototype, "_y", void 0), w = e([b("web-ui-context-menu")], w);
275
+ })], N.prototype, "noScrollLock", void 0), e([j()], N.prototype, "_isOpen", void 0), e([j()], N.prototype, "_x", void 0), e([j()], N.prototype, "_y", void 0), N = e([k("web-ui-context-menu")], N);
389
276
  //#endregion
390
- export { w as WebUiContextMenu };
277
+ export { N as WebUiContextMenu };
391
278
 
392
279
  //# sourceMappingURL=index.js.map