@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/tooltip/index.ts"],"sourcesContent":["import type { Placement } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport overlayMotion from '@/assets/overlay-motion.css?inline'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { normalizeLiteral, normalizeNumber } from '@/shared/normalize'\nimport { defineAnchoredPanel } from '@/shared/overlay/anchored-panel'\nimport { defineOverlayPortal } from '@/shared/overlay/portal'\nimport type { OverlayContainer, OverlayPortal } from '@/shared/overlay/portal'\n\nimport style from './style.css?inline'\n\nconst ALLOWED_PLACEMENTS = [\n 'top',\n 'top-start',\n 'top-end',\n 'bottom',\n 'bottom-start',\n 'bottom-end',\n 'left',\n 'left-start',\n 'left-end',\n 'right',\n 'right-start',\n 'right-end'\n] as const\n\nlet visibleTooltipCount = 0\n\n@customElement('web-ui-tooltip')\nexport class WebUiTooltip extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(overlayMotion), unsafeCSS(style)]\n\n @property({ type: String, reflect: true })\n get placement(): Placement {\n return this._placement\n }\n set placement(v: string) {\n const old = this._placement\n this._placement = normalizeLiteral(v, ALLOWED_PLACEMENTS, 'top')\n this.requestUpdate('placement', old)\n }\n private _placement: Placement = 'top'\n\n @property({ type: Number, attribute: 'show-delay' })\n get showDelay(): number {\n return this._showDelay\n }\n set showDelay(v: number) {\n const old = this._showDelay\n this._showDelay = normalizeNumber(v, 0, 5000, 200)\n this.requestUpdate('showDelay', old)\n }\n private _showDelay = 200\n\n @property({ type: Number, attribute: 'hide-delay' })\n get hideDelay(): number {\n return this._hideDelay\n }\n set hideDelay(v: number) {\n const old = this._hideDelay\n this._hideDelay = normalizeNumber(v, 0, 5000, 100)\n this.requestUpdate('hideDelay', old)\n }\n private _hideDelay = 100\n\n @property({ type: Boolean, reflect: true }) disabled = false\n\n @property({ type: Number })\n get offset(): number {\n return this._offset\n }\n set offset(v: number) {\n const old = this._offset\n this._offset = normalizeNumber(v, 0, 100, 6)\n this.requestUpdate('offset', old)\n }\n private _offset = 6\n\n @property({ type: String }) content = ''\n\n @property({ type: Boolean, reflect: true }) open = false\n @property({ type: Boolean, reflect: true }) portal = false\n @property({ attribute: false }) overlayContainer?: OverlayContainer\n\n private _showTimer?: ReturnType<typeof setTimeout>\n private _hideTimer?: ReturnType<typeof setTimeout>\n private _isCountedVisible = false\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n private readonly _panel = defineAnchoredPanel().make({\n getAnchor: () => this.shadowRoot?.querySelector<HTMLElement>('.tooltip-trigger') ?? null,\n getLocalPanel: () => this.shadowRoot?.querySelector<HTMLElement>('.tooltip-panel') ?? null,\n getPositioning: () => ({\n placement: this.placement,\n offset: this.offset,\n strategy: this.portal ? 'fixed' : 'absolute'\n }),\n isPortal: () => this.portal,\n createPortal: () => this._createPortal()\n })\n\n get isOpen(): boolean {\n return this.open\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this.addEventListener('pointerenter', this._onPointerEnter)\n this.addEventListener('pointerleave', this._onPointerLeave)\n this.addEventListener('focusin', this._onFocusIn)\n this.addEventListener('focusout', this._onFocusOut)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('pointerenter', this._onPointerEnter)\n this.removeEventListener('pointerleave', this._onPointerLeave)\n this.removeEventListener('focusin', this._onFocusIn)\n this.removeEventListener('focusout', this._onFocusOut)\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n this._syncVisibleTooltipCount(false)\n this._panel.dispose()\n }\n\n override firstUpdated() {\n if (this.open) {\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n }\n }\n\n protected override updated(changed: Map<string, unknown>) {\n if (changed.has('portal') || changed.has('overlayContainer')) {\n requestAnimationFrame(() => this._reconfigureOverlay())\n } else if (changed.has('placement') || changed.has('offset'))\n requestAnimationFrame(() => this._panel.updatePosition())\n\n if (changed.has('open')) {\n this._syncVisibleTooltipCount(this.open)\n if (this.open) {\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n } else {\n void this._closeOverlay()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this.open)\n }\n if (changed.has('content')) this._syncPortalContent()\n }\n\n private _onPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled) return\n clearTimeout(this._hideTimer)\n this._showTimer = setTimeout(() => this._show(), visibleTooltipCount > 0 ? 0 : this.showDelay)\n }\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled) return\n clearTimeout(this._showTimer)\n this._hideTimer = setTimeout(() => this._hide(), this.hideDelay)\n }\n\n private _onFocusIn = (event: FocusEvent) => {\n if (this.disabled) return\n if (!(event.target instanceof Element) || !event.target.matches(':focus-visible')) return\n\n clearTimeout(this._hideTimer)\n this._show(true)\n }\n\n private _onFocusOut = () => {\n if (this.disabled) return\n clearTimeout(this._showTimer)\n this._hide()\n }\n\n private _show(isInstant = false) {\n if (this.open) return\n this._shouldOpenInstantly = isInstant\n this._userOpenChange.mark()\n this.open = true\n }\n\n private _hide() {\n if (!this.open) return\n this._userOpenChange.mark()\n this.open = false\n }\n\n private _openOverlay(isInstant = false) {\n this._panel.open(isInstant)\n }\n\n private _createPortal(): OverlayPortal {\n const portal = defineOverlayPortal().make({\n container: this.overlayContainer,\n target: this,\n style: `${glass}\\n${overlayMotion}\\n${style}`,\n className: 'tooltip-panel portal wui-glass wui-floating-panel'\n })\n if (this.content) {\n const text = document.createElement('span')\n text.className = 'tooltip-text'\n text.textContent = this.content\n portal.panel.append(text)\n } else portal.moveContent(Array.from(this.querySelectorAll('[slot=\"content\"]')))\n portal.panel.setAttribute('role', 'tooltip')\n return portal\n }\n\n private async _closeOverlay() {\n await this._panel.close(() => this.open)\n }\n\n private _reconfigureOverlay() {\n this._panel.reconfigure(this.open)\n }\n\n private _syncPortalContent() {\n if (!this.portal || !this.open) return\n const panel = this._panel.getPanel()\n if (!panel) return\n const text = panel.querySelector<HTMLElement>('.tooltip-text')\n if (text) {\n text.textContent = this.content\n return\n }\n if (!this.content) return\n panel.replaceChildren()\n const nextText = document.createElement('span')\n nextText.className = 'tooltip-text'\n nextText.textContent = this.content\n panel.append(nextText)\n }\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 private _syncVisibleTooltipCount(isVisible: boolean) {\n if (isVisible && !this._isCountedVisible) {\n visibleTooltipCount += 1\n this._isCountedVisible = true\n } else if (!isVisible && this._isCountedVisible) {\n visibleTooltipCount = Math.max(0, visibleTooltipCount - 1)\n this._isCountedVisible = false\n }\n }\n\n override render() {\n return html`\n <div class=\"tooltip-anchor\">\n <div class=\"tooltip-trigger\"><slot></slot></div>\n <div class=\"tooltip-panel wui-glass wui-floating-panel\" hidden role=\"tooltip\">\n ${this.content ? html`<span class=\"tooltip-text\">${this.content}</span>` : html`<slot name=\"content\"></slot>`}\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-tooltip': WebUiTooltip\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/tooltip/index.ts"],"sourcesContent":["import type { Placement } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport overlayMotion from '@/assets/overlay-motion.css?inline'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { normalizeLiteral, normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineAnchoredPanel } from '@/shared/overlay/anchored-panel'\nimport { FLOATING_PLACEMENTS } from '@/shared/overlay/placement-props'\nimport { defineOverlayPortal } from '@/shared/overlay/portal'\nimport type { OverlayContainer, OverlayPortal } from '@/shared/overlay/portal'\n\nimport style from './style.css?inline'\n\nlet visibleTooltipCount = 0\n\n@customElement('web-ui-tooltip')\nexport class WebUiTooltip extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(overlayMotion), unsafeCSS(style)]\n\n @property({ type: String, reflect: true })\n get placement(): Placement {\n return this._placement\n }\n set placement(v: string) {\n const old = this._placement\n this._placement = normalizeLiteral(v, FLOATING_PLACEMENTS, 'top')\n this.requestUpdate('placement', old)\n }\n private _placement: Placement = 'top'\n\n @property({ type: Number, attribute: 'show-delay' })\n get showDelay(): number {\n return this._showDelay\n }\n set showDelay(v: number) {\n const old = this._showDelay\n this._showDelay = normalizeNumber(v, 0, 5000, 200)\n this.requestUpdate('showDelay', old)\n }\n private _showDelay = 200\n\n @property({ type: Number, attribute: 'hide-delay' })\n get hideDelay(): number {\n return this._hideDelay\n }\n set hideDelay(v: number) {\n const old = this._hideDelay\n this._hideDelay = normalizeNumber(v, 0, 5000, 100)\n this.requestUpdate('hideDelay', old)\n }\n private _hideDelay = 100\n\n @property({ type: Boolean, reflect: true }) disabled = false\n\n @property({ type: Number })\n get offset(): number {\n return this._offset\n }\n set offset(v: number) {\n const old = this._offset\n this._offset = normalizeNumber(v, 0, 100, 6)\n this.requestUpdate('offset', old)\n }\n private _offset = 6\n\n @property({ type: String }) content = ''\n\n @property({ type: Boolean, reflect: true }) open = false\n @property({ type: Boolean, reflect: true }) portal = false\n @property({ attribute: false }) overlayContainer?: OverlayContainer\n\n private _showTimer?: ReturnType<typeof setTimeout>\n private _hideTimer?: ReturnType<typeof setTimeout>\n private _isCountedVisible = false\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n private _portal?: OverlayPortal\n private readonly _panel = defineAnchoredPanel().make({\n getAnchor: () => this.shadowRoot?.querySelector<HTMLElement>('.tooltip-trigger') ?? null,\n getLocalPanel: () => this.shadowRoot?.querySelector<HTMLElement>('.tooltip-panel') ?? null,\n getPositioning: () => ({\n placement: this.placement,\n offset: this.offset,\n strategy: this.portal ? 'fixed' : 'absolute'\n }),\n isPortal: () => this.portal,\n createPortal: () => this._createPortal()\n })\n\n get isOpen(): boolean {\n return this.open\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this.addEventListener('pointerenter', this._onPointerEnter)\n this.addEventListener('pointerleave', this._onPointerLeave)\n this.addEventListener('focusin', this._onFocusIn)\n this.addEventListener('focusout', this._onFocusOut)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('pointerenter', this._onPointerEnter)\n this.removeEventListener('pointerleave', this._onPointerLeave)\n this.removeEventListener('focusin', this._onFocusIn)\n this.removeEventListener('focusout', this._onFocusOut)\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n this._syncVisibleTooltipCount(false)\n this._panel.dispose()\n }\n\n override firstUpdated() {\n if (this.open) {\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n }\n }\n\n protected override updated(changed: Map<string, unknown>) {\n if (changed.has('portal') || changed.has('overlayContainer')) {\n requestAnimationFrame(() => this._reconfigureOverlay())\n } else if (changed.has('placement') || changed.has('offset'))\n requestAnimationFrame(() => this._panel.updatePosition())\n\n if (changed.has('open')) {\n this._syncVisibleTooltipCount(this.open)\n if (this.open) {\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n } else {\n void this._closeOverlay()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this.open)\n }\n if (changed.has('content')) this._syncPortalContent()\n }\n\n private _onPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled) return\n clearTimeout(this._hideTimer)\n this._showTimer = setTimeout(() => this._show(), visibleTooltipCount > 0 ? 0 : this.showDelay)\n }\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled) return\n clearTimeout(this._showTimer)\n this._hideTimer = setTimeout(() => this._hide(), this.hideDelay)\n }\n\n private _onFocusIn = (event: FocusEvent) => {\n if (this.disabled) return\n if (!(event.target instanceof Element) || !event.target.matches(':focus-visible')) return\n\n clearTimeout(this._hideTimer)\n this._show(true)\n }\n\n private _onFocusOut = () => {\n if (this.disabled) return\n clearTimeout(this._showTimer)\n this._hide()\n }\n\n private _show(isInstant = false) {\n if (this.open) return\n this._shouldOpenInstantly = isInstant\n this._userOpenChange.mark()\n this.open = true\n }\n\n private _hide() {\n if (!this.open) return\n this._userOpenChange.mark()\n this.open = false\n }\n\n private _openOverlay(isInstant = false) {\n this._panel.open(isInstant)\n }\n\n private _createPortal(): OverlayPortal {\n const portal = defineOverlayPortal().make({\n container: this.overlayContainer,\n target: this,\n style: `${glass}\\n${overlayMotion}\\n${style}`,\n className: 'tooltip-panel portal wui-glass wui-floating-panel',\n // 已迁移节点被框架删除后的解除追踪是 portal 内建默认行为,这里无需 onContentChange\n // 打开期实时渲染:新增的 slot=content 元素实时迁入面板(注释锚点无法携带\n // slot 属性,天然留在宿主作为框架 patch 的插入基准)。\n migrateAddedNodes: addedNodes =>\n addedNodes.filter(node => node instanceof Element && node.getAttribute('slot') === 'content')\n })\n this._portal = portal\n if (this.content) {\n const text = document.createElement('span')\n text.className = 'tooltip-text'\n text.textContent = this.content\n portal.panel.append(text)\n } else portal.moveContent(Array.from(this.querySelectorAll('[slot=\"content\"]')))\n portal.panel.setAttribute('role', 'tooltip')\n return portal\n }\n\n private async _closeOverlay() {\n await this._panel.close(() => this.open)\n }\n\n private _reconfigureOverlay() {\n this._panel.reconfigure(this.open)\n }\n\n private _syncPortalContent() {\n if (!this.portal || !this.open) return\n const panel = this._panel.getPanel()\n if (!panel) return\n const text = panel.querySelector<HTMLElement>('.tooltip-text')\n if (text) {\n text.textContent = this.content\n return\n }\n if (!this.content) return\n panel.replaceChildren()\n const nextText = document.createElement('span')\n nextText.className = 'tooltip-text'\n nextText.textContent = this.content\n panel.append(nextText)\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _syncVisibleTooltipCount(isVisible: boolean) {\n if (isVisible && !this._isCountedVisible) {\n visibleTooltipCount += 1\n this._isCountedVisible = true\n } else if (!isVisible && this._isCountedVisible) {\n visibleTooltipCount = Math.max(0, visibleTooltipCount - 1)\n this._isCountedVisible = false\n }\n }\n\n override render() {\n return html`\n <div class=\"tooltip-anchor\">\n <div class=\"tooltip-trigger\"><slot></slot></div>\n <div class=\"tooltip-panel wui-glass wui-floating-panel\" hidden role=\"tooltip\">\n ${this.content ? html`<span class=\"tooltip-text\">${this.content}</span>` : html`<slot name=\"content\"></slot>`}\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-tooltip': WebUiTooltip\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  //#region src/components/tooltip/style.css?inline
2
- var e = "*,:before,:after{box-sizing:border-box}:host,.tooltip-trigger{display:inline-flex}.tooltip-anchor{display:inline-flex;position:relative}.tooltip-panel{--wui-internal-glass-shadow:var(--wui-shadow-panel,0 3px 9px #00000045);--wui-glass-corner-radius:20px;pointer-events:none;z-index:var(--wui-layer-inline-overlay,1);box-sizing:border-box;width:max-content;max-width:var(--wui-tooltip-max-width,240px);font-size:var(--wui-tooltip-font-size,13px);color:var(--wui-color-text,#1b1b1b);background-color:var(--wui-color-surface-menu,#f6f6f6d1);border-radius:20px;padding:6px 10px;position:absolute;top:-9999px;left:-9999px}.tooltip-panel.portal{z-index:var(--wui-layer-menu,100);position:fixed}.tooltip-panel.hidden{display:none}.tooltip-text{overflow-wrap:break-word;white-space:normal;display:block}:host([portal]) .tooltip-panel{display:none}";
2
+ var e = "*,:before,:after{box-sizing:border-box}:host,.tooltip-trigger{display:inline-flex}.tooltip-anchor{display:inline-flex;position:relative}.tooltip-panel{--wui-internal-glass-shadow:var(--wui-shadow-panel,0 3px 9px #00000045);--wui-glass-corner-radius:var(--wui-radius-menu,18px);pointer-events:none;z-index:var(--wui-layer-inline-overlay,1);box-sizing:border-box;width:max-content;max-width:var(--wui-tooltip-max-width,240px);border-radius:var(--wui-radius-menu,18px);font-size:var(--wui-tooltip-font-size,13px);color:var(--wui-color-text,#1b1b1b);background-color:var(--wui-color-surface-menu,#f6f6f6d1);padding:6px 10px;position:absolute;top:-9999px;left:-9999px}.tooltip-panel.portal{z-index:var(--wui-layer-menu,100);position:fixed}.tooltip-panel.hidden{display:none}.tooltip-text{overflow-wrap:break-word;white-space:normal;display:block}:host([portal]) .tooltip-panel{display:none}";
3
3
  //#endregion
4
4
  export { e as default };
5
5
 
@@ -1 +1 @@
1
- {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/tooltip/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n display: inline-flex;\n}\n\n.tooltip-trigger {\n display: inline-flex;\n}\n\n.tooltip-anchor {\n position: relative;\n display: inline-flex;\n}\n\n.tooltip-panel {\n --wui-internal-glass-shadow: var(--wui-shadow-panel, 0 3px 9px rgb(0 0 0 / 0.27));\n --wui-glass-corner-radius: 20px;\n\n pointer-events: none;\n\n position: absolute;\n z-index: var(--wui-layer-inline-overlay, 1);\n top: -9999px;\n left: -9999px;\n\n box-sizing: border-box;\n width: max-content;\n max-width: var(--wui-tooltip-max-width, 240px);\n padding: 6px 10px;\n border-radius: 20px;\n\n font-size: var(--wui-tooltip-font-size, 13px);\n color: var(--wui-color-text, #1b1b1b);\n\n background-color: var(--wui-color-surface-menu, rgb(246 246 246 / 0.82));\n}\n\n.tooltip-panel.portal {\n position: fixed;\n z-index: var(--wui-layer-menu, 100);\n}\n\n.tooltip-panel.hidden {\n display: none;\n}\n\n.tooltip-text {\n display: block;\n overflow-wrap: break-word;\n white-space: normal;\n}\n\n:host([portal]) .tooltip-panel {\n display: none;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/tooltip/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n display: inline-flex;\n}\n\n.tooltip-trigger {\n display: inline-flex;\n}\n\n.tooltip-anchor {\n position: relative;\n display: inline-flex;\n}\n\n.tooltip-panel {\n --wui-internal-glass-shadow: var(--wui-shadow-panel, 0 3px 9px rgb(0 0 0 / 0.27));\n --wui-glass-corner-radius: var(--wui-radius-menu, 18px);\n\n pointer-events: none;\n\n position: absolute;\n z-index: var(--wui-layer-inline-overlay, 1);\n top: -9999px;\n left: -9999px;\n\n box-sizing: border-box;\n width: max-content;\n max-width: var(--wui-tooltip-max-width, 240px);\n padding: 6px 10px;\n border-radius: var(--wui-radius-menu, 18px);\n\n font-size: var(--wui-tooltip-font-size, 13px);\n color: var(--wui-color-text, #1b1b1b);\n\n background-color: var(--wui-color-surface-menu, rgb(246 246 246 / 0.82));\n}\n\n.tooltip-panel.portal {\n position: fixed;\n z-index: var(--wui-layer-menu, 100);\n}\n\n.tooltip-panel.hidden {\n display: none;\n}\n\n.tooltip-text {\n display: block;\n overflow-wrap: break-word;\n white-space: normal;\n}\n\n:host([portal]) .tooltip-panel {\n display: none;\n}\n"],"mappings":""}
@@ -1,6 +1,16 @@
1
1
  import { definePlugin as e } from "@greypan/js-kit";
2
2
  //#region src/shared/form-association/index.ts
3
- function t(t) {
3
+ function t(e, t, n, r = !1) {
4
+ if (typeof e.setValidity != "function") return;
5
+ if (r || t.validity.valid) {
6
+ e.setValidity({});
7
+ return;
8
+ }
9
+ let i = {};
10
+ for (let e of n) t.validity[e] && (i[e] = !0);
11
+ e.setValidity(i, t.validationMessage, t);
12
+ }
13
+ function n(t) {
4
14
  return e(() => {
5
15
  let e, n, r = !1, i = !1, a = t.copyState ?? ((e) => e), o = () => {
6
16
  e && (e.setFormValue?.(t.getFormValue(), t.getFormState()), t.syncValidity?.(e));
@@ -24,9 +34,26 @@ function t(t) {
24
34
  };
25
35
  });
26
36
  }
27
- var n = class {
37
+ function r(e) {
38
+ class t extends e {
39
+ static {
40
+ this.formAssociated = !0;
41
+ }
42
+ formResetCallback() {
43
+ this.__formLifecycleProvider?.().reset();
44
+ }
45
+ formDisabledCallback(e) {
46
+ this.__formLifecycleProvider?.().setDisabled(e);
47
+ }
48
+ formStateRestoreCallback(e) {
49
+ this.__formLifecycleProvider?.().restore(e);
50
+ }
51
+ }
52
+ return t;
53
+ }
54
+ var i = class {
28
55
  constructor(e, t) {
29
- this.host = e, this.lifecycle = t, e.addController(this);
56
+ this.host = e, this.lifecycle = t, e.addController(this), e.__formLifecycleProvider = () => t;
30
57
  }
31
58
  hostConnected() {
32
59
  this.lifecycle.connect();
@@ -36,6 +63,6 @@ var n = class {
36
63
  }
37
64
  };
38
65
  //#endregion
39
- export { n as FormAssociationController, t as defineFormAssociation };
66
+ export { r as FormAssociated, i as FormAssociationController, n as defineFormAssociation, t as forwardInputValidity };
40
67
 
41
68
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/form-association/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\nimport type { ReactiveController, ReactiveControllerHost } from 'lit'\n\ntype FormSubmissionValue = File | FormData | string | null\ntype FormStateValue = File | FormData | string | null\n\ninterface FormAssociationHost extends HTMLElement, ReactiveControllerHost {}\n\nexport interface FormAssociationOptions<State> {\n readonly host: FormAssociationHost\n readonly initialize?: () => void\n readonly getState: () => State\n readonly setState: (state: State) => void\n readonly copyState?: (state: State) => State\n readonly getFormValue: () => FormSubmissionValue\n readonly getFormState: () => FormStateValue\n readonly restoreState: (state: FormStateValue) => void\n readonly isStateManaged?: () => boolean\n readonly syncValidity?: (internals: ElementInternals) => void\n}\n\nexport interface FormAssociationLifecycle {\n connect(): void\n sync(): void\n reset(): void\n restore(state: FormStateValue): void\n setDisabled(disabled: boolean): void\n isFormDisabled(): boolean\n getInternals(): ElementInternals | undefined\n}\n\n/**\n * 承接原生 custom element 表单生命周期,值序列化与校验语义仍由各控件组件自己表达。\n */\nexport function defineFormAssociation<State>(options: FormAssociationOptions<State>) {\n return definePlugin((): FormAssociationLifecycle => {\n let internals: ElementInternals | undefined\n let initialState: State | undefined\n let initialized = false\n let formDisabled = false\n const copyState = options.copyState ?? (state => state)\n\n const sync = () => {\n if (!internals) return\n internals.setFormValue?.(options.getFormValue(), options.getFormState())\n options.syncValidity?.(internals)\n }\n\n return {\n connect() {\n if (!internals) internals = options.host.attachInternals()\n if (!initialized) {\n options.initialize?.()\n initialState = copyState(options.getState())\n initialized = true\n }\n sync()\n },\n sync,\n reset() {\n if (!initialized || options.isStateManaged?.()) return\n options.setState(copyState(initialState as State))\n sync()\n },\n restore(state) {\n if (options.isStateManaged?.()) return\n options.restoreState(state)\n sync()\n },\n setDisabled(disabled) {\n if (formDisabled === disabled) return\n formDisabled = disabled\n options.host.requestUpdate()\n sync()\n },\n isFormDisabled: () => formDisabled,\n getInternals: () => internals\n }\n })\n}\n\n/** 把表单生命周期插件接入宿主,无需共享组件基类。 */\nexport class FormAssociationController implements ReactiveController {\n constructor(\n private readonly host: FormAssociationHost,\n private readonly lifecycle: FormAssociationLifecycle\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n this.lifecycle.connect()\n }\n\n hostUpdated() {\n this.lifecycle.sync()\n }\n}\n"],"mappings":";;AAkCA,SAAgB,EAA6B,GAAwC;CACnF,OAAO,QAA6C;EAClD,IAAI,GACA,GACA,IAAc,IACd,IAAe,IACb,IAAY,EAAQ,eAAc,MAAS,IAE3C,UAAa;GACZ,MACL,EAAU,eAAe,EAAQ,aAAa,GAAG,EAAQ,aAAa,CAAC,GACvE,EAAQ,eAAe,CAAS;EAClC;EAEA,OAAO;GACL,UAAU;IAOR,AANA,AAAgB,MAAY,EAAQ,KAAK,gBAAgB,GACzD,AAGE,OAFA,EAAQ,aAAa,GACrB,IAAe,EAAU,EAAQ,SAAS,CAAC,GAC7B,KAEhB,EAAK;GACP;GACA;GACA,QAAQ;IACF,CAAC,KAAe,EAAQ,iBAAiB,MAC7C,EAAQ,SAAS,EAAU,CAAqB,CAAC,GACjD,EAAK;GACP;GACA,QAAQ,GAAO;IACT,EAAQ,iBAAiB,MAC7B,EAAQ,aAAa,CAAK,GAC1B,EAAK;GACP;GACA,YAAY,GAAU;IAChB,MAAiB,MACrB,IAAe,GACf,EAAQ,KAAK,cAAc,GAC3B,EAAK;GACP;GACA,sBAAsB;GACtB,oBAAoB;EACtB;CACF,CAAC;AACH;AAGA,IAAa,IAAb,MAAqE;CACnE,YACE,GACA,GACA;EACA,AAHiB,KAAA,OAAA,GACA,KAAA,YAAA,GAEjB,EAAK,cAAc,IAAI;CACzB;CAEA,gBAAgB;EACd,KAAK,UAAU,QAAQ;CACzB;CAEA,cAAc;EACZ,KAAK,UAAU,KAAK;CACtB;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/form-association/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\nimport type { ReactiveController, ReactiveControllerHost } from 'lit'\n\n// Lit 官方 mixin 签名需要构造器接受任意参数(paramCount 各异的子类构造器都要能赋给\n// 基座);never[] 变体在 extends 链上会丢失 Lit 静态侧类型。any 收敛在这个类型别名内。\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Constructor<T = object> = new (...args: any[]) => T\n\ntype FormSubmissionValue = File | FormData | string | null\ntype FormStateValue = File | FormData | string | null\n\ninterface FormAssociationHost extends HTMLElement, ReactiveControllerHost {}\n\nexport interface FormAssociationOptions<State> {\n readonly host: FormAssociationHost\n readonly initialize?: () => void\n readonly getState: () => State\n readonly setState: (state: State) => void\n readonly copyState?: (state: State) => State\n readonly getFormValue: () => FormSubmissionValue\n readonly getFormState: () => FormStateValue\n readonly restoreState: (state: FormStateValue) => void\n readonly isStateManaged?: () => boolean\n readonly syncValidity?: (internals: ElementInternals) => void\n}\n\nexport interface FormAssociationLifecycle {\n connect(): void\n sync(): void\n reset(): void\n restore(state: FormStateValue): void\n setDisabled(disabled: boolean): void\n isFormDisabled(): boolean\n getInternals(): ElementInternals | undefined\n}\n\n/**\n * 把原生 input 的 ValidityState 转发为 ElementInternals 的校验状态。\n * flagNames 是控件自己声明的校验契约(input 转发文本类 5 项、input-number\n * 转发范围类 4 项),这里只收敛 guard / valid 判定 / setValidity 接线。\n * disabled 元素被约束验证豁免,必须上报干净状态;isDisabled 传入控件的\n * 组合禁用态(含 formDisabled),因为 formDisabledCallback 触发时原生\n * input 的 disabled 属性可能尚未重渲染,不能依赖 input.validity.valid。\n */\nexport function forwardInputValidity(\n internals: ElementInternals,\n input: HTMLInputElement | HTMLTextAreaElement,\n flagNames: readonly (keyof ValidityStateFlags)[],\n isDisabled = false\n): void {\n if (typeof internals.setValidity !== 'function') return\n if (isDisabled || input.validity.valid) {\n internals.setValidity({})\n return\n }\n const flags: ValidityStateFlags = {}\n for (const name of flagNames) {\n if (input.validity[name]) flags[name] = true\n }\n internals.setValidity(flags, input.validationMessage, input)\n}\n\n/**\n * 承接原生 custom element 表单生命周期,值序列化与校验语义仍由各控件组件自己表达。\n */\nexport function defineFormAssociation<State>(options: FormAssociationOptions<State>) {\n return definePlugin((): FormAssociationLifecycle => {\n let internals: ElementInternals | undefined\n let initialState: State | undefined\n let initialized = false\n let formDisabled = false\n const copyState = options.copyState ?? (state => state)\n\n const sync = () => {\n if (!internals) return\n internals.setFormValue?.(options.getFormValue(), options.getFormState())\n options.syncValidity?.(internals)\n }\n\n return {\n connect() {\n if (!internals) internals = options.host.attachInternals()\n if (!initialized) {\n options.initialize?.()\n initialState = copyState(options.getState())\n initialized = true\n }\n sync()\n },\n sync,\n reset() {\n if (!initialized || options.isStateManaged?.()) return\n options.setState(copyState(initialState as State))\n sync()\n },\n restore(state) {\n if (options.isStateManaged?.()) return\n options.restoreState(state)\n sync()\n },\n setDisabled(disabled) {\n if (formDisabled === disabled) return\n formDisabled = disabled\n options.host.requestUpdate()\n sync()\n },\n isFormDisabled: () => formDisabled,\n getInternals: () => internals\n }\n })\n}\n\n/**\n * 表单关联元素 mixin:安装 `formAssociated` 与三个生命周期回调。\n *\n * 生命周期实现仍由 defineFormAssociation 表达;FormAssociationController\n * 在构造时把 lifecycle 注册到实例的 provider,回调经它转发。组件可继续\n * 覆写单个回调注入自己的额外逻辑(如 autocomplete 的禁用收口)。\n * 这是字段组合(mixin),不是共享基类:除回调接线外不继承任何实现。\n */\nexport interface FormAssociatedLifecycleProvider {\n __formLifecycleProvider?: () => FormAssociationLifecycle\n}\n\nexport function FormAssociated<T extends Constructor<HTMLElement>>(superClass: T) {\n class FormAssociatedElement extends superClass {\n static formAssociated = true\n\n formResetCallback(): void {\n ;(this as FormAssociatedLifecycleProvider).__formLifecycleProvider?.().reset()\n }\n\n formDisabledCallback(disabled: boolean): void {\n ;(this as FormAssociatedLifecycleProvider).__formLifecycleProvider?.().setDisabled(disabled)\n }\n\n formStateRestoreCallback(state: File | FormData | string | null): void {\n ;(this as FormAssociatedLifecycleProvider).__formLifecycleProvider?.().restore(state)\n }\n }\n return FormAssociatedElement\n}\n\n/** 把表单生命周期插件接入宿主(回调接线经 FormAssociated mixin,无需共享基类)。 */\nexport class FormAssociationController implements ReactiveController {\n constructor(\n private readonly host: FormAssociationHost,\n private readonly lifecycle: FormAssociationLifecycle\n ) {\n host.addController(this)\n ;(host as FormAssociationHost & FormAssociatedLifecycleProvider).__formLifecycleProvider = () => lifecycle\n }\n\n hostConnected() {\n this.lifecycle.connect()\n }\n\n hostUpdated() {\n this.lifecycle.sync()\n }\n}\n"],"mappings":";;AA4CA,SAAgB,EACd,GACA,GACA,GACA,IAAa,IACP;CACN,IAAI,OAAO,EAAU,eAAgB,YAAY;CACjD,IAAI,KAAc,EAAM,SAAS,OAAO;EACtC,EAAU,YAAY,CAAC,CAAC;EACxB;CACF;CACA,IAAM,IAA4B,CAAC;CACnC,KAAK,IAAM,KAAQ,GACjB,AAAI,EAAM,SAAS,OAAO,EAAM,KAAQ;CAE1C,EAAU,YAAY,GAAO,EAAM,mBAAmB,CAAK;AAC7D;AAKA,SAAgB,EAA6B,GAAwC;CACnF,OAAO,QAA6C;EAClD,IAAI,GACA,GACA,IAAc,IACd,IAAe,IACb,IAAY,EAAQ,eAAc,MAAS,IAE3C,UAAa;GACZ,MACL,EAAU,eAAe,EAAQ,aAAa,GAAG,EAAQ,aAAa,CAAC,GACvE,EAAQ,eAAe,CAAS;EAClC;EAEA,OAAO;GACL,UAAU;IAOR,AANA,AAAgB,MAAY,EAAQ,KAAK,gBAAgB,GACzD,AAGE,OAFA,EAAQ,aAAa,GACrB,IAAe,EAAU,EAAQ,SAAS,CAAC,GAC7B,KAEhB,EAAK;GACP;GACA;GACA,QAAQ;IACF,CAAC,KAAe,EAAQ,iBAAiB,MAC7C,EAAQ,SAAS,EAAU,CAAqB,CAAC,GACjD,EAAK;GACP;GACA,QAAQ,GAAO;IACT,EAAQ,iBAAiB,MAC7B,EAAQ,aAAa,CAAK,GAC1B,EAAK;GACP;GACA,YAAY,GAAU;IAChB,MAAiB,MACrB,IAAe,GACf,EAAQ,KAAK,cAAc,GAC3B,EAAK;GACP;GACA,sBAAsB;GACtB,oBAAoB;EACtB;CACF,CAAC;AACH;AAcA,SAAgB,EAAmD,GAAe;CAChF,MAAM,UAA8B,EAAW;;GACrB,KAAA,iBAAA;;EAExB,oBAA0B;GACvB,KAA0C,0BAA0B,CAAC,CAAC,MAAM;EAC/E;EAEA,qBAAqB,GAAyB;GAC3C,KAA0C,0BAA0B,CAAC,CAAC,YAAY,CAAQ;EAC7F;EAEA,yBAAyB,GAA8C;GACpE,KAA0C,0BAA0B,CAAC,CAAC,QAAQ,CAAK;EACtF;CACF;CACA,OAAO;AACT;AAGA,IAAa,IAAb,MAAqE;CACnE,YACE,GACA,GACA;EAEC,AAJgB,KAAA,OAAA,GACA,KAAA,YAAA,GAEjB,EAAK,cAAc,IAAI,GACtB,EAAgE,gCAAgC;CACnG;CAEA,gBAAgB;EACd,KAAK,UAAU,QAAQ;CACzB;CAEA,cAAc;EACZ,KAAK,UAAU,KAAK;CACtB;AACF"}
@@ -0,0 +1,112 @@
1
+ import { hideOverlayPresence as e } from "../overlay/presence.js";
2
+ import { findFocusedMenuItem as t, focusMenuItem as n, getEnabledMenuItems as r } from "../menu-portal/menu-tree.js";
3
+ //#region src/shared/menu-behavior/index.ts
4
+ function i(e, t) {
5
+ let n = !1, r;
6
+ return {
7
+ arm() {
8
+ n = !0, clearTimeout(r), r = setTimeout(() => {
9
+ n = !1;
10
+ });
11
+ },
12
+ isArmed: () => n,
13
+ isInside: (n) => {
14
+ for (let r of n.composedPath()) if (r === e || r === e.shadowRoot || r instanceof Node && r.getRootNode() === e.shadowRoot || r instanceof Node && t(r)) return !0;
15
+ return !1;
16
+ },
17
+ dispose() {
18
+ clearTimeout(r), n = !1;
19
+ }
20
+ };
21
+ }
22
+ var a = 200;
23
+ function o(e, t) {
24
+ let n, r = [];
25
+ function i() {
26
+ r.forEach((e) => e()), r = [];
27
+ let i = e.getOpenDepth();
28
+ for (let o = 0; o <= i + 1; o++) {
29
+ let i = e.getLevelItems(o);
30
+ i.length && i.forEach((i) => {
31
+ if (!i.matches(t) || i.hasAttribute("disabled")) return;
32
+ let s = (t) => {
33
+ t.pointerType !== "touch" && (clearTimeout(n), e.isSubmenuItem(i) ? e.getActiveItem(o) !== i && (n = setTimeout(() => e.openSubmenu(i), a)) : e.getOpenDepth() > o && e.closeFrom(o));
34
+ };
35
+ i.addEventListener("pointerenter", s, { passive: !0 }), r.push(() => i.removeEventListener("pointerenter", s));
36
+ });
37
+ }
38
+ }
39
+ return {
40
+ bind: i,
41
+ dispose() {
42
+ clearTimeout(n), r.forEach((e) => e()), r = [];
43
+ }
44
+ };
45
+ }
46
+ function s(e, t) {
47
+ if (t.key === "Escape") {
48
+ e.closeDeepestOrAll(), t.preventDefault();
49
+ return;
50
+ }
51
+ let r = e.getFocusedItem(t), i = r ? e.getLevelOf(r) : void 0;
52
+ if (i === void 0) return;
53
+ let a = e.getEnabledItems(i), o = r ? a.indexOf(r) : -1;
54
+ switch (t.key) {
55
+ case "ArrowDown":
56
+ n(a[(o + 1 + a.length) % a.length]);
57
+ break;
58
+ case "ArrowUp":
59
+ n(a[(o - 1 + a.length) % a.length]);
60
+ break;
61
+ case "Home":
62
+ n(a[0]);
63
+ break;
64
+ case "End":
65
+ n(a.at(-1));
66
+ break;
67
+ case "ArrowRight":
68
+ r?.hasAttribute("submenu") && (e.openSubmenuInstant(r), requestAnimationFrame(() => requestAnimationFrame(() => n(e.getEnabledItems(i + 1)[0]))));
69
+ break;
70
+ case "ArrowLeft":
71
+ i > 0 && n(e.closeToParent(i));
72
+ break;
73
+ case "Enter":
74
+ case " ":
75
+ r?.click();
76
+ break;
77
+ default: return;
78
+ }
79
+ t.preventDefault(), t.stopPropagation();
80
+ }
81
+ function c(e, n, r) {
82
+ return e?.composedPath().find((e) => e instanceof HTMLElement && e.matches(r)) || t(n);
83
+ }
84
+ function l(e) {
85
+ return e ? r(e) : [];
86
+ }
87
+ function u(t) {
88
+ let n = /* @__PURE__ */ new Map();
89
+ return {
90
+ take(e) {
91
+ let t = n.get(e);
92
+ return t && n.delete(e), t;
93
+ },
94
+ closeAsync(r, i) {
95
+ n.set(r, i), (async () => {
96
+ await e(t.getPanel(i)) && n.get(r) === i && (n.delete(r), t.restoreItems(i, r), t.dispose(i));
97
+ })();
98
+ },
99
+ restoreAll() {
100
+ n.forEach((e, n) => {
101
+ t.restoreItems(e, n), t.dispose(e);
102
+ }), n.clear();
103
+ },
104
+ disposeAll() {
105
+ n.forEach((e) => t.dispose(e)), n.clear();
106
+ }
107
+ };
108
+ }
109
+ //#endregion
110
+ export { u as createClosingSubmenuStack, o as createMenuHoverBinder, i as createMenuOutsideClickGuard, l as getEnabledMenuLevelItems, c as getFocusedMenuItemFromPanels, s as handleMenuKeyboard };
111
+
112
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/menu-behavior/index.ts"],"sourcesContent":["/**\n * 菜单行为层:dropdown 与 context-menu 共用的交互行为。\n *\n * 结构层(面板构建、迁移、定位)由 menu-portal/menu-tree 与各组件自己承载;\n * 这里只收口四种在两个组件中逐行复制的行为:\n * 1. outside-click 守卫(同帧吞掉打开点击 + composedPath 内含判定)\n * 2. 层级 hover 绑定(200ms 打开计时、touch 守卫、hover 收起更深层级)\n * 3. roving 键盘导航(Arrow/Home/End/Enter/Space/Escape)\n * 4. 关闭中的 submenu presence 收尾栈(动画结束后归还子项并销毁面板)\n *\n * 组件是 adapter:提供层级容器/项的查询与打开/关闭原语,行为由此驱动。\n */\nimport { findFocusedMenuItem, focusMenuItem, getEnabledMenuItems } from '@/shared/menu-portal/menu-tree'\nimport { hideOverlayPresence as hidePanelPresence } from '@/shared/overlay/presence'\n\n/** 菜单层级面板的最小视图:行为层只需要面板(presence/contains)与内容容器(项查询)。 */\nexport interface MenuLevelSurface {\n panel: HTMLElement\n content: HTMLElement\n}\n\n/* ------------------------------------------------------------------ *\n * Outside-click 守卫\n * ------------------------------------------------------------------ */\n\nexport interface MenuOutsideClickGuard {\n /** 打开动作后调用:吞掉同一帧内随后冒泡到 document 的本次 click。 */\n arm(): void\n isArmed(): boolean\n /** 事件是否发生在宿主 shadow root 或任何已打开面板内部。 */\n isInside(event: MouseEvent): boolean\n dispose(): void\n}\n\nexport function createMenuOutsideClickGuard(\n host: HTMLElement,\n isInsideOverlayPanel: (node: Node) => boolean\n): MenuOutsideClickGuard {\n let armed = false\n let disarmTimer: ReturnType<typeof setTimeout> | undefined\n\n const isInside = (event: MouseEvent): boolean => {\n for (const node of event.composedPath()) {\n if (node === host || node === host.shadowRoot) return true\n if (node instanceof Node && node.getRootNode() === host.shadowRoot) return true\n if (node instanceof Node && isInsideOverlayPanel(node)) return true\n }\n return false\n }\n\n return {\n arm() {\n armed = true\n clearTimeout(disarmTimer)\n disarmTimer = setTimeout(() => {\n armed = false\n })\n },\n isArmed: () => armed,\n isInside,\n dispose() {\n clearTimeout(disarmTimer)\n armed = false\n }\n }\n}\n\n/* ------------------------------------------------------------------ *\n * 层级 hover 绑定\n * ------------------------------------------------------------------ */\n\nexport interface MenuHoverDelegate {\n /** 当前展开的 submenu 深度;行为层会绑定 0..depth+1 每一层。 */\n getOpenDepth(): number\n getLevelItems(level: number): HTMLElement[]\n /** 该层当前激活(子面板已展开)的项;用于避免重复打开。 */\n getActiveItem(level: number): HTMLElement | undefined\n isSubmenuItem(item: HTMLElement): boolean\n /** hover 到 submenu 项且它不是当前激活项。 */\n openSubmenu(item: HTMLElement): void\n /** hover 到叶子项且存在更深层级:关闭比它深的子面板(内部自行重新 bind)。 */\n closeFrom(level: number): void\n}\n\nexport interface MenuHoverBinder {\n bind(): void\n dispose(): void\n}\n\nconst SUBMENU_HOVER_DELAY = 200\n\nexport function createMenuHoverBinder(delegate: MenuHoverDelegate, itemSelector: string): MenuHoverBinder {\n let openTimer: ReturnType<typeof setTimeout> | undefined\n let cleanupFns: (() => void)[] = []\n\n function bind() {\n cleanupFns.forEach(fn => fn())\n cleanupFns = []\n\n const depth = delegate.getOpenDepth()\n for (let level = 0; level <= depth + 1; level++) {\n const items = delegate.getLevelItems(level)\n if (!items.length) continue\n items.forEach(item => {\n if (!item.matches(itemSelector) || item.hasAttribute('disabled')) return\n const handler = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return\n clearTimeout(openTimer)\n if (delegate.isSubmenuItem(item)) {\n if (delegate.getActiveItem(level) !== item) {\n openTimer = setTimeout(() => delegate.openSubmenu(item), SUBMENU_HOVER_DELAY)\n }\n } else if (delegate.getOpenDepth() > level) {\n delegate.closeFrom(level)\n }\n }\n item.addEventListener('pointerenter', handler, { passive: true })\n cleanupFns.push(() => item.removeEventListener('pointerenter', handler))\n })\n }\n }\n\n return {\n bind,\n dispose() {\n clearTimeout(openTimer)\n cleanupFns.forEach(fn => fn())\n cleanupFns = []\n }\n }\n}\n\n/* ------------------------------------------------------------------ *\n * roving 键盘导航\n * ------------------------------------------------------------------ */\n\nexport interface MenuKeyboardDelegate {\n getFocusedItem(event?: KeyboardEvent): HTMLElement | undefined\n /** 焦点项所在层级;不在任何面板内时返回 undefined。 */\n getLevelOf(item: HTMLElement): number | undefined\n getEnabledItems(level: number): HTMLElement[]\n isSubmenuItem(item: HTMLElement): boolean\n /** 键盘 ArrowRight:以 instant 语义打开该项的子面板。 */\n openSubmenuInstant(item: HTMLElement): void\n /** 键盘 ArrowLeft:关闭 level 层子面板并返回其父项。 */\n closeToParent(level: number): HTMLElement | undefined\n /** Escape:关闭最深一层子面板;没有子面板时关闭整个菜单。 */\n closeDeepestOrAll(): void\n}\n\n/**\n * 处理菜单键盘事件;未命中的键原样返回,调用方不做 preventDefault。\n * 事件监听的挂载位置(宿主 / 面板 / document)由组件自己决定。\n */\nexport function handleMenuKeyboard(delegate: MenuKeyboardDelegate, e: KeyboardEvent): void {\n if (e.key === 'Escape') {\n delegate.closeDeepestOrAll()\n e.preventDefault()\n return\n }\n\n const focused = delegate.getFocusedItem(e)\n const level = focused ? delegate.getLevelOf(focused) : undefined\n if (level === undefined) return\n const items = delegate.getEnabledItems(level)\n const currentIndex = focused ? items.indexOf(focused) : -1\n\n switch (e.key) {\n case 'ArrowDown':\n focusMenuItem(items[(currentIndex + 1 + items.length) % items.length])\n break\n case 'ArrowUp':\n focusMenuItem(items[(currentIndex - 1 + items.length) % items.length])\n break\n case 'Home':\n focusMenuItem(items[0])\n break\n case 'End':\n focusMenuItem(items.at(-1))\n break\n case 'ArrowRight':\n if (focused?.hasAttribute('submenu')) {\n delegate.openSubmenuInstant(focused)\n // 双 rAF:等子面板构建/迁移完成后再聚焦其第一个可用项\n requestAnimationFrame(() => requestAnimationFrame(() => focusMenuItem(delegate.getEnabledItems(level + 1)[0])))\n }\n break\n case 'ArrowLeft':\n if (level > 0) {\n focusMenuItem(delegate.closeToParent(level))\n }\n break\n case 'Enter':\n case ' ':\n focused?.click()\n break\n default:\n return\n }\n e.preventDefault()\n e.stopPropagation()\n}\n\n/** 供 getFocusedItem 实现:优先取事件 composedPath 上的菜单项,其次查找面板内已聚焦项。 */\nexport function getFocusedMenuItemFromPanels(\n event: KeyboardEvent | undefined,\n panels: Iterable<HTMLElement | undefined>,\n itemSelector: string\n): HTMLElement | undefined {\n const eventItem = event\n ?.composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.matches(itemSelector))\n if (eventItem) return eventItem\n\n return findFocusedMenuItem(panels)\n}\n\n/** 供 getEnabledItems 实现:从层级 content 查询可用项。 */\nexport function getEnabledMenuLevelItems(content: HTMLElement | undefined): HTMLElement[] {\n return content ? getEnabledMenuItems(content) : []\n}\n\n/* ------------------------------------------------------------------ *\n * 关闭中的 submenu presence 收尾栈\n * ------------------------------------------------------------------ */\n\nexport interface ClosingSubmenuStack<C> {\n /** 取出正在关闭的容器以复用(同帧关闭→重开同一父项)。 */\n take(parentItem: HTMLElement): C | undefined\n /** 挂起关闭:等出场动画结束后归还子项并销毁面板。 */\n closeAsync(parentItem: HTMLElement, container: C): void\n /** 立即归还并销毁全部关闭中的容器(整菜单关闭 / 重开路径)。 */\n restoreAll(): void\n /** 全部强制销毁(断开连接时),不再归还子项。 */\n disposeAll(): void\n}\n\nexport interface ClosingSubmenuStackAdapter<C> {\n getPanel(container: C): HTMLElement\n /** 归子项回父项(moveMenuChildren 与隐藏补丁由 adapter 决定)。 */\n restoreItems(container: C, parentItem: HTMLElement): void\n /** 销毁面板(remove + overlay api.dispose 等)。 */\n dispose(container: C): void\n}\n\nexport function createClosingSubmenuStack<C>(adapter: ClosingSubmenuStackAdapter<C>): ClosingSubmenuStack<C> {\n const closing = new Map<HTMLElement, C>()\n\n return {\n take(parentItem) {\n const container = closing.get(parentItem)\n if (container) closing.delete(parentItem)\n return container\n },\n closeAsync(parentItem, container) {\n // 先登记再等出场动画:同帧关闭→重开时 take() 会从这里取走复用\n closing.set(parentItem, container)\n void (async () => {\n if (!(await hidePanelPresence(adapter.getPanel(container)))) return\n if (closing.get(parentItem) !== container) return\n\n closing.delete(parentItem)\n adapter.restoreItems(container, parentItem)\n adapter.dispose(container)\n })()\n },\n restoreAll() {\n closing.forEach((container, parentItem) => {\n adapter.restoreItems(container, parentItem)\n adapter.dispose(container)\n })\n closing.clear()\n },\n disposeAll() {\n closing.forEach(container => adapter.dispose(container))\n closing.clear()\n }\n }\n}\n"],"mappings":"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"}
@@ -38,7 +38,66 @@ function c(e) {
38
38
  function l(e) {
39
39
  return [...e].filter((e) => e !== void 0).flatMap((e) => Array.from(e.querySelectorAll(t))).find((e) => !!e.shadowRoot?.activeElement);
40
40
  }
41
+ function u(e, t, n, r) {
42
+ let a = i(t), o = i(e), s = /* @__PURE__ */ new Set([...a, ...o]);
43
+ n.forEach((e, t) => {
44
+ s.has(t) || (e.remove(), n.delete(t));
45
+ });
46
+ let c = (t, i, a) => {
47
+ let o = n.get(t);
48
+ if (o && o.parentNode === e) return o;
49
+ o?.remove();
50
+ let s = document.createComment(r);
51
+ return i ? e.insertBefore(s, t) : a && a.parentNode === e ? e.insertBefore(s, a) : e.appendChild(s), n.set(t, s), s;
52
+ };
53
+ for (let e = a.length - 1; e >= 0; e--) {
54
+ let t = a[e], r = a[e + 1];
55
+ c(t, !1, r ? n.get(r) : void 0);
56
+ }
57
+ for (let e of o) c(e, !0);
58
+ for (let e of o) t.appendChild(e);
59
+ }
60
+ function d(e, t, n, r) {
61
+ let i = [];
62
+ for (let a of Array.from(e.childNodes)) a.nodeType === Node.COMMENT_NODE && a.textContent === r && n.forEach((e, n) => {
63
+ e === a && n.parentNode === t && i.push(n);
64
+ });
65
+ if (i.length === 0) return;
66
+ let a = Array.from(t.children);
67
+ if (!(a.length === i.length && a.every((e, t) => e === i[t]))) for (let e = i.length - 1; e >= 0; e--) {
68
+ let n = i[e], r = i[e + 1];
69
+ r ? n.nextElementSibling !== r && t.insertBefore(n, r) : t.appendChild(n);
70
+ }
71
+ }
72
+ function f(e, t) {
73
+ let n = /* @__PURE__ */ new Map();
74
+ for (let r of Array.from(e.childNodes)) if (r instanceof Comment && r.textContent !== t) {
75
+ let e = r.nextElementSibling;
76
+ n.set(r, e instanceof HTMLElement ? e : null);
77
+ }
78
+ return n;
79
+ }
80
+ function p(e, t) {
81
+ for (let [n, r] of t) n.parentNode === e && (r && r.parentNode === e ? n.nextElementSibling !== r && e.insertBefore(n, r) : e.appendChild(n));
82
+ }
83
+ function m(e, t, n) {
84
+ let r = [];
85
+ for (let i of Array.from(t.childNodes)) {
86
+ if (i instanceof Comment) {
87
+ r.push(i);
88
+ continue;
89
+ }
90
+ if (!(i instanceof HTMLElement)) continue;
91
+ let t = n.get(i);
92
+ if (t) {
93
+ for (let n of r) t.parentNode === e ? e.insertBefore(n, t) : e.appendChild(n);
94
+ r.length = 0, t.parentNode === e ? e.insertBefore(i, t) : e.appendChild(i), t.remove(), n.delete(i);
95
+ }
96
+ }
97
+ for (let t of r) e.appendChild(t);
98
+ n.forEach((e) => e.remove()), n.clear();
99
+ }
41
100
  //#endregion
42
- export { e as MENU_CONTENT_SELECTOR, t as MENU_ITEM_SELECTOR, l as findFocusedMenuItem, c as focusMenuItem, r as getEnabledMenuItems, n as getMenuChildren, s as getMenuItemFromEvent, i as getMovableMenuSubtrees, o as hideNestedMenuChildren, a as moveMenuChildren };
101
+ export { e as MENU_CONTENT_SELECTOR, t as MENU_ITEM_SELECTOR, f as captureFrameworkAnchors, l as findFocusedMenuItem, c as focusMenuItem, r as getEnabledMenuItems, n as getMenuChildren, s as getMenuItemFromEvent, i as getMovableMenuSubtrees, o as hideNestedMenuChildren, a as moveMenuChildren, d as orderManagedMenuItems, u as reconcileManagedMenuItems, p as restoreFrameworkAnchors, m as returnManagedMenuItemsToSlot };
43
102
 
44
103
  //# sourceMappingURL=menu-tree.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"menu-tree.js","names":[],"sources":["../../../src/shared/menu-portal/menu-tree.ts"],"sourcesContent":["export const MENU_CONTENT_SELECTOR = 'web-ui-dropdown-item, web-ui-dropdown-divider, web-ui-dropdown-header'\nexport const MENU_ITEM_SELECTOR = 'web-ui-dropdown-item'\n\nexport function getMenuChildren(container: ParentNode): HTMLElement[] {\n const children: HTMLElement[] = []\n for (const child of Array.from(container.children)) {\n if (!(child instanceof HTMLElement)) continue\n if (child.matches(MENU_CONTENT_SELECTOR)) {\n children.push(child)\n continue\n }\n\n // 框架条件渲染会用元素包裹自有节点,或留下注释锚点。搜索 wrapper,\n // 但不进入 menu item 内部;其子节点属于嵌套 submenu。\n children.push(...getMenuChildren(child))\n }\n return children\n}\n\nexport function getEnabledMenuItems(container: ParentNode): HTMLElement[] {\n return getMenuChildren(container).filter(item => item.matches(`${MENU_ITEM_SELECTOR}:not([disabled])`))\n}\n\nexport function getMovableMenuSubtrees(container: ParentNode): HTMLElement[] {\n const subtrees: HTMLElement[] = []\n for (const child of Array.from(container.children)) {\n if (!(child instanceof HTMLElement)) continue\n if (child.matches(MENU_CONTENT_SELECTOR) || getMenuChildren(child).length > 0) subtrees.push(child)\n }\n return subtrees\n}\n\nexport function moveMenuChildren(source: ParentNode, target: HTMLElement) {\n getMovableMenuSubtrees(source).forEach(subtree => {\n // 正常调用 source !== target,移走后项不再属于 source,天然收敛;\n // 这里兜底防御 source === target 的误用,避免 appendChild 自移动触发观察者刷新循环。\n if (subtree.parentNode === target) return\n target.appendChild(subtree)\n })\n}\n\nexport function hideNestedMenuChildren(root: ParentNode, slot: string) {\n root.querySelectorAll<HTMLElement>('web-ui-dropdown-item[submenu]').forEach(item => {\n getMenuChildren(item).forEach(child => child.setAttribute('slot', slot))\n })\n}\n\nexport function getMenuItemFromEvent(event: Event): HTMLElement | null {\n return (\n event\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.matches(MENU_ITEM_SELECTOR)) ?? null\n )\n}\n\nexport function focusMenuItem(item: HTMLElement | undefined) {\n if (!item || item.hasAttribute('disabled')) return\n ;(item as HTMLElement & { focusItem?: () => void }).focusItem?.()\n}\n\nexport function findFocusedMenuItem(panels: Iterable<HTMLElement | undefined>): HTMLElement | undefined {\n return [...panels]\n .filter((panel): panel is HTMLElement => panel !== undefined)\n .flatMap(panel => Array.from(panel.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR)))\n .find(item => Boolean(item.shadowRoot?.activeElement))\n}\n"],"mappings":";AAAA,IAAa,IAAwB,yEACxB,IAAqB;AAElC,SAAgB,EAAgB,GAAsC;CACpE,IAAM,IAA0B,CAAC;CACjC,KAAK,IAAM,KAAS,MAAM,KAAK,EAAU,QAAQ,GACzC,iBAAiB,aACvB;MAAI,EAAM,QAAA,uEAA6B,GAAG;GACxC,EAAS,KAAK,CAAK;GACnB;EACF;EAIA,EAAS,KAAK,GAAG,EAAgB,CAAK,CAAC;CAJvC;CAMF,OAAO;AACT;AAEA,SAAgB,EAAoB,GAAsC;CACxE,OAAO,EAAgB,CAAS,CAAC,CAAC,QAAO,MAAQ,EAAK,QAAQ,GAAG,EAAmB,iBAAiB,CAAC;AACxG;AAEA,SAAgB,EAAuB,GAAsC;CAC3E,IAAM,IAA0B,CAAC;CACjC,KAAK,IAAM,KAAS,MAAM,KAAK,EAAU,QAAQ,GACzC,aAAiB,gBACnB,EAAM,QAAA,uEAA6B,KAAK,EAAgB,CAAK,CAAC,CAAC,SAAS,MAAG,EAAS,KAAK,CAAK;CAEpG,OAAO;AACT;AAEA,SAAgB,EAAiB,GAAoB,GAAqB;CACxE,EAAuB,CAAM,CAAC,CAAC,SAAQ,MAAW;EAG5C,EAAQ,eAAe,KAC3B,EAAO,YAAY,CAAO;CAC5B,CAAC;AACH;AAEA,SAAgB,EAAuB,GAAkB,GAAc;CACrE,EAAK,iBAA8B,+BAA+B,CAAC,CAAC,SAAQ,MAAQ;EAClF,EAAgB,CAAI,CAAC,CAAC,SAAQ,MAAS,EAAM,aAAa,QAAQ,CAAI,CAAC;CACzE,CAAC;AACH;AAEA,SAAgB,EAAqB,GAAkC;CACrE,OACE,EACG,aAAa,CAAC,CACd,MAAM,MAA8B,aAAgB,eAAe,EAAK,QAAA,sBAA0B,CAAC,KAAK;AAE/G;AAEA,SAAgB,EAAc,GAA+B;CACvD,CAAC,KAAQ,EAAK,aAAa,UAAU,KACxC,EAAmD,YAAY;AAClE;AAEA,SAAgB,EAAoB,GAAoE;CACtG,OAAO,CAAC,GAAG,CAAM,CAAC,CACf,QAAQ,MAAgC,MAAU,KAAA,CAAS,CAAC,CAC5D,SAAQ,MAAS,MAAM,KAAK,EAAM,iBAA8B,CAAkB,CAAC,CAAC,CAAC,CACrF,MAAK,MAAQ,EAAQ,EAAK,YAAY,aAAc;AACzD"}
1
+ {"version":3,"file":"menu-tree.js","names":[],"sources":["../../../src/shared/menu-portal/menu-tree.ts"],"sourcesContent":["export const MENU_CONTENT_SELECTOR = 'web-ui-dropdown-item, web-ui-dropdown-divider, web-ui-dropdown-header'\nexport const MENU_ITEM_SELECTOR = 'web-ui-dropdown-item'\n\nexport function getMenuChildren(container: ParentNode): HTMLElement[] {\n const children: HTMLElement[] = []\n for (const child of Array.from(container.children)) {\n if (!(child instanceof HTMLElement)) continue\n if (child.matches(MENU_CONTENT_SELECTOR)) {\n children.push(child)\n continue\n }\n\n // 框架条件渲染会用元素包裹自有节点,或留下注释锚点。搜索 wrapper,\n // 但不进入 menu item 内部;其子节点属于嵌套 submenu。\n children.push(...getMenuChildren(child))\n }\n return children\n}\n\nexport function getEnabledMenuItems(container: ParentNode): HTMLElement[] {\n return getMenuChildren(container).filter(item => item.matches(`${MENU_ITEM_SELECTOR}:not([disabled])`))\n}\n\nexport function getMovableMenuSubtrees(container: ParentNode): HTMLElement[] {\n const subtrees: HTMLElement[] = []\n for (const child of Array.from(container.children)) {\n if (!(child instanceof HTMLElement)) continue\n if (child.matches(MENU_CONTENT_SELECTOR) || getMenuChildren(child).length > 0) subtrees.push(child)\n }\n return subtrees\n}\n\nexport function moveMenuChildren(source: ParentNode, target: HTMLElement) {\n getMovableMenuSubtrees(source).forEach(subtree => {\n // 正常调用 source !== target,移走后项不再属于 source,天然收敛;\n // 这里兜底防御 source === target 的误用,避免 appendChild 自移动触发观察者刷新循环。\n if (subtree.parentNode === target) return\n target.appendChild(subtree)\n })\n}\n\nexport function hideNestedMenuChildren(root: ParentNode, slot: string) {\n root.querySelectorAll<HTMLElement>('web-ui-dropdown-item[submenu]').forEach(item => {\n getMenuChildren(item).forEach(child => child.setAttribute('slot', slot))\n })\n}\n\nexport function getMenuItemFromEvent(event: Event): HTMLElement | null {\n return (\n event\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.matches(MENU_ITEM_SELECTOR)) ?? null\n )\n}\n\nexport function focusMenuItem(item: HTMLElement | undefined) {\n if (!item || item.hasAttribute('disabled')) return\n ;(item as HTMLElement & { focusItem?: () => void }).focusItem?.()\n}\n\nexport function findFocusedMenuItem(panels: Iterable<HTMLElement | undefined>): HTMLElement | undefined {\n return [...panels]\n .filter((panel): panel is HTMLElement => panel !== undefined)\n .flatMap(panel => Array.from(panel.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR)))\n .find(item => Boolean(item.shadowRoot?.activeElement))\n}\n\n/**\n * 托管菜单项的模板位锚:每个被迁入面板的项在宿主 light DOM 留一个 marker 注释,\n * 表示其模板位。宿主因此始终持有完整有序骨架(marker + 框架注释锚点 + 非菜单节点),\n * 框架(Vue/React 条件渲染)打开期的插入以宿主为容器、以真实锚点为基准;\n * 面板项的顺序按 marker 序重排;关闭时按 marker 位双向归还。\n * context-menu 与 dropdown 的打开期实时渲染共用本机制。\n */\nexport type MenuItemAnchors = Map<HTMLElement, Comment>\n\n/**\n * 全集 reconcile:以「content 现有元素 ∪ 宿主元素」为托管全集。\n * 框架(如 Vue 的 v-if 翻转)可能不经宿主直接改写 portal 内节点——卸载旧项留下\n * 注释锚点、把新项直接插进 content——它们都会被这里收编(补 marker + 补隐藏由调用方负责),\n * 已被框架删除/移出两处的条目则清理 marker。最后把宿主中尚存的托管项移入 content。\n */\nexport function reconcileManagedMenuItems(\n host: HTMLElement,\n content: HTMLElement,\n anchors: MenuItemAnchors,\n markerText: string\n): void {\n // 宿主与 portal 面板分别位于不同容器,结构上不相交且收集只看直接子层,\n // 两个集合不会重复。\n const inContent = getMovableMenuSubtrees(content)\n const inHost = getMovableMenuSubtrees(host)\n const managed = new Set([...inContent, ...inHost])\n\n // 清理已失效条目:托管元素既不在 content 也不在宿主,说明已被框架删除。\n // 仍在 content 的元素必被上面收集进 managed,故这里只需处理宿主侧。\n anchors.forEach((marker, subtree) => {\n if (managed.has(subtree)) return\n marker.remove()\n anchors.delete(subtree)\n })\n\n // 确保每个托管元素都有 marker:框架直接插入 content 的项在此收编。\n // content 项按其当前 DOM 序(框架语义序)**反向**处理,新 marker 插到后继项 marker 之前——\n // 这样补建的 marker 序无需重排即可与 content 对齐;宿主项的 marker 原地补插。\n const ensureMarker = (subtree: HTMLElement, inHost: boolean, before?: Comment) => {\n const existing = anchors.get(subtree)\n if (existing && existing.parentNode === host) return existing\n existing?.remove()\n const marker = document.createComment(markerText)\n if (inHost) {\n host.insertBefore(marker, subtree)\n } else if (before && before.parentNode === host) {\n host.insertBefore(marker, before)\n } else {\n host.appendChild(marker)\n }\n anchors.set(subtree, marker)\n return marker\n }\n for (let index = inContent.length - 1; index >= 0; index--) {\n const subtree = inContent[index]\n const next = inContent[index + 1]\n const nextMarker = next ? anchors.get(next) : undefined\n ensureMarker(subtree, false, nextMarker)\n }\n for (const subtree of inHost) {\n ensureMarker(subtree, true)\n }\n\n // 宿主中尚存的托管项移入 content。\n for (const subtree of inHost) {\n content.appendChild(subtree)\n }\n}\n\n/** 按宿主 marker 序重排 content 项;顺序已一致时早退。 */\nexport function orderManagedMenuItems(\n host: HTMLElement,\n content: HTMLElement,\n anchors: MenuItemAnchors,\n markerText: string\n): void {\n const targetItems: HTMLElement[] = []\n for (const node of Array.from(host.childNodes)) {\n if (node.nodeType !== Node.COMMENT_NODE || node.textContent !== markerText) continue\n anchors.forEach((marker, subtree) => {\n if (marker !== node || subtree.parentNode !== content) return\n targetItems.push(subtree)\n })\n }\n\n if (targetItems.length === 0) return\n\n const currentItems = Array.from(content.children)\n if (currentItems.length === targetItems.length && currentItems.every((item, index) => item === targetItems[index]))\n return\n\n // 把每个元素移动到目标序中下一个元素之前,而不是 appendChild 全部移到末尾。\n // appendChild 会把元素越过 content 中的框架注释锚点(v-if 锚点),导致锚点脱离\n // 模板位置,下次框架翻转时据此锚点插入新分支项就会落错位;insertBefore 只重排\n // 元素相对顺序,锚点保持在原模板位置。锚点复位由 restoreFrameworkAnchors 独立完成。\n for (let index = targetItems.length - 1; index >= 0; index--) {\n const item = targetItems[index]\n const next = targetItems[index + 1]\n if (next) {\n if (item.nextElementSibling !== next) content.insertBefore(item, next)\n } else {\n content.appendChild(item)\n }\n }\n}\n\n/**\n * 捕获框架 v-if 注释锚点相对其后继元素的绑定,供重排后复位。\n * 后继以 nextElementSibling 解析(跳过注释/文本,多个相邻锚点绑定同一后继)。\n * 锚点在 content 末尾、无后继元素时记为 null(尾部锚点,复位时保持末尾)。\n */\nexport function captureFrameworkAnchors(content: HTMLElement, markerText: string): Map<Comment, HTMLElement | null> {\n const anchors = new Map<Comment, HTMLElement | null>()\n for (const node of Array.from(content.childNodes)) {\n if (node instanceof Comment && node.textContent !== markerText) {\n const nextEl = node.nextElementSibling\n anchors.set(node, nextEl instanceof HTMLElement ? nextEl : null)\n }\n }\n return anchors\n}\n\n/**\n * 把锚点复位到捕获时其后继元素之前(尾部锚点保持末尾)。\n * 元素重排用 insertBefore/appendChild 不会移动注释,锚点必须显式复位,\n * 否则脱离模板位置后 Vue 下次 v-if 翻转会把新分支项插到错误插入点。\n */\nexport function restoreFrameworkAnchors(content: HTMLElement, anchors: Map<Comment, HTMLElement | null>): void {\n for (const [anchor, successor] of anchors) {\n if (anchor.parentNode !== content) continue\n if (successor && successor.parentNode === content) {\n if (anchor.nextElementSibling !== successor) content.insertBefore(anchor, successor)\n } else {\n // 尾部锚点:重排可能把元素 append 到末尾越过锚点,恢复其末尾位置。\n content.appendChild(anchor)\n }\n }\n}\n\n/**\n * 关闭恢复:按 content 子节点序双向归还——元素回到宿主中自己的 marker 位,\n * 框架 v-if 注释锚点插到对应元素之前。Vue 仍持有这些锚点的 vnode.el 引用,\n * 随元素一起迁回宿主可确保框架下次 patch 以宿主为容器,不会因 parentNode === null 崩溃。\n * 归还后清除全部 marker 与登记。\n */\nexport function returnManagedMenuItemsToSlot(host: HTMLElement, content: HTMLElement, anchors: MenuItemAnchors): void {\n const pending: Comment[] = []\n for (const node of Array.from(content.childNodes)) {\n // instanceof 收窄:Comment 接口为空,nodeType 继承自 Node 的 number,无法用 === 收窄\n if (node instanceof Comment) {\n pending.push(node)\n continue\n }\n if (!(node instanceof HTMLElement)) continue\n const marker = anchors.get(node)\n if (!marker) continue\n for (const c of pending) {\n if (marker.parentNode === host) host.insertBefore(c, marker)\n else host.appendChild(c)\n }\n pending.length = 0\n if (marker.parentNode === host) {\n host.insertBefore(node, marker)\n } else {\n host.appendChild(node)\n }\n marker.remove()\n anchors.delete(node)\n }\n // 尾部框架锚点追加到宿主末尾\n for (const c of pending) host.appendChild(c)\n // 映射中剩余 = 已不在 content 的条目(如翻转中被框架卸载的),仅清理 marker\n anchors.forEach(marker => marker.remove())\n anchors.clear()\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ //#region src/shared/open-state/index.ts
2
+ function e(e, t) {
3
+ e.dispatchEvent(new CustomEvent("open-change", {
4
+ detail: { open: t },
5
+ bubbles: !0,
6
+ composed: !0
7
+ }));
8
+ }
9
+ //#endregion
10
+ export { e as dispatchOpenChangeEvent };
11
+
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/open-state/index.ts"],"sourcesContent":["import type { LitElement } from 'lit'\n\n/**\n * overlay 族 `open-change` 事件的单一发出点与契约说明。\n *\n * 同名事件存在两种 flavor(决策见 ADR-0005 / ADR-0007,勿混淆):\n * - **request 语义**(dialog / drawer / layout):controlled 属性开启时,\n * `open-change` 是用户的关闭/打开\"请求\",组件不自行修改 `open`,等待回写;\n * - **notification 语义**(popover / tooltip / dropdown / context-menu /\n * select / autocomplete / collapse):组件总是自行变更 `open`,事件只作通知。\n *\n * 两种 flavor 的机械发出在此收敛;flavor 决策(是否 controlled 门控)留在各组件。\n */\nexport interface OpenChangeEventDetail {\n open: boolean\n}\n\nexport function dispatchOpenChangeEvent(host: LitElement, open: boolean): void {\n host.dispatchEvent(\n new CustomEvent<OpenChangeEventDetail>('open-change', {\n detail: { open },\n bubbles: true,\n composed: true\n })\n )\n}\n"],"mappings":";AAiBA,SAAgB,EAAwB,GAAkB,GAAqB;CAC7E,EAAK,cACH,IAAI,YAAmC,eAAe;EACpD,QAAQ,EAAE,QAAK;EACf,SAAS;EACT,UAAU;CACZ,CAAC,CACH;AACF"}
@@ -0,0 +1,41 @@
1
+ //#region src/shared/option-portal/combobox-shell.ts
2
+ function e(e) {
3
+ function t(t = !1) {
4
+ e.canOpen() === !1 || e.getIsOpen() || (e.setIsOpen(!0), e.dispatchOpenChange(), e.onOpen?.(t), e.syncScrollLock(!0), e.isPortal() ? requestAnimationFrame(() => {
5
+ e.getIsOpen() && e.openOverlay(t);
6
+ }) : e.openOverlay(t));
7
+ }
8
+ function n() {
9
+ e.getIsOpen() && (e.onBeforeClose?.(), e.setIsOpen(!1), e.dispatchOpenChange(), e.onAfterClose?.(), e.syncScrollLock(!1), e.closeOverlay());
10
+ }
11
+ return {
12
+ open: t,
13
+ close: n
14
+ };
15
+ }
16
+ function t(e) {
17
+ function t(t) {
18
+ t.addEventListener("click", e.onClick), t.addEventListener("pointerover", e.onPointerOver), t.addEventListener("pointerdown", e.onPointerDown), t.addEventListener("option-update", e.onUpdate);
19
+ }
20
+ function n(t) {
21
+ t.removeEventListener("click", e.onClick), t.removeEventListener("pointerover", e.onPointerOver), t.removeEventListener("pointerdown", e.onPointerDown), t.removeEventListener("option-update", e.onUpdate);
22
+ }
23
+ return {
24
+ bind: t,
25
+ unbind: n
26
+ };
27
+ }
28
+ function n(e, t, n, r) {
29
+ requestAnimationFrame(() => {
30
+ n() && !e.matches(":focus-within") && !t()?.matches(":focus-within") && r();
31
+ });
32
+ }
33
+ function r(e, t, n) {
34
+ if (e === 0) return -1;
35
+ let r = t + n;
36
+ return r < 0 && (r = e - 1), r >= e && (r = 0), r;
37
+ }
38
+ //#endregion
39
+ export { e as createComboboxOpenController, t as createOptionListenerBinding, n as handleComboboxFocusOut, r as nextWrappingIndex };
40
+
41
+ //# sourceMappingURL=combobox-shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combobox-shell.js","names":[],"sources":["../../../src/shared/option-portal/combobox-shell.ts"],"sourcesContent":["import type { WebUiOption } from '@/components/option'\n\n/**\n * combobox 开合与 option 交互的共享骨架(select / autocomplete)。\n *\n * 与 option-portal 的边界约定一致:交互语义的\"差异点\"经回调注入,\n * 这里收口两组件逐行复制的生命周期不变量——\n * - open/close 状态迁移顺序:guard → 状态翻转 → open-change 派发 →\n * 激活钩子 → scroll-lock 配对 → portal 模式的 rAF 延迟 openOverlay\n * - close 路径:before-close 钩子(portal 内容先收敛)→ 激活清理 →\n * scroll-lock 释放 → closeOverlay(关闭后使 portal 引用失效)\n * - option 四事件监听对的绑定/解绑\n * - focusout 的 rAF :focus-within 复核\n * - 键盘导航的环绕游标算法\n */\n\nexport interface ComboboxOpenHooks {\n /** 是否允许打开(disabled / readonly 等由组件判断)。 */\n canOpen(): boolean\n getIsOpen(): boolean\n setIsOpen(open: boolean): void\n dispatchOpenChange(): void\n syncScrollLock(open: boolean): void\n /** 是否 portal 模式(决定 openOverlay 是否延迟到下一帧)。 */\n isPortal(): boolean\n openOverlay(isKeyboardNavigation: boolean): void\n closeOverlay(): void\n /** 打开时的激活项初始化(键盘导航取选中/首项,指针打开保留现状)。 */\n onOpen?(isKeyboardNavigation: boolean): void\n /** 关闭前钩子:autocomplete 需先把 light DOM 新增节点收敛进当前 portal content。 */\n onBeforeClose?(): void\n /** 关闭时清理激活状态(清 _activeIndex 与 active 属性)。 */\n onAfterClose?(): void\n}\n\nexport interface ComboboxOpenController {\n open(isKeyboardNavigation?: boolean): void\n close(): void\n}\n\nexport function createComboboxOpenController(hooks: ComboboxOpenHooks): ComboboxOpenController {\n function open(isKeyboardNavigation = false) {\n if (hooks.canOpen() === false || hooks.getIsOpen()) return\n hooks.setIsOpen(true)\n hooks.dispatchOpenChange()\n hooks.onOpen?.(isKeyboardNavigation)\n hooks.syncScrollLock(true)\n if (hooks.isPortal()) {\n requestAnimationFrame(() => {\n if (hooks.getIsOpen()) hooks.openOverlay(isKeyboardNavigation)\n })\n } else {\n hooks.openOverlay(isKeyboardNavigation)\n }\n }\n\n function close() {\n if (!hooks.getIsOpen()) return\n hooks.onBeforeClose?.()\n hooks.setIsOpen(false)\n hooks.dispatchOpenChange()\n hooks.onAfterClose?.()\n hooks.syncScrollLock(false)\n hooks.closeOverlay()\n }\n\n return { open, close }\n}\n\nexport interface OptionListenerHandlers {\n onClick: (event: Event) => void\n onPointerOver: (event: PointerEvent) => void\n onPointerDown: (event: PointerEvent) => void\n onUpdate: () => void\n}\n\n/** option 四事件监听对的绑定/解绑;两组件的 handler 签名一致,仅实现不同。 */\nexport function createOptionListenerBinding(handlers: OptionListenerHandlers) {\n function bind(option: WebUiOption) {\n option.addEventListener('click', handlers.onClick)\n option.addEventListener('pointerover', handlers.onPointerOver)\n option.addEventListener('pointerdown', handlers.onPointerDown)\n option.addEventListener('option-update', handlers.onUpdate)\n }\n function unbind(option: WebUiOption) {\n option.removeEventListener('click', handlers.onClick)\n option.removeEventListener('pointerover', handlers.onPointerOver)\n option.removeEventListener('pointerdown', handlers.onPointerDown)\n option.removeEventListener('option-update', handlers.onUpdate)\n }\n return { bind, unbind }\n}\n\n/**\n * focusout 关闭复核:焦点是否真的离开了宿主与面板。\n * rAF 等待 click/pointerdown 相关的焦点迁移完成后再判定。\n */\nexport function handleComboboxFocusOut(\n host: HTMLElement,\n getPanel: () => HTMLElement | null | undefined,\n getIsOpen: () => boolean,\n close: () => void\n): void {\n requestAnimationFrame(() => {\n if (getIsOpen() && !host.matches(':focus-within') && !getPanel()?.matches(':focus-within')) {\n close()\n }\n })\n}\n\n/**\n * 环绕游标:在 enabled 列表内按 delta 移动;从 -1(无激活)向下取首项、向上取末项。\n * 返回在 enabled 中的下一索引;调用方负责换算回全量 options 索引。\n */\nexport function nextWrappingIndex(enabledCount: number, currentIdx: number, delta: number): number {\n if (enabledCount === 0) return -1\n let nextIdx = currentIdx + delta\n if (nextIdx < 0) nextIdx = enabledCount - 1\n if (nextIdx >= enabledCount) nextIdx = 0\n return nextIdx\n}\n"],"mappings":";AAwCA,SAAgB,EAA6B,GAAkD;CAC7F,SAAS,EAAK,IAAuB,IAAO;EACtC,EAAM,QAAQ,MAAM,MAAS,EAAM,UAAU,MACjD,EAAM,UAAU,EAAI,GACpB,EAAM,mBAAmB,GACzB,EAAM,SAAS,CAAoB,GACnC,EAAM,eAAe,EAAI,GACrB,EAAM,SAAS,IACjB,4BAA4B;GAC1B,AAAI,EAAM,UAAU,KAAG,EAAM,YAAY,CAAoB;EAC/D,CAAC,IAED,EAAM,YAAY,CAAoB;CAE1C;CAEA,SAAS,IAAQ;EACV,EAAM,UAAU,MACrB,EAAM,gBAAgB,GACtB,EAAM,UAAU,EAAK,GACrB,EAAM,mBAAmB,GACzB,EAAM,eAAe,GACrB,EAAM,eAAe,EAAK,GAC1B,EAAM,aAAa;CACrB;CAEA,OAAO;EAAE;EAAM;CAAM;AACvB;AAUA,SAAgB,EAA4B,GAAkC;CAC5E,SAAS,EAAK,GAAqB;EAIjC,AAHA,EAAO,iBAAiB,SAAS,EAAS,OAAO,GACjD,EAAO,iBAAiB,eAAe,EAAS,aAAa,GAC7D,EAAO,iBAAiB,eAAe,EAAS,aAAa,GAC7D,EAAO,iBAAiB,iBAAiB,EAAS,QAAQ;CAC5D;CACA,SAAS,EAAO,GAAqB;EAInC,AAHA,EAAO,oBAAoB,SAAS,EAAS,OAAO,GACpD,EAAO,oBAAoB,eAAe,EAAS,aAAa,GAChE,EAAO,oBAAoB,eAAe,EAAS,aAAa,GAChE,EAAO,oBAAoB,iBAAiB,EAAS,QAAQ;CAC/D;CACA,OAAO;EAAE;EAAM;CAAO;AACxB;AAMA,SAAgB,EACd,GACA,GACA,GACA,GACM;CACN,4BAA4B;EAC1B,AAAI,EAAU,KAAK,CAAC,EAAK,QAAQ,eAAe,KAAK,CAAC,EAAS,CAAC,EAAE,QAAQ,eAAe,KACvF,EAAM;CAEV,CAAC;AACH;AAMA,SAAgB,EAAkB,GAAsB,GAAoB,GAAuB;CACjG,IAAI,MAAiB,GAAG,OAAO;CAC/B,IAAI,IAAU,IAAa;CAG3B,OAFI,IAAU,MAAG,IAAU,IAAe,IACtC,KAAW,MAAc,IAAU,IAChC;AACT"}
@@ -1,48 +1,49 @@
1
- import { definePlugin as e } from "@greypan/js-kit";
1
+ import { createComboboxOpenController as e, createOptionListenerBinding as t, handleComboboxFocusOut as n, nextWrappingIndex as r } from "./combobox-shell.js";
2
+ import { definePlugin as i } from "@greypan/js-kit";
2
3
  //#region src/shared/option-portal/index.ts
3
- var t = "web-ui-option", n = () => e((e) => {
4
- let n = /* @__PURE__ */ new Set(), r = /* @__PURE__ */ new WeakMap(), i = 0, a = (t) => {
4
+ var a = "web-ui-option", o = () => i((e) => {
5
+ let t = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new WeakMap(), r = 0, i = (t) => {
5
6
  e.bindOption(t), t.addEventListener("option-unregister", s);
6
7
  }, o = (t) => {
7
8
  e.unbindOption(t), t.removeEventListener("option-unregister", s);
8
9
  }, s = (e) => {
9
- e.target instanceof HTMLElement && e.target.localName === t && n.add(e.target), l.scheduleRefresh();
10
+ e.target instanceof HTMLElement && e.target.localName === a && t.add(e.target), l.scheduleRefresh();
10
11
  }, c = () => {
11
12
  l.scheduleRefresh();
12
13
  }, l = {
13
14
  queryOptions() {
14
- let n = e.getPortal()?.panel ?? e.getPortalContent(), r = n ? [...n.querySelectorAll(t)] : [];
15
- return r.length > 0 ? r : [...e.element.querySelectorAll(t)];
15
+ let t = e.getPortal()?.panel ?? e.getPortalContent(), n = t ? [...t.querySelectorAll(a)] : [];
16
+ return n.length > 0 ? n : [...e.element.querySelectorAll(a)];
16
17
  },
17
18
  refresh(e, t) {
18
19
  let n = l.queryOptions();
19
20
  for (let t of e) n.includes(t) || o(t);
20
- return n.forEach(a), {
21
+ return n.forEach(i), {
21
22
  options: n,
22
23
  activeIndex: t && n.includes(t) ? n.indexOf(t) : -1
23
24
  };
24
25
  },
25
26
  ensureOptionIds(t) {
26
- let n = new Set(t.map((e) => e.id).filter((e) => !!e));
27
+ let i = new Set(t.map((e) => e.id).filter((e) => !!e));
27
28
  t.forEach((t) => {
28
29
  if (t.id) return;
29
- let a = r.get(t);
30
- if (!a || n.has(a)) {
30
+ let a = n.get(t);
31
+ if (!a || i.has(a)) {
31
32
  do
32
- a = `${e.idPrefix}-option-${++i}`;
33
- while (n.has(a));
34
- r.set(t, a);
33
+ a = `${e.idPrefix}-option-${++r}`;
34
+ while (i.has(a));
35
+ n.set(t, a);
35
36
  }
36
- t.id = a, n.add(a);
37
+ t.id = a, i.add(a);
37
38
  });
38
39
  },
39
40
  syncPortalContent() {
40
- let t = e.getPortal(), r = e.getPortalContent();
41
- if (!t || !r) return;
42
- for (let i of n) !r.contains(i) && !e.element.contains(i) && t.removeContent([i]);
43
- if (n.clear(), !e.isOpen()) return;
44
- let i = e.getMigratableNodes();
45
- i.length && t.appendContent(i, r);
41
+ let n = e.getPortal(), r = e.getPortalContent();
42
+ if (!n || !r) return;
43
+ for (let i of t) !r.contains(i) && !e.element.contains(i) && n.removeContent([i]);
44
+ if (t.clear(), !e.isOpen()) return;
45
+ let i = e.getMigratableNodes().filter((e) => !(e instanceof Comment));
46
+ i.length && n.appendContent(i, r);
46
47
  },
47
48
  scheduleRefresh() {
48
49
  queueMicrotask(() => {
@@ -54,12 +55,12 @@ var t = "web-ui-option", n = () => e((e) => {
54
55
  e.element.addEventListener("option-register", c), e.element.addEventListener("option-unregister", s);
55
56
  },
56
57
  dispose() {
57
- e.element.removeEventListener("option-register", c), e.element.removeEventListener("option-unregister", s), n.clear();
58
+ e.element.removeEventListener("option-register", c), e.element.removeEventListener("option-unregister", s), t.clear();
58
59
  }
59
60
  };
60
61
  return l;
61
62
  });
62
63
  //#endregion
63
- export { n as defineOptionPortal };
64
+ export { e as createComboboxOpenController, t as createOptionListenerBinding, o as defineOptionPortal, n as handleComboboxFocusOut, r as nextWrappingIndex };
64
65
 
65
66
  //# sourceMappingURL=index.js.map