@greypan/web-ui 12.1.0 → 12.2.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 (49) hide show
  1. package/README.CN.md +2 -0
  2. package/README.md +2 -0
  3. package/dist/components/autocomplete/index.d.ts.map +1 -1
  4. package/dist/components/autocomplete/index.js +1 -1
  5. package/dist/components/autocomplete/index.js.map +1 -1
  6. package/dist/components/drawer/index.d.ts +11 -0
  7. package/dist/components/drawer/index.d.ts.map +1 -1
  8. package/dist/components/drawer/index.js +23 -1
  9. package/dist/components/drawer/index.js.map +1 -1
  10. package/dist/components/drawer/page-shift.js +113 -0
  11. package/dist/components/drawer/page-shift.js.map +1 -0
  12. package/dist/components/grouped-text/grouping.d.ts +25 -0
  13. package/dist/components/grouped-text/grouping.d.ts.map +1 -0
  14. package/dist/components/grouped-text/grouping.js +147 -0
  15. package/dist/components/grouped-text/grouping.js.map +1 -0
  16. package/dist/components/grouped-text/index.d.ts +36 -0
  17. package/dist/components/grouped-text/index.d.ts.map +1 -0
  18. package/dist/components/grouped-text/index.js +28 -0
  19. package/dist/components/grouped-text/index.js.map +1 -0
  20. package/dist/components/grouped-text/style.js +5 -0
  21. package/dist/components/grouped-text/style.js.map +1 -0
  22. package/dist/components/index.d.ts +4 -1
  23. package/dist/components/index.d.ts.map +1 -1
  24. package/dist/components/index.js +3 -1
  25. package/dist/components/layout/index.d.ts.map +1 -1
  26. package/dist/components/layout/index.js +1 -0
  27. package/dist/components/layout/index.js.map +1 -1
  28. package/dist/components/middle-ellipsis/index.js +1 -1
  29. package/dist/components/popover/index.d.ts +5 -2
  30. package/dist/components/popover/index.d.ts.map +1 -1
  31. package/dist/components/popover/index.js +31 -20
  32. package/dist/components/popover/index.js.map +1 -1
  33. package/dist/components/shake/index.d.ts +43 -0
  34. package/dist/components/shake/index.d.ts.map +1 -0
  35. package/dist/components/shake/index.js +103 -0
  36. package/dist/components/shake/index.js.map +1 -0
  37. package/dist/components/tooltip/index.d.ts +8 -0
  38. package/dist/components/tooltip/index.d.ts.map +1 -1
  39. package/dist/components/tooltip/index.js +59 -9
  40. package/dist/components/tooltip/index.js.map +1 -1
  41. package/dist/shared/overlay/anchored-panel.js +2 -2
  42. package/dist/shared/overlay/anchored-panel.js.map +1 -1
  43. package/dist/shared/overlay/presence.js +2 -2
  44. package/dist/shared/overlay/presence.js.map +1 -1
  45. package/dist/shared/overlay/trigger-props.d.ts +10 -0
  46. package/dist/shared/overlay/trigger-props.d.ts.map +1 -0
  47. package/dist/shared/overlay/trigger-props.js +42 -0
  48. package/dist/shared/overlay/trigger-props.js.map +1 -0
  49. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["glass","overlayMotion","style"],"sources":["../../../src/components/tooltip/index.ts"],"sourcesContent":["import type { Placement } from '@floating-ui/dom'\nimport { html, LitElement, nothing, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport overlayMotion from '@/assets/overlay-motion.css?inline'\nimport glass from '@/classes/glass.css?inline'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { findFocusTarget } from '@/shared/focus/tabbable'\nimport { normalizeLiteral, normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineAnchoredPanel } from '@/shared/overlay/anchored-panel'\nimport { defineOpenOverlay } from '@/shared/overlay/open-overlay'\nimport { FLOATING_PLACEMENTS } from '@/shared/overlay/placement-props'\nimport { defineOverlayPortal, type OverlayContainer, type OverlayPortal } from '@/shared/overlay/portal'\n\nimport style from './style.css?inline'\n\nlet visibleTooltipCount = 0\nlet tooltipIdCounter = 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 _panelId = `wui-tooltip-panel-${++tooltipIdCounter}`\n /*\n * 面板在 shadow DOM 里,而 trigger 的可聚焦元素可能在更内层的 shadow root:\n * `aria-describedby` 的 IDREF 无法跨 shadow 边界解析,面板 id 因此只能供消费者自行引用,\n * 读屏可达性由 `aria-description` 承担(打开时写入、关闭时按原值还原)。\n * 保存原值是为了不吞掉消费者自己写在 trigger 上的描述。\n */\n private readonly _descriptionTargets = new Map<HTMLElement, string | null>()\n /**\n * 实例作用域的帧事务入口。`requestClose` 是关闭入口的唯一接线:panel 的\n * `arbitration: 'none'` 让 tooltip 在组合树里但永不成候选,因此这条**现在不会被\n * 模块调用**。保留它是不想把「怎么关」藏起来,也让仲裁策略变化时依然成立。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._hide(),\n isConnected: () => this.isConnected\n })\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 openOverlay: this._overlay,\n // tooltip 在逻辑组合树里(panel 迁移后仍要判对「谁在谁里面」),但从不作为 Escape\n // 候选:按键穿过它落到下层 overlay。\n arbitration: 'none'\n })\n\n get isOpen(): boolean {\n return this.open\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._overlay.resume()\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 // 停止帧调度;撤销登记由 _panel.dispose() 完成(handle 归它持有)。\n this._overlay.suspend()\n this._syncVisibleTooltipCount(false)\n this._clearDescription()\n this._panel.dispose()\n }\n\n override firstUpdated() {\n if (this.open) {\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n this._syncDescription()\n }\n }\n\n protected override willUpdate(changed: Map<string, unknown>) {\n if (this.disabled && (changed.has('disabled') || changed.has('open'))) {\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n this.open = false\n }\n }\n\n protected override updated(changed: Map<string, unknown>) {\n if (changed.has('portal') || changed.has('overlayContainer')) {\n // 同帧 open + reconfigure 可能重复建事务;invalidate 保证 reconfigure 是当前唯一帧回调。\n this._overlay.invalidate()\n this._overlay.scheduleFrame(() => 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 clearTimeout(this._hideTimer)\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n this._syncDescription()\n } else {\n this._overlay.invalidate()\n this._clearDescription()\n void this._closeOverlay()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this.open)\n }\n if (changed.has('content')) {\n this._syncPortalContent()\n if (this.open) this._syncDescription()\n }\n }\n\n private _onPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled) return\n clearTimeout(this._hideTimer)\n // 已有 tooltip 在场时(指针滑到相邻目标):既不等延迟,也不播入场动画。\n const isRepeat = visibleTooltipCount > 0\n this._showTimer = setTimeout(() => this._show(isRepeat), isRepeat ? 0 : this.showDelay)\n }\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') 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\n const focusedElement = event.composedPath()[0] ?? event.target\n if (!(focusedElement instanceof Element) || !focusedElement.matches(':focus-visible')) return\n\n clearTimeout(this._hideTimer)\n this._show(true)\n }\n\n private _onFocusOut = () => {\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 元素实时迁入 panel(注释锚点无法携带\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 portal.panel.id = this._panelId\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 localPanel = this._panel.getPanel()\n if (!localPanel) return\n const panel = this._portal?.panel ?? localPanel\n const text = localPanel.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 _getDescriptionTargets(): HTMLElement[] {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('.tooltip-trigger > slot')\n const targets: HTMLElement[] = []\n for (const element of slot?.assignedElements({ flatten: true }) ?? []) {\n if (!(element instanceof HTMLElement)) continue\n targets.push(findFocusTarget(element) ?? element)\n }\n return targets\n }\n\n private _readDescription(): string {\n if (this.content) return this.content.trim()\n const portalText = this.portal ? (this._portal?.panel.textContent ?? '').trim() : ''\n if (portalText) return portalText\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('.tooltip-panel > slot[name=\"content\"]')\n return (slot?.assignedNodes({ flatten: true }) ?? [])\n .map(node => node.textContent ?? '')\n .join('')\n .trim()\n }\n\n private _syncDescription() {\n if (!this.open) {\n this._clearDescription()\n return\n }\n const description = this._readDescription()\n if (!description) {\n this._clearDescription()\n return\n }\n const targets = new Set(this._getDescriptionTargets())\n for (const [target, previous] of this._descriptionTargets) {\n if (targets.has(target)) continue\n this._restoreDescription(target, previous)\n this._descriptionTargets.delete(target)\n }\n for (const target of targets) {\n if (!this._descriptionTargets.has(target)) {\n this._descriptionTargets.set(target, target.getAttribute('aria-description'))\n }\n target.setAttribute('aria-description', description)\n }\n }\n\n private _clearDescription() {\n for (const [target, previous] of this._descriptionTargets) this._restoreDescription(target, previous)\n this._descriptionTargets.clear()\n }\n\n private _restoreDescription(target: HTMLElement, previous: string | null) {\n if (previous === null) target.removeAttribute('aria-description')\n else target.setAttribute('aria-description', previous)\n }\n\n private _onTriggerSlotChange = () => {\n if (this.open) this._syncDescription()\n }\n\n private _onContentSlotChange = () => {\n if (this.open) this._syncDescription()\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 @slotchange=${this._onTriggerSlotChange}></slot></div>\n <div\n class=\"tooltip-panel wui-floating-panel wui-glass\"\n hidden\n role=\"tooltip\"\n id=${this.portal ? nothing : this._panelId}\n >\n ${\n this.content\n ? html`<span class=\"tooltip-text\">${this.content}</span>`\n : html`<slot name=\"content\" @slotchange=${this._onContentSlotChange}></slot>`\n }\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":["glass","overlayMotion","style"],"sources":["../../../src/components/tooltip/index.ts"],"sourcesContent":["import type { Placement } from '@floating-ui/dom'\nimport { html, LitElement, nothing, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport overlayMotion from '@/assets/overlay-motion.css?inline'\nimport glass from '@/classes/glass.css?inline'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { findFocusTarget } from '@/shared/focus/tabbable'\nimport { normalizeLiteral, normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineAnchoredPanel } from '@/shared/overlay/anchored-panel'\nimport { defineOpenOverlay } from '@/shared/overlay/open-overlay'\nimport { FLOATING_PLACEMENTS } from '@/shared/overlay/placement-props'\nimport { defineOverlayPortal, type OverlayContainer, type OverlayPortal } from '@/shared/overlay/portal'\nimport { hasTriggerMode, normalizeTriggerSet, type OverlayTriggerMode } from '@/shared/overlay/trigger-props'\n\nimport style from './style.css?inline'\n\n/** 指针悬停与键盘聚焦是 tooltip 的常驻手势;`manual` 表示只由 `open` 控制。 */\nconst DEFAULT_TRIGGERS = ['hover', 'focus'] as const\n\nlet visibleTooltipCount = 0\nlet tooltipIdCounter = 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: String, reflect: true })\n get trigger(): string {\n return this._trigger\n }\n set trigger(v: string | readonly OverlayTriggerMode[]) {\n const old = this._trigger\n this._trigger = normalizeTriggerSet(v, DEFAULT_TRIGGERS)\n this.requestUpdate('trigger', old)\n }\n private _trigger: string = DEFAULT_TRIGGERS.join(' ')\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 _panelId = `wui-tooltip-panel-${++tooltipIdCounter}`\n /*\n * 面板在 shadow DOM 里,而 trigger 的可聚焦元素可能在更内层的 shadow root:\n * `aria-describedby` 的 IDREF 无法跨 shadow 边界解析,面板 id 因此只能供消费者自行引用,\n * 读屏可达性由 `aria-description` 承担(打开时写入、关闭时按原值还原)。\n * 保存原值是为了不吞掉消费者自己写在 trigger 上的描述。\n */\n private readonly _descriptionTargets = new Map<HTMLElement, string | null>()\n /**\n * 实例作用域的帧事务入口。`requestClose` 是关闭入口的唯一接线:panel 的\n * `arbitration: 'none'` 让 tooltip 在组合树里但永不成候选,因此这条**现在不会被\n * 模块调用**。保留它是不想把「怎么关」藏起来,也让仲裁策略变化时依然成立。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._hide(),\n isConnected: () => this.isConnected\n })\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 openOverlay: this._overlay,\n // tooltip 在逻辑组合树里(panel 迁移后仍要判对「谁在谁里面」),但从不作为 Escape\n // 候选:按键穿过它落到下层 overlay。\n arbitration: 'none'\n })\n\n get isOpen(): boolean {\n return this.open\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._overlay.resume()\n this._syncTriggerListeners()\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._clearTriggerListeners()\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n // 停止帧调度;撤销登记由 _panel.dispose() 完成(handle 归它持有)。\n this._overlay.suspend()\n this._syncVisibleTooltipCount(false)\n this._clearDescription()\n this._panel.dispose()\n }\n\n override firstUpdated() {\n if (this.open) {\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n this._syncDescription()\n }\n }\n\n protected override willUpdate(changed: Map<string, unknown>) {\n if (this.disabled && (changed.has('disabled') || changed.has('open'))) {\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n this.open = false\n }\n }\n\n protected override updated(changed: Map<string, unknown>) {\n if (changed.has('trigger')) {\n this._clearTriggerListeners()\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n this._syncTriggerListeners()\n }\n if (changed.has('portal') || changed.has('overlayContainer')) {\n // 同帧 open + reconfigure 可能重复建事务;invalidate 保证 reconfigure 是当前唯一帧回调。\n this._overlay.invalidate()\n this._overlay.scheduleFrame(() => 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 clearTimeout(this._hideTimer)\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n this._syncDescription()\n } else {\n this._overlay.invalidate()\n this._clearDescription()\n void this._closeOverlay()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this.open)\n }\n if (changed.has('content')) {\n this._syncPortalContent()\n if (this.open) this._syncDescription()\n }\n }\n\n private _syncTriggerListeners() {\n if (hasTriggerMode(this.trigger, 'hover')) {\n this.addEventListener('pointerenter', this._onPointerEnter)\n this.addEventListener('pointerleave', this._onPointerLeave)\n }\n if (hasTriggerMode(this.trigger, 'focus')) {\n this.addEventListener('focusin', this._onFocusIn)\n this.addEventListener('focusout', this._onFocusOut)\n }\n if (hasTriggerMode(this.trigger, 'click')) document.addEventListener('click', this._onClickOutside)\n }\n\n private _clearTriggerListeners() {\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 document.removeEventListener('click', this._onClickOutside)\n }\n\n private _onTriggerClick = (event: MouseEvent) => {\n if (this.disabled || !hasTriggerMode(this.trigger, 'click')) return\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n // 键盘激活(detail 0)与聚焦同等对待:不播入场动画。\n if (this.open) this._hide()\n else this._show(event.detail === 0)\n }\n\n private _onClickOutside = (event: MouseEvent) => {\n if (!this.open) return\n /*\n * 在监听器内部判定:composedPath() 在派发结束后会被清空。trigger 及其 light DOM\n * 内容都在宿主路径里;portal 后的 panel 挂在主题的 overlay 容器里,走 handle 判定。\n */\n if (event.composedPath().includes(this)) return\n if (this._panel.getHandle()?.containsEvent(event)) return\n this._hide()\n }\n\n private _onPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled) return\n clearTimeout(this._hideTimer)\n // 已有 tooltip 在场时(指针滑到相邻目标):既不等延迟,也不播入场动画。\n const isRepeat = visibleTooltipCount > 0\n this._showTimer = setTimeout(() => this._show(isRepeat), isRepeat ? 0 : this.showDelay)\n }\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') 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\n const focusedElement = event.composedPath()[0] ?? event.target\n if (!(focusedElement instanceof Element) || !focusedElement.matches(':focus-visible')) return\n\n clearTimeout(this._hideTimer)\n this._show(true)\n }\n\n private _onFocusOut = () => {\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 元素实时迁入 panel(注释锚点无法携带\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 portal.panel.id = this._panelId\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 localPanel = this._panel.getPanel()\n if (!localPanel) return\n const panel = this._portal?.panel ?? localPanel\n const text = localPanel.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 _getDescriptionTargets(): HTMLElement[] {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('.tooltip-trigger > slot')\n const targets: HTMLElement[] = []\n for (const element of slot?.assignedElements({ flatten: true }) ?? []) {\n if (!(element instanceof HTMLElement)) continue\n targets.push(findFocusTarget(element) ?? element)\n }\n return targets\n }\n\n private _readDescription(): string {\n if (this.content) return this.content.trim()\n const portalText = this.portal ? (this._portal?.panel.textContent ?? '').trim() : ''\n if (portalText) return portalText\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('.tooltip-panel > slot[name=\"content\"]')\n return (slot?.assignedNodes({ flatten: true }) ?? [])\n .map(node => node.textContent ?? '')\n .join('')\n .trim()\n }\n\n private _syncDescription() {\n if (!this.open) {\n this._clearDescription()\n return\n }\n const description = this._readDescription()\n if (!description) {\n this._clearDescription()\n return\n }\n const targets = new Set(this._getDescriptionTargets())\n for (const [target, previous] of this._descriptionTargets) {\n if (targets.has(target)) continue\n this._restoreDescription(target, previous)\n this._descriptionTargets.delete(target)\n }\n for (const target of targets) {\n if (!this._descriptionTargets.has(target)) {\n this._descriptionTargets.set(target, target.getAttribute('aria-description'))\n }\n target.setAttribute('aria-description', description)\n }\n }\n\n private _clearDescription() {\n for (const [target, previous] of this._descriptionTargets) this._restoreDescription(target, previous)\n this._descriptionTargets.clear()\n }\n\n private _restoreDescription(target: HTMLElement, previous: string | null) {\n if (previous === null) target.removeAttribute('aria-description')\n else target.setAttribute('aria-description', previous)\n }\n\n private _onTriggerSlotChange = () => {\n if (this.open) this._syncDescription()\n }\n\n private _onContentSlotChange = () => {\n if (this.open) this._syncDescription()\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 <!-- 点击只挂在 trigger 包装层:panel 里的内容被点击不该把提示切走。 -->\n <div class=\"tooltip-trigger\" @click=${this._onTriggerClick}>\n <slot @slotchange=${this._onTriggerSlotChange}></slot>\n </div>\n <div\n class=\"tooltip-panel wui-floating-panel wui-glass\"\n hidden\n role=\"tooltip\"\n id=${this.portal ? nothing : this._panelId}\n >\n ${\n this.content\n ? html`<span class=\"tooltip-text\">${this.content}</span>`\n : html`<slot name=\"content\" @slotchange=${this._onContentSlotChange}></slot>`\n }\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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"}
@@ -63,11 +63,11 @@ const defineAnchoredPanel = () => definePlugin((ctx) => {
63
63
  return handle;
64
64
  },
65
65
  open,
66
- async close(isStillOpen) {
66
+ async close(isStillOpen, isInstant = false) {
67
67
  handle?.deferArbitration();
68
68
  overlay?.close();
69
69
  const panel = getPanel();
70
- if (panel && !await hideOverlayPresence(panel)) return false;
70
+ if (panel && !await hideOverlayPresence(panel, { isInstant })) return false;
71
71
  if (isStillOpen()) {
72
72
  handle?.deferArbitration(false);
73
73
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"anchored-panel.js","names":[],"sources":["../../../src/shared/overlay/anchored-panel.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport type { OpenOverlay, OpenOverlayHandle, OverlayArbitration } from './open-overlay'\nimport { defineOverlay, type OverlayApi, type OverlayOptions } from './overlay'\nimport type { OverlayPortal } from './portal'\nimport { hideOverlayPresence, showOverlayPresence } from './presence'\n\nexport interface AnchoredPanelOptions {\n getAnchor(): HTMLElement | null\n getLocalPanel(): HTMLElement | null\n getPositioning(): OverlayOptions\n isPortal(): boolean\n createPortal(): OverlayPortal\n /**\n * 承载「本 panel 是一次开启态 overlay」的实例:登记与 Escape 归属由它完成,本模块不\n * 自己维护逻辑父子关系。\n *\n * 必须是**调用方持有的那个实例**(而不是这里新建一个)——调用方同时要用它的帧\n * 事务入口,两个实例会让「我开着吗」重新分裂。\n */\n openOverlay: OpenOverlay\n /** 本 panel 与 Escape 仲裁的关系。tooltip 传 `none`;缺省 `escape`。 */\n arbitration?: OverlayArbitration\n}\n\n/**\n * 管理锚定 panel 的本地/Portal 容器、定位器和可中断 presence。\n * 内容迁移与交互语义由各组件保留,避免把不同组件的 public contract 混入此模块。\n */\nexport interface AnchoredPanelApi {\n getPanel(): HTMLElement | null\n /**\n * 当前在场会话的 handle;没有任何在场会话时为 `null`。\n * 查询(`contains` / `containsEvent` / `hasFocusWithin`)与 `setInert` 都走它,\n * 调用方因此不必持有 panel 引用,也不知道存在全局注册表。\n * 退场等待期间仍是同一个 handle(暂缓仲裁),动画播完或重新 `open()` 才换人。\n */\n getHandle(): OpenOverlayHandle | null\n open(isInstant?: boolean): void\n close(isStillOpen: () => boolean): Promise<boolean>\n updatePosition(): void\n reconfigure(isOpen: boolean): void\n dispose(): void\n}\n\nexport const defineAnchoredPanel = () =>\n definePlugin<AnchoredPanelApi, AnchoredPanelOptions>(ctx => {\n let overlay: OverlayApi | undefined\n let portal: OverlayPortal | undefined\n let handle: OpenOverlayHandle | null = null\n\n const getPanel = () => portal?.panel ?? ctx.getLocalPanel()\n\n const releaseHandle = () => {\n handle?.release()\n handle = null\n }\n\n const disposePortal = () => {\n if (!portal) return\n portal.restoreContent()\n portal.remove()\n portal = undefined\n overlay = undefined\n }\n\n const getOrCreatePanel = (): HTMLElement | null => {\n if (!ctx.isPortal()) return ctx.getLocalPanel()\n portal ??= ctx.createPortal()\n return portal.panel\n }\n\n const ensureOverlay = (anchor: HTMLElement, panel: HTMLElement) => {\n if (overlay) {\n overlay.updateAnchor(anchor)\n overlay.update(ctx.getPositioning())\n return\n }\n overlay = defineOverlay().make({ anchor, overlay: panel, ...ctx.getPositioning() })\n }\n\n const open = (isInstant = false) => {\n const anchor = ctx.getAnchor()\n if (!anchor) return\n const panel = getOrCreatePanel()\n if (!panel) return\n // portal panel 与宿主物理分离;本地 panel 也可能隔着 shadow 边界。\n // 以 anchor 祖先链寻找最近的已开启 overlay,建立统一逻辑父子关系。\n // claim ⟺ open:重复 open 视为新的一次开启,旧会话整体作废。\n releaseHandle()\n handle = ctx.openOverlay.claim(panel, { arbitration: ctx.arbitration, ancestryFrom: anchor })\n ensureOverlay(anchor, panel)\n overlay?.open()\n showOverlayPresence(panel, { isInstant })\n }\n\n const dispose = () => {\n overlay?.dispose()\n overlay = undefined\n releaseHandle()\n disposePortal()\n }\n\n return {\n getPanel() {\n return getPanel()\n },\n\n getHandle() {\n return handle\n },\n\n open,\n\n async close(isStillOpen) {\n /*\n * 第三态「可见但暂缓仲裁」:退场等待期间 panel 仍在场,登记不撤,但不再走关闭\n * 入口——它已经在关的路上了。撤销推迟到动画结束,或被 `open()` 以新会话取代。\n *\n * 若在这里立即 `releaseHandle()`,两条提前返回的路径都会把还看得见的 panel\n * 留在未登记状态:未登记即不参与「谁是最内层」的仲裁,直到下一次 `open()` 才\n * 恢复(issue #138)。\n *\n * 暂缓层只是兜底候选:仍在开启的外层永远优先。否则「内层关掉后立刻再按一次\n * Escape」会被退场动画吞掉,外层永远等不到自己那一次——issue #120 的契约。\n *\n * 退场被打断时 `showOverlayPresence` 已把 panel 带回场,`open()` 同时重新 claim\n * (与「重复 open 视为新的一次开启」同一条路径),本条 `close()` 随后的\n * `return false` 不碰那个新 handle。\n *\n * 走到 `isStillOpen()` 时退场已经播完:panel 已 `hidden`。此刻宿主仍认为开着,\n * 是 panel 状态与宿主状态不一致的那一格——把层交还仲裁而不是继续暂缓。继续暂缓的\n * 前提是「panel 可见」,而这里 panel 已经看不见了:一个隐藏的 panel 无限吞掉 Escape,\n * 用户再也没有按键路径把这个状态收敛掉(按一次 Escape 走宿主的关闭入口,宿主\n * 回写 open=false 后由随后的 close() 正常收尾)。登记仍然不撤,「宿主认为开着\n * 就不掉出仲裁」的 #138 语义不变。\n */\n handle?.deferArbitration()\n overlay?.close()\n const panel = getPanel()\n if (panel && !(await hideOverlayPresence(panel))) return false\n if (isStillOpen()) {\n handle?.deferArbitration(false)\n return false\n }\n releaseHandle()\n disposePortal()\n return true\n },\n\n updatePosition() {\n overlay?.update(ctx.getPositioning())\n },\n\n reconfigure(isOpen) {\n const shouldAnimate = getPanel()?.dataset.wuiPresence === 'entering'\n dispose()\n if (isOpen) open(!shouldAnimate)\n },\n\n dispose\n }\n })\n"],"mappings":";;;;AA6CA,MAAa,4BACX,cAAqD,QAAO;CAC1D,IAAI;CACJ,IAAI;CACJ,IAAI,SAAmC;CAEvC,MAAM,iBAAiB,QAAQ,SAAS,IAAI,cAAc;CAE1D,MAAM,sBAAsB;EAC1B,QAAQ,QAAQ;EAChB,SAAS;CACX;CAEA,MAAM,sBAAsB;EAC1B,IAAI,CAAC,QAAQ;EACb,OAAO,eAAe;EACtB,OAAO,OAAO;EACd,SAAS,KAAA;EACT,UAAU,KAAA;CACZ;CAEA,MAAM,yBAA6C;EACjD,IAAI,CAAC,IAAI,SAAS,GAAG,OAAO,IAAI,cAAc;EAC9C,WAAW,IAAI,aAAa;EAC5B,OAAO,OAAO;CAChB;CAEA,MAAM,iBAAiB,QAAqB,UAAuB;EACjE,IAAI,SAAS;GACX,QAAQ,aAAa,MAAM;GAC3B,QAAQ,OAAO,IAAI,eAAe,CAAC;GACnC;EACF;EACA,UAAU,cAAc,CAAC,CAAC,KAAK;GAAE;GAAQ,SAAS;GAAO,GAAG,IAAI,eAAe;EAAE,CAAC;CACpF;CAEA,MAAM,QAAQ,YAAY,UAAU;EAClC,MAAM,SAAS,IAAI,UAAU;EAC7B,IAAI,CAAC,QAAQ;EACb,MAAM,QAAQ,iBAAiB;EAC/B,IAAI,CAAC,OAAO;EAIZ,cAAc;EACd,SAAS,IAAI,YAAY,MAAM,OAAO;GAAE,aAAa,IAAI;GAAa,cAAc;EAAO,CAAC;EAC5F,cAAc,QAAQ,KAAK;EAC3B,SAAS,KAAK;EACd,oBAAoB,OAAO,EAAE,UAAU,CAAC;CAC1C;CAEA,MAAM,gBAAgB;EACpB,SAAS,QAAQ;EACjB,UAAU,KAAA;EACV,cAAc;EACd,cAAc;CAChB;CAEA,OAAO;EACL,WAAW;GACT,OAAO,SAAS;EAClB;EAEA,YAAY;GACV,OAAO;EACT;EAEA;EAEA,MAAM,MAAM,aAAa;GAuBvB,QAAQ,iBAAiB;GACzB,SAAS,MAAM;GACf,MAAM,QAAQ,SAAS;GACvB,IAAI,SAAS,CAAE,MAAM,oBAAoB,KAAK,GAAI,OAAO;GACzD,IAAI,YAAY,GAAG;IACjB,QAAQ,iBAAiB,KAAK;IAC9B,OAAO;GACT;GACA,cAAc;GACd,cAAc;GACd,OAAO;EACT;EAEA,iBAAiB;GACf,SAAS,OAAO,IAAI,eAAe,CAAC;EACtC;EAEA,YAAY,QAAQ;GAClB,MAAM,gBAAgB,SAAS,CAAC,EAAE,QAAQ,gBAAgB;GAC1D,QAAQ;GACR,IAAI,QAAQ,KAAK,CAAC,aAAa;EACjC;EAEA;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"anchored-panel.js","names":[],"sources":["../../../src/shared/overlay/anchored-panel.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport type { OpenOverlay, OpenOverlayHandle, OverlayArbitration } from './open-overlay'\nimport { defineOverlay, type OverlayApi, type OverlayOptions } from './overlay'\nimport type { OverlayPortal } from './portal'\nimport { hideOverlayPresence, showOverlayPresence } from './presence'\n\nexport interface AnchoredPanelOptions {\n getAnchor(): HTMLElement | null\n getLocalPanel(): HTMLElement | null\n getPositioning(): OverlayOptions\n isPortal(): boolean\n createPortal(): OverlayPortal\n /**\n * 承载「本 panel 是一次开启态 overlay」的实例:登记与 Escape 归属由它完成,本模块不\n * 自己维护逻辑父子关系。\n *\n * 必须是**调用方持有的那个实例**(而不是这里新建一个)——调用方同时要用它的帧\n * 事务入口,两个实例会让「我开着吗」重新分裂。\n */\n openOverlay: OpenOverlay\n /** 本 panel 与 Escape 仲裁的关系。tooltip 传 `none`;缺省 `escape`。 */\n arbitration?: OverlayArbitration\n}\n\n/**\n * 管理锚定 panel 的本地/Portal 容器、定位器和可中断 presence。\n * 内容迁移与交互语义由各组件保留,避免把不同组件的 public contract 混入此模块。\n */\nexport interface AnchoredPanelApi {\n getPanel(): HTMLElement | null\n /**\n * 当前在场会话的 handle;没有任何在场会话时为 `null`。\n * 查询(`contains` / `containsEvent` / `hasFocusWithin`)与 `setInert` 都走它,\n * 调用方因此不必持有 panel 引用,也不知道存在全局注册表。\n * 退场等待期间仍是同一个 handle(暂缓仲裁),动画播完或重新 `open()` 才换人。\n */\n getHandle(): OpenOverlayHandle | null\n open(isInstant?: boolean): void\n /**\n * 播放退场并收尾。`isInstant` 表示这次没有可退场的内容(例如候选被全部过滤掉),\n * panel 直接落隐藏态;不传则照常播退场过渡。\n */\n close(isStillOpen: () => boolean, isInstant?: boolean): Promise<boolean>\n updatePosition(): void\n reconfigure(isOpen: boolean): void\n dispose(): void\n}\n\nexport const defineAnchoredPanel = () =>\n definePlugin<AnchoredPanelApi, AnchoredPanelOptions>(ctx => {\n let overlay: OverlayApi | undefined\n let portal: OverlayPortal | undefined\n let handle: OpenOverlayHandle | null = null\n\n const getPanel = () => portal?.panel ?? ctx.getLocalPanel()\n\n const releaseHandle = () => {\n handle?.release()\n handle = null\n }\n\n const disposePortal = () => {\n if (!portal) return\n portal.restoreContent()\n portal.remove()\n portal = undefined\n overlay = undefined\n }\n\n const getOrCreatePanel = (): HTMLElement | null => {\n if (!ctx.isPortal()) return ctx.getLocalPanel()\n portal ??= ctx.createPortal()\n return portal.panel\n }\n\n const ensureOverlay = (anchor: HTMLElement, panel: HTMLElement) => {\n if (overlay) {\n overlay.updateAnchor(anchor)\n overlay.update(ctx.getPositioning())\n return\n }\n overlay = defineOverlay().make({ anchor, overlay: panel, ...ctx.getPositioning() })\n }\n\n const open = (isInstant = false) => {\n const anchor = ctx.getAnchor()\n if (!anchor) return\n const panel = getOrCreatePanel()\n if (!panel) return\n // portal panel 与宿主物理分离;本地 panel 也可能隔着 shadow 边界。\n // 以 anchor 祖先链寻找最近的已开启 overlay,建立统一逻辑父子关系。\n // claim ⟺ open:重复 open 视为新的一次开启,旧会话整体作废。\n releaseHandle()\n handle = ctx.openOverlay.claim(panel, { arbitration: ctx.arbitration, ancestryFrom: anchor })\n ensureOverlay(anchor, panel)\n overlay?.open()\n showOverlayPresence(panel, { isInstant })\n }\n\n const dispose = () => {\n overlay?.dispose()\n overlay = undefined\n releaseHandle()\n disposePortal()\n }\n\n return {\n getPanel() {\n return getPanel()\n },\n\n getHandle() {\n return handle\n },\n\n open,\n\n async close(isStillOpen, isInstant = false) {\n /*\n * 第三态「可见但暂缓仲裁」:退场等待期间 panel 仍在场,登记不撤,但不再走关闭\n * 入口——它已经在关的路上了。撤销推迟到动画结束,或被 `open()` 以新会话取代。\n *\n * 若在这里立即 `releaseHandle()`,两条提前返回的路径都会把还看得见的 panel\n * 留在未登记状态:未登记即不参与「谁是最内层」的仲裁,直到下一次 `open()` 才\n * 恢复(issue #138)。\n *\n * 暂缓层只是兜底候选:仍在开启的外层永远优先。否则「内层关掉后立刻再按一次\n * Escape」会被退场动画吞掉,外层永远等不到自己那一次——issue #120 的契约。\n *\n * 退场被打断时 `showOverlayPresence` 已把 panel 带回场,`open()` 同时重新 claim\n * (与「重复 open 视为新的一次开启」同一条路径),本条 `close()` 随后的\n * `return false` 不碰那个新 handle。\n *\n * 走到 `isStillOpen()` 时退场已经播完:panel 已 `hidden`。此刻宿主仍认为开着,\n * 是 panel 状态与宿主状态不一致的那一格——把层交还仲裁而不是继续暂缓。继续暂缓的\n * 前提是「panel 可见」,而这里 panel 已经看不见了:一个隐藏的 panel 无限吞掉 Escape,\n * 用户再也没有按键路径把这个状态收敛掉(按一次 Escape 走宿主的关闭入口,宿主\n * 回写 open=false 后由随后的 close() 正常收尾)。登记仍然不撤,「宿主认为开着\n * 就不掉出仲裁」的 #138 语义不变。\n */\n handle?.deferArbitration()\n overlay?.close()\n const panel = getPanel()\n if (panel && !(await hideOverlayPresence(panel, { isInstant }))) return false\n if (isStillOpen()) {\n handle?.deferArbitration(false)\n return false\n }\n releaseHandle()\n disposePortal()\n return true\n },\n\n updatePosition() {\n overlay?.update(ctx.getPositioning())\n },\n\n reconfigure(isOpen) {\n const shouldAnimate = getPanel()?.dataset.wuiPresence === 'entering'\n dispose()\n if (isOpen) open(!shouldAnimate)\n },\n\n dispose\n }\n })\n"],"mappings":";;;;AAiDA,MAAa,4BACX,cAAqD,QAAO;CAC1D,IAAI;CACJ,IAAI;CACJ,IAAI,SAAmC;CAEvC,MAAM,iBAAiB,QAAQ,SAAS,IAAI,cAAc;CAE1D,MAAM,sBAAsB;EAC1B,QAAQ,QAAQ;EAChB,SAAS;CACX;CAEA,MAAM,sBAAsB;EAC1B,IAAI,CAAC,QAAQ;EACb,OAAO,eAAe;EACtB,OAAO,OAAO;EACd,SAAS,KAAA;EACT,UAAU,KAAA;CACZ;CAEA,MAAM,yBAA6C;EACjD,IAAI,CAAC,IAAI,SAAS,GAAG,OAAO,IAAI,cAAc;EAC9C,WAAW,IAAI,aAAa;EAC5B,OAAO,OAAO;CAChB;CAEA,MAAM,iBAAiB,QAAqB,UAAuB;EACjE,IAAI,SAAS;GACX,QAAQ,aAAa,MAAM;GAC3B,QAAQ,OAAO,IAAI,eAAe,CAAC;GACnC;EACF;EACA,UAAU,cAAc,CAAC,CAAC,KAAK;GAAE;GAAQ,SAAS;GAAO,GAAG,IAAI,eAAe;EAAE,CAAC;CACpF;CAEA,MAAM,QAAQ,YAAY,UAAU;EAClC,MAAM,SAAS,IAAI,UAAU;EAC7B,IAAI,CAAC,QAAQ;EACb,MAAM,QAAQ,iBAAiB;EAC/B,IAAI,CAAC,OAAO;EAIZ,cAAc;EACd,SAAS,IAAI,YAAY,MAAM,OAAO;GAAE,aAAa,IAAI;GAAa,cAAc;EAAO,CAAC;EAC5F,cAAc,QAAQ,KAAK;EAC3B,SAAS,KAAK;EACd,oBAAoB,OAAO,EAAE,UAAU,CAAC;CAC1C;CAEA,MAAM,gBAAgB;EACpB,SAAS,QAAQ;EACjB,UAAU,KAAA;EACV,cAAc;EACd,cAAc;CAChB;CAEA,OAAO;EACL,WAAW;GACT,OAAO,SAAS;EAClB;EAEA,YAAY;GACV,OAAO;EACT;EAEA;EAEA,MAAM,MAAM,aAAa,YAAY,OAAO;GAuB1C,QAAQ,iBAAiB;GACzB,SAAS,MAAM;GACf,MAAM,QAAQ,SAAS;GACvB,IAAI,SAAS,CAAE,MAAM,oBAAoB,OAAO,EAAE,UAAU,CAAC,GAAI,OAAO;GACxE,IAAI,YAAY,GAAG;IACjB,QAAQ,iBAAiB,KAAK;IAC9B,OAAO;GACT;GACA,cAAc;GACd,cAAc;GACd,OAAO;EACT;EAEA,iBAAiB;GACf,SAAS,OAAO,IAAI,eAAe,CAAC;EACtC;EAEA,YAAY,QAAQ;GAClB,MAAM,gBAAgB,SAAS,CAAC,EAAE,QAAQ,gBAAgB;GAC1D,QAAQ;GACR,IAAI,QAAQ,KAAK,CAAC,aAAa;EACjC;EAEA;CACF;AACF,CAAC"}
@@ -22,10 +22,10 @@ function showOverlayPresence(panel, options = {}) {
22
22
  if (panel.dataset.wuiPresence === "entering") panel.dataset.wuiPresence = "open";
23
23
  });
24
24
  }
25
- function hideOverlayPresence(panel) {
25
+ function hideOverlayPresence(panel, options = {}) {
26
26
  pendingExits.get(panel)?.();
27
27
  panel.dataset.wuiPresence = "closing";
28
- if (getTransitionDuration(panel, 0) === 0) {
28
+ if (options.isInstant || getTransitionDuration(panel, 0) === 0) {
29
29
  panel.hidden = true;
30
30
  return Promise.resolve(true);
31
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"presence.js","names":[],"sources":["../../../src/shared/overlay/presence.ts"],"sourcesContent":["import { parseDuration } from '@/shared/theme/duration'\n\nconst DEFAULT_EXIT_DURATION = 160\nconst EXIT_FALLBACK_BUFFER = 80\n\nconst pendingExits = new WeakMap<HTMLElement, () => void>()\n\ninterface OverlayPresenceOptions {\n isInstant?: boolean\n}\n\n// 读取元素实际 CSS transition 的最长时长,供可覆盖主题 token 的 JS 生命周期使用。\nexport function getTransitionDuration(panel: HTMLElement, fallback = DEFAULT_EXIT_DURATION): number {\n const style = getComputedStyle(panel)\n const durations = style.transitionDuration.split(',').map(value => parseDuration(value) ?? 0)\n const delays = style.transitionDelay.split(',').map(value => parseDuration(value) ?? 0)\n const longest = durations.reduce(\n (max, duration, index) => Math.max(max, duration + (delays[index] ?? delays[0] ?? 0)),\n 0\n )\n\n return longest || fallback\n}\n\n// 以可中断的方式显示 overlay,避免快速切换时重启动画。\nexport function showOverlayPresence(panel: HTMLElement, options: OverlayPresenceOptions = {}) {\n pendingExits.get(panel)?.()\n panel.hidden = false\n if (options.isInstant) {\n panel.dataset.wuiPresence = 'open'\n return\n }\n panel.dataset.wuiPresence = 'entering'\n\n // 提交初始状态,让新挂载的 panel 拥有过渡起点。\n void panel.offsetWidth\n\n requestAnimationFrame(() => {\n if (panel.dataset.wuiPresence === 'entering') panel.dataset.wuiPresence = 'open'\n })\n}\n\n// 播放退出过渡;若在结束前重新显示则返回 false。\nexport function hideOverlayPresence(panel: HTMLElement): Promise<boolean> {\n pendingExits.get(panel)?.()\n panel.dataset.wuiPresence = 'closing'\n\n // jsdom 等没有计算 CSS transition 的环境无需伪造退场等待。\n if (getTransitionDuration(panel, 0) === 0) {\n panel.hidden = true\n return Promise.resolve(true)\n }\n\n return new Promise(resolve => {\n let settled = false\n const finish = (didClose: boolean) => {\n if (settled) return\n settled = true\n panel.removeEventListener('transitionend', onTransitionEnd)\n clearTimeout(timeout)\n pendingExits.delete(panel)\n if (didClose && panel.dataset.wuiPresence === 'closing') panel.hidden = true\n resolve(didClose)\n }\n\n const onTransitionEnd = (event: TransitionEvent) => {\n if (event.target === panel && (event.propertyName === 'opacity' || event.propertyName === 'transform'))\n finish(true)\n }\n\n pendingExits.set(panel, () => finish(false))\n panel.addEventListener('transitionend', onTransitionEnd)\n const timeout = setTimeout(() => finish(true), getTransitionDuration(panel) + EXIT_FALLBACK_BUFFER)\n })\n}\n"],"mappings":";;AAEA,MAAM,wBAAwB;AAC9B,MAAM,uBAAuB;AAE7B,MAAM,+BAAe,IAAI,QAAiC;AAO1D,SAAgB,sBAAsB,OAAoB,WAAW,uBAA+B;CAClG,MAAM,QAAQ,iBAAiB,KAAK;CACpC,MAAM,YAAY,MAAM,mBAAmB,MAAM,GAAG,CAAC,CAAC,KAAI,UAAS,cAAc,KAAK,KAAK,CAAC;CAC5F,MAAM,SAAS,MAAM,gBAAgB,MAAM,GAAG,CAAC,CAAC,KAAI,UAAS,cAAc,KAAK,KAAK,CAAC;CAMtF,OALgB,UAAU,QACvB,KAAK,UAAU,UAAU,KAAK,IAAI,KAAK,YAAY,OAAO,UAAU,OAAO,MAAM,EAAE,GACpF,CAGW,KAAK;AACpB;AAGA,SAAgB,oBAAoB,OAAoB,UAAkC,CAAC,GAAG;CAC5F,aAAa,IAAI,KAAK,CAAC,GAAG;CAC1B,MAAM,SAAS;CACf,IAAI,QAAQ,WAAW;EACrB,MAAM,QAAQ,cAAc;EAC5B;CACF;CACA,MAAM,QAAQ,cAAc;CAG5B,MAAW;CAEX,4BAA4B;EAC1B,IAAI,MAAM,QAAQ,gBAAgB,YAAY,MAAM,QAAQ,cAAc;CAC5E,CAAC;AACH;AAGA,SAAgB,oBAAoB,OAAsC;CACxE,aAAa,IAAI,KAAK,CAAC,GAAG;CAC1B,MAAM,QAAQ,cAAc;CAG5B,IAAI,sBAAsB,OAAO,CAAC,MAAM,GAAG;EACzC,MAAM,SAAS;EACf,OAAO,QAAQ,QAAQ,IAAI;CAC7B;CAEA,OAAO,IAAI,SAAQ,YAAW;EAC5B,IAAI,UAAU;EACd,MAAM,UAAU,aAAsB;GACpC,IAAI,SAAS;GACb,UAAU;GACV,MAAM,oBAAoB,iBAAiB,eAAe;GAC1D,aAAa,OAAO;GACpB,aAAa,OAAO,KAAK;GACzB,IAAI,YAAY,MAAM,QAAQ,gBAAgB,WAAW,MAAM,SAAS;GACxE,QAAQ,QAAQ;EAClB;EAEA,MAAM,mBAAmB,UAA2B;GAClD,IAAI,MAAM,WAAW,UAAU,MAAM,iBAAiB,aAAa,MAAM,iBAAiB,cACxF,OAAO,IAAI;EACf;EAEA,aAAa,IAAI,aAAa,OAAO,KAAK,CAAC;EAC3C,MAAM,iBAAiB,iBAAiB,eAAe;EACvD,MAAM,UAAU,iBAAiB,OAAO,IAAI,GAAG,sBAAsB,KAAK,IAAI,oBAAoB;CACpG,CAAC;AACH"}
1
+ {"version":3,"file":"presence.js","names":[],"sources":["../../../src/shared/overlay/presence.ts"],"sourcesContent":["import { parseDuration } from '@/shared/theme/duration'\n\nconst DEFAULT_EXIT_DURATION = 160\nconst EXIT_FALLBACK_BUFFER = 80\n\nconst pendingExits = new WeakMap<HTMLElement, () => void>()\n\ninterface OverlayPresenceOptions {\n isInstant?: boolean\n}\n\n// 读取元素实际 CSS transition 的最长时长,供可覆盖主题 token 的 JS 生命周期使用。\nexport function getTransitionDuration(panel: HTMLElement, fallback = DEFAULT_EXIT_DURATION): number {\n const style = getComputedStyle(panel)\n const durations = style.transitionDuration.split(',').map(value => parseDuration(value) ?? 0)\n const delays = style.transitionDelay.split(',').map(value => parseDuration(value) ?? 0)\n const longest = durations.reduce(\n (max, duration, index) => Math.max(max, duration + (delays[index] ?? delays[0] ?? 0)),\n 0\n )\n\n return longest || fallback\n}\n\n// 以可中断的方式显示 overlay,避免快速切换时重启动画。\nexport function showOverlayPresence(panel: HTMLElement, options: OverlayPresenceOptions = {}) {\n pendingExits.get(panel)?.()\n panel.hidden = false\n if (options.isInstant) {\n panel.dataset.wuiPresence = 'open'\n return\n }\n panel.dataset.wuiPresence = 'entering'\n\n // 提交初始状态,让新挂载的 panel 拥有过渡起点。\n void panel.offsetWidth\n\n requestAnimationFrame(() => {\n if (panel.dataset.wuiPresence === 'entering') panel.dataset.wuiPresence = 'open'\n })\n}\n\n// 播放退出过渡;若在结束前重新显示则返回 false。\n// `isInstant` 由调用方声明「这次没有可退场的内容」:直接落隐藏态,不播过渡。\nexport function hideOverlayPresence(panel: HTMLElement, options: OverlayPresenceOptions = {}): Promise<boolean> {\n pendingExits.get(panel)?.()\n panel.dataset.wuiPresence = 'closing'\n\n // 两条不播过渡的路:调用方要求立即隐藏,以及 jsdom 这类不计算 CSS transition 的环境。\n if (options.isInstant || getTransitionDuration(panel, 0) === 0) {\n panel.hidden = true\n return Promise.resolve(true)\n }\n\n return new Promise(resolve => {\n let settled = false\n const finish = (didClose: boolean) => {\n if (settled) return\n settled = true\n panel.removeEventListener('transitionend', onTransitionEnd)\n clearTimeout(timeout)\n pendingExits.delete(panel)\n if (didClose && panel.dataset.wuiPresence === 'closing') panel.hidden = true\n resolve(didClose)\n }\n\n const onTransitionEnd = (event: TransitionEvent) => {\n if (event.target === panel && (event.propertyName === 'opacity' || event.propertyName === 'transform'))\n finish(true)\n }\n\n pendingExits.set(panel, () => finish(false))\n panel.addEventListener('transitionend', onTransitionEnd)\n const timeout = setTimeout(() => finish(true), getTransitionDuration(panel) + EXIT_FALLBACK_BUFFER)\n })\n}\n"],"mappings":";;AAEA,MAAM,wBAAwB;AAC9B,MAAM,uBAAuB;AAE7B,MAAM,+BAAe,IAAI,QAAiC;AAO1D,SAAgB,sBAAsB,OAAoB,WAAW,uBAA+B;CAClG,MAAM,QAAQ,iBAAiB,KAAK;CACpC,MAAM,YAAY,MAAM,mBAAmB,MAAM,GAAG,CAAC,CAAC,KAAI,UAAS,cAAc,KAAK,KAAK,CAAC;CAC5F,MAAM,SAAS,MAAM,gBAAgB,MAAM,GAAG,CAAC,CAAC,KAAI,UAAS,cAAc,KAAK,KAAK,CAAC;CAMtF,OALgB,UAAU,QACvB,KAAK,UAAU,UAAU,KAAK,IAAI,KAAK,YAAY,OAAO,UAAU,OAAO,MAAM,EAAE,GACpF,CAGW,KAAK;AACpB;AAGA,SAAgB,oBAAoB,OAAoB,UAAkC,CAAC,GAAG;CAC5F,aAAa,IAAI,KAAK,CAAC,GAAG;CAC1B,MAAM,SAAS;CACf,IAAI,QAAQ,WAAW;EACrB,MAAM,QAAQ,cAAc;EAC5B;CACF;CACA,MAAM,QAAQ,cAAc;CAG5B,MAAW;CAEX,4BAA4B;EAC1B,IAAI,MAAM,QAAQ,gBAAgB,YAAY,MAAM,QAAQ,cAAc;CAC5E,CAAC;AACH;AAIA,SAAgB,oBAAoB,OAAoB,UAAkC,CAAC,GAAqB;CAC9G,aAAa,IAAI,KAAK,CAAC,GAAG;CAC1B,MAAM,QAAQ,cAAc;CAG5B,IAAI,QAAQ,aAAa,sBAAsB,OAAO,CAAC,MAAM,GAAG;EAC9D,MAAM,SAAS;EACf,OAAO,QAAQ,QAAQ,IAAI;CAC7B;CAEA,OAAO,IAAI,SAAQ,YAAW;EAC5B,IAAI,UAAU;EACd,MAAM,UAAU,aAAsB;GACpC,IAAI,SAAS;GACb,UAAU;GACV,MAAM,oBAAoB,iBAAiB,eAAe;GAC1D,aAAa,OAAO;GACpB,aAAa,OAAO,KAAK;GACzB,IAAI,YAAY,MAAM,QAAQ,gBAAgB,WAAW,MAAM,SAAS;GACxE,QAAQ,QAAQ;EAClB;EAEA,MAAM,mBAAmB,UAA2B;GAClD,IAAI,MAAM,WAAW,UAAU,MAAM,iBAAiB,aAAa,MAAM,iBAAiB,cACxF,OAAO,IAAI;EACf;EAEA,aAAa,IAAI,aAAa,OAAO,KAAK,CAAC;EAC3C,MAAM,iBAAiB,iBAAiB,eAAe;EACvD,MAAM,UAAU,iBAAiB,OAAO,IAAI,GAAG,sBAAsB,KAAK,IAAI,oBAAoB;CACpG,CAAC;AACH"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * tooltip / popover 共享的触发方式词表。
3
+ *
4
+ * `trigger` 是空格分隔的集合,不是单值枚举:「悬停和键盘聚焦都能开」是真实需求,
5
+ * 用单值表达同样的空间要拆成十几种字面量。这里只负责把取值规范化,
6
+ * 每种手势接哪些事件由组件自己决定。
7
+ */
8
+ export declare const OVERLAY_TRIGGER_MODES: readonly ["click", "hover", "focus", "manual"];
9
+ export type OverlayTriggerMode = (typeof OVERLAY_TRIGGER_MODES)[number];
10
+ //# sourceMappingURL=trigger-props.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trigger-props.d.ts","names":[],"sources":["../../../src/shared/overlay/trigger-props.ts"],"mappings":";;;;;;;qBAQa;YAED,6BAA6B"}
@@ -0,0 +1,42 @@
1
+ // src/shared/overlay/trigger-props.ts
2
+ /**
3
+ * tooltip / popover 共享的触发方式词表。
4
+ *
5
+ * `trigger` 是空格分隔的集合,不是单值枚举:「悬停和键盘聚焦都能开」是真实需求,
6
+ * 用单值表达同样的空间要拆成十几种字面量。这里只负责把取值规范化,
7
+ * 每种手势接哪些事件由组件自己决定。
8
+ */
9
+ const OVERLAY_TRIGGER_MODES = [
10
+ "click",
11
+ "hover",
12
+ "focus",
13
+ "manual"
14
+ ];
15
+ /**
16
+ * 规范化为空格分隔的规范串:未知 token 丢弃、重复去除、按声明顺序输出。
17
+ *
18
+ * 出现 `manual` 时整串只保留 `manual`:它的语义是「不接受任何自动开合」,
19
+ * 与并存的手势矛盾,取严的一侧。空集合回退到 `fallback`,所以 `trigger=""`
20
+ * 读作「没写」而不是「没有手势」——要没有手势写 `manual`。
21
+ */
22
+ function normalizeTriggerSet(value, fallback) {
23
+ const requested = new Set(readTokens(value));
24
+ const modes = requested.has("manual") ? ["manual"] : OVERLAY_TRIGGER_MODES.filter((mode) => requested.has(mode));
25
+ return (modes.length > 0 ? modes : fallback).join(" ");
26
+ }
27
+ /** 规范串里是否含某个 mode。 */
28
+ function hasTriggerMode(trigger, mode) {
29
+ return trigger.split(/\s+/u).includes(mode);
30
+ }
31
+ /**
32
+ * 数组走 property 通道(`:trigger="['hover', 'focus']"`),字符串走 attribute;
33
+ * 其余取值一律当作没写。
34
+ */
35
+ function readTokens(value) {
36
+ if (typeof value === "string") return value.split(/\s+/u).filter(Boolean);
37
+ if (Array.isArray(value)) return value.flatMap((item) => readTokens(item));
38
+ return [];
39
+ }
40
+ export { OVERLAY_TRIGGER_MODES, hasTriggerMode, normalizeTriggerSet };
41
+
42
+ //# sourceMappingURL=trigger-props.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trigger-props.js","names":[],"sources":["../../../src/shared/overlay/trigger-props.ts"],"sourcesContent":["/**\n * tooltip / popover 共享的触发方式词表。\n *\n * `trigger` 是空格分隔的集合,不是单值枚举:「悬停和键盘聚焦都能开」是真实需求,\n * 用单值表达同样的空间要拆成十几种字面量。这里只负责把取值规范化,\n * 每种手势接哪些事件由组件自己决定。\n */\n\nexport const OVERLAY_TRIGGER_MODES = ['click', 'hover', 'focus', 'manual'] as const\n\nexport type OverlayTriggerMode = (typeof OVERLAY_TRIGGER_MODES)[number]\n\n/**\n * 规范化为空格分隔的规范串:未知 token 丢弃、重复去除、按声明顺序输出。\n *\n * 出现 `manual` 时整串只保留 `manual`:它的语义是「不接受任何自动开合」,\n * 与并存的手势矛盾,取严的一侧。空集合回退到 `fallback`,所以 `trigger=\"\"`\n * 读作「没写」而不是「没有手势」——要没有手势写 `manual`。\n */\nexport function normalizeTriggerSet(value: unknown, fallback: readonly OverlayTriggerMode[]): string {\n const requested = new Set(readTokens(value))\n const modes: readonly OverlayTriggerMode[] = requested.has('manual')\n ? ['manual']\n : OVERLAY_TRIGGER_MODES.filter(mode => requested.has(mode))\n return (modes.length > 0 ? modes : fallback).join(' ')\n}\n\n/** 规范串里是否含某个 mode。 */\nexport function hasTriggerMode(trigger: string, mode: OverlayTriggerMode): boolean {\n return trigger.split(/\\s+/u).includes(mode)\n}\n\n/**\n * 数组走 property 通道(`:trigger=\"['hover', 'focus']\"`),字符串走 attribute;\n * 其余取值一律当作没写。\n */\nfunction readTokens(value: unknown): string[] {\n if (typeof value === 'string') return value.split(/\\s+/u).filter(Boolean)\n if (Array.isArray(value)) return value.flatMap(item => readTokens(item))\n return []\n}\n"],"mappings":";;;;;;;;AAQA,MAAa,wBAAwB;CAAC;CAAS;CAAS;CAAS;AAAQ;;;;;;;;AAWzE,SAAgB,oBAAoB,OAAgB,UAAiD;CACnG,MAAM,YAAY,IAAI,IAAI,WAAW,KAAK,CAAC;CAC3C,MAAM,QAAuC,UAAU,IAAI,QAAQ,IAC/D,CAAC,QAAQ,IACT,sBAAsB,QAAO,SAAQ,UAAU,IAAI,IAAI,CAAC;CAC5D,QAAQ,MAAM,SAAS,IAAI,QAAQ,SAAA,CAAU,KAAK,GAAG;AACvD;;AAGA,SAAgB,eAAe,SAAiB,MAAmC;CACjF,OAAO,QAAQ,MAAM,MAAM,CAAC,CAAC,SAAS,IAAI;AAC5C;;;;;AAMA,SAAS,WAAW,OAA0B;CAC5C,IAAI,OAAO,UAAU,UAAU,OAAO,MAAM,MAAM,MAAM,CAAC,CAAC,OAAO,OAAO;CACxE,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,SAAQ,SAAQ,WAAW,IAAI,CAAC;CACvE,OAAO,CAAC;AACV"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greypan/web-ui",
3
- "version": "12.1.0",
3
+ "version": "12.2.0",
4
4
  "description": "Lit-based web components for React, Vue, and vanilla JS",
5
5
  "license": "MIT",
6
6
  "repository": {