@greypan/web-ui 6.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.
- package/README.CN.md +89 -26
- package/README.md +80 -30
- package/dist/assets/glass.js +1 -1
- package/dist/assets/glass.js.map +1 -1
- package/dist/assets/menu-portal.js +1 -1
- package/dist/assets/menu-portal.js.map +1 -1
- package/dist/components/autocomplete/index.d.ts +367 -4
- package/dist/components/autocomplete/index.js +83 -77
- package/dist/components/autocomplete/index.js.map +1 -1
- package/dist/components/autocomplete/style.js +1 -1
- package/dist/components/autocomplete/style.js.map +1 -1
- package/dist/components/avatar/style.js +1 -1
- package/dist/components/avatar/style.js.map +1 -1
- package/dist/components/back-top/style.js +1 -1
- package/dist/components/back-top/style.js.map +1 -1
- package/dist/components/badge/style.js +1 -1
- package/dist/components/badge/style.js.map +1 -1
- package/dist/components/button/style.js +1 -1
- package/dist/components/button/style.js.map +1 -1
- package/dist/components/button-group/style.js +1 -1
- package/dist/components/button-group/style.js.map +1 -1
- package/dist/components/checkbox/index.d.ts +366 -5
- package/dist/components/checkbox/index.js +19 -31
- package/dist/components/checkbox/index.js.map +1 -1
- package/dist/components/checkbox/style.js +1 -1
- package/dist/components/checkbox/style.js.map +1 -1
- package/dist/components/checkbox-group/index.d.ts +366 -4
- package/dist/components/checkbox-group/index.js +16 -25
- package/dist/components/checkbox-group/index.js.map +1 -1
- package/dist/components/collapse/index.js +24 -27
- package/dist/components/collapse/index.js.map +1 -1
- package/dist/components/context-menu/index.d.ts +6 -14
- package/dist/components/context-menu/index.js +88 -143
- package/dist/components/context-menu/index.js.map +1 -1
- package/dist/components/dialog/index.js +20 -23
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/dialog/style.js +1 -1
- package/dist/components/dialog/style.js.map +1 -1
- package/dist/components/drawer/index.js +54 -57
- package/dist/components/drawer/index.js.map +1 -1
- package/dist/components/drawer/style.js +1 -1
- package/dist/components/drawer/style.js.map +1 -1
- package/dist/components/dropdown/index.d.ts +5 -14
- package/dist/components/dropdown/index.js +94 -155
- package/dist/components/dropdown/index.js.map +1 -1
- package/dist/components/dropdown-header/style.js +1 -1
- package/dist/components/dropdown-header/style.js.map +1 -1
- package/dist/components/dropdown-item/style.js +1 -1
- package/dist/components/dropdown-item/style.js.map +1 -1
- package/dist/components/empty/style.js +1 -1
- package/dist/components/empty/style.js.map +1 -1
- package/dist/components/input/index.d.ts +366 -5
- package/dist/components/input/index.js +35 -47
- package/dist/components/input/index.js.map +1 -1
- package/dist/components/input/style.js +1 -1
- package/dist/components/input/style.js.map +1 -1
- package/dist/components/input-number/index.d.ts +366 -5
- package/dist/components/input-number/index.js +31 -44
- package/dist/components/input-number/index.js.map +1 -1
- package/dist/components/input-number/style.js +1 -1
- package/dist/components/input-number/style.js.map +1 -1
- package/dist/components/layout/style.js +1 -1
- package/dist/components/layout/style.js.map +1 -1
- package/dist/components/option/style.js +1 -1
- package/dist/components/option/style.js.map +1 -1
- package/dist/components/popover/index.js +27 -46
- package/dist/components/popover/index.js.map +1 -1
- package/dist/components/popover/style.js +1 -1
- package/dist/components/popover/style.js.map +1 -1
- package/dist/components/radio/index.d.ts +366 -5
- package/dist/components/radio/index.js +19 -31
- package/dist/components/radio/index.js.map +1 -1
- package/dist/components/radio/style.js +1 -1
- package/dist/components/radio/style.js.map +1 -1
- package/dist/components/radio-group/index.d.ts +366 -4
- package/dist/components/radio-group/index.js +16 -25
- package/dist/components/radio-group/index.js.map +1 -1
- package/dist/components/segmented/index.d.ts +366 -4
- package/dist/components/segmented/index.js +26 -35
- package/dist/components/segmented/index.js.map +1 -1
- package/dist/components/segmented/style.js +1 -1
- package/dist/components/segmented/style.js.map +1 -1
- package/dist/components/segmented-trigger/style.js +1 -1
- package/dist/components/segmented-trigger/style.js.map +1 -1
- package/dist/components/select/index.d.ts +368 -5
- package/dist/components/select/index.js +64 -66
- package/dist/components/select/index.js.map +1 -1
- package/dist/components/select/style.js +1 -1
- package/dist/components/select/style.js.map +1 -1
- package/dist/components/slider/index.d.ts +366 -5
- package/dist/components/slider/index.js +38 -50
- package/dist/components/slider/index.js.map +1 -1
- package/dist/components/slider/style.js +1 -1
- package/dist/components/slider/style.js.map +1 -1
- package/dist/components/spinner/style.js +1 -1
- package/dist/components/spinner/style.js.map +1 -1
- package/dist/components/switch/index.d.ts +366 -5
- package/dist/components/switch/index.js +26 -38
- package/dist/components/switch/index.js.map +1 -1
- package/dist/components/switch/style.js +1 -1
- package/dist/components/switch/style.js.map +1 -1
- package/dist/components/textarea/index.d.ts +366 -5
- package/dist/components/textarea/index.js +43 -57
- package/dist/components/textarea/index.js.map +1 -1
- package/dist/components/textarea/style.js +1 -1
- package/dist/components/textarea/style.js.map +1 -1
- package/dist/components/theme/style.js +1 -1
- package/dist/components/theme/style.js.map +1 -1
- package/dist/components/toast/style.js +1 -1
- package/dist/components/toast/style.js.map +1 -1
- package/dist/components/tooltip/index.js +29 -48
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/components/tooltip/style.js +1 -1
- package/dist/components/tooltip/style.js.map +1 -1
- package/dist/shared/form-association/index.js +31 -4
- package/dist/shared/form-association/index.js.map +1 -1
- package/dist/shared/menu-behavior/index.js +112 -0
- package/dist/shared/menu-behavior/index.js.map +1 -0
- package/dist/shared/open-state/index.js +12 -0
- package/dist/shared/open-state/index.js.map +1 -0
- package/dist/shared/option-portal/combobox-shell.js +41 -0
- package/dist/shared/option-portal/combobox-shell.js.map +1 -0
- package/dist/shared/option-portal/index.js +22 -21
- package/dist/shared/option-portal/index.js.map +1 -1
- package/dist/shared/overlay/placement-props.js +19 -0
- package/dist/shared/overlay/placement-props.js.map +1 -0
- package/dist/shared/overlay/portal.js +4 -2
- package/dist/shared/overlay/portal.js.map +1 -1
- package/package.json +3 -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 _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 onContentChange: mutations => {\n // Vue 等框架在打开期物理删除已迁移节点:从追踪列表摘除,否则关闭恢复时\n // 会把已删除节点复活回宿主 light DOM。面板内部的移动(重排 insertBefore)\n // 在同一 mutation record 中同时出现在 removed/added,不属于删除,须排除。\n const addedNodes = new Set(mutations.flatMap(mutation => [...mutation.addedNodes]))\n const removedNodes = mutations\n .flatMap(mutation => [...mutation.removedNodes])\n .filter(node => !addedNodes.has(node))\n if (removedNodes.length) this._portal?.removeContent(removedNodes)\n },\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 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":";;;;;;;;;;;AAcA,IAAM,IAAqB;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,GAEI,IAAsB,GAGb,IAAN,cAA2B,EAAW;;EAiJf,aArII,KAAA,aAAA,OAWX,KAAA,aAAA,KAWA,KAAA,aAAA,KAEkC,KAAA,WAAA,IAWrC,KAAA,UAAA,GAEoB,KAAA,UAAA,IAEa,KAAA,OAAA,IACE,KAAA,SAAA,IAKzB,KAAA,oBAAA,IACO,KAAA,kBAAA,IAAI,EAAqB,GAC7B,KAAA,uBAAA,IAEL,KAAA,SAAA,EAAoB,CAAC,CAAC,KAAK;GACnD,iBAAiB,KAAK,YAAY,cAA2B,kBAAkB,KAAK;GACpF,qBAAqB,KAAK,YAAY,cAA2B,gBAAgB,KAAK;GACtF,uBAAuB;IACrB,WAAW,KAAK;IAChB,QAAQ,KAAK;IACb,UAAU,KAAK,SAAS,UAAU;GACpC;GACA,gBAAgB,KAAK;GACrB,oBAAoB,KAAK,cAAc;EACzC,CAAC,GAoD0B,KAAA,mBAAA,MAAoB;GACzC,EAAE,gBAAgB,YAClB,KAAK,aACT,aAAa,KAAK,UAAU,GAC5B,KAAK,aAAa,iBAAiB,KAAK,MAAM,GAAG,IAAsB,IAAI,IAAI,KAAK,SAAS;EAC/F,GAE2B,KAAA,mBAAA,MAAoB;GACzC,EAAE,gBAAgB,YAClB,KAAK,aACT,aAAa,KAAK,UAAU,GAC5B,KAAK,aAAa,iBAAiB,KAAK,MAAM,GAAG,KAAK,SAAS;EACjE,GAEsB,KAAA,cAAA,MAAsB;GACtC,KAAK,YACL,EAAE,EAAM,kBAAkB,YAAY,CAAC,EAAM,OAAO,QAAQ,gBAAgB,MAEhF,aAAa,KAAK,UAAU,GAC5B,KAAK,MAAM,EAAI;EACjB,GAE4B,KAAA,oBAAA;GACtB,KAAK,aACT,aAAa,KAAK,UAAU,GAC5B,KAAK,MAAM;EACb;;;EApJyB,KAAA,SAAA;GAAC,EAAU,CAAK;GAAG,EAAU,CAAa;GAAG,EAAU,CAAK;EAAC;;CAEtF,IACI,YAAuB;EACzB,OAAO,KAAK;CACd;CACA,IAAI,UAAU,GAAW;EACvB,IAAM,IAAM,KAAK;EAEjB,AADA,KAAK,aAAa,EAAiB,GAAG,GAAoB,KAAK,GAC/D,KAAK,cAAc,aAAa,CAAG;CACrC;CAGA,IACI,YAAoB;EACtB,OAAO,KAAK;CACd;CACA,IAAI,UAAU,GAAW;EACvB,IAAM,IAAM,KAAK;EAEjB,AADA,KAAK,aAAa,EAAgB,GAAG,GAAG,KAAM,GAAG,GACjD,KAAK,cAAc,aAAa,CAAG;CACrC;CAGA,IACI,YAAoB;EACtB,OAAO,KAAK;CACd;CACA,IAAI,UAAU,GAAW;EACvB,IAAM,IAAM,KAAK;EAEjB,AADA,KAAK,aAAa,EAAgB,GAAG,GAAG,KAAM,GAAG,GACjD,KAAK,cAAc,aAAa,CAAG;CACrC;CAKA,IACI,SAAiB;EACnB,OAAO,KAAK;CACd;CACA,IAAI,OAAO,GAAW;EACpB,IAAM,IAAM,KAAK;EAEjB,AADA,KAAK,UAAU,EAAgB,GAAG,GAAG,KAAK,CAAC,GAC3C,KAAK,cAAc,UAAU,CAAG;CAClC;CA2BA,IAAI,SAAkB;EACpB,OAAO,KAAK;CACd;CAEA,oBAA6B;EAK3B,AAJA,MAAM,kBAAkB,GACxB,KAAK,iBAAiB,gBAAgB,KAAK,eAAe,GAC1D,KAAK,iBAAiB,gBAAgB,KAAK,eAAe,GAC1D,KAAK,iBAAiB,WAAW,KAAK,UAAU,GAChD,KAAK,iBAAiB,YAAY,KAAK,WAAW;CACpD;CAEA,uBAAgC;EAS9B,AARA,MAAM,qBAAqB,GAC3B,KAAK,oBAAoB,gBAAgB,KAAK,eAAe,GAC7D,KAAK,oBAAoB,gBAAgB,KAAK,eAAe,GAC7D,KAAK,oBAAoB,WAAW,KAAK,UAAU,GACnD,KAAK,oBAAoB,YAAY,KAAK,WAAW,GACrD,aAAa,KAAK,UAAU,GAC5B,aAAa,KAAK,UAAU,GAC5B,KAAK,yBAAyB,EAAK,GACnC,KAAK,OAAO,QAAQ;CACtB;CAEA,eAAwB;EACtB,AAAI,KAAK,SACP,KAAK,aAAa,KAAK,oBAAoB,GAC3C,KAAK,uBAAuB;CAEhC;CAEA,QAA2B,GAA+B;EAgBxD,AAfI,EAAQ,IAAI,QAAQ,KAAK,EAAQ,IAAI,kBAAkB,IACzD,4BAA4B,KAAK,oBAAoB,CAAC,KAC7C,EAAQ,IAAI,WAAW,KAAK,EAAQ,IAAI,QAAQ,MACzD,4BAA4B,KAAK,OAAO,eAAe,CAAC,GAEtD,EAAQ,IAAI,MAAM,MACpB,KAAK,yBAAyB,KAAK,IAAI,GACnC,KAAK,QACP,KAAK,aAAa,KAAK,oBAAoB,GAC3C,KAAK,uBAAuB,MAE5B,KAAU,cAAc,GAEtB,KAAK,gBAAgB,QAAQ,KAAG,KAAK,gBAAgB,KAAK,IAAI,IAEhE,EAAQ,IAAI,SAAS,KAAG,KAAK,mBAAmB;CACtD;CA8BA,MAAc,IAAY,IAAO;EAC3B,AAGJ,KAAK,UAFL,KAAK,uBAAuB,GAC5B,KAAK,gBAAgB,KAAK,GACd;CACd;CAEA,QAAgB;EACT,AAEL,KAAK,UADL,KAAK,gBAAgB,KAAK,GACd;CACd;CAEA,aAAqB,IAAY,IAAO;EACtC,KAAK,OAAO,KAAK,CAAS;CAC5B;CAEA,gBAAuC;EACrC,IAAM,IAAS,EAAoB,CAAC,CAAC,KAAK;GACxC,WAAW,KAAK;GAChB,QAAQ;GACR,OAAO,GAAG,EAAM,IAAI,EAAc,IAAI;GACtC,WAAW;GACX,kBAAiB,MAAa;IAI5B,IAAM,IAAa,IAAI,IAAI,EAAU,SAAQ,MAAY,CAAC,GAAG,EAAS,UAAU,CAAC,CAAC,GAC5E,IAAe,EAClB,SAAQ,MAAY,CAAC,GAAG,EAAS,YAAY,CAAC,CAAC,CAC/C,QAAO,MAAQ,CAAC,EAAW,IAAI,CAAI,CAAC;IACvC,AAAI,EAAa,UAAQ,KAAK,SAAS,cAAc,CAAY;GACnE;GAGA,oBAAmB,MACjB,EAAW,QAAO,MAAQ,aAAgB,WAAW,EAAK,aAAa,MAAM,MAAM,SAAS;EAChG,CAAC;EAED,IADA,KAAK,UAAU,GACX,KAAK,SAAS;GAChB,IAAM,IAAO,SAAS,cAAc,MAAM;GAG1C,AAFA,EAAK,YAAY,gBACjB,EAAK,cAAc,KAAK,SACxB,EAAO,MAAM,OAAO,CAAI;EAC1B,OAAO,EAAO,YAAY,MAAM,KAAK,KAAK,iBAAiB,oBAAkB,CAAC,CAAC;EAE/E,OADA,EAAO,MAAM,aAAa,QAAQ,SAAS,GACpC;CACT;CAEA,MAAc,gBAAgB;EAC5B,MAAM,KAAK,OAAO,YAAY,KAAK,IAAI;CACzC;CAEA,sBAA8B;EAC5B,KAAK,OAAO,YAAY,KAAK,IAAI;CACnC;CAEA,qBAA6B;EAC3B,IAAI,CAAC,KAAK,UAAU,CAAC,KAAK,MAAM;EAChC,IAAM,IAAQ,KAAK,OAAO,SAAS;EACnC,IAAI,CAAC,GAAO;EACZ,IAAM,IAAO,EAAM,cAA2B,eAAe;EAC7D,IAAI,GAAM;GACR,EAAK,cAAc,KAAK;GACxB;EACF;EACA,IAAI,CAAC,KAAK,SAAS;EACnB,EAAM,gBAAgB;EACtB,IAAM,IAAW,SAAS,cAAc,MAAM;EAG9C,AAFA,EAAS,YAAY,gBACrB,EAAS,cAAc,KAAK,SAC5B,EAAM,OAAO,CAAQ;CACvB;CAEA,gBAAwB,GAAe;EACrC,KAAK,cACH,IAAI,YAAY,eAAe;GAC7B,QAAQ,EAAE,QAAK;GACf,SAAS;GACT,UAAU;EACZ,CAAC,CACH;CACF;CAEA,yBAAiC,GAAoB;EACnD,AAAI,KAAa,CAAC,KAAK,qBACrB,KAAuB,GACvB,KAAK,oBAAoB,MAChB,CAAC,KAAa,KAAK,sBAC5B,IAAsB,KAAK,IAAI,GAAG,IAAsB,CAAC,GACzD,KAAK,oBAAoB;CAE7B;CAEA,SAAkB;EAChB,OAAO,CAAI;;;;YAIH,KAAK,UAAU,CAAI,8BAA8B,KAAK,QAAQ,WAAW,CAAI,+BAA+B;;;;CAItH;AAKF;AAhQG,EAAA,CAAA,EAAS;CAAE,MAAM;CAAQ,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,aAAA,IAAA,GAWxC,EAAA,CAAA,EAAS;CAAE,MAAM;CAAQ,WAAW;AAAa,CAAC,CAAA,GAAA,EAAA,WAAA,aAAA,IAAA,GAWlD,EAAA,CAAA,EAAS;CAAE,MAAM;CAAQ,WAAW;AAAa,CAAC,CAAA,GAAA,EAAA,WAAA,aAAA,IAAA,GAWlD,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,YAAA,KAAA,CAAA,GAEzC,EAAA,CAAA,EAAS,EAAE,MAAM,OAAO,CAAC,CAAA,GAAA,EAAA,WAAA,UAAA,IAAA,GAWzB,EAAA,CAAA,EAAS,EAAE,MAAM,OAAO,CAAC,CAAA,GAAA,EAAA,WAAA,WAAA,KAAA,CAAA,GAEzB,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,QAAA,KAAA,CAAA,GACzC,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,UAAA,KAAA,CAAA,GACzC,EAAA,CAAA,EAAS,EAAE,WAAW,GAAM,CAAC,CAAA,GAAA,EAAA,WAAA,oBAAA,KAAA,CAAA,GAtD/B,IAAA,EAAA,CAAA,EAAc,gBAAgB,CAAA,GAAA,CAAA"}
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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 {
|
|
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/**
|
|
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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"}
|
|
@@ -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 {
|
|
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
|
|
4
|
-
let
|
|
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 ===
|
|
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
|
|
15
|
-
return
|
|
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(
|
|
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
|
|
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 =
|
|
30
|
-
if (!a ||
|
|
30
|
+
let a = n.get(t);
|
|
31
|
+
if (!a || i.has(a)) {
|
|
31
32
|
do
|
|
32
|
-
a = `${e.idPrefix}-option-${++
|
|
33
|
-
while (
|
|
34
|
-
|
|
33
|
+
a = `${e.idPrefix}-option-${++r}`;
|
|
34
|
+
while (i.has(a));
|
|
35
|
+
n.set(t, a);
|
|
35
36
|
}
|
|
36
|
-
t.id = a,
|
|
37
|
+
t.id = a, i.add(a);
|
|
37
38
|
});
|
|
38
39
|
},
|
|
39
40
|
syncPortalContent() {
|
|
40
|
-
let
|
|
41
|
-
if (!
|
|
42
|
-
for (let i of
|
|
43
|
-
if (
|
|
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;
|
|
44
45
|
let i = e.getMigratableNodes().filter((e) => !(e instanceof Comment));
|
|
45
|
-
i.length &&
|
|
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),
|
|
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 {
|
|
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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/option-portal/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport type { WebUiOption } from '@/components/option'\nimport type { OverlayPortal } from '@/shared/overlay/portal'\n\n/**\n * select / autocomplete 共用的 portal option 域:宿主侧 option 注册表、\n * portal 内容同步(打开期迁移 + 真删除清理)与微任务调度。\n *\n * 交互语义与 Lit 响应式由各组件保留(同 anchored-panel 的边界约定):\n * 组件通过 host 回调注入差异点(迁移节点来源、打开期即时同步等),\n * 并持有 _options/_activeIndex 的 @state,shared 只经返回值同步数据。\n */\n\nexport interface OptionPortalHost {\n /** 承载宿主级监听的元素(select / autocomplete 宿主自身)。 */\n readonly element: HTMLElement\n /** option id 前缀;宿主应保证跨实例唯一(如含实例序号),供 aria 引用与样式定位。 */\n readonly idPrefix: string\n getPortal(): OverlayPortal | undefined\n getPortalContent(): HTMLElement | undefined\n /** 打开判定:syncPortalContent 仅在打开期执行内容迁移。 */\n isOpen(): boolean\n /**\n * 打开期待迁移的节点来源。两组件粒度不同(select 迁裸 option,\n * autocomplete 迁全部 childNodes),保持各自现状不在此统一。\n */\n getMigratableNodes(): Node[]\n /** 微任务回调中判断宿主是否已完成首次渲染(Lit hasUpdated)。 */\n hasUpdated(): boolean\n /** 微任务末的宿主刷新(requestUpdate)。 */\n requestUpdate(): void\n /**\n * 打开期即时同步钩子。select 在微任务内立即刷新标签与激活态\n * (portal 打开时 willUpdate 查不到面板外的新增项),autocomplete 无此需要。\n */\n onImmediateSync?(): void\n /**\n * 组件侧的逐元素交互监听(click/pointerover/pointerdown/option-update)。\n * shared 负责随 refresh 配对调用,语义由组件定义。\n */\n bindOption(option: WebUiOption): void\n unbindOption(option: WebUiOption): void\n}\n\nexport interface OptionPortalApi {\n /** 面板优先查询,面板无 option 或关闭时回退 light DOM。 */\n queryOptions(): WebUiOption[]\n /**\n * diff 出旧集合解绑、新集合绑定;按 option 身份保持激活索引。\n * 返回新注册表数组与新的激活索引(-1 表示激活项已失效,如移除/迁移)。\n */\n refresh(\n previous: readonly WebUiOption[],\n activeOption: WebUiOption | undefined\n ): {\n options: WebUiOption[]\n activeIndex: number\n }\n /** 稳定 id 分配(WeakMap 身份 + 冲突循环),跨 remove/reorder 保持不变。 */\n ensureOptionIds(options: readonly WebUiOption[]): void\n /** pending 真删除清理 + 打开期把滞留 light DOM 的节点迁入面板。 */\n syncPortalContent(): void\n /** register/unregister/contentChange 的统一微任务收敛入口(slot 监听由组件模板持有)。 */\n scheduleRefresh(): void\n /** 宿主级与逐元素 unregister 监听共用的处理器。 */\n handleOptionUnregister(event: Event): void\n /** 宿主级监听配对(connectedCallback 调用,与 dispose 配对)。 */\n bindHost(): void\n /** 断连清理:解绑宿主监听并清空 pending 集合。 */\n dispose(): void\n}\n\nconst OPTION_TAG = 'web-ui-option'\n\nexport const defineOptionPortal = () =>\n definePlugin<OptionPortalApi, OptionPortalHost>(host => {\n const pendingUnregistered = new Set<WebUiOption>()\n // 稳定 id:WeakMap 按 option 身份记忆分配结果,remove/reorder 后 id 不变;\n // aria-activedescendant / 外部样式都依赖 id 的时序稳定性。\n const optionIds = new WeakMap<WebUiOption, string>()\n let nextOptionId = 0\n\n const bindOption = (option: WebUiOption) => {\n host.bindOption(option)\n option.addEventListener('option-unregister', handleOptionUnregister)\n }\n\n const unbindOption = (option: WebUiOption) => {\n host.unbindOption(option)\n option.removeEventListener('option-unregister', handleOptionUnregister)\n }\n\n const handleOptionUnregister = (event: Event) => {\n // Portal 迁移与真实删除都会触发 unregister;先记录候选,\n // 微任务末 syncPortalContent 按最终位置区分二者。\n if (event.target instanceof HTMLElement && event.target.localName === OPTION_TAG) {\n pendingUnregistered.add(event.target as WebUiOption)\n }\n api.scheduleRefresh()\n }\n\n const handleRegister = () => {\n api.scheduleRefresh()\n }\n\n const api: OptionPortalApi = {\n queryOptions() {\n const panel = host.getPortal()?.panel ?? host.getPortalContent()\n const panelOptions = panel ? [...panel.querySelectorAll<WebUiOption>(OPTION_TAG)] : []\n // 面板内容仅在浮层打开、moveContent 之后存在;关闭时回退 light DOM,\n // 否则 portal 组件首次加载时注册表为空。\n return panelOptions.length > 0 ? panelOptions : [...host.element.querySelectorAll<WebUiOption>(OPTION_TAG)]\n },\n\n refresh(previous, activeOption) {\n const nextOptions = api.queryOptions()\n\n // register/unregister 是 composed 事件:Portal 迁移时 target 会 retarget 成宿主,\n // 不能作为监听器绑定依据;绑定只发生在按 DOM 查询确认身份的 option 上。\n for (const option of previous) if (!nextOptions.includes(option)) unbindOption(option)\n nextOptions.forEach(bindOption)\n\n // 激活索引按 option 身份保持,避免移除中间项后静默偏移到相邻项\n return {\n options: nextOptions,\n activeIndex: activeOption && nextOptions.includes(activeOption) ? nextOptions.indexOf(activeOption) : -1\n }\n },\n\n ensureOptionIds(registryOptions) {\n const usedIds = new Set(registryOptions.map(option => option.id).filter((id): id is string => Boolean(id)))\n registryOptions.forEach(option => {\n if (option.id) return\n\n let id = optionIds.get(option)\n if (!id || usedIds.has(id)) {\n do {\n id = `${host.idPrefix}-option-${++nextOptionId}`\n } while (usedIds.has(id))\n optionIds.set(option, id)\n }\n option.id = id\n usedIds.add(id)\n })\n },\n\n syncPortalContent() {\n const portal = host.getPortal()\n const portalContent = host.getPortalContent()\n if (!portal || !portalContent) return\n\n // 真删除(既不在面板也不在 light DOM)的 option 从 portal 追踪列表摘除,\n // 否则关闭时 restoreContent 会把它复活回 light DOM。\n for (const option of pendingUnregistered) {\n if (!portalContent.contains(option) && !host.element.contains(option)) portal.removeContent([option])\n }\n pendingUnregistered.clear()\n\n if (!host.isOpen()) return\n\n // Portal 打开期间框架条件渲染(v-if)可能在 light DOM 插入新节点;\n // 面板内已有内容时 queryOptions 不会回退 light DOM,必须显式迁入面板。\n // 注释(框架锚点与 portal 的 marker)不迁移:锚点留守宿主是实时渲染契约。\n const nodes = host.getMigratableNodes().filter(node => !(node instanceof Comment))\n if (nodes.length) portal.appendContent(nodes, portalContent)\n },\n\n scheduleRefresh() {\n queueMicrotask(() => {\n if (!host.element.isConnected) return\n api.syncPortalContent()\n host.requestUpdate()\n // 关闭状态下 willUpdate 查不到 portal 面板内容;打开期的新增/删除\n // 由宿主在微任务内立即同步标签与激活态。\n if (host.isOpen() && host.hasUpdated()) host.onImmediateSync?.()\n })\n },\n\n handleOptionUnregister,\n\n bindHost() {\n host.element.addEventListener('option-register', handleRegister)\n host.element.addEventListener('option-unregister', handleOptionUnregister)\n },\n\n dispose() {\n host.element.removeEventListener('option-register', handleRegister)\n host.element.removeEventListener('option-unregister', handleOptionUnregister)\n pendingUnregistered.clear()\n }\n }\n\n return api\n })\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/option-portal/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport type { WebUiOption } from '@/components/option'\nimport type { OverlayPortal } from '@/shared/overlay/portal'\n\n/**\n * select / autocomplete 共用的 portal option 域:宿主侧 option 注册表、\n * portal 内容同步(打开期迁移 + 真删除清理)与微任务调度。\n *\n * 交互语义与 Lit 响应式由各组件保留(同 anchored-panel 的边界约定):\n * 组件通过 host 回调注入差异点(迁移节点来源、打开期即时同步等),\n * 并持有 _options/_activeIndex 的 @state,shared 只经返回值同步数据。\n */\n\nexport interface OptionPortalHost {\n /** 承载宿主级监听的元素(select / autocomplete 宿主自身)。 */\n readonly element: HTMLElement\n /** option id 前缀;宿主应保证跨实例唯一(如含实例序号),供 aria 引用与样式定位。 */\n readonly idPrefix: string\n getPortal(): OverlayPortal | undefined\n getPortalContent(): HTMLElement | undefined\n /** 打开判定:syncPortalContent 仅在打开期执行内容迁移。 */\n isOpen(): boolean\n /**\n * 打开期待迁移的节点来源。两组件粒度不同(select 迁裸 option,\n * autocomplete 迁全部 childNodes),保持各自现状不在此统一。\n */\n getMigratableNodes(): Node[]\n /** 微任务回调中判断宿主是否已完成首次渲染(Lit hasUpdated)。 */\n hasUpdated(): boolean\n /** 微任务末的宿主刷新(requestUpdate)。 */\n requestUpdate(): void\n /**\n * 打开期即时同步钩子。select 在微任务内立即刷新标签与激活态\n * (portal 打开时 willUpdate 查不到面板外的新增项),autocomplete 无此需要。\n */\n onImmediateSync?(): void\n /**\n * 组件侧的逐元素交互监听(click/pointerover/pointerdown/option-update)。\n * shared 负责随 refresh 配对调用,语义由组件定义。\n */\n bindOption(option: WebUiOption): void\n unbindOption(option: WebUiOption): void\n}\n\nexport interface OptionPortalApi {\n /** 面板优先查询,面板无 option 或关闭时回退 light DOM。 */\n queryOptions(): WebUiOption[]\n /**\n * diff 出旧集合解绑、新集合绑定;按 option 身份保持激活索引。\n * 返回新注册表数组与新的激活索引(-1 表示激活项已失效,如移除/迁移)。\n */\n refresh(\n previous: readonly WebUiOption[],\n activeOption: WebUiOption | undefined\n ): {\n options: WebUiOption[]\n activeIndex: number\n }\n /** 稳定 id 分配(WeakMap 身份 + 冲突循环),跨 remove/reorder 保持不变。 */\n ensureOptionIds(options: readonly WebUiOption[]): void\n /** pending 真删除清理 + 打开期把滞留 light DOM 的节点迁入面板。 */\n syncPortalContent(): void\n /** register/unregister/contentChange 的统一微任务收敛入口(slot 监听由组件模板持有)。 */\n scheduleRefresh(): void\n /** 宿主级与逐元素 unregister 监听共用的处理器。 */\n handleOptionUnregister(event: Event): void\n /** 宿主级监听配对(connectedCallback 调用,与 dispose 配对)。 */\n bindHost(): void\n /** 断连清理:解绑宿主监听并清空 pending 集合。 */\n dispose(): void\n}\n\nconst OPTION_TAG = 'web-ui-option'\n\nexport const defineOptionPortal = () =>\n definePlugin<OptionPortalApi, OptionPortalHost>(host => {\n const pendingUnregistered = new Set<WebUiOption>()\n // 稳定 id:WeakMap 按 option 身份记忆分配结果,remove/reorder 后 id 不变;\n // aria-activedescendant / 外部样式都依赖 id 的时序稳定性。\n const optionIds = new WeakMap<WebUiOption, string>()\n let nextOptionId = 0\n\n const bindOption = (option: WebUiOption) => {\n host.bindOption(option)\n option.addEventListener('option-unregister', handleOptionUnregister)\n }\n\n const unbindOption = (option: WebUiOption) => {\n host.unbindOption(option)\n option.removeEventListener('option-unregister', handleOptionUnregister)\n }\n\n const handleOptionUnregister = (event: Event) => {\n // Portal 迁移与真实删除都会触发 unregister;先记录候选,\n // 微任务末 syncPortalContent 按最终位置区分二者。\n if (event.target instanceof HTMLElement && event.target.localName === OPTION_TAG) {\n pendingUnregistered.add(event.target as WebUiOption)\n }\n api.scheduleRefresh()\n }\n\n const handleRegister = () => {\n api.scheduleRefresh()\n }\n\n const api: OptionPortalApi = {\n queryOptions() {\n const panel = host.getPortal()?.panel ?? host.getPortalContent()\n const panelOptions = panel ? [...panel.querySelectorAll<WebUiOption>(OPTION_TAG)] : []\n // 面板内容仅在浮层打开、moveContent 之后存在;关闭时回退 light DOM,\n // 否则 portal 组件首次加载时注册表为空。\n return panelOptions.length > 0 ? panelOptions : [...host.element.querySelectorAll<WebUiOption>(OPTION_TAG)]\n },\n\n refresh(previous, activeOption) {\n const nextOptions = api.queryOptions()\n\n // register/unregister 是 composed 事件:Portal 迁移时 target 会 retarget 成宿主,\n // 不能作为监听器绑定依据;绑定只发生在按 DOM 查询确认身份的 option 上。\n for (const option of previous) if (!nextOptions.includes(option)) unbindOption(option)\n nextOptions.forEach(bindOption)\n\n // 激活索引按 option 身份保持,避免移除中间项后静默偏移到相邻项\n return {\n options: nextOptions,\n activeIndex: activeOption && nextOptions.includes(activeOption) ? nextOptions.indexOf(activeOption) : -1\n }\n },\n\n ensureOptionIds(registryOptions) {\n const usedIds = new Set(registryOptions.map(option => option.id).filter((id): id is string => Boolean(id)))\n registryOptions.forEach(option => {\n if (option.id) return\n\n let id = optionIds.get(option)\n if (!id || usedIds.has(id)) {\n do {\n id = `${host.idPrefix}-option-${++nextOptionId}`\n } while (usedIds.has(id))\n optionIds.set(option, id)\n }\n option.id = id\n usedIds.add(id)\n })\n },\n\n syncPortalContent() {\n const portal = host.getPortal()\n const portalContent = host.getPortalContent()\n if (!portal || !portalContent) return\n\n // 真删除(既不在面板也不在 light DOM)的 option 从 portal 追踪列表摘除,\n // 否则关闭时 restoreContent 会把它复活回 light DOM。\n for (const option of pendingUnregistered) {\n if (!portalContent.contains(option) && !host.element.contains(option)) portal.removeContent([option])\n }\n pendingUnregistered.clear()\n\n if (!host.isOpen()) return\n\n // Portal 打开期间框架条件渲染(v-if)可能在 light DOM 插入新节点;\n // 面板内已有内容时 queryOptions 不会回退 light DOM,必须显式迁入面板。\n // 注释(框架锚点与 portal 的 marker)不迁移:锚点留守宿主是实时渲染契约。\n const nodes = host.getMigratableNodes().filter(node => !(node instanceof Comment))\n if (nodes.length) portal.appendContent(nodes, portalContent)\n },\n\n scheduleRefresh() {\n queueMicrotask(() => {\n if (!host.element.isConnected) return\n api.syncPortalContent()\n host.requestUpdate()\n // 关闭状态下 willUpdate 查不到 portal 面板内容;打开期的新增/删除\n // 由宿主在微任务内立即同步标签与激活态。\n if (host.isOpen() && host.hasUpdated()) host.onImmediateSync?.()\n })\n },\n\n handleOptionUnregister,\n\n bindHost() {\n host.element.addEventListener('option-register', handleRegister)\n host.element.addEventListener('option-unregister', handleOptionUnregister)\n },\n\n dispose() {\n host.element.removeEventListener('option-register', handleRegister)\n host.element.removeEventListener('option-unregister', handleOptionUnregister)\n pendingUnregistered.clear()\n }\n }\n\n return api\n })\nexport * from './combobox-shell'\n"],"mappings":";;;AAyEA,IAAM,IAAa,iBAEN,UACX,GAAgD,MAAQ;CACtD,IAAM,oBAAsB,IAAI,IAAiB,GAG3C,oBAAY,IAAI,QAA6B,GAC/C,IAAe,GAEb,KAAc,MAAwB;EAE1C,AADA,EAAK,WAAW,CAAM,GACtB,EAAO,iBAAiB,qBAAqB,CAAsB;CACrE,GAEM,KAAgB,MAAwB;EAE5C,AADA,EAAK,aAAa,CAAM,GACxB,EAAO,oBAAoB,qBAAqB,CAAsB;CACxE,GAEM,KAA0B,MAAiB;EAM/C,AAHI,EAAM,kBAAkB,eAAe,EAAM,OAAO,cAAc,KACpE,EAAoB,IAAI,EAAM,MAAqB,GAErD,EAAI,gBAAgB;CACtB,GAEM,UAAuB;EAC3B,EAAI,gBAAgB;CACtB,GAEM,IAAuB;EAC3B,eAAe;GACb,IAAM,IAAQ,EAAK,UAAU,CAAC,EAAE,SAAS,EAAK,iBAAiB,GACzD,IAAe,IAAQ,CAAC,GAAG,EAAM,iBAA8B,CAAU,CAAC,IAAI,CAAC;GAGrF,OAAO,EAAa,SAAS,IAAI,IAAe,CAAC,GAAG,EAAK,QAAQ,iBAA8B,CAAU,CAAC;EAC5G;EAEA,QAAQ,GAAU,GAAc;GAC9B,IAAM,IAAc,EAAI,aAAa;GAIrC,KAAK,IAAM,KAAU,GAAU,AAAK,EAAY,SAAS,CAAM,KAAG,EAAa,CAAM;GAIrF,OAHA,EAAY,QAAQ,CAAU,GAGvB;IACL,SAAS;IACT,aAAa,KAAgB,EAAY,SAAS,CAAY,IAAI,EAAY,QAAQ,CAAY,IAAI;GACxG;EACF;EAEA,gBAAgB,GAAiB;GAC/B,IAAM,IAAU,IAAI,IAAI,EAAgB,KAAI,MAAU,EAAO,EAAE,CAAC,CAAC,QAAQ,MAAqB,EAAQ,CAAG,CAAC;GAC1G,EAAgB,SAAQ,MAAU;IAChC,IAAI,EAAO,IAAI;IAEf,IAAI,IAAK,EAAU,IAAI,CAAM;IAC7B,IAAI,CAAC,KAAM,EAAQ,IAAI,CAAE,GAAG;KAC1B;MACE,IAAK,GAAG,EAAK,SAAS,UAAU,EAAE;YAC3B,EAAQ,IAAI,CAAE;KACvB,EAAU,IAAI,GAAQ,CAAE;IAC1B;IAEA,AADA,EAAO,KAAK,GACZ,EAAQ,IAAI,CAAE;GAChB,CAAC;EACH;EAEA,oBAAoB;GAClB,IAAM,IAAS,EAAK,UAAU,GACxB,IAAgB,EAAK,iBAAiB;GAC5C,IAAI,CAAC,KAAU,CAAC,GAAe;GAI/B,KAAK,IAAM,KAAU,GACnB,AAAI,CAAC,EAAc,SAAS,CAAM,KAAK,CAAC,EAAK,QAAQ,SAAS,CAAM,KAAG,EAAO,cAAc,CAAC,CAAM,CAAC;GAItG,IAFA,EAAoB,MAAM,GAEtB,CAAC,EAAK,OAAO,GAAG;GAKpB,IAAM,IAAQ,EAAK,mBAAmB,CAAC,CAAC,QAAO,MAAQ,EAAE,aAAgB,QAAQ;GACjF,AAAI,EAAM,UAAQ,EAAO,cAAc,GAAO,CAAa;EAC7D;EAEA,kBAAkB;GAChB,qBAAqB;IACd,EAAK,QAAQ,gBAClB,EAAI,kBAAkB,GACtB,EAAK,cAAc,GAGf,EAAK,OAAO,KAAK,EAAK,WAAW,KAAG,EAAK,kBAAkB;GACjE,CAAC;EACH;EAEA;EAEA,WAAW;GAET,AADA,EAAK,QAAQ,iBAAiB,mBAAmB,CAAc,GAC/D,EAAK,QAAQ,iBAAiB,qBAAqB,CAAsB;EAC3E;EAEA,UAAU;GAGR,AAFA,EAAK,QAAQ,oBAAoB,mBAAmB,CAAc,GAClE,EAAK,QAAQ,oBAAoB,qBAAqB,CAAsB,GAC5E,EAAoB,MAAM;EAC5B;CACF;CAEA,OAAO;AACT,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/shared/overlay/placement-props.ts
|
|
2
|
+
var e = [
|
|
3
|
+
"top",
|
|
4
|
+
"top-start",
|
|
5
|
+
"top-end",
|
|
6
|
+
"bottom",
|
|
7
|
+
"bottom-start",
|
|
8
|
+
"bottom-end",
|
|
9
|
+
"left",
|
|
10
|
+
"left-start",
|
|
11
|
+
"left-end",
|
|
12
|
+
"right",
|
|
13
|
+
"right-start",
|
|
14
|
+
"right-end"
|
|
15
|
+
];
|
|
16
|
+
//#endregion
|
|
17
|
+
export { e as FLOATING_PLACEMENTS };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=placement-props.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placement-props.js","names":[],"sources":["../../../src/shared/overlay/placement-props.ts"],"sourcesContent":["/**\n * Floating UI anchored overlay 组件共享的 placement 白名单。\n *\n * 12 个方向成员相同、声明顺序在各组件历史上有差异;normalizeLiteral 只做\n * 成员判定,顺序不参与语义,因此这里以稳定顺序单源化,组件不再各自声明。\n */\nexport const FLOATING_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\nexport type FloatingPlacement = (typeof FLOATING_PLACEMENTS)[number]\n"],"mappings":";AAMA,IAAa,IAAsB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
|
|
@@ -17,7 +17,9 @@ var a = "wui-portal-content-anchor", o = () => r((e) => {
|
|
|
17
17
|
let i = document.createElement("div");
|
|
18
18
|
i.className = e.className, i.dataset.wuiPresence = "entering", n.append(r, i);
|
|
19
19
|
let o = [], s = new MutationObserver((t) => {
|
|
20
|
-
f(t)
|
|
20
|
+
f(t);
|
|
21
|
+
let n = new Set(t.flatMap((e) => [...e.addedNodes])), r = t.flatMap((e) => [...e.removedNodes]).filter((e) => !n.has(e));
|
|
22
|
+
r.length && l(r), e.onContentChange?.(t);
|
|
21
23
|
}), c = (e) => {
|
|
22
24
|
let t = e.parentNode;
|
|
23
25
|
if (!t) return null;
|
|
@@ -62,7 +64,7 @@ var a = "wui-portal-content-anchor", o = () => r((e) => {
|
|
|
62
64
|
r && r.parentNode === e.target ? e.target.insertBefore(n, r) : e.target.appendChild(n);
|
|
63
65
|
}
|
|
64
66
|
};
|
|
65
|
-
u(e.container, e.target).appendChild(t),
|
|
67
|
+
u(e.container, e.target).appendChild(t), s.observe(i, {
|
|
66
68
|
childList: !0,
|
|
67
69
|
subtree: !0
|
|
68
70
|
});
|