@greypan/web-ui 7.0.0 → 8.0.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 +80 -13
- package/README.md +80 -13
- package/dist/components/autocomplete/index.d.ts +17 -5
- package/dist/components/autocomplete/index.js +136 -101
- 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/index.js +4 -4
- package/dist/components/button/index.js +15 -15
- package/dist/components/button-group/index.js +5 -5
- package/dist/components/checkbox/index.js +11 -11
- package/dist/components/checkbox-group/index.js +8 -8
- package/dist/components/context-menu/index.js +4 -1
- package/dist/components/context-menu/index.js.map +1 -1
- package/dist/components/dialog/index.js +11 -8
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/drawer/index.js +11 -8
- package/dist/components/drawer/index.js.map +1 -1
- package/dist/components/dropdown/index.js +4 -1
- package/dist/components/dropdown/index.js.map +1 -1
- package/dist/components/editable-text/index.d.ts +471 -0
- package/dist/components/editable-text/index.js +206 -0
- package/dist/components/editable-text/index.js.map +1 -0
- package/dist/components/editable-text/style.js +6 -0
- package/dist/components/editable-text/style.js.map +1 -0
- package/dist/components/image-preview/index.js +21 -18
- package/dist/components/image-preview/index.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +39 -38
- package/dist/components/input/index.d.ts +2 -0
- package/dist/components/input/index.js +11 -5
- package/dist/components/input/index.js.map +1 -1
- package/dist/components/input-number/index.js +15 -15
- package/dist/components/layout/index.js +7 -7
- package/dist/components/layout/style.js +1 -1
- package/dist/components/layout/style.js.map +1 -1
- package/dist/components/popover/index.js +13 -10
- package/dist/components/popover/index.js.map +1 -1
- package/dist/components/radio/index.js +9 -9
- package/dist/components/radio-group/index.js +8 -8
- package/dist/components/segmented/index.d.ts +4 -1
- package/dist/components/segmented/index.js +42 -29
- 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.js +35 -32
- package/dist/components/select/index.js.map +1 -1
- package/dist/components/slider/index.js +11 -11
- package/dist/components/switch/index.js +12 -12
- package/dist/components/textarea/index.js +5 -5
- package/dist/components/theme/index.d.ts +1 -3
- package/dist/components/theme/index.js +51 -54
- package/dist/components/theme/index.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/toast.js +13 -13
- package/dist/components/tooltip/index.js +12 -9
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/shared/option-portal/combobox-shell.js +47 -5
- package/dist/shared/option-portal/combobox-shell.js.map +1 -1
- package/dist/shared/option-portal/index.js +9 -9
- package/dist/shared/overlay/anchored-panel.js +2 -2
- package/dist/shared/overlay/anchored-panel.js.map +1 -1
- package/dist/shared/overlay/open-overlay.js +38 -22
- package/dist/shared/overlay/open-overlay.js.map +1 -1
- package/dist/shared/theme/root-sync.js +42 -0
- package/dist/shared/theme/root-sync.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/editable-text/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { ifDefined } from 'lit/directives/if-defined.js'\n\nimport { FormAssociated, defineFormAssociation, FormAssociationController } from '@/shared/form-association'\n\nimport style from './style.css?inline'\n\n/** 空值且无 placeholder 时占位,保留一个行盒,避免宿主退化成 0 高度、无法点击进入编辑。 */\nconst BLANK_GLYPH = '\\u200B'\n\n/** 行/字符落点二分的容差(px),吸收次像素与舍入差异。 */\nconst CARET_TOLERANCE = 1\n\n/**\n * 取文本层承载文案的 Text 节点。\n *\n * 绑定表达式位于元素首个位置时,Lit 会在它前面留一个注释标记节点,因此不能用\n * `firstChild`:那会拿到没有布局的注释,Range 量不到任何矩形。\n */\nfunction textNodeOf(layer: HTMLElement): Text | null {\n for (const child of layer.childNodes) {\n if (child instanceof Text) return child\n }\n return null\n}\n\n/**\n * 把视口坐标映射为文本层的字符偏移。\n *\n * `document.caretPositionFromPoint` 不进入 shadow DOM(对 shadow 内容返回 shadow host\n * 之外的节点),因此在文本层上用 Range 空矩形二分:先按 y 选行,再在行内按 x 选字符。\n * 文本层与编辑层同盒同排版,偏移与 textarea 的 selectionStart 一一对应。\n */\nfunction caretOffsetFromPoint(textLayer: HTMLElement, x: number, y: number): number {\n const node = textNodeOf(textLayer)\n if (!node) return 0\n const length = node.data.length\n const range = document.createRange()\n const rectAt = (offset: number): DOMRect => {\n range.setStart(node, offset)\n range.setEnd(node, offset)\n return range.getBoundingClientRect()\n }\n\n // 同一行的盒可能被拆成多个 client rect;按 top 去重得到行列表,取与 y 最近的一行,\n // 距离相等时取更靠下的一行(与浏览器在行间的落点行为一致)。\n const lines: DOMRect[] = []\n for (const rect of textLayer.getClientRects()) {\n if (!lines.some(line => Math.abs(line.top - rect.top) <= CARET_TOLERANCE)) lines.push(rect)\n }\n if (lines.length === 0) return length\n let line = lines[0]\n let nearest = Infinity\n for (const candidate of lines) {\n const distance = Math.abs((candidate.top + candidate.bottom) / 2 - y)\n if (distance <= nearest) {\n nearest = distance\n line = candidate\n }\n }\n\n // 行首:caret top 首次落入本行的最小偏移\n let lo = 0\n let hi = length\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (rectAt(mid).top >= line.top - CARET_TOLERANCE) hi = mid\n else lo = mid + 1\n }\n const lineStart = lo\n // 行尾:caret top 仍落在本行的最大偏移\n lo = lineStart\n hi = length\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1\n if (rectAt(mid).top <= line.top + CARET_TOLERANCE) lo = mid\n else hi = mid - 1\n }\n const lineEnd = Math.max(lo, lineStart)\n // 行内:caret left 不超过 x 的最大偏移\n lo = lineStart\n hi = lineEnd\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1\n if (rectAt(mid).left <= x + CARET_TOLERANCE) lo = mid\n else hi = mid - 1\n }\n return lo\n}\n\n@customElement('web-ui-editable-text')\nexport class WebUiEditableText extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(style)]\n\n @property({ type: String, reflect: true }) name = ''\n @property({ type: String, reflect: true }) placeholder = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: String, attribute: 'aria-label' }) override ariaLabel: string | null = null\n\n @state() private _value = ''\n\n /**\n * `value` attribute 承载 form reset 的初值,不随当前值反射;property 暴露当前值,\n * 与原生 input 的 value / defaultValue 关系一致。\n */\n @property({ type: String })\n get value(): string {\n return this._value\n }\n\n set value(v: string) {\n const old = this._value\n this._value = v\n this._formAssociation.sync()\n this.requestUpdate('value', old)\n }\n\n @query('.editor') private _editor?: HTMLTextAreaElement\n @query('.text') private _textLayer?: HTMLElement\n\n private _editing = false\n private _editBase = ''\n private _pendingCaret: number | null = null\n private _refocusing = false\n private _resizeObserver: ResizeObserver | null = null\n\n private get _isDisabled(): boolean {\n return this.disabled || this._formAssociation.isFormDisabled()\n }\n\n private readonly _formAssociation = defineFormAssociation<string>({\n host: this,\n getState: () => this._value,\n setState: value => {\n this.value = value\n },\n getFormValue: () => this._value,\n getFormState: () => this._value,\n restoreState: state => {\n if (typeof state === 'string') this.value = state\n }\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n constructor() {\n super()\n // focus 不冒泡:宿主监听只在焦点从组件外进入时触发,shadow 内聚焦不会误触\n this.addEventListener('focus', this._onHostFocus)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._releaseEditingKeys()\n this._teardownAutosize()\n }\n\n override connectedCallback() {\n super.connectedCallback()\n /*\n * autosize 的 ResizeObserver 在 disconnectedCallback 被拆掉,搭建若只放在\n * firstUpdated(一生一次),元素断开再重连后 RO 永久丢失、autosize 静默失效。\n * connectedCallback 每次连接都跑:_setupAutosize 自带 teardown,重复进入无副作用。\n * 首连接时渲染尚未发生(_editor 为 null),_autosizeEditor 与 RO 回调都有判空守卫。\n */\n this._setupAutosize()\n // 同类缺口:编辑态被断开时 _releaseEditingKeys 撤了 window 监听,重连后 _editing\n // 仍为 true(blur 取消未必先于断开到达),此时补领,避免重连后按键失灵。\n if (this._editing) this._claimEditingKeys()\n }\n\n override updated() {\n // disabled 时移出 tab 序列;编辑态由 editing attribute 表达,二者互斥\n if (this._isDisabled) this.removeAttribute('tabindex')\n else this.setAttribute('tabindex', '0')\n // 禁用态生效时(属性或 fieldset)退出编辑:值保持当前草稿,不派发提交事件\n if (this._editing && this._isDisabled) this._exitEditing()\n this._autosizeEditor()\n }\n\n private _onHostFocus() {\n if (this._refocusing) {\n this._refocusing = false\n return\n }\n this._enterEditing()\n }\n\n /**\n * pointerdown 时文本层仍是命中目标,此时记录落点;浏览器随后的默认聚焦会触发\n * host focus 进入编辑,偏移在那里应用到 textarea。\n */\n private _onPointerDown(e: PointerEvent) {\n if (this._isDisabled || this._editing || !this._textLayer) return\n this._pendingCaret = caretOffsetFromPoint(this._textLayer, e.clientX, e.clientY)\n }\n\n /** mousedown 默认聚焦被拦截等场景由 click 兜底进入编辑。 */\n private _onClick() {\n if (this._isDisabled || this._editing) return\n this._enterEditing()\n }\n\n /**\n * 公共 API:进入编辑态并全选内容;已在编辑态时只重新全选。\n *\n * `disabled` 时不进入编辑,与 `focus()` 一致。\n */\n select() {\n if (this._isDisabled) return\n if (!this._editing) this._enterEditing()\n const editor = this._editor\n // 全选:末端显式取当前文本长度,覆盖 _enterEditing 落下的光标位置\n editor?.setSelectionRange(0, editor.value.length)\n }\n\n private _enterEditing() {\n if (this._isDisabled || this._editing) return\n this._editing = true\n this._editBase = this._value\n this.toggleAttribute('editing', true)\n this._claimEditingKeys()\n const editor = this._editor\n if (!editor) return\n editor.focus()\n // Tab / 编程式聚焦光标到末尾;指针落点用 pointerdown 记下的偏移\n const offset = this._pendingCaret ?? this._value.length\n this._pendingCaret = null\n const clamped = Math.max(0, Math.min(offset, editor.value.length))\n editor.setSelectionRange(clamped, clamped)\n }\n\n private _exitEditing() {\n if (!this._editing) return\n this._editing = false\n this._releaseEditingKeys()\n this.toggleAttribute('editing', false)\n }\n\n private _onInput() {\n // 原生 input 已 composed 冒泡出 shadow root 且 target 重定向到宿主,无需补发\n this._value = this._editor?.value ?? ''\n this._formAssociation.sync()\n this._autosizeEditor()\n }\n\n /**\n * blur 提交:与 Enter 同一条提交路径,草稿成为新值并派发 change。\n *\n * 不交还焦点:焦点已被用户移走,抢回宿主会吞掉消费者的目标焦点。\n */\n private _onBlur() {\n if (!this._editing || this._isDisabled) return\n this._commitEditing(false)\n }\n\n /**\n * 提交:草稿成为新值、退出编辑,并恰好派发一次 change。Enter 与 blur 同走这一条路。\n *\n * `returnFocus` 区分两种来路:Enter 主动结束时把焦点交还宿主(标记防重入编辑);\n * blur 是被动失焦,焦点已被用户移走,再抢回宿主会吞掉消费者的目标焦点。\n * 先退出编辑再动焦点:退出时 textarea 隐藏触发的 blur 看到 _editing 已为 false,\n * 不会把这次提交误判成又一次 blur 提交。\n */\n private _commitEditing(returnFocus: boolean) {\n if (!this._editing) return\n this._exitEditing()\n if (returnFocus) this._returnFocusToHost()\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n /**\n * 取消:恢复进入编辑时的值并派发 cancel。仅 Escape 走这一条路径。\n *\n * 调用方(Escape 分支)随后才交还焦点:本方法先摘除编辑态,焦点迁移触发的 blur\n * 到达时 `_editing` 已为 false 而早退,不会把取消误判成 blur 提交。\n */\n private _cancelEditing() {\n if (!this._editing) return\n this._value = this._editBase\n this._formAssociation.sync()\n this._exitEditing()\n /*\n * 重入守卫先于 dispatch 置位:cancel 同步派发,消费者可能在监听器里调\n * el.focus()——那是一次真实的宿主 focus,没有守卫就会重新进入编辑,随后一次\n * blur 便把恢复后的原值当成新草稿提交,用户什么都没改却收到 change。守卫在此\n * 置位后即被该次 focus 消费,与取消后由组件自己交还焦点走同一条早退路径。\n */\n this._refocusing = true\n /*\n * cancel 与原生 <dialog> 的 cancel 同名,而 dialog 的关闭管线就监听这个事件名。\n * 组件被消费方投映进浮层 shadow(drawer/dialog 标题)时,任何冒泡的 cancel——\n * composed 与否都一样,slot 都会把事件带进 shadow 树——都会被那个 dialog 当成\n * 一次关闭请求:issue #159 的实测症状是 drawer 标题聚焦编辑时按 Escape,或点到\n * 抽屉别处触发 blur,抽屉跟着编辑一起关。因此只在宿主上派发,不冒泡不组合;\n * 监听挂在组件本身即可(vue @cancel、React addEventListener 均不受影响)。\n * 手法同 checkbox/radio 受管时的事件策略(见各自 handleClick 注释)。\n */\n this.dispatchEvent(new Event('cancel', { bubbles: false, composed: false }))\n }\n\n /**\n * 焦点交还宿主:先标记再聚焦,宿主 focus 到达时标记已消费,不会再次进入编辑;\n * 标记在微任务里清掉,此后的聚焦照常进入编辑。\n */\n private _returnFocusToHost() {\n this._refocusing = true\n this.focus()\n queueMicrotask(() => {\n this._refocusing = false\n })\n }\n\n /**\n * 编辑态按键归属:Enter 提交、Escape 取消。\n *\n * 监听挂在 window 捕获阶段而不是 shadow 内的 textarea 上:消费方(浮层仲裁者)在\n * document 捕获阶段收 Escape,组件若只在 textarea 上处理,按键先被仲裁者收走,\n * 于是一次 Escape 会同时取消编辑和关闭外层浮层。window 在 document 上游,\n * stopPropagation 只有挂在这里才拦得住仲裁者。\n */\n private _claimEditingKeys() {\n window.addEventListener('keydown', this._onEditingKeydown, true)\n }\n\n private _releaseEditingKeys() {\n window.removeEventListener('keydown', this._onEditingKeydown, true)\n }\n\n private readonly _onEditingKeydown = (e: KeyboardEvent) => {\n // 输入法组合期间的 Enter/Escape 属于组合会话,交给 IME\n if (e.isComposing) return\n if (!e.composedPath().includes(this)) return\n if (e.key === 'Escape') {\n // 按键本身也被编辑层消费:外层浮层不应收到同一次 Escape\n e.preventDefault()\n e.stopPropagation()\n // 先取消(内部摘除编辑态)再交还焦点:focus 触发的 blur 看到 _editing 已为\n // false 而早退,不会把这次取消误判成 blur 提交\n this._cancelEditing()\n this._returnFocusToHost()\n return\n }\n if (e.key === 'Enter') {\n /*\n * 与 Escape 同套消费策略:preventDefault 压掉 textarea 的换行默认行为,\n * stopPropagation 让按键不外泄。取舍是明说的:编辑态 Enter 属于提交,外层\n * 表单的隐式提交与浮层监听不应收到同一次按键——正如 Escape 属于取消。\n * 此前只 preventDefault 不对称,Enter 会漏进外层监听。\n */\n e.preventDefault()\n e.stopPropagation()\n this._commitEditing(true)\n }\n }\n\n private _setupAutosize() {\n this._teardownAutosize()\n // jsdom 等无 ResizeObserver 的环境退化为按渲染 autosize,够用且不抛错\n if (typeof ResizeObserver === 'undefined') {\n this._autosizeEditor()\n return\n }\n this._resizeObserver = new ResizeObserver(() => this._autosizeEditor())\n this._resizeObserver.observe(this)\n this._autosizeEditor()\n }\n\n private _teardownAutosize() {\n this._resizeObserver?.disconnect()\n this._resizeObserver = null\n this._editor?.style.removeProperty('height')\n }\n\n /**\n * 编辑层高度始终跟随自身内容,不依赖文本层折出的行盒。\n *\n * 编辑层绝对定位并用 top/bottom 撑高,`height: auto` 量到的是包含块高度而不是内容高;\n * 先归零再读 scrollHeight,拿到的才是自身内容的高度(含 1px 内边距)。\n * 观察宿主而非编辑层:编辑层在文档流外,改它的高度不会反过来改动宿主尺寸。\n */\n private _autosizeEditor() {\n const editor = this._editor\n if (!editor) return\n const previous = editor.style.height\n editor.style.height = '0px'\n const height = editor.scrollHeight\n editor.style.height = height > 0 ? `${height}px` : previous\n }\n\n private get _displayText(): string {\n if (this._value !== '') return this._value\n if (this.placeholder !== '') return this.placeholder\n return BLANK_GLYPH\n }\n\n override render() {\n return html`\n <div class=\"layers\" @pointerdown=${this._onPointerDown} @click=${this._onClick}>\n <span class=${classMap({ text: true, placeholder: this._value === '' })}>${this._displayText}</span>\n <textarea\n class=\"editor\"\n .value=${this._value}\n .placeholder=${this.placeholder}\n ?disabled=${this._isDisabled}\n aria-label=${ifDefined(this.ariaLabel)}\n @input=${this._onInput}\n @blur=${this._onBlur}\n ></textarea>\n </div>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n cancel: Event\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-editable-text': WebUiEditableText\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/components/editable-text/style.css?inline
|
|
2
|
+
var e = "*,:before,:after{box-sizing:border-box}:host{display:inline-block}.layers{display:block;position:relative}.text,.editor{overflow-wrap:var(--wui-editable-text-overflow-wrap,anywhere);white-space:var(--wui-editable-text-white-space,pre-wrap)}.text{display:inline}.placeholder{color:var(--wui-color-text-secondary,#6a6a6a)}.editor{resize:none;font:inherit;line-height:inherit;color:inherit;text-align:inherit;text-transform:inherit;letter-spacing:inherit;word-break:inherit;appearance:none;visibility:hidden;caret-color:var(--wui-color-accent,#08f);background:0 0;border:0;outline:none;margin:0;padding:1px;position:absolute;inset:-1px;overflow:hidden}.editor::placeholder{color:var(--wui-color-text-secondary,#6a6a6a)}:host([editing]) .editor{visibility:visible;min-width:1ch}:host([editing]) .text{visibility:hidden}:host([disabled]){cursor:not-allowed}";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { e as default };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.js","names":[],"sources":["../../../src/components/editable-text/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n/* 组件对外表现为同级 span:行内级盒子,排版与颜色全部继承外部上下文。\n :host 只设 display(ADR-0005),其余样式在 shadow root 内。 */\n:host {\n display: inline-block;\n}\n\n/* 文本层与编辑层共用的排版盒:编辑层 absolute 定位覆盖它,切换只改可见性,\n 因此文字态与编辑态逐像素同盒。 */\n.layers {\n position: relative;\n display: block;\n}\n\n.text,\n.editor {\n /* 长无空格串两层都必须能断:文本层若只继承宿主默认的 normal,长字母数字串不\n 截断、直接溢出宿主盒(文字态),而编辑层在盒内裁剪(编辑态),两层断行\n 不一致。默认 anywhere:仅在单词放不下时断,且参与 min-content 计算,收缩\n 上下文(flex 项 min-width:0)里长串也不会撑爆宿主;两层取同一变量,外部\n 覆盖时仍保持一致。 */\n overflow-wrap: var(--wui-editable-text-overflow-wrap, anywhere);\n\n /* 多行编辑要求换行符成行,但 pre-wrap 只能落在文本层上:模板里元素之间的空白\n 文本节点若被 pre-wrap 保留,会各自生成幻影行盒,把文本层顶偏、盒高翻倍,\n 文字态与编辑态随之不再同盒。这里保持 .layers 为默认 normal 让空白折叠,\n 两层取同一变量,外部覆盖(如 nowrap)时仍保持一致。 */\n white-space: var(--wui-editable-text-white-space, pre-wrap);\n}\n\n.text {\n display: inline;\n}\n\n.placeholder {\n color: var(--wui-color-text-secondary, #6a6a6a);\n}\n\n/* 原生控件 reset:不贡献尺寸、无边框背景描边、不可手拖改变大小 */\n.editor {\n resize: none;\n\n position: absolute;\n\n /* 四边各外扩 1px,再用 1px 内边距把文本起点与换行宽度拉回内容盒。\n 两层必须同盒,但盒的边界同时也是原生控件的裁剪边界:文本层的字形会按\n 边距溢出内容盒半个像素左右,紧凑行高下 textarea 内部内容也会比内容盒高\n 出 1px。裁剪盒若与内容盒重合,这些墨色只存在于文字态,编辑态被裁掉或顶偏,\n 两态逐像素比对就对不上;外扩 1px 后编辑层的裁剪盒覆盖文本层的墨色范围。 */\n inset: -1px;\n\n overflow: hidden;\n\n margin: 0;\n padding: 1px;\n border: 0;\n\n /* 原生控件不继承排版:显式继承让编辑层与文本层共用同一套字体度量 */\n font: inherit;\n line-height: inherit;\n color: inherit;\n text-align: inherit;\n text-transform: inherit;\n letter-spacing: inherit;\n word-break: inherit;\n\n appearance: none;\n visibility: hidden;\n background: transparent;\n outline: none;\n\n /* 光标颜色组件级默认:跟随共享语义 token,消费方在宿主或任意祖先上设\n --wui-color-accent 即可覆盖(自定义属性穿透 shadow 边界继承);\n 此前只有 interweave 页面级设置,demo 等消费方拿到的是浏览器默认黑。 */\n caret-color: var(--wui-color-accent, #08f);\n\n &::placeholder {\n color: var(--wui-color-text-secondary, #6a6a6a);\n }\n}\n\n/* 可见性切换:文字态隐藏编辑层、编辑态显露;盒模型、滚动与换行不随之变化 */\n:host([editing]) .editor {\n /* 空内容时宿主可能被收缩到 0 宽:行内级盒子配上零宽占位符,收缩上下文(flex 项\n min-width:0、表格单元格等)里就量不到宽度,编辑层随之没有可绘制光标的地盘,\n 清空全部内容后光标消失。留一个字符宽,保证空草稿下光标仍然可见。\n 规则落在 shadow 内:宿主上的 min-width 属于外层文档样式,会盖过 :host 声明。 */\n min-width: 1ch;\n visibility: visible;\n}\n\n/* 编辑态隐藏文本层:两层同盒同排版,若同时可见会把同一份文字画两遍,\n 抗锯齿边缘的墨色会加重,破坏文字态/编辑态的逐像素一致。 */\n:host([editing]) .text {\n visibility: hidden;\n}\n\n:host([disabled]) {\n cursor: not-allowed;\n}\n"],"mappings":""}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import e from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
|
|
2
2
|
import "../icon/index.js";
|
|
3
|
-
import t from "../../
|
|
4
|
-
import n from "../../icons/generated/lucide-chevron-
|
|
5
|
-
import r from "../../icons/generated/lucide-
|
|
6
|
-
import i from "../../icons/generated/lucide-
|
|
7
|
-
import a from "../../icons/generated/
|
|
8
|
-
import o from "../../icons/generated/
|
|
9
|
-
import s from "../../
|
|
3
|
+
import t from "../../assets/glass.js";
|
|
4
|
+
import n from "../../icons/generated/lucide-chevron-left.js";
|
|
5
|
+
import r from "../../icons/generated/lucide-chevron-right.js";
|
|
6
|
+
import i from "../../icons/generated/lucide-plus.js";
|
|
7
|
+
import a from "../../icons/generated/lucide-minus.js";
|
|
8
|
+
import o from "../../icons/generated/ooui-close.js";
|
|
9
|
+
import s from "../../icons/generated/radix-icons-reset.js";
|
|
10
|
+
import "../button/index.js";
|
|
10
11
|
import { defineOpenOverlay as c } from "../../shared/overlay/open-overlay.js";
|
|
11
12
|
import { getFallbackOverlayRoot as l } from "../../shared/overlay/overlay-root.js";
|
|
12
13
|
import { findNearestTheme as u, findRootTheme as d } from "../../shared/overlay/theme-overlay-scope.js";
|
|
13
14
|
import { defineScrollLockLease as f } from "../../shared/scroll-lock/scroll-lock.js";
|
|
14
|
-
import "../button/index.js";
|
|
15
15
|
import { defineNativeDialogPresence as p } from "../../shared/overlay/native-dialog-presence.js";
|
|
16
16
|
import { attachDragGesture as m } from "../../shared/gesture/drag-gesture.js";
|
|
17
17
|
import { clamp as h } from "../../shared/gesture/physics.js";
|
|
@@ -24,7 +24,10 @@ import { styleMap as E } from "lit/directives/style-map.js";
|
|
|
24
24
|
//#region src/components/image-preview/index.ts
|
|
25
25
|
var D = 1, O = 4, k = .5, A = .002, j = 3, M = .15, N = 320, P = 16, F = 160, I = 320, L = class extends v {
|
|
26
26
|
constructor(...e) {
|
|
27
|
-
super(...e), this.images = [], this.loop = !0, this.nav = !1, this.toolbar = !1, this.closable = !1, this.indicator = !1, this.swipe = !1, this.noScrollLock = !1, this.noBackdropClose = !1, this._open = !1, this._index = 0, this._scale = D, this._offsetX = 0, this._offsetY = 0, this._swipeOffset = 0, this._swipeSettling = !1, this._dragging = !1, this._loadedSources = /* @__PURE__ */ new Set(), this._swipeDir = 0, this._swipeStartTime = 0, this._swipeSettleCommit = !1, this._swipeSettleDir = 1, this._scrollLock = f().make(), this._overlay = c().make({
|
|
27
|
+
super(...e), this.images = [], this.loop = !0, this.nav = !1, this.toolbar = !1, this.closable = !1, this.indicator = !1, this.swipe = !1, this.noScrollLock = !1, this.noBackdropClose = !1, this._open = !1, this._index = 0, this._scale = D, this._offsetX = 0, this._offsetY = 0, this._swipeOffset = 0, this._swipeSettling = !1, this._dragging = !1, this._loadedSources = /* @__PURE__ */ new Set(), this._swipeDir = 0, this._swipeStartTime = 0, this._swipeSettleCommit = !1, this._swipeSettleDir = 1, this._scrollLock = f().make(), this._overlay = c().make({
|
|
28
|
+
requestClose: () => this.close(),
|
|
29
|
+
isConnected: () => this.isConnected
|
|
30
|
+
}), this._handle = null, this._presence = p().make({
|
|
28
31
|
getDialog: () => this.dialog,
|
|
29
32
|
isConnected: () => this.isConnected,
|
|
30
33
|
isOpen: () => this._open
|
|
@@ -110,7 +113,7 @@ var D = 1, O = 4, k = .5, A = .002, j = 3, M = .15, N = 320, P = 16, F = 160, I
|
|
|
110
113
|
};
|
|
111
114
|
}
|
|
112
115
|
static {
|
|
113
|
-
this.styles = [x(
|
|
116
|
+
this.styles = [x(t), x(_)];
|
|
114
117
|
}
|
|
115
118
|
get dialog() {
|
|
116
119
|
return this.shadowRoot?.querySelector("dialog") ?? null;
|
|
@@ -287,7 +290,7 @@ var D = 1, O = 4, k = .5, A = .002, j = 3, M = .15, N = 320, P = 16, F = 160, I
|
|
|
287
290
|
this._offsetX = h(this._panOriginX + e, -n.x, n.x), this._offsetY = h(this._panOriginY + t, -n.y, n.y);
|
|
288
291
|
}
|
|
289
292
|
render() {
|
|
290
|
-
let e = this.images.length,
|
|
293
|
+
let e = this.images.length, t = this._scale > D, c = e > 0 ? this._adjacentIndex(-1) : -1, l = e > 0 ? this._adjacentIndex(1) : -1, u = `translate3d(${this._swipeOffset}px, 0, 0)`, d = `translate3d(${this._offsetX}px, ${this._offsetY}px, 0) scale(${this._scale})`, f = t ? "translate3d(0, 0, 0) scale(1)" : `translate3d(0, ${this._offsetY}px, 0) scale(1)`;
|
|
291
294
|
return y`
|
|
292
295
|
<dialog
|
|
293
296
|
aria-label="图片预览"
|
|
@@ -306,7 +309,7 @@ var D = 1, O = 4, k = .5, A = .002, j = 3, M = .15, N = 320, P = 16, F = 160, I
|
|
|
306
309
|
<div
|
|
307
310
|
class=${T({
|
|
308
311
|
"wui-image-preview-stage": !0,
|
|
309
|
-
"is-zoomed":
|
|
312
|
+
"is-zoomed": t,
|
|
310
313
|
"is-dragging": this._dragging,
|
|
311
314
|
"is-settling": this._swipeSettling
|
|
312
315
|
})}
|
|
@@ -380,7 +383,7 @@ var D = 1, O = 4, k = .5, A = .002, j = 3, M = .15, N = 320, P = 16, F = 160, I
|
|
|
380
383
|
aria-label="关闭"
|
|
381
384
|
@click=${this.close}
|
|
382
385
|
>
|
|
383
|
-
<web-ui-icon size="16" .icon=${
|
|
386
|
+
<web-ui-icon size="16" .icon=${o}></web-ui-icon>
|
|
384
387
|
</web-ui-button>
|
|
385
388
|
` : b}
|
|
386
389
|
${this.nav && e > 1 ? y`
|
|
@@ -393,7 +396,7 @@ var D = 1, O = 4, k = .5, A = .002, j = 3, M = .15, N = 320, P = 16, F = 160, I
|
|
|
393
396
|
?disabled=${!this.loop && this._index === 0}
|
|
394
397
|
@click=${this.prev}
|
|
395
398
|
>
|
|
396
|
-
<web-ui-icon size="20" .icon=${
|
|
399
|
+
<web-ui-icon size="20" .icon=${n}></web-ui-icon>
|
|
397
400
|
</web-ui-button>
|
|
398
401
|
<web-ui-button
|
|
399
402
|
class="wui-image-preview-nav wui-image-preview-nav-next"
|
|
@@ -404,7 +407,7 @@ var D = 1, O = 4, k = .5, A = .002, j = 3, M = .15, N = 320, P = 16, F = 160, I
|
|
|
404
407
|
?disabled=${!this.loop && this._index === e - 1}
|
|
405
408
|
@click=${this.next}
|
|
406
409
|
>
|
|
407
|
-
<web-ui-icon size="20" .icon=${
|
|
410
|
+
<web-ui-icon size="20" .icon=${r}></web-ui-icon>
|
|
408
411
|
</web-ui-button>
|
|
409
412
|
` : b}
|
|
410
413
|
${this.toolbar ? y`
|
|
@@ -417,7 +420,7 @@ var D = 1, O = 4, k = .5, A = .002, j = 3, M = .15, N = 320, P = 16, F = 160, I
|
|
|
417
420
|
?disabled=${this._scale <= D}
|
|
418
421
|
@click=${this.zoomOut}
|
|
419
422
|
>
|
|
420
|
-
<web-ui-icon size="16" .icon=${
|
|
423
|
+
<web-ui-icon size="16" .icon=${a}></web-ui-icon>
|
|
421
424
|
</web-ui-button>
|
|
422
425
|
<span class="wui-image-preview-scale">${Math.round(this._scale * 100)}%</span>
|
|
423
426
|
<web-ui-button
|
|
@@ -428,7 +431,7 @@ var D = 1, O = 4, k = .5, A = .002, j = 3, M = .15, N = 320, P = 16, F = 160, I
|
|
|
428
431
|
?disabled=${this._scale >= O}
|
|
429
432
|
@click=${this.zoomIn}
|
|
430
433
|
>
|
|
431
|
-
<web-ui-icon size="16" .icon=${
|
|
434
|
+
<web-ui-icon size="16" .icon=${i}></web-ui-icon>
|
|
432
435
|
</web-ui-button>
|
|
433
436
|
<web-ui-button
|
|
434
437
|
variant="glass"
|
|
@@ -438,7 +441,7 @@ var D = 1, O = 4, k = .5, A = .002, j = 3, M = .15, N = 320, P = 16, F = 160, I
|
|
|
438
441
|
?disabled=${this._scale <= D && !this._isPanned}
|
|
439
442
|
@click=${this.resetZoom}
|
|
440
443
|
>
|
|
441
|
-
<web-ui-icon size="16" .icon=${
|
|
444
|
+
<web-ui-icon size="16" .icon=${s}></web-ui-icon>
|
|
442
445
|
</web-ui-button>
|
|
443
446
|
</div>
|
|
444
447
|
` : b}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/image-preview/index.ts"],"sourcesContent":["import { html, LitElement, nothing, type PropertyValues, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { styleMap } from 'lit/directives/style-map.js'\n\nimport '@/components/button'\nimport '@/components/icon'\nimport glass from '@/assets/glass.css?inline'\nimport { lucideChevronLeft, lucideChevronRight, lucideMinus, lucidePlus, oouiClose, radixIconsReset } from '@/icons'\nimport { attachDragGesture, type DragGestureHandle } from '@/shared/gesture/drag-gesture'\nimport { clamp } from '@/shared/gesture/physics'\nimport {\n attachPinchGesture,\n type PinchGestureHandle,\n type PinchMoveInfo,\n type PinchStartInfo\n} from '@/shared/gesture/pinch-gesture'\nimport { defineNativeDialogPresence } from '@/shared/overlay/native-dialog-presence'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { getFallbackOverlayRoot } from '@/shared/overlay/overlay-root'\nimport { findNearestTheme, findRootTheme } from '@/shared/overlay/theme-overlay-scope'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\n\nimport style from './style.css?inline'\n\nexport interface ImagePreviewItem {\n src: string\n /** 无障碍替代文本;也用于图片加载失败时的回退展示。 */\n alt?: string\n}\n\nexport interface ImagePreviewOptions {\n images: ImagePreviewItem[]\n /** 初始图片索引,越界时钳制到有效区间。默认 0。 */\n index?: number\n /** 首尾循环切换。默认 true。 */\n loop?: boolean\n /** 用于解析最近 web-ui-theme 作用域的触发元素。 */\n target?: Element\n /** 显式挂载容器,优先级高于 target 和主题作用域。 */\n container?: HTMLElement\n /** 展示上一张 / 下一张按钮;仅在图片多于一张时渲染。默认 false。 */\n nav?: boolean\n /** 展示缩放工具条。默认 false。 */\n toolbar?: boolean\n /** 展示关闭按钮。默认 false。 */\n closable?: boolean\n /** 展示「当前 / 总数」指示器。默认 false。 */\n indicator?: boolean\n /**\n * 1x 且图片多于一张时,允许左右滑动切换图片:该手势接管整个拖拽,纵向位移被忽略\n * (不驱平移、不跟手),横向用于切图。放大后拖拽一律平移(纵横都跟)、不再切图。\n * 默认 false。\n */\n swipe?: boolean\n /** 打开期间不锁定页面滚动,语义与 `<web-ui-dialog>` 的 `noScrollLock` 一致。默认 false。 */\n noScrollLock?: boolean\n /** 点击图片以外的空白区域不关闭,语义与 `<web-ui-dialog>` 的 `noBackdropClose` 一致。默认 false。 */\n noBackdropClose?: boolean\n}\n\nexport interface ImagePreviewHandle {\n /** 当前图片索引;关闭后仍保留最后一次的值。 */\n readonly index: number\n /** 当前缩放倍率,区间 [1, 4];切换图片时重置为 1。 */\n readonly scale: number\n readonly images: readonly ImagePreviewItem[]\n /** 退场过渡结束、宿主已从 DOM 移除后兑现。 */\n readonly closed: Promise<void>\n next(): void\n prev(): void\n goTo(index: number): void\n zoomIn(): void\n zoomOut(): void\n resetZoom(): void\n close(): void\n}\n\ntype ResolvedImagePreviewOptions = Required<Omit<ImagePreviewOptions, 'target' | 'container'>>\n\nconst MIN_SCALE = 1\nconst MAX_SCALE = 4\nconst ZOOM_STEP = 0.5\n// 滚轮 deltaY(px) 到缩放的指数系数;每 120px 步进约放大 27%。\nconst WHEEL_ZOOM_SENSITIVITY = 0.002\n// 判定为拖拽(平移或滑动)而非点击的最小位移。\nconst PAN_MOVE_THRESHOLD = 3\n// 滑动切图的距离阈值 = 舞台宽度 × 该比例(取整),速度阈值任一满足即切图。\n// 用户决策:阈值参考舞台宽度百分比而非固定 px(48px 在 320px 舞台 ≈ 15%),\n// 不同舞台宽度下切图手感一致(窄屏更容易、宽屏要求更长距离)。\nconst SWIPE_DISTANCE_RATIO = 0.15\nconst SWIPE_VELOCITY = 320\n// 快速轻扫兜底:真实触摸事件流可能被合并/延迟稀释(CDP 取证:40px/167ms 的轻扫被\n// 100ms 速度窗口低估到 ~115-165px/s),单靠 320px/s 会把快速轻扫误判为回弹。方向明确\n// (位移达到小距离)且速度过半阈值时仍提交;速度取「窗口速度」与「整段位移平均速度」\n// 的较大者——后者对稀释的事件流鲁棒(CDP 复现下整段平均约 240px/s)。慢拖两种速度\n// 都趋近 0,不会误提交。\nconst SWIPE_FLICK_DISTANCE = 16\nconst SWIPE_FLICK_VELOCITY = 160\n// 滑入/回位的兜底时长:正常由 transform 的 transitionend 提前收尾;jsdom、无过渡\n// (prefers-reduced-motion)等场景没有 transitionend,按该时长兜底提交。\nconst SWIPE_SETTLE_MS = 320\n\n/**\n * 图像预览的内部宿主。对外只通过 `imagePreview()` 使用,因此不导出类、\n * 不注册 `HTMLElementTagNameMap`,避免形成声明式标签契约。\n *\n * 内部使用原生 `<dialog>.showModal()`,与 `<web-ui-dialog>` 共享\n * `native-dialog-presence` 与 `scroll-lock` 两个底层插件,而不是复用该组件:\n * 本组件需要自身铺满视口的 dialog(遮罩即 dialog 背景)与自有指针交互,\n * 与 dialog 组件的玻璃卡片 / 插槽契约不同源。\n */\n@customElement('web-ui-image-preview')\nclass WebUiImagePreview extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n @property({ attribute: false }) images: ImagePreviewItem[] = []\n @property({ attribute: false }) loop = true\n @property({ attribute: false }) nav = false\n @property({ attribute: false }) toolbar = false\n @property({ attribute: false }) closable = false\n @property({ attribute: false }) indicator = false\n @property({ attribute: false }) swipe = false\n @property({ attribute: false }) noScrollLock = false\n @property({ attribute: false }) noBackdropClose = false\n\n @state() private _open = false\n @state() private _index = 0\n @state() private _scale = MIN_SCALE\n @state() private _offsetX = 0\n @state() private _offsetY = 0\n /** 滑动跟手位移,只作用于左右方向;可叠加在平移位移之上。 */\n @state() private _swipeOffset = 0\n /** 滑入/回位过渡中:stage 挂 is-settling,启用轨道 transform 过渡。 */\n @state() private _swipeSettling = false\n @state() private _dragging = false\n\n /** 已加载完成的图片 src 集合;每张图首次 load/error 后淡入,切换回已加载图不再闪烁。 */\n private _loadedSources = new Set<string>()\n\n /** 相邻图方向:1 = 下一张(位于右侧),-1 = 上一张(位于左侧)。非响应式,随 _swipeOffset 一同更新。 */\n private _swipeDir: 1 | -1 | 0 = 0\n /** 当前滑动手势的起始时间戳;松手时与 endInfo.timeStamp 求整段平均速度(对稀释事件流鲁棒)。 */\n private _swipeStartTime = 0\n private _swipeSettleCommit = false\n private _swipeSettleDir: 1 | -1 = 1\n private _swipeSettleTimer: ReturnType<typeof setTimeout> | undefined\n\n private readonly _scrollLock = defineScrollLockLease().make()\n /*\n * 开启态浮层(issue #120 Block 1)。预览的 Escape 原本只走原生 <dialog> 的 cancel 事件,\n * 自己从不参与登记 —— 于是它既不是仲裁候选、又指望原生 cancel 兜底,而仲裁者一旦命中\n * 别的层就会 preventDefault 压掉那个 cancel:与任何已登记的层同时打开时,一次 Escape\n * 关掉的是下面那层,最上层的预览留在屏幕上。登记之后它与其它层同处一棵逻辑树。\n *\n * 没有惰性通道:预览没有 `no-escape-close` 这类策略,Escape 恒可关闭(与 `closable`\n * 只管关闭按钮一样,不表达「能否关闭」)。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this.close()\n })\n /** 当前开启会话的句柄;未开启时为 null。 */\n private _handle: OpenOverlayHandle | null = null\n private readonly _presence = defineNativeDialogPresence().make({\n getDialog: () => this.dialog,\n isConnected: () => this.isConnected,\n isOpen: () => this._open\n })\n\n private _dismissed = false\n private _gesture: DragGestureHandle | undefined\n private _swipeGesture = false\n private _panOriginX = 0\n private _panOriginY = 0\n private _dragged = false\n private _blankPointerDown = false\n private _pinch: PinchGestureHandle | undefined\n /** 双指手势起始倍率,以及起始两指中点所对应的图片局部(1x)坐标。 */\n private _pinchScale = MIN_SCALE\n private _pinchLocalX = 0\n private _pinchLocalY = 0\n\n private get dialog() {\n return this.shadowRoot?.querySelector('dialog') ?? null\n }\n\n private get _stage() {\n return this.shadowRoot?.querySelector<HTMLElement>('.wui-image-preview-stage') ?? null\n }\n\n private get _image() {\n return (\n this.shadowRoot?.querySelector<HTMLImageElement>(\n '.wui-image-preview-slide.is-current .wui-image-preview-image'\n ) ?? null\n )\n }\n\n get index(): number {\n return this._index\n }\n\n get scale(): number {\n return this._scale\n }\n\n configure(options: ResolvedImagePreviewOptions): void {\n this.images = options.images\n this.loop = options.loop\n this.nav = options.nav\n this.toolbar = options.toolbar\n this.closable = options.closable\n this.indicator = options.indicator\n this.swipe = options.swipe\n this.noScrollLock = options.noScrollLock\n this.noBackdropClose = options.noBackdropClose\n this._index = clamp(options.index, 0, Math.max(0, options.images.length - 1))\n }\n\n show(): void {\n if (this._open) return\n this._dismissed = false\n this._open = true\n }\n\n close(): void {\n if (!this._open) return\n this._endGesture()\n this._open = false\n }\n\n goTo(target: number): void {\n const count = this.images.length\n if (count === 0) return\n\n const next = this.loop ? ((target % count) + count) % count : clamp(target, 0, count - 1)\n if (next === this._index) return\n\n this._index = next\n this.resetZoom()\n // 轨道渲染所有图:目标图若已加载直接展示(无淡入闪烁),未加载由图自身 load 淡入。\n }\n\n next(): void {\n this.goTo(this._index + 1)\n }\n\n prev(): void {\n this.goTo(this._index - 1)\n }\n\n zoomIn(): void {\n this._setScale(this._scale + ZOOM_STEP)\n }\n\n zoomOut(): void {\n this._setScale(this._scale - ZOOM_STEP)\n }\n\n resetZoom(): void {\n if (this._scale === MIN_SCALE && this._offsetX === 0 && this._offsetY === 0) return\n this._scale = MIN_SCALE\n this._offsetX = 0\n this._offsetY = 0\n }\n\n override connectedCallback() {\n super.connectedCallback()\n // 重挂载对账:断连时 presence 与滚动锁已被 dispose/release,而 `_open` 未变化时\n // `updated()` 不会补跑任何 sync 分支。首次连接时 shadow 尚未渲染、`this.dialog`\n // 为 null,reconcile 内部直接返回;打开态的首次进入仍由 updated() 的\n // `props.has('_open')` 分支处理。\n this._presence.reconcile()\n this._syncScrollLock()\n this._attachPinch()\n this._reclaimIfOpen()\n }\n\n /*\n * 「重挂载恢复」的调用方一半:模块刻意不观察 DOM 连接状态,所以断连撤销登记之后,\n * 重连时仍有开启态就必须由组件重新声明。首次连接时 shadow 尚未渲染、`this.dialog`\n * 为 null,直接跳过;打开态的首次进入仍由 updated() 的 `props.has('_open')` 分支处理。\n */\n private _reclaimIfOpen() {\n if (!this._open || this._handle) return\n const dialog = this.dialog\n if (!dialog) return\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n }\n\n protected override firstUpdated() {\n this._attachPinch()\n }\n\n /**\n * 双指缩放手势挂载。`firstUpdated` 整个生命周期只跑一次,而断连时会 destroy\n * 并清空 `_pinch`,宿主被移出文档再接回后手势会永久失效——因此首连与重挂\n * 共用本方法,重挂时补挂。\n */\n private _attachPinch() {\n if (this._pinch) return\n const stage = this._stage\n if (!stage) return\n\n this._pinch = attachPinchGesture(stage, {\n onStart: this._handlePinchStart,\n onMove: this._handlePinchMove,\n onEnd: this._handlePinchEnd,\n onCancel: this._handlePinchCancel\n })\n }\n\n protected override updated(props: PropertyValues) {\n super.updated(props)\n if (!this.isConnected) return\n\n if (props.has('_open')) {\n this._presence.sync(this._open)\n // 原生 dialog 登记为开启态浮层:Escape 仲裁据此判出最内层(issue #120 Block 1)。\n const dialog = this.dialog\n if (dialog) {\n if (this._open) {\n // 同一面板重新 claim = 新的一次开启:旧会话整体作废。\n this._handle?.release()\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n } else {\n this._handle?.release()\n this._handle = null\n }\n }\n // 无原生 dialog 支持或 dialog 未能打开时不会收到 close 事件,这里补齐退场结算。\n if (!this._open && !this.dialog?.open) queueMicrotask(() => this._notifyDismissed())\n }\n if (props.has('_open') || props.has('noScrollLock')) this._syncScrollLock()\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._endGesture()\n this._pinch?.destroy()\n this._pinch = undefined\n this._presence.dispose()\n this._scrollLock.release()\n // 断连即撤销登记:层不留在全局注册表里,重连后由 _reclaimIfOpen 显式重新声明。\n this._handle?.release()\n this._handle = null\n }\n\n private _syncScrollLock() {\n this._scrollLock.sync(this._open && !this.noScrollLock)\n }\n\n private _notifyDismissed() {\n if (this._dismissed || this._open) return\n this._dismissed = true\n this.dispatchEvent(new CustomEvent('preview-dismissed'))\n }\n\n /**\n * 以锚点缩放:锚点是相对舞台中心的偏移,它覆盖的图片内容在缩放前后保持不动。\n * 滚轮传光标位置,两指传两指中点,工具栏与键盘传 (0, 0)(即视口中心)。\n */\n private _zoomAt(next: number, anchorX: number, anchorY: number) {\n const scale = clamp(next, MIN_SCALE, MAX_SCALE)\n if (scale === this._scale) return\n\n const ratio = scale / this._scale\n this._applyScale(scale, anchorX - (anchorX - this._offsetX) * ratio, anchorY - (anchorY - this._offsetY) * ratio)\n }\n\n private _applyScale(scale: number, offsetX: number, offsetY: number) {\n this._scale = scale\n if (scale === MIN_SCALE) {\n this._offsetX = 0\n this._offsetY = 0\n return\n }\n this._offsetX = offsetX\n this._offsetY = offsetY\n this._clampOffsets()\n }\n\n private _setScale(next: number) {\n this._zoomAt(next, 0, 0)\n }\n\n /** 舞台中心的视口坐标;所有缩放锚点都以它为原点。 */\n private _stageOrigin(): { x: number; y: number } {\n const stage = this._stage\n if (!stage) return { x: 0, y: 0 }\n const rect = stage.getBoundingClientRect()\n return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }\n }\n\n /**\n * 平移边界 = 图片与舞台在缩放后「尺寸差」的一半。\n *\n * 图片比舞台大时它是「能露出多少边缘」,比舞台小时它是「能在视口内移动多远」——\n * 两种情形是同一个绝对差,所以 1x 也能拖动,且图片永远拖不出视口。\n * 图片只受 max-width/max-height 收缩(不放大),因此 offsetWidth/offsetHeight\n * 就是绘制尺寸,不受 CSS transform 影响。\n */\n private _offsetBounds(): { x: number; y: number } {\n const stage = this._stage\n const image = this._image\n if (!stage || !image) return { x: 0, y: 0 }\n\n const width = image.offsetWidth\n const height = image.offsetHeight\n if (!width || !height) return { x: 0, y: 0 }\n\n return {\n x: Math.abs(width * this._scale - stage.clientWidth) / 2,\n y: Math.abs(height * this._scale - stage.clientHeight) / 2\n }\n }\n\n /** 当前是否存在位移。1x 也能平移,因此「重置缩放」是否可用不能只看倍率。 */\n private get _isPanned() {\n return this._offsetX !== 0 || this._offsetY !== 0\n }\n\n private _clampOffsets() {\n const bounds = this._offsetBounds()\n this._offsetX = clamp(this._offsetX, -bounds.x, bounds.x)\n this._offsetY = clamp(this._offsetY, -bounds.y, bounds.y)\n }\n\n /**\n * 空白 = dialog 本体或未被图片 / 控件覆盖的舞台区域。\n *\n * 只能在 pointerdown(指针捕获生效前)判定:一旦舞台通过 setPointerCapture\n * 取得捕获,后续 click 的 target 会被重定向到舞台,用它判断会把「点图片」\n * 误判成「点空白」而关闭浮层。\n */\n private _isBlank(target: EventTarget | null): boolean {\n return target === this.dialog || target === this._stage\n }\n\n private _handleImageSettled = (event: Event) => {\n const image = event.currentTarget as HTMLImageElement | null\n if (!image) return\n this._loadedSources.add(image.src)\n this.requestUpdate()\n }\n\n /*\n * 登记之后 Escape 由仲裁者接管(它在 capture 阶段 preventDefault,UA 因此不再派发原生\n * cancel),但本 handler **不可删**:任何到达 dialog 的 cancel 都要 preventDefault,\n * 把 top layer 保留到视觉退场结束,否则原生关闭会跳过退出动画。jsdom 与重挂载用例\n * 正是直接派发 cancel 来压这条断言。\n */\n private _handleCancel = (event: Event) => {\n // 保留 top layer 直到退出过渡完成,避免原生关闭跳过退出动画。\n event.preventDefault()\n this.close()\n }\n\n private _handleNativeClose = () => {\n this._notifyDismissed()\n }\n\n private _handleTransitionEnd = (event: TransitionEvent) => {\n this._presence.handleTransitionEnd(event)\n }\n\n private _handleKeydown = (event: KeyboardEvent) => {\n if (!this._open || event.defaultPrevented) return\n if (event.altKey || event.ctrlKey || event.metaKey) return\n\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n this.prev()\n break\n case 'ArrowRight':\n event.preventDefault()\n this.next()\n break\n case '+':\n case '=':\n event.preventDefault()\n this.zoomIn()\n break\n case '-':\n case '_':\n event.preventDefault()\n this.zoomOut()\n break\n case '0':\n event.preventDefault()\n this.resetZoom()\n break\n default:\n break\n }\n }\n\n private _handleWheel = (event: WheelEvent) => {\n if (!this._open) return\n\n event.preventDefault()\n // deltaMode 1 为按行滚动(Firefox),折算成像素后再套用同一系数。\n const delta = event.deltaMode === 1 ? event.deltaY * 16 : event.deltaY\n const origin = this._stageOrigin()\n this._zoomAt(\n this._scale * Math.exp(-delta * WHEEL_ZOOM_SENSITIVITY),\n event.clientX - origin.x,\n event.clientY - origin.y\n )\n }\n\n private _handleClick = (event: MouseEvent) => {\n if (!this._open) return\n\n const blank = this._blankPointerDown\n this._blankPointerDown = false\n if (!blank) return\n\n // detail 为 0 表示这次 click 不来自指针(键盘激活按钮、程序化 .click()),\n // 此时 pointerdown 起点可能是上一次指针交互的残留,不能据此判定遮罩点击。\n if (event.detail === 0) return\n\n // 拖拽(平移或滑动)结束后的 click 不再当作遮罩点击。\n if (this._dragged) {\n this._dragged = false\n return\n }\n if (this.noBackdropClose) return\n this.close()\n }\n\n private _handleDoubleClick = (event: MouseEvent) => {\n if (!this._open || event.target !== this._image) return\n if (this._scale > MIN_SCALE) this.resetZoom()\n else this._setScale(MAX_SCALE / 2)\n }\n\n /**\n * 双指接管时先中止可能已开始的单指平移/滑动,避免两套手势同时写 offset。\n * 手势起点在图片上的位置被记住,缩放与两指中点位移都以它为基准。\n */\n private _handlePinchStart = (info: PinchStartInfo) => {\n if (!this._open) return\n\n this._endGesture()\n this._dragging = true\n // 双指操作后浏览器不会补发 click,但混合输入的兼容 click 不应被当作遮罩点击。\n this._dragged = true\n\n const origin = this._stageOrigin()\n this._pinchScale = this._scale\n this._pinchLocalX = (info.centerX - origin.x - this._offsetX) / this._scale\n this._pinchLocalY = (info.centerY - origin.y - this._offsetY) / this._scale\n }\n\n private _handlePinchMove = (info: PinchMoveInfo) => {\n if (!this._open) return\n\n const scale = clamp(this._pinchScale * info.ratio, MIN_SCALE, MAX_SCALE)\n const origin = this._stageOrigin()\n this._applyScale(\n scale,\n info.centerX - origin.x - this._pinchLocalX * scale,\n info.centerY - origin.y - this._pinchLocalY * scale\n )\n }\n\n private _handlePinchEnd = () => {\n this._dragging = false\n }\n\n private _handlePinchCancel = () => {\n this._dragging = false\n }\n\n private _endGesture() {\n this._gesture?.destroy()\n this._gesture = undefined\n this._dragging = false\n this._cancelSwipeSettle()\n this._swipeDir = 0\n this._swipeOffset = 0\n }\n\n /** 轮播滑动的相邻图索引;loop 环绕,非 loop 越界返回 -1(表示没有可滑入的图)。 */\n private _adjacentIndex(dir: 1 | -1): number {\n const count = this.images.length\n if (count === 0) return -1\n if (this.loop) return (((this._index + dir) % count) + count) % count\n const target = this._index + dir\n return target >= 0 && target < count ? target : -1\n }\n\n /** 取消未完成的滑入/回位过渡(新手势、pointercancel、关闭等接管时)。 */\n private _cancelSwipeSettle() {\n if (this._swipeSettleTimer !== undefined) {\n clearTimeout(this._swipeSettleTimer)\n this._swipeSettleTimer = undefined\n }\n this._swipeSettling = false\n this._swipeSettleCommit = false\n this._swipeSettleDir = 1\n }\n\n /** 滑入/回位过渡结束:按目标提交索引切换,或复位到单图静止态。 */\n private _finishSwipeSettle() {\n if (this._swipeSettleTimer === undefined) return\n clearTimeout(this._swipeSettleTimer)\n this._swipeSettleTimer = undefined\n const commit = this._swipeSettleCommit\n const dir = this._swipeSettleDir\n this._swipeSettling = false\n this._swipeSettleCommit = false\n this._swipeDir = 0\n this._swipeOffset = 0\n if (commit) this._commitSwipeTo(dir)\n }\n\n /** 把当前索引切换到相邻图;相邻图在滑动期间已在 DOM 中呈现,提交后直接以已加载态展示,避免重复淡入闪烁。 */\n private _commitSwipeTo(dir: 1 | -1) {\n const target = this._adjacentIndex(dir)\n if (target < 0 || target === this._index) return\n this._index = target\n this.resetZoom()\n }\n\n /** 释放后的滑入/回位:把 _swipeOffset 从跟手位移过渡到目标位(越界滑入相邻图,否则回 0)。 */\n private _settleSwipe(deltaX: number, velocityX: number, endTime: number) {\n const dir = this._swipeDir\n const stageW = this._stage?.clientWidth ?? 0\n if (dir === 0 || stageW <= 0) {\n // 无横向位移或无布局(jsdom):直接复位。\n this._swipeOffset = 0\n return\n }\n const distanceThreshold = Math.max(1, Math.round(stageW * SWIPE_DISTANCE_RATIO))\n // 整段位移平均速度:事件流被合并/延迟稀释时,100ms 窗口速度只采到中后段而偏低,\n // 平均速度(整段位移 / 整段时间)对快速轻扫更鲁棒。合成事件时间戳相同/缺失时\n // 跨度不足 2ms,视为 0(与 drag-gesture 的速度守卫一致),慢拖不会误提交。\n const spanMs = endTime - this._swipeStartTime\n const fullSpanVelocity = spanMs >= 2 ? (deltaX / spanMs) * 1000 : 0\n const flickVelocity = Math.max(Math.abs(velocityX), Math.abs(fullSpanVelocity))\n const crossed =\n Math.abs(deltaX) >= distanceThreshold ||\n Math.abs(velocityX) >= SWIPE_VELOCITY ||\n (Math.abs(deltaX) >= SWIPE_FLICK_DISTANCE && flickVelocity >= SWIPE_FLICK_VELOCITY)\n const commit = crossed && this._adjacentIndex(dir) !== -1\n this._swipeSettling = true\n this._swipeSettleCommit = commit\n this._swipeSettleDir = dir\n // 过渡目标:越过阈值 → 轨道再滑一个舞台宽度让相邻图入位(相邻图本就在 ±100% 处,\n // 提交后新当前图回到轨道原点,视觉位置不变、无跳变);否则弹回原位。\n this._swipeOffset = commit ? -dir * stageW : 0\n this._swipeSettleTimer = setTimeout(() => this._finishSwipeSettle(), SWIPE_SETTLE_MS)\n }\n\n private _handleSwipeSettleEnd = (event: TransitionEvent) => {\n if (!this._swipeSettling || event.propertyName !== 'transform') return\n this._finishSwipeSettle()\n }\n\n private _panTo(deltaX: number, deltaY: number) {\n const bounds = this._offsetBounds()\n this._offsetX = clamp(this._panOriginX + deltaX, -bounds.x, bounds.x)\n this._offsetY = clamp(this._panOriginY + deltaY, -bounds.y, bounds.y)\n }\n\n /**\n * 平移与左右滑动共用 `attachDragGesture`:它把指针捕获推迟到确认拖拽之后,\n * 因此不会像立即捕获那样破坏图片自身的 click / dblclick 命中;\n * 拖拽过程中的窗口级监听同时兜住指针移出元素甚至移出视口的情况。\n *\n * 拖拽默认平移且不限方向,与倍率无关:放大后是查看被裁切的边缘,1x 是在视口内\n * 移动图片。唯一的例外是 `swipe` 独占 1x——未放大、开启 swipe 且多于一张图时,\n * 整个拖拽归切图(横向驱动轮播、纵向 delta 完全忽略,不做任何平移)。鼠标按住\n * 拖拽与单指拖拽走的是同一条 Pointer 路径。\n *\n * 监听挂在 dialog 而非舞台上:控件层里的按下不会经过舞台,挂在舞台会漏掉\n * 这些按下而留下过期的「起于空白」标记。\n */\n private _handlePointerDown = (event: PointerEvent) => {\n if (!this._open) return\n\n // 新手势接管:中止可能未完成的滑入/回位过渡并复位轮播状态。\n this._cancelSwipeSettle()\n this._swipeDir = 0\n this._swipeOffset = 0\n\n // 只有主指针左键才可能生成 click;副指针或其它键的按下不产生 click,\n // 若让它们覆盖标记,主指针随后释放时的 click 会拿副指针的命中结果误判遮罩点击。\n const primary = event.button === 0 && event.isPrimary !== false\n if (!primary) return\n\n // 指针捕获会把后续 click 的 target 重定向到捕获元素,因此在按下时(捕获生效前)\n // 记录真实命中元素,作为「是否起于空白区域」的唯一判据。\n this._blankPointerDown = this._isBlank(event.target)\n this._dragged = false\n\n const stage = this._stage\n if (!stage || (event.target !== stage && !stage.contains(event.target as Node))) return\n\n // swipe 独占 1x:只有未放大、开启 swipe 且多于一张图时,横向拖拽才归切图;\n // 其余情形拖拽一律平移,且不限方向(边界见 _offsetBounds)。\n this._swipeGesture = this._scale === MIN_SCALE && this.swipe && this.images.length > 1\n this._swipeStartTime = event.timeStamp\n this._panOriginX = this._offsetX\n this._panOriginY = this._offsetY\n\n this._gesture?.destroy()\n this._gesture = attachDragGesture(event, {\n // 不能用 axis: 'x':那会把以纵向为主的拖拽整体判定为「取消」,1x 下纵向就\n // 无法平移了。这里始终按 both 追踪,让 swipe 只接管横向分量。\n axis: 'both',\n threshold: PAN_MOVE_THRESHOLD,\n onMove: info => {\n if (this._swipeGesture) {\n // 横向跟手位移驱动轨道整体平移(单轨道轮播);方向随拖拽符号更新,\n // 轨道位移随 _swipeOffset 反转时自然换向。纵向 delta 完全忽略:\n // 该手势独占拖拽,不驱平移、不产生纵向跟手(放大态的 pan 不受影响)。\n this._swipeOffset = info.deltaX\n if (info.deltaX !== 0) this._swipeDir = info.deltaX < 0 ? 1 : -1\n } else this._panTo(info.deltaX, info.deltaY)\n },\n onEnd: info => {\n this._dragged = true\n this._dragging = false\n if (this._swipeGesture) this._settleSwipe(info.deltaX, info.velocityX, info.timeStamp)\n },\n onTap: () => {\n this._dragging = false\n },\n onCancel: () => {\n this._dragging = false\n this._cancelSwipeSettle()\n this._swipeDir = 0\n this._swipeOffset = 0\n }\n })\n this._dragging = true\n }\n\n override render() {\n const count = this.images.length\n const zoomed = this._scale > MIN_SCALE\n // 单轨道轮播(相邻图渲染):轨道只放当前图与左右相邻,当前图恒在轨道原点。\n // 相邻图用绝对定位放在 ±100%(= 舞台宽度)处,与当前图同尺寸同基线;loop\n // 环绕时首尾相邻恒在两侧,任何索引拖拽(含首尾环绕)相邻图都真实跟手,不会\n // 出现「越过轨道末端滑空」的空白(全量渲染轨道在 last→first 环绕时相邻图在\n // 远端,拖拽中间态必然空白)。\n // 轨道整体随 _swipeOffset 平移:松手越过阈值再滑一个舞台宽度让相邻图入位,\n // 否则弹回原点。图片自身只负责平移/缩放(swipe 只动轨道、不碰图片 transform,\n // 缩放锚点数学不变)。\n const prevIndex = count > 0 ? this._adjacentIndex(-1) : -1\n const nextIndex = count > 0 ? this._adjacentIndex(1) : -1\n const trackTransform = `translate3d(${this._swipeOffset}px, 0, 0)`\n // 缩放/平移只作用于当前图的 img:相邻 slide 永远 1x(scale(1)),放大态连纵向 pan\n // 也不跟随(pan 只动当前图),避免放大后相邻图被带大/带进视口「穿过来」。\n // 1x 非 swipe 的平移路径仍可纵向拖拽,相邻图共享 offsetY 保持行内对齐;swipe 生效\n // 期间纵向 delta 被忽略(offsetY 恒 0),相邻图同样不产生纵向跟手。\n const imageTransform = `translate3d(${this._offsetX}px, ${this._offsetY}px, 0) scale(${this._scale})`\n const neighborTransform = zoomed\n ? 'translate3d(0, 0, 0) scale(1)'\n : `translate3d(0, ${this._offsetY}px, 0) scale(1)`\n\n return html`\n <dialog\n aria-label=\"图片预览\"\n @cancel=${this._handleCancel}\n @close=${this._handleNativeClose}\n @pointerdown=${this._handlePointerDown}\n @click=${this._handleClick}\n @dblclick=${this._handleDoubleClick}\n @keydown=${this._handleKeydown}\n @wheel=${this._handleWheel}\n @transitionend=${this._handleTransitionEnd}\n >\n <div class=\"wui-image-preview-backdrop\" aria-hidden=\"true\"></div>\n <div class=\"wui-image-preview-content\">\n <div class=\"wui-image-preview-surface\">\n <div\n class=${classMap({\n 'wui-image-preview-stage': true,\n 'is-zoomed': zoomed,\n 'is-dragging': this._dragging,\n 'is-settling': this._swipeSettling\n })}\n >\n <div\n class=\"wui-image-preview-track\"\n style=${styleMap({ transform: trackTransform })}\n @transitionend=${this._handleSwipeSettleEnd}\n >\n ${\n prevIndex >= 0\n ? html`\n <div class=\"wui-image-preview-slide is-prev\" aria-hidden=\"true\">\n <img\n class=${classMap({\n 'wui-image-preview-image': true,\n 'is-loaded': this._loadedSources.has(this.images[prevIndex].src)\n })}\n src=${this.images[prevIndex].src}\n alt=${this.images[prevIndex].alt ?? ''}\n draggable=\"false\"\n style=${styleMap({ transform: neighborTransform })}\n @load=${this._handleImageSettled}\n @error=${this._handleImageSettled}\n />\n </div>\n `\n : nothing\n }\n ${\n count > 0\n ? html`\n <div class=\"wui-image-preview-slide is-current\">\n <img\n class=${classMap({\n 'wui-image-preview-image': true,\n 'is-loaded': this._loadedSources.has(this.images[this._index].src)\n })}\n src=${this.images[this._index].src}\n alt=${this.images[this._index].alt ?? ''}\n draggable=\"false\"\n style=${styleMap({ transform: imageTransform })}\n @load=${this._handleImageSettled}\n @error=${this._handleImageSettled}\n />\n </div>\n `\n : nothing\n }\n ${\n nextIndex >= 0\n ? html`\n <div class=\"wui-image-preview-slide is-next\" aria-hidden=\"true\">\n <img\n class=${classMap({\n 'wui-image-preview-image': true,\n 'is-loaded': this._loadedSources.has(this.images[nextIndex].src)\n })}\n src=${this.images[nextIndex].src}\n alt=${this.images[nextIndex].alt ?? ''}\n draggable=\"false\"\n style=${styleMap({ transform: neighborTransform })}\n @load=${this._handleImageSettled}\n @error=${this._handleImageSettled}\n />\n </div>\n `\n : nothing\n }\n </div>\n </div>\n </div>\n <div class=\"wui-image-preview-controls\">\n ${\n this.indicator\n ? html`<span class=\"wui-image-preview-counter wui-glass\" aria-live=\"polite\"\n >${this._index + 1} / ${count}</span\n >`\n : nothing\n }\n ${\n this.closable\n ? html`\n <web-ui-button\n class=\"wui-image-preview-close\"\n variant=\"glass\"\n icon\n size=\"36\"\n aria-label=\"关闭\"\n @click=${this.close}\n >\n <web-ui-icon size=\"16\" .icon=${oouiClose}></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n }\n ${\n this.nav && count > 1\n ? html`\n <web-ui-button\n class=\"wui-image-preview-nav wui-image-preview-nav-prev\"\n variant=\"glass\"\n icon\n size=\"44\"\n aria-label=\"上一张\"\n ?disabled=${!this.loop && this._index === 0}\n @click=${this.prev}\n >\n <web-ui-icon size=\"20\" .icon=${lucideChevronLeft}></web-ui-icon>\n </web-ui-button>\n <web-ui-button\n class=\"wui-image-preview-nav wui-image-preview-nav-next\"\n variant=\"glass\"\n icon\n size=\"44\"\n aria-label=\"下一张\"\n ?disabled=${!this.loop && this._index === count - 1}\n @click=${this.next}\n >\n <web-ui-icon size=\"20\" .icon=${lucideChevronRight}></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n }\n ${\n this.toolbar\n ? html`\n <div class=\"wui-image-preview-toolbar wui-glass\">\n <web-ui-button\n variant=\"glass\"\n icon\n size=\"30\"\n aria-label=\"缩小\"\n ?disabled=${this._scale <= MIN_SCALE}\n @click=${this.zoomOut}\n >\n <web-ui-icon size=\"16\" .icon=${lucideMinus}></web-ui-icon>\n </web-ui-button>\n <span class=\"wui-image-preview-scale\">${Math.round(this._scale * 100)}%</span>\n <web-ui-button\n variant=\"glass\"\n icon\n size=\"30\"\n aria-label=\"放大\"\n ?disabled=${this._scale >= MAX_SCALE}\n @click=${this.zoomIn}\n >\n <web-ui-icon size=\"16\" .icon=${lucidePlus}></web-ui-icon>\n </web-ui-button>\n <web-ui-button\n variant=\"glass\"\n icon\n size=\"30\"\n aria-label=\"重置缩放\"\n ?disabled=${this._scale <= MIN_SCALE && !this._isPanned}\n @click=${this.resetZoom}\n >\n <web-ui-icon size=\"16\" .icon=${radixIconsReset}></web-ui-icon>\n </web-ui-button>\n </div>\n `\n : nothing\n }\n </div>\n </div>\n </dialog>\n `\n }\n}\n\n/**\n * 打开一个命令式图片预览。\n *\n * 组件挂载到目标 `web-ui-theme` 的 overlay 容器(无主题作用域时回退到全局\n * fallback root),返回的句柄是唯一的控制入口。所有展示类选项默认关闭,\n * 默认只渲染图片本身。\n */\nexport function imagePreview(options: ImagePreviewOptions): ImagePreviewHandle {\n const images = (options.images ?? []).map(item => ({ src: item.src, alt: item.alt ?? '' }))\n if (images.length === 0) {\n throw new Error('[web-ui-image-preview] imagePreview requires at least one image')\n }\n\n const theme = options.target ? findNearestTheme(options.target) : findRootTheme()\n const container = options.container ?? theme?.getOverlayRoot() ?? getFallbackOverlayRoot()\n\n const element = document.createElement('web-ui-image-preview') as WebUiImagePreview\n element.configure({\n images,\n index: options.index ?? 0,\n loop: options.loop ?? true,\n nav: options.nav ?? false,\n toolbar: options.toolbar ?? false,\n closable: options.closable ?? false,\n indicator: options.indicator ?? false,\n swipe: options.swipe ?? false,\n noScrollLock: options.noScrollLock ?? false,\n noBackdropClose: options.noBackdropClose ?? false\n })\n\n let settle: (() => void) | undefined\n const closed = new Promise<void>(resolve => {\n settle = resolve\n })\n\n element.addEventListener(\n 'preview-dismissed',\n () => {\n element.remove()\n settle?.()\n settle = undefined\n },\n { once: true }\n )\n\n container.appendChild(element)\n element.show()\n\n return {\n get index() {\n return element.index\n },\n get scale() {\n return element.scale\n },\n get images() {\n return element.images\n },\n get closed() {\n return closed\n },\n next: () => element.next(),\n prev: () => element.prev(),\n goTo: (index: number) => element.goTo(index),\n zoomIn: () => element.zoomIn(),\n zoomOut: () => element.zoomOut(),\n resetZoom: () => element.resetZoom(),\n close: () => element.close()\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/image-preview/index.ts"],"sourcesContent":["import { html, LitElement, nothing, type PropertyValues, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { styleMap } from 'lit/directives/style-map.js'\n\nimport '@/components/button'\nimport '@/components/icon'\nimport glass from '@/assets/glass.css?inline'\nimport { lucideChevronLeft, lucideChevronRight, lucideMinus, lucidePlus, oouiClose, radixIconsReset } from '@/icons'\nimport { attachDragGesture, type DragGestureHandle } from '@/shared/gesture/drag-gesture'\nimport { clamp } from '@/shared/gesture/physics'\nimport {\n attachPinchGesture,\n type PinchGestureHandle,\n type PinchMoveInfo,\n type PinchStartInfo\n} from '@/shared/gesture/pinch-gesture'\nimport { defineNativeDialogPresence } from '@/shared/overlay/native-dialog-presence'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { getFallbackOverlayRoot } from '@/shared/overlay/overlay-root'\nimport { findNearestTheme, findRootTheme } from '@/shared/overlay/theme-overlay-scope'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\n\nimport style from './style.css?inline'\n\nexport interface ImagePreviewItem {\n src: string\n /** 无障碍替代文本;也用于图片加载失败时的回退展示。 */\n alt?: string\n}\n\nexport interface ImagePreviewOptions {\n images: ImagePreviewItem[]\n /** 初始图片索引,越界时钳制到有效区间。默认 0。 */\n index?: number\n /** 首尾循环切换。默认 true。 */\n loop?: boolean\n /** 用于解析最近 web-ui-theme 作用域的触发元素。 */\n target?: Element\n /** 显式挂载容器,优先级高于 target 和主题作用域。 */\n container?: HTMLElement\n /** 展示上一张 / 下一张按钮;仅在图片多于一张时渲染。默认 false。 */\n nav?: boolean\n /** 展示缩放工具条。默认 false。 */\n toolbar?: boolean\n /** 展示关闭按钮。默认 false。 */\n closable?: boolean\n /** 展示「当前 / 总数」指示器。默认 false。 */\n indicator?: boolean\n /**\n * 1x 且图片多于一张时,允许左右滑动切换图片:该手势接管整个拖拽,纵向位移被忽略\n * (不驱平移、不跟手),横向用于切图。放大后拖拽一律平移(纵横都跟)、不再切图。\n * 默认 false。\n */\n swipe?: boolean\n /** 打开期间不锁定页面滚动,语义与 `<web-ui-dialog>` 的 `noScrollLock` 一致。默认 false。 */\n noScrollLock?: boolean\n /** 点击图片以外的空白区域不关闭,语义与 `<web-ui-dialog>` 的 `noBackdropClose` 一致。默认 false。 */\n noBackdropClose?: boolean\n}\n\nexport interface ImagePreviewHandle {\n /** 当前图片索引;关闭后仍保留最后一次的值。 */\n readonly index: number\n /** 当前缩放倍率,区间 [1, 4];切换图片时重置为 1。 */\n readonly scale: number\n readonly images: readonly ImagePreviewItem[]\n /** 退场过渡结束、宿主已从 DOM 移除后兑现。 */\n readonly closed: Promise<void>\n next(): void\n prev(): void\n goTo(index: number): void\n zoomIn(): void\n zoomOut(): void\n resetZoom(): void\n close(): void\n}\n\ntype ResolvedImagePreviewOptions = Required<Omit<ImagePreviewOptions, 'target' | 'container'>>\n\nconst MIN_SCALE = 1\nconst MAX_SCALE = 4\nconst ZOOM_STEP = 0.5\n// 滚轮 deltaY(px) 到缩放的指数系数;每 120px 步进约放大 27%。\nconst WHEEL_ZOOM_SENSITIVITY = 0.002\n// 判定为拖拽(平移或滑动)而非点击的最小位移。\nconst PAN_MOVE_THRESHOLD = 3\n// 滑动切图的距离阈值 = 舞台宽度 × 该比例(取整),速度阈值任一满足即切图。\n// 用户决策:阈值参考舞台宽度百分比而非固定 px(48px 在 320px 舞台 ≈ 15%),\n// 不同舞台宽度下切图手感一致(窄屏更容易、宽屏要求更长距离)。\nconst SWIPE_DISTANCE_RATIO = 0.15\nconst SWIPE_VELOCITY = 320\n// 快速轻扫兜底:真实触摸事件流可能被合并/延迟稀释(CDP 取证:40px/167ms 的轻扫被\n// 100ms 速度窗口低估到 ~115-165px/s),单靠 320px/s 会把快速轻扫误判为回弹。方向明确\n// (位移达到小距离)且速度过半阈值时仍提交;速度取「窗口速度」与「整段位移平均速度」\n// 的较大者——后者对稀释的事件流鲁棒(CDP 复现下整段平均约 240px/s)。慢拖两种速度\n// 都趋近 0,不会误提交。\nconst SWIPE_FLICK_DISTANCE = 16\nconst SWIPE_FLICK_VELOCITY = 160\n// 滑入/回位的兜底时长:正常由 transform 的 transitionend 提前收尾;jsdom、无过渡\n// (prefers-reduced-motion)等场景没有 transitionend,按该时长兜底提交。\nconst SWIPE_SETTLE_MS = 320\n\n/**\n * 图像预览的内部宿主。对外只通过 `imagePreview()` 使用,因此不导出类、\n * 不注册 `HTMLElementTagNameMap`,避免形成声明式标签契约。\n *\n * 内部使用原生 `<dialog>.showModal()`,与 `<web-ui-dialog>` 共享\n * `native-dialog-presence` 与 `scroll-lock` 两个底层插件,而不是复用该组件:\n * 本组件需要自身铺满视口的 dialog(遮罩即 dialog 背景)与自有指针交互,\n * 与 dialog 组件的玻璃卡片 / 插槽契约不同源。\n */\n@customElement('web-ui-image-preview')\nclass WebUiImagePreview extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n @property({ attribute: false }) images: ImagePreviewItem[] = []\n @property({ attribute: false }) loop = true\n @property({ attribute: false }) nav = false\n @property({ attribute: false }) toolbar = false\n @property({ attribute: false }) closable = false\n @property({ attribute: false }) indicator = false\n @property({ attribute: false }) swipe = false\n @property({ attribute: false }) noScrollLock = false\n @property({ attribute: false }) noBackdropClose = false\n\n @state() private _open = false\n @state() private _index = 0\n @state() private _scale = MIN_SCALE\n @state() private _offsetX = 0\n @state() private _offsetY = 0\n /** 滑动跟手位移,只作用于左右方向;可叠加在平移位移之上。 */\n @state() private _swipeOffset = 0\n /** 滑入/回位过渡中:stage 挂 is-settling,启用轨道 transform 过渡。 */\n @state() private _swipeSettling = false\n @state() private _dragging = false\n\n /** 已加载完成的图片 src 集合;每张图首次 load/error 后淡入,切换回已加载图不再闪烁。 */\n private _loadedSources = new Set<string>()\n\n /** 相邻图方向:1 = 下一张(位于右侧),-1 = 上一张(位于左侧)。非响应式,随 _swipeOffset 一同更新。 */\n private _swipeDir: 1 | -1 | 0 = 0\n /** 当前滑动手势的起始时间戳;松手时与 endInfo.timeStamp 求整段平均速度(对稀释事件流鲁棒)。 */\n private _swipeStartTime = 0\n private _swipeSettleCommit = false\n private _swipeSettleDir: 1 | -1 = 1\n private _swipeSettleTimer: ReturnType<typeof setTimeout> | undefined\n\n private readonly _scrollLock = defineScrollLockLease().make()\n /*\n * 开启态浮层(issue #120 Block 1)。预览的 Escape 原本只走原生 <dialog> 的 cancel 事件,\n * 自己从不参与登记 —— 于是它既不是仲裁候选、又指望原生 cancel 兜底,而仲裁者一旦命中\n * 别的层就会 preventDefault 压掉那个 cancel:与任何已登记的层同时打开时,一次 Escape\n * 关掉的是下面那层,最上层的预览留在屏幕上。登记之后它与其它层同处一棵逻辑树。\n *\n * 没有惰性通道:预览没有 `no-escape-close` 这类策略,Escape 恒可关闭(与 `closable`\n * 只管关闭按钮一样,不表达「能否关闭」)。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this.close(),\n isConnected: () => this.isConnected\n })\n /** 当前开启会话的句柄;未开启时为 null。 */\n private _handle: OpenOverlayHandle | null = null\n private readonly _presence = defineNativeDialogPresence().make({\n getDialog: () => this.dialog,\n isConnected: () => this.isConnected,\n isOpen: () => this._open\n })\n\n private _dismissed = false\n private _gesture: DragGestureHandle | undefined\n private _swipeGesture = false\n private _panOriginX = 0\n private _panOriginY = 0\n private _dragged = false\n private _blankPointerDown = false\n private _pinch: PinchGestureHandle | undefined\n /** 双指手势起始倍率,以及起始两指中点所对应的图片局部(1x)坐标。 */\n private _pinchScale = MIN_SCALE\n private _pinchLocalX = 0\n private _pinchLocalY = 0\n\n private get dialog() {\n return this.shadowRoot?.querySelector('dialog') ?? null\n }\n\n private get _stage() {\n return this.shadowRoot?.querySelector<HTMLElement>('.wui-image-preview-stage') ?? null\n }\n\n private get _image() {\n return (\n this.shadowRoot?.querySelector<HTMLImageElement>(\n '.wui-image-preview-slide.is-current .wui-image-preview-image'\n ) ?? null\n )\n }\n\n get index(): number {\n return this._index\n }\n\n get scale(): number {\n return this._scale\n }\n\n configure(options: ResolvedImagePreviewOptions): void {\n this.images = options.images\n this.loop = options.loop\n this.nav = options.nav\n this.toolbar = options.toolbar\n this.closable = options.closable\n this.indicator = options.indicator\n this.swipe = options.swipe\n this.noScrollLock = options.noScrollLock\n this.noBackdropClose = options.noBackdropClose\n this._index = clamp(options.index, 0, Math.max(0, options.images.length - 1))\n }\n\n show(): void {\n if (this._open) return\n this._dismissed = false\n this._open = true\n }\n\n close(): void {\n if (!this._open) return\n this._endGesture()\n this._open = false\n }\n\n goTo(target: number): void {\n const count = this.images.length\n if (count === 0) return\n\n const next = this.loop ? ((target % count) + count) % count : clamp(target, 0, count - 1)\n if (next === this._index) return\n\n this._index = next\n this.resetZoom()\n // 轨道渲染所有图:目标图若已加载直接展示(无淡入闪烁),未加载由图自身 load 淡入。\n }\n\n next(): void {\n this.goTo(this._index + 1)\n }\n\n prev(): void {\n this.goTo(this._index - 1)\n }\n\n zoomIn(): void {\n this._setScale(this._scale + ZOOM_STEP)\n }\n\n zoomOut(): void {\n this._setScale(this._scale - ZOOM_STEP)\n }\n\n resetZoom(): void {\n if (this._scale === MIN_SCALE && this._offsetX === 0 && this._offsetY === 0) return\n this._scale = MIN_SCALE\n this._offsetX = 0\n this._offsetY = 0\n }\n\n override connectedCallback() {\n super.connectedCallback()\n // 重挂载对账:断连时 presence 与滚动锁已被 dispose/release,而 `_open` 未变化时\n // `updated()` 不会补跑任何 sync 分支。首次连接时 shadow 尚未渲染、`this.dialog`\n // 为 null,reconcile 内部直接返回;打开态的首次进入仍由 updated() 的\n // `props.has('_open')` 分支处理。\n this._presence.reconcile()\n this._syncScrollLock()\n this._attachPinch()\n this._reclaimIfOpen()\n }\n\n /*\n * 「重挂载恢复」的调用方一半:模块刻意不观察 DOM 连接状态,所以断连撤销登记之后,\n * 重连时仍有开启态就必须由组件重新声明。首次连接时 shadow 尚未渲染、`this.dialog`\n * 为 null,直接跳过;打开态的首次进入仍由 updated() 的 `props.has('_open')` 分支处理。\n */\n private _reclaimIfOpen() {\n if (!this._open || this._handle) return\n const dialog = this.dialog\n if (!dialog) return\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n }\n\n protected override firstUpdated() {\n this._attachPinch()\n }\n\n /**\n * 双指缩放手势挂载。`firstUpdated` 整个生命周期只跑一次,而断连时会 destroy\n * 并清空 `_pinch`,宿主被移出文档再接回后手势会永久失效——因此首连与重挂\n * 共用本方法,重挂时补挂。\n */\n private _attachPinch() {\n if (this._pinch) return\n const stage = this._stage\n if (!stage) return\n\n this._pinch = attachPinchGesture(stage, {\n onStart: this._handlePinchStart,\n onMove: this._handlePinchMove,\n onEnd: this._handlePinchEnd,\n onCancel: this._handlePinchCancel\n })\n }\n\n protected override updated(props: PropertyValues) {\n super.updated(props)\n if (!this.isConnected) return\n\n if (props.has('_open')) {\n this._presence.sync(this._open)\n // 原生 dialog 登记为开启态浮层:Escape 仲裁据此判出最内层(issue #120 Block 1)。\n const dialog = this.dialog\n if (dialog) {\n if (this._open) {\n // 同一面板重新 claim = 新的一次开启:旧会话整体作废。\n this._handle?.release()\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n } else {\n this._handle?.release()\n this._handle = null\n }\n }\n // 无原生 dialog 支持或 dialog 未能打开时不会收到 close 事件,这里补齐退场结算。\n if (!this._open && !this.dialog?.open) queueMicrotask(() => this._notifyDismissed())\n }\n if (props.has('_open') || props.has('noScrollLock')) this._syncScrollLock()\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._endGesture()\n this._pinch?.destroy()\n this._pinch = undefined\n this._presence.dispose()\n this._scrollLock.release()\n // 断连即撤销登记:层不留在全局注册表里,重连后由 _reclaimIfOpen 显式重新声明。\n this._handle?.release()\n this._handle = null\n }\n\n private _syncScrollLock() {\n this._scrollLock.sync(this._open && !this.noScrollLock)\n }\n\n private _notifyDismissed() {\n if (this._dismissed || this._open) return\n this._dismissed = true\n this.dispatchEvent(new CustomEvent('preview-dismissed'))\n }\n\n /**\n * 以锚点缩放:锚点是相对舞台中心的偏移,它覆盖的图片内容在缩放前后保持不动。\n * 滚轮传光标位置,两指传两指中点,工具栏与键盘传 (0, 0)(即视口中心)。\n */\n private _zoomAt(next: number, anchorX: number, anchorY: number) {\n const scale = clamp(next, MIN_SCALE, MAX_SCALE)\n if (scale === this._scale) return\n\n const ratio = scale / this._scale\n this._applyScale(scale, anchorX - (anchorX - this._offsetX) * ratio, anchorY - (anchorY - this._offsetY) * ratio)\n }\n\n private _applyScale(scale: number, offsetX: number, offsetY: number) {\n this._scale = scale\n if (scale === MIN_SCALE) {\n this._offsetX = 0\n this._offsetY = 0\n return\n }\n this._offsetX = offsetX\n this._offsetY = offsetY\n this._clampOffsets()\n }\n\n private _setScale(next: number) {\n this._zoomAt(next, 0, 0)\n }\n\n /** 舞台中心的视口坐标;所有缩放锚点都以它为原点。 */\n private _stageOrigin(): { x: number; y: number } {\n const stage = this._stage\n if (!stage) return { x: 0, y: 0 }\n const rect = stage.getBoundingClientRect()\n return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }\n }\n\n /**\n * 平移边界 = 图片与舞台在缩放后「尺寸差」的一半。\n *\n * 图片比舞台大时它是「能露出多少边缘」,比舞台小时它是「能在视口内移动多远」——\n * 两种情形是同一个绝对差,所以 1x 也能拖动,且图片永远拖不出视口。\n * 图片只受 max-width/max-height 收缩(不放大),因此 offsetWidth/offsetHeight\n * 就是绘制尺寸,不受 CSS transform 影响。\n */\n private _offsetBounds(): { x: number; y: number } {\n const stage = this._stage\n const image = this._image\n if (!stage || !image) return { x: 0, y: 0 }\n\n const width = image.offsetWidth\n const height = image.offsetHeight\n if (!width || !height) return { x: 0, y: 0 }\n\n return {\n x: Math.abs(width * this._scale - stage.clientWidth) / 2,\n y: Math.abs(height * this._scale - stage.clientHeight) / 2\n }\n }\n\n /** 当前是否存在位移。1x 也能平移,因此「重置缩放」是否可用不能只看倍率。 */\n private get _isPanned() {\n return this._offsetX !== 0 || this._offsetY !== 0\n }\n\n private _clampOffsets() {\n const bounds = this._offsetBounds()\n this._offsetX = clamp(this._offsetX, -bounds.x, bounds.x)\n this._offsetY = clamp(this._offsetY, -bounds.y, bounds.y)\n }\n\n /**\n * 空白 = dialog 本体或未被图片 / 控件覆盖的舞台区域。\n *\n * 只能在 pointerdown(指针捕获生效前)判定:一旦舞台通过 setPointerCapture\n * 取得捕获,后续 click 的 target 会被重定向到舞台,用它判断会把「点图片」\n * 误判成「点空白」而关闭浮层。\n */\n private _isBlank(target: EventTarget | null): boolean {\n return target === this.dialog || target === this._stage\n }\n\n private _handleImageSettled = (event: Event) => {\n const image = event.currentTarget as HTMLImageElement | null\n if (!image) return\n this._loadedSources.add(image.src)\n this.requestUpdate()\n }\n\n /*\n * 登记之后 Escape 由仲裁者接管(它在 capture 阶段 preventDefault,UA 因此不再派发原生\n * cancel),但本 handler **不可删**:任何到达 dialog 的 cancel 都要 preventDefault,\n * 把 top layer 保留到视觉退场结束,否则原生关闭会跳过退出动画。jsdom 与重挂载用例\n * 正是直接派发 cancel 来压这条断言。\n */\n private _handleCancel = (event: Event) => {\n // 保留 top layer 直到退出过渡完成,避免原生关闭跳过退出动画。\n event.preventDefault()\n this.close()\n }\n\n private _handleNativeClose = () => {\n this._notifyDismissed()\n }\n\n private _handleTransitionEnd = (event: TransitionEvent) => {\n this._presence.handleTransitionEnd(event)\n }\n\n private _handleKeydown = (event: KeyboardEvent) => {\n if (!this._open || event.defaultPrevented) return\n if (event.altKey || event.ctrlKey || event.metaKey) return\n\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n this.prev()\n break\n case 'ArrowRight':\n event.preventDefault()\n this.next()\n break\n case '+':\n case '=':\n event.preventDefault()\n this.zoomIn()\n break\n case '-':\n case '_':\n event.preventDefault()\n this.zoomOut()\n break\n case '0':\n event.preventDefault()\n this.resetZoom()\n break\n default:\n break\n }\n }\n\n private _handleWheel = (event: WheelEvent) => {\n if (!this._open) return\n\n event.preventDefault()\n // deltaMode 1 为按行滚动(Firefox),折算成像素后再套用同一系数。\n const delta = event.deltaMode === 1 ? event.deltaY * 16 : event.deltaY\n const origin = this._stageOrigin()\n this._zoomAt(\n this._scale * Math.exp(-delta * WHEEL_ZOOM_SENSITIVITY),\n event.clientX - origin.x,\n event.clientY - origin.y\n )\n }\n\n private _handleClick = (event: MouseEvent) => {\n if (!this._open) return\n\n const blank = this._blankPointerDown\n this._blankPointerDown = false\n if (!blank) return\n\n // detail 为 0 表示这次 click 不来自指针(键盘激活按钮、程序化 .click()),\n // 此时 pointerdown 起点可能是上一次指针交互的残留,不能据此判定遮罩点击。\n if (event.detail === 0) return\n\n // 拖拽(平移或滑动)结束后的 click 不再当作遮罩点击。\n if (this._dragged) {\n this._dragged = false\n return\n }\n if (this.noBackdropClose) return\n this.close()\n }\n\n private _handleDoubleClick = (event: MouseEvent) => {\n if (!this._open || event.target !== this._image) return\n if (this._scale > MIN_SCALE) this.resetZoom()\n else this._setScale(MAX_SCALE / 2)\n }\n\n /**\n * 双指接管时先中止可能已开始的单指平移/滑动,避免两套手势同时写 offset。\n * 手势起点在图片上的位置被记住,缩放与两指中点位移都以它为基准。\n */\n private _handlePinchStart = (info: PinchStartInfo) => {\n if (!this._open) return\n\n this._endGesture()\n this._dragging = true\n // 双指操作后浏览器不会补发 click,但混合输入的兼容 click 不应被当作遮罩点击。\n this._dragged = true\n\n const origin = this._stageOrigin()\n this._pinchScale = this._scale\n this._pinchLocalX = (info.centerX - origin.x - this._offsetX) / this._scale\n this._pinchLocalY = (info.centerY - origin.y - this._offsetY) / this._scale\n }\n\n private _handlePinchMove = (info: PinchMoveInfo) => {\n if (!this._open) return\n\n const scale = clamp(this._pinchScale * info.ratio, MIN_SCALE, MAX_SCALE)\n const origin = this._stageOrigin()\n this._applyScale(\n scale,\n info.centerX - origin.x - this._pinchLocalX * scale,\n info.centerY - origin.y - this._pinchLocalY * scale\n )\n }\n\n private _handlePinchEnd = () => {\n this._dragging = false\n }\n\n private _handlePinchCancel = () => {\n this._dragging = false\n }\n\n private _endGesture() {\n this._gesture?.destroy()\n this._gesture = undefined\n this._dragging = false\n this._cancelSwipeSettle()\n this._swipeDir = 0\n this._swipeOffset = 0\n }\n\n /** 轮播滑动的相邻图索引;loop 环绕,非 loop 越界返回 -1(表示没有可滑入的图)。 */\n private _adjacentIndex(dir: 1 | -1): number {\n const count = this.images.length\n if (count === 0) return -1\n if (this.loop) return (((this._index + dir) % count) + count) % count\n const target = this._index + dir\n return target >= 0 && target < count ? target : -1\n }\n\n /** 取消未完成的滑入/回位过渡(新手势、pointercancel、关闭等接管时)。 */\n private _cancelSwipeSettle() {\n if (this._swipeSettleTimer !== undefined) {\n clearTimeout(this._swipeSettleTimer)\n this._swipeSettleTimer = undefined\n }\n this._swipeSettling = false\n this._swipeSettleCommit = false\n this._swipeSettleDir = 1\n }\n\n /** 滑入/回位过渡结束:按目标提交索引切换,或复位到单图静止态。 */\n private _finishSwipeSettle() {\n if (this._swipeSettleTimer === undefined) return\n clearTimeout(this._swipeSettleTimer)\n this._swipeSettleTimer = undefined\n const commit = this._swipeSettleCommit\n const dir = this._swipeSettleDir\n this._swipeSettling = false\n this._swipeSettleCommit = false\n this._swipeDir = 0\n this._swipeOffset = 0\n if (commit) this._commitSwipeTo(dir)\n }\n\n /** 把当前索引切换到相邻图;相邻图在滑动期间已在 DOM 中呈现,提交后直接以已加载态展示,避免重复淡入闪烁。 */\n private _commitSwipeTo(dir: 1 | -1) {\n const target = this._adjacentIndex(dir)\n if (target < 0 || target === this._index) return\n this._index = target\n this.resetZoom()\n }\n\n /** 释放后的滑入/回位:把 _swipeOffset 从跟手位移过渡到目标位(越界滑入相邻图,否则回 0)。 */\n private _settleSwipe(deltaX: number, velocityX: number, endTime: number) {\n const dir = this._swipeDir\n const stageW = this._stage?.clientWidth ?? 0\n if (dir === 0 || stageW <= 0) {\n // 无横向位移或无布局(jsdom):直接复位。\n this._swipeOffset = 0\n return\n }\n const distanceThreshold = Math.max(1, Math.round(stageW * SWIPE_DISTANCE_RATIO))\n // 整段位移平均速度:事件流被合并/延迟稀释时,100ms 窗口速度只采到中后段而偏低,\n // 平均速度(整段位移 / 整段时间)对快速轻扫更鲁棒。合成事件时间戳相同/缺失时\n // 跨度不足 2ms,视为 0(与 drag-gesture 的速度守卫一致),慢拖不会误提交。\n const spanMs = endTime - this._swipeStartTime\n const fullSpanVelocity = spanMs >= 2 ? (deltaX / spanMs) * 1000 : 0\n const flickVelocity = Math.max(Math.abs(velocityX), Math.abs(fullSpanVelocity))\n const crossed =\n Math.abs(deltaX) >= distanceThreshold ||\n Math.abs(velocityX) >= SWIPE_VELOCITY ||\n (Math.abs(deltaX) >= SWIPE_FLICK_DISTANCE && flickVelocity >= SWIPE_FLICK_VELOCITY)\n const commit = crossed && this._adjacentIndex(dir) !== -1\n this._swipeSettling = true\n this._swipeSettleCommit = commit\n this._swipeSettleDir = dir\n // 过渡目标:越过阈值 → 轨道再滑一个舞台宽度让相邻图入位(相邻图本就在 ±100% 处,\n // 提交后新当前图回到轨道原点,视觉位置不变、无跳变);否则弹回原位。\n this._swipeOffset = commit ? -dir * stageW : 0\n this._swipeSettleTimer = setTimeout(() => this._finishSwipeSettle(), SWIPE_SETTLE_MS)\n }\n\n private _handleSwipeSettleEnd = (event: TransitionEvent) => {\n if (!this._swipeSettling || event.propertyName !== 'transform') return\n this._finishSwipeSettle()\n }\n\n private _panTo(deltaX: number, deltaY: number) {\n const bounds = this._offsetBounds()\n this._offsetX = clamp(this._panOriginX + deltaX, -bounds.x, bounds.x)\n this._offsetY = clamp(this._panOriginY + deltaY, -bounds.y, bounds.y)\n }\n\n /**\n * 平移与左右滑动共用 `attachDragGesture`:它把指针捕获推迟到确认拖拽之后,\n * 因此不会像立即捕获那样破坏图片自身的 click / dblclick 命中;\n * 拖拽过程中的窗口级监听同时兜住指针移出元素甚至移出视口的情况。\n *\n * 拖拽默认平移且不限方向,与倍率无关:放大后是查看被裁切的边缘,1x 是在视口内\n * 移动图片。唯一的例外是 `swipe` 独占 1x——未放大、开启 swipe 且多于一张图时,\n * 整个拖拽归切图(横向驱动轮播、纵向 delta 完全忽略,不做任何平移)。鼠标按住\n * 拖拽与单指拖拽走的是同一条 Pointer 路径。\n *\n * 监听挂在 dialog 而非舞台上:控件层里的按下不会经过舞台,挂在舞台会漏掉\n * 这些按下而留下过期的「起于空白」标记。\n */\n private _handlePointerDown = (event: PointerEvent) => {\n if (!this._open) return\n\n // 新手势接管:中止可能未完成的滑入/回位过渡并复位轮播状态。\n this._cancelSwipeSettle()\n this._swipeDir = 0\n this._swipeOffset = 0\n\n // 只有主指针左键才可能生成 click;副指针或其它键的按下不产生 click,\n // 若让它们覆盖标记,主指针随后释放时的 click 会拿副指针的命中结果误判遮罩点击。\n const primary = event.button === 0 && event.isPrimary !== false\n if (!primary) return\n\n // 指针捕获会把后续 click 的 target 重定向到捕获元素,因此在按下时(捕获生效前)\n // 记录真实命中元素,作为「是否起于空白区域」的唯一判据。\n this._blankPointerDown = this._isBlank(event.target)\n this._dragged = false\n\n const stage = this._stage\n if (!stage || (event.target !== stage && !stage.contains(event.target as Node))) return\n\n // swipe 独占 1x:只有未放大、开启 swipe 且多于一张图时,横向拖拽才归切图;\n // 其余情形拖拽一律平移,且不限方向(边界见 _offsetBounds)。\n this._swipeGesture = this._scale === MIN_SCALE && this.swipe && this.images.length > 1\n this._swipeStartTime = event.timeStamp\n this._panOriginX = this._offsetX\n this._panOriginY = this._offsetY\n\n this._gesture?.destroy()\n this._gesture = attachDragGesture(event, {\n // 不能用 axis: 'x':那会把以纵向为主的拖拽整体判定为「取消」,1x 下纵向就\n // 无法平移了。这里始终按 both 追踪,让 swipe 只接管横向分量。\n axis: 'both',\n threshold: PAN_MOVE_THRESHOLD,\n onMove: info => {\n if (this._swipeGesture) {\n // 横向跟手位移驱动轨道整体平移(单轨道轮播);方向随拖拽符号更新,\n // 轨道位移随 _swipeOffset 反转时自然换向。纵向 delta 完全忽略:\n // 该手势独占拖拽,不驱平移、不产生纵向跟手(放大态的 pan 不受影响)。\n this._swipeOffset = info.deltaX\n if (info.deltaX !== 0) this._swipeDir = info.deltaX < 0 ? 1 : -1\n } else this._panTo(info.deltaX, info.deltaY)\n },\n onEnd: info => {\n this._dragged = true\n this._dragging = false\n if (this._swipeGesture) this._settleSwipe(info.deltaX, info.velocityX, info.timeStamp)\n },\n onTap: () => {\n this._dragging = false\n },\n onCancel: () => {\n this._dragging = false\n this._cancelSwipeSettle()\n this._swipeDir = 0\n this._swipeOffset = 0\n }\n })\n this._dragging = true\n }\n\n override render() {\n const count = this.images.length\n const zoomed = this._scale > MIN_SCALE\n // 单轨道轮播(相邻图渲染):轨道只放当前图与左右相邻,当前图恒在轨道原点。\n // 相邻图用绝对定位放在 ±100%(= 舞台宽度)处,与当前图同尺寸同基线;loop\n // 环绕时首尾相邻恒在两侧,任何索引拖拽(含首尾环绕)相邻图都真实跟手,不会\n // 出现「越过轨道末端滑空」的空白(全量渲染轨道在 last→first 环绕时相邻图在\n // 远端,拖拽中间态必然空白)。\n // 轨道整体随 _swipeOffset 平移:松手越过阈值再滑一个舞台宽度让相邻图入位,\n // 否则弹回原点。图片自身只负责平移/缩放(swipe 只动轨道、不碰图片 transform,\n // 缩放锚点数学不变)。\n const prevIndex = count > 0 ? this._adjacentIndex(-1) : -1\n const nextIndex = count > 0 ? this._adjacentIndex(1) : -1\n const trackTransform = `translate3d(${this._swipeOffset}px, 0, 0)`\n // 缩放/平移只作用于当前图的 img:相邻 slide 永远 1x(scale(1)),放大态连纵向 pan\n // 也不跟随(pan 只动当前图),避免放大后相邻图被带大/带进视口「穿过来」。\n // 1x 非 swipe 的平移路径仍可纵向拖拽,相邻图共享 offsetY 保持行内对齐;swipe 生效\n // 期间纵向 delta 被忽略(offsetY 恒 0),相邻图同样不产生纵向跟手。\n const imageTransform = `translate3d(${this._offsetX}px, ${this._offsetY}px, 0) scale(${this._scale})`\n const neighborTransform = zoomed\n ? 'translate3d(0, 0, 0) scale(1)'\n : `translate3d(0, ${this._offsetY}px, 0) scale(1)`\n\n return html`\n <dialog\n aria-label=\"图片预览\"\n @cancel=${this._handleCancel}\n @close=${this._handleNativeClose}\n @pointerdown=${this._handlePointerDown}\n @click=${this._handleClick}\n @dblclick=${this._handleDoubleClick}\n @keydown=${this._handleKeydown}\n @wheel=${this._handleWheel}\n @transitionend=${this._handleTransitionEnd}\n >\n <div class=\"wui-image-preview-backdrop\" aria-hidden=\"true\"></div>\n <div class=\"wui-image-preview-content\">\n <div class=\"wui-image-preview-surface\">\n <div\n class=${classMap({\n 'wui-image-preview-stage': true,\n 'is-zoomed': zoomed,\n 'is-dragging': this._dragging,\n 'is-settling': this._swipeSettling\n })}\n >\n <div\n class=\"wui-image-preview-track\"\n style=${styleMap({ transform: trackTransform })}\n @transitionend=${this._handleSwipeSettleEnd}\n >\n ${\n prevIndex >= 0\n ? html`\n <div class=\"wui-image-preview-slide is-prev\" aria-hidden=\"true\">\n <img\n class=${classMap({\n 'wui-image-preview-image': true,\n 'is-loaded': this._loadedSources.has(this.images[prevIndex].src)\n })}\n src=${this.images[prevIndex].src}\n alt=${this.images[prevIndex].alt ?? ''}\n draggable=\"false\"\n style=${styleMap({ transform: neighborTransform })}\n @load=${this._handleImageSettled}\n @error=${this._handleImageSettled}\n />\n </div>\n `\n : nothing\n }\n ${\n count > 0\n ? html`\n <div class=\"wui-image-preview-slide is-current\">\n <img\n class=${classMap({\n 'wui-image-preview-image': true,\n 'is-loaded': this._loadedSources.has(this.images[this._index].src)\n })}\n src=${this.images[this._index].src}\n alt=${this.images[this._index].alt ?? ''}\n draggable=\"false\"\n style=${styleMap({ transform: imageTransform })}\n @load=${this._handleImageSettled}\n @error=${this._handleImageSettled}\n />\n </div>\n `\n : nothing\n }\n ${\n nextIndex >= 0\n ? html`\n <div class=\"wui-image-preview-slide is-next\" aria-hidden=\"true\">\n <img\n class=${classMap({\n 'wui-image-preview-image': true,\n 'is-loaded': this._loadedSources.has(this.images[nextIndex].src)\n })}\n src=${this.images[nextIndex].src}\n alt=${this.images[nextIndex].alt ?? ''}\n draggable=\"false\"\n style=${styleMap({ transform: neighborTransform })}\n @load=${this._handleImageSettled}\n @error=${this._handleImageSettled}\n />\n </div>\n `\n : nothing\n }\n </div>\n </div>\n </div>\n <div class=\"wui-image-preview-controls\">\n ${\n this.indicator\n ? html`<span class=\"wui-image-preview-counter wui-glass\" aria-live=\"polite\"\n >${this._index + 1} / ${count}</span\n >`\n : nothing\n }\n ${\n this.closable\n ? html`\n <web-ui-button\n class=\"wui-image-preview-close\"\n variant=\"glass\"\n icon\n size=\"36\"\n aria-label=\"关闭\"\n @click=${this.close}\n >\n <web-ui-icon size=\"16\" .icon=${oouiClose}></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n }\n ${\n this.nav && count > 1\n ? html`\n <web-ui-button\n class=\"wui-image-preview-nav wui-image-preview-nav-prev\"\n variant=\"glass\"\n icon\n size=\"44\"\n aria-label=\"上一张\"\n ?disabled=${!this.loop && this._index === 0}\n @click=${this.prev}\n >\n <web-ui-icon size=\"20\" .icon=${lucideChevronLeft}></web-ui-icon>\n </web-ui-button>\n <web-ui-button\n class=\"wui-image-preview-nav wui-image-preview-nav-next\"\n variant=\"glass\"\n icon\n size=\"44\"\n aria-label=\"下一张\"\n ?disabled=${!this.loop && this._index === count - 1}\n @click=${this.next}\n >\n <web-ui-icon size=\"20\" .icon=${lucideChevronRight}></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n }\n ${\n this.toolbar\n ? html`\n <div class=\"wui-image-preview-toolbar wui-glass\">\n <web-ui-button\n variant=\"glass\"\n icon\n size=\"30\"\n aria-label=\"缩小\"\n ?disabled=${this._scale <= MIN_SCALE}\n @click=${this.zoomOut}\n >\n <web-ui-icon size=\"16\" .icon=${lucideMinus}></web-ui-icon>\n </web-ui-button>\n <span class=\"wui-image-preview-scale\">${Math.round(this._scale * 100)}%</span>\n <web-ui-button\n variant=\"glass\"\n icon\n size=\"30\"\n aria-label=\"放大\"\n ?disabled=${this._scale >= MAX_SCALE}\n @click=${this.zoomIn}\n >\n <web-ui-icon size=\"16\" .icon=${lucidePlus}></web-ui-icon>\n </web-ui-button>\n <web-ui-button\n variant=\"glass\"\n icon\n size=\"30\"\n aria-label=\"重置缩放\"\n ?disabled=${this._scale <= MIN_SCALE && !this._isPanned}\n @click=${this.resetZoom}\n >\n <web-ui-icon size=\"16\" .icon=${radixIconsReset}></web-ui-icon>\n </web-ui-button>\n </div>\n `\n : nothing\n }\n </div>\n </div>\n </dialog>\n `\n }\n}\n\n/**\n * 打开一个命令式图片预览。\n *\n * 组件挂载到目标 `web-ui-theme` 的 overlay 容器(无主题作用域时回退到全局\n * fallback root),返回的句柄是唯一的控制入口。所有展示类选项默认关闭,\n * 默认只渲染图片本身。\n */\nexport function imagePreview(options: ImagePreviewOptions): ImagePreviewHandle {\n const images = (options.images ?? []).map(item => ({ src: item.src, alt: item.alt ?? '' }))\n if (images.length === 0) {\n throw new Error('[web-ui-image-preview] imagePreview requires at least one image')\n }\n\n const theme = options.target ? findNearestTheme(options.target) : findRootTheme()\n const container = options.container ?? theme?.getOverlayRoot() ?? getFallbackOverlayRoot()\n\n const element = document.createElement('web-ui-image-preview') as WebUiImagePreview\n element.configure({\n images,\n index: options.index ?? 0,\n loop: options.loop ?? true,\n nav: options.nav ?? false,\n toolbar: options.toolbar ?? false,\n closable: options.closable ?? false,\n indicator: options.indicator ?? false,\n swipe: options.swipe ?? false,\n noScrollLock: options.noScrollLock ?? false,\n noBackdropClose: options.noBackdropClose ?? false\n })\n\n let settle: (() => void) | undefined\n const closed = new Promise<void>(resolve => {\n settle = resolve\n })\n\n element.addEventListener(\n 'preview-dismissed',\n () => {\n element.remove()\n settle?.()\n settle = undefined\n },\n { once: true }\n )\n\n container.appendChild(element)\n element.show()\n\n return {\n get index() {\n return element.index\n },\n get scale() {\n return element.scale\n },\n get images() {\n return element.images\n },\n get closed() {\n return closed\n },\n next: () => element.next(),\n prev: () => element.prev(),\n goTo: (index: number) => element.goTo(index),\n zoomIn: () => element.zoomIn(),\n zoomOut: () => element.zoomOut(),\n resetZoom: () => element.resetZoom(),\n close: () => element.close()\n }\n}\n"],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ export * from './dropdown-divider';
|
|
|
14
14
|
export * from './dropdown-header';
|
|
15
15
|
export * from './dropdown-item';
|
|
16
16
|
export * from './dropdown';
|
|
17
|
+
export * from './editable-text';
|
|
17
18
|
export * from './empty';
|
|
18
19
|
export * from './icon';
|
|
19
20
|
export * from './image-preview';
|
package/dist/components/index.js
CHANGED
|
@@ -1,40 +1,41 @@
|
|
|
1
1
|
import { WebUiIcon as e } from "./icon/index.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
2
|
+
import { WebUiButton as t } from "./button/index.js";
|
|
3
|
+
import { WebUiInput as n } from "./input/index.js";
|
|
4
|
+
import { WebUiOption as r } from "./option/index.js";
|
|
5
|
+
import { WebUiAutocomplete as i } from "./autocomplete/index.js";
|
|
6
|
+
import { WebUiAvatar as a } from "./avatar/index.js";
|
|
7
|
+
import { WebUiBackTop as o } from "./back-top/index.js";
|
|
8
|
+
import { WebUiBadge as s } from "./badge/index.js";
|
|
9
|
+
import { WebUiButtonGroup as c } from "./button-group/index.js";
|
|
10
|
+
import { WebUiSvgDrawLines as l } from "./svg-draw-lines/index.js";
|
|
11
|
+
import { WebUiCheckbox as u } from "./checkbox/index.js";
|
|
12
|
+
import { WebUiCheckboxGroup as d } from "./checkbox-group/index.js";
|
|
13
|
+
import { WebUiCollapse as f } from "./collapse/index.js";
|
|
14
|
+
import { WebUiDropdownDivider as p } from "./dropdown-divider/index.js";
|
|
15
|
+
import { WebUiDropdownHeader as m } from "./dropdown-header/index.js";
|
|
16
|
+
import { WebUiDropdownItem as h } from "./dropdown-item/index.js";
|
|
17
|
+
import { WebUiContextMenu as g } from "./context-menu/index.js";
|
|
18
|
+
import { WebUiDialog as _ } from "./dialog/index.js";
|
|
19
|
+
import { WebUiDrawer as v } from "./drawer/index.js";
|
|
20
|
+
import { WebUiDropdown as y } from "./dropdown/index.js";
|
|
21
|
+
import { WebUiEditableText as b } from "./editable-text/index.js";
|
|
22
|
+
import { WebUiEmpty as x } from "./empty/index.js";
|
|
23
|
+
import { imagePreview as S } from "./image-preview/index.js";
|
|
24
|
+
import { WebUiInputNumber as C } from "./input-number/index.js";
|
|
25
|
+
import { WebUiLayout as w } from "./layout/index.js";
|
|
26
|
+
import { WebUiPopover as T } from "./popover/index.js";
|
|
27
|
+
import { WebUiRadio as E } from "./radio/index.js";
|
|
28
|
+
import { WebUiRadioGroup as D } from "./radio-group/index.js";
|
|
29
|
+
import { WebUiSegmented as O } from "./segmented/index.js";
|
|
30
|
+
import { WebUiSegmentedTrigger as k } from "./segmented-trigger/index.js";
|
|
31
|
+
import { WebUiSelect as A } from "./select/index.js";
|
|
32
|
+
import { WebUiSlider as j } from "./slider/index.js";
|
|
33
|
+
import { WebUiSpinner as M } from "./spinner/index.js";
|
|
34
|
+
import { WebUiTextarea as N } from "./textarea/index.js";
|
|
35
|
+
import { WebUiTheme as P } from "./theme/index.js";
|
|
36
|
+
import { toast as F } from "./toast/manager.js";
|
|
37
|
+
import { WebUiToast as I } from "./toast/toast.js";
|
|
37
38
|
import "./toast/index.js";
|
|
38
|
-
import { WebUiSwitch as
|
|
39
|
-
import { WebUiTooltip as
|
|
40
|
-
export {
|
|
39
|
+
import { WebUiSwitch as L } from "./switch/index.js";
|
|
40
|
+
import { WebUiTooltip as R } from "./tooltip/index.js";
|
|
41
|
+
export { i as WebUiAutocomplete, a as WebUiAvatar, o as WebUiBackTop, s as WebUiBadge, t as WebUiButton, c as WebUiButtonGroup, u as WebUiCheckbox, d as WebUiCheckboxGroup, f as WebUiCollapse, g as WebUiContextMenu, _ as WebUiDialog, v as WebUiDrawer, y as WebUiDropdown, p as WebUiDropdownDivider, m as WebUiDropdownHeader, h as WebUiDropdownItem, b as WebUiEditableText, x as WebUiEmpty, e as WebUiIcon, n as WebUiInput, C as WebUiInputNumber, w as WebUiLayout, r as WebUiOption, T as WebUiPopover, E as WebUiRadio, D as WebUiRadioGroup, O as WebUiSegmented, k as WebUiSegmentedTrigger, A as WebUiSelect, j as WebUiSlider, M as WebUiSpinner, l as WebUiSvgDrawLines, L as WebUiSwitch, N as WebUiTextarea, P as WebUiTheme, I as WebUiToast, R as WebUiTooltip, S as imagePreview, F as toast };
|
|
@@ -394,6 +394,8 @@ export declare class WebUiInput extends WebUiInput_base {
|
|
|
394
394
|
private handleClear;
|
|
395
395
|
private preventMouseDownBlur;
|
|
396
396
|
private focusInput;
|
|
397
|
+
focus(options?: FocusOptions): void;
|
|
398
|
+
blur(): void;
|
|
397
399
|
render(): import('lit').TemplateResult<1>;
|
|
398
400
|
readonly $events: {
|
|
399
401
|
input: Event;
|