@greypan/web-ui 10.0.0 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.CN.md +29 -0
  2. package/README.md +31 -0
  3. package/dist/components/autocomplete/index.js.map +1 -1
  4. package/dist/components/avatar/index.js +5 -5
  5. package/dist/components/avatar/index.js.map +1 -1
  6. package/dist/components/back-top/index.js.map +1 -1
  7. package/dist/components/badge/index.js +5 -5
  8. package/dist/components/button/index.js +5 -5
  9. package/dist/components/button/index.js.map +1 -1
  10. package/dist/components/checkbox/index.js.map +1 -1
  11. package/dist/components/context-menu/index.js +65 -65
  12. package/dist/components/dialog/index.js.map +1 -1
  13. package/dist/components/drawer/index.js.map +1 -1
  14. package/dist/components/dropdown-item/index.js.map +1 -1
  15. package/dist/components/editable-text/index.js +4 -4
  16. package/dist/components/empty/index.js.map +1 -1
  17. package/dist/components/image-preview/index.js.map +1 -1
  18. package/dist/components/input/index.js +5 -5
  19. package/dist/components/input/index.js.map +1 -1
  20. package/dist/components/input-number/index.js.map +1 -1
  21. package/dist/components/layout/index.js.map +1 -1
  22. package/dist/components/option/index.js.map +1 -1
  23. package/dist/components/select/index.js.map +1 -1
  24. package/dist/components/switch/index.js.map +1 -1
  25. package/dist/components/textarea/index.js +8 -8
  26. package/dist/components/textarea/index.js.map +1 -1
  27. package/dist/components/toast/toast.js.map +1 -1
  28. package/dist/scrollbars/index.js.map +1 -1
  29. package/dist/shared/overlay/overlay.js +33 -33
  30. package/dist/vite/index.d.ts +28 -0
  31. package/dist/vite/index.js +16 -0
  32. package/dist/vite/index.js.map +1 -0
  33. package/package.json +5 -1
  34. package/dist/node_modules/.pnpm/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js +0 -246
  35. package/dist/node_modules/.pnpm/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js.map +0 -1
  36. package/dist/node_modules/.pnpm/@floating-ui_dom@1.8.0/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +0 -324
  37. package/dist/node_modules/.pnpm/@floating-ui_dom@1.8.0/node_modules/@floating-ui/dom/dist/floating-ui.dom.js.map +0 -1
  38. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js +0 -100
  39. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js.map +0 -1
  40. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +0 -102
  41. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.js.map +0 -1
@@ -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 modalSurface from '@/assets/modal-surface.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(modalSurface), 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 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 if (this._open) {\n // presence.sync 内的 showModal() 把 dialog 提升进 top layer,和 dialog /\n // drawer 走同一条路径。dialog / drawer 上实测到的机制是:双击手势留下的活选区\n // 会被浏览器拿去和提升后的新布局重新解析,落进刚挂载的面板正文。脆弱的是\n // 「手势来源的活选区」,不是 dblclick 的判定窗口(把打开推迟 250ms 仍复现);\n // 选区由浏览器在 showModal() 调用内部生成,脚本没有任何 Selection API 调用,\n // 在 dblclick 上 preventDefault 也拦不住。\n //\n // 但**双击路径在本组件上不复现**:预览层自身是 user-select: none(见\n // style.css),重解析拿不到可落的文本。实测 native showModal() 刚返回时\n // 选区本就是 rangeCount === 0,既没有可见高亮,也没有残留的塌陷 range;\n // 且这与触发目标本身无关——换成一块可选中文本 div 当触发目标,仍是 0。\n //\n // 真正让这一行有意义的是另一条事实:showModal() 不会动打开前就已存在的选区。\n // 所以它清的是「先选中页面文本、再打开预览」这条路径,属防御性清理,同时让\n // 三个组件的打开行为保持一致(dialog / drawer 上这行是真修复,本组件上是\n // 兜底)。这里只清文档选区,不动 user-select。\n window.getSelection()?.removeAllRanges()\n }\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._markLoaded(image)\n this.requestUpdate()\n }\n\n /**\n * 登记加载完成的图片,键与 render 侧 `is-loaded` 的查询同源。\n *\n * 不能用 `image.src`:那是 IDL 属性,返回**相对 src 解析后的绝对 URL**,而查询用的是\n * `this.images[i].src`(调用方原始串)。相对 src 下两者永不相等 → `is-loaded` 永不\n * 施加 → 图片停在 `opacity: 0`,尽管 img 早已 `complete`。\n *\n * 所属下标由模板写在 `data-index` 上(与该 img 的 `src` 出自同一处),因此多图 /\n * lazy 渲染下不会错位。\n */\n private _markLoaded(image: HTMLImageElement): void {\n const src = this.images[Number(image.dataset.index)]?.src\n if (src !== undefined) this._loadedSources.add(src)\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 data-index=${prevIndex}\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 data-index=${this._index}\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 data-index=${nextIndex}\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 '@/components/button'\nimport '@/components/icon'\n\nimport { 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 glass from '@/assets/glass.css?inline'\nimport modalSurface from '@/assets/modal-surface.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(modalSurface), 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 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 if (this._open) {\n // presence.sync 内的 showModal() 把 dialog 提升进 top layer,和 dialog /\n // drawer 走同一条路径。dialog / drawer 上实测到的机制是:双击手势留下的活选区\n // 会被浏览器拿去和提升后的新布局重新解析,落进刚挂载的面板正文。脆弱的是\n // 「手势来源的活选区」,不是 dblclick 的判定窗口(把打开推迟 250ms 仍复现);\n // 选区由浏览器在 showModal() 调用内部生成,脚本没有任何 Selection API 调用,\n // 在 dblclick 上 preventDefault 也拦不住。\n //\n // 但**双击路径在本组件上不复现**:预览层自身是 user-select: none(见\n // style.css),重解析拿不到可落的文本。实测 native showModal() 刚返回时\n // 选区本就是 rangeCount === 0,既没有可见高亮,也没有残留的塌陷 range;\n // 且这与触发目标本身无关——换成一块可选中文本 div 当触发目标,仍是 0。\n //\n // 真正让这一行有意义的是另一条事实:showModal() 不会动打开前就已存在的选区。\n // 所以它清的是「先选中页面文本、再打开预览」这条路径,属防御性清理,同时让\n // 三个组件的打开行为保持一致(dialog / drawer 上这行是真修复,本组件上是\n // 兜底)。这里只清文档选区,不动 user-select。\n window.getSelection()?.removeAllRanges()\n }\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._markLoaded(image)\n this.requestUpdate()\n }\n\n /**\n * 登记加载完成的图片,键与 render 侧 `is-loaded` 的查询同源。\n *\n * 不能用 `image.src`:那是 IDL 属性,返回**相对 src 解析后的绝对 URL**,而查询用的是\n * `this.images[i].src`(调用方原始串)。相对 src 下两者永不相等 → `is-loaded` 永不\n * 施加 → 图片停在 `opacity: 0`,尽管 img 早已 `complete`。\n *\n * 所属下标由模板写在 `data-index` 上(与该 img 的 `src` 出自同一处),因此多图 /\n * lazy 渲染下不会错位。\n */\n private _markLoaded(image: HTMLImageElement): void {\n const src = this.images[Number(image.dataset.index)]?.src\n if (src !== undefined) this._loadedSources.add(src)\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 data-index=${prevIndex}\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 data-index=${this._index}\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 data-index=${nextIndex}\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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"}
@@ -8,8 +8,8 @@ import { createFieldId as s } from "../../shared/form-association/field-id.js";
8
8
  import c from "./style.js";
9
9
  import { LitElement as l, html as u, unsafeCSS as d } from "lit";
10
10
  import { customElement as f, property as p, state as m } from "lit/decorators.js";
11
- import { ifDefined as h } from "lit/directives/if-defined.js";
12
- import { classMap as g } from "lit/directives/class-map.js";
11
+ import { classMap as h } from "lit/directives/class-map.js";
12
+ import { ifDefined as g } from "lit/directives/if-defined.js";
13
13
  //#region src/components/input/index.ts
14
14
  var _ = class extends r(l) {
15
15
  constructor(...e) {
@@ -102,13 +102,13 @@ var _ = class extends r(l) {
102
102
  let e = this.clearable && this._value && !this.readonly;
103
103
  return u`
104
104
  <div class="wui-glass wui-input-inner" @click=${this.focusInput}>
105
- <slot name="prefix" class=${g({ empty: !this._hasPrefix })} @slotchange=${this._onSlotChange}></slot>
105
+ <slot name="prefix" class=${h({ empty: !this._hasPrefix })} @slotchange=${this._onSlotChange}></slot>
106
106
  <input
107
107
  id=${this._fieldId}
108
108
  type=${this.type}
109
109
  placeholder=${this.placeholder}
110
110
  name=${this.name}
111
- aria-label=${h(this.ariaLabel)}
111
+ aria-label=${g(this.ariaLabel)}
112
112
  ?disabled=${this._isDisabled}
113
113
  ?readonly=${this.readonly}
114
114
  ?required=${this.required}
@@ -128,7 +128,7 @@ var _ = class extends r(l) {
128
128
  >
129
129
  <web-ui-icon .icon=${n} size="14"></web-ui-icon>
130
130
  </web-ui-button>` : ""}
131
- <slot name="suffix" class=${g({ empty: !this._hasSuffix })} @slotchange=${this._onSlotChange}></slot>
131
+ <slot name="suffix" class=${h({ empty: !this._hasSuffix })} @slotchange=${this._onSlotChange}></slot>
132
132
  </div>
133
133
  `;
134
134
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/input/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { ifDefined } from 'lit/directives/if-defined.js'\n\nimport '@/components/icon'\nimport '@/components/button'\nimport glass from '@/assets/glass.css?inline'\nimport { jamCloseCircleF } from '@/icons'\nimport {\n FormAssociated,\n defineFormAssociation,\n forwardInputValidity,\n FormAssociationController\n} from '@/shared/form-association'\nimport { createFieldId } from '@/shared/form-association/field-id'\n\nimport style from './style.css?inline'\n\n@customElement('web-ui-input')\nexport class WebUiInput extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n @property({ type: String, reflect: true }) type = 'text'\n @property({ type: String, reflect: true }) placeholder = ''\n @property({ type: String, reflect: true }) name = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) readonly = false\n @property({ type: Boolean, reflect: true }) required = false\n @property({ type: Boolean, reflect: true }) clearable = false\n @property({ type: Boolean, reflect: true }) full = false\n @property({ type: Boolean, reflect: true }) borderless = false\n @property({ type: String, attribute: 'aria-label' }) override ariaLabel: string | null = null\n\n @state() private _value = ''\n @state() private _focused = false\n @state() private _hasPrefix = false\n @state() private _hasSuffix = false\n\n private readonly _fieldId = createFieldId('web-ui-input')\n\n @property({ type: String, reflect: true })\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 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 syncValidity: () => this._syncValidity()\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('_value')) {\n const input = this.shadowRoot?.querySelector('input')\n if (input && input.value !== this._value) {\n input.value = this._value\n }\n }\n this._syncValidity()\n this.toggleAttribute('focused', this._focused)\n }\n\n private _syncValidity() {\n const input = this.shadowRoot?.querySelector('input')\n const internals = this._formAssociation.getInternals()\n if (!internals || !input || typeof internals.setValidity !== 'function') return\n forwardInputValidity(\n internals,\n input,\n ['valueMissing', 'typeMismatch', 'patternMismatch', 'tooLong', 'tooShort'],\n this._isDisabled\n )\n }\n\n private _onSlotChange(e: Event) {\n if (!(e.target instanceof HTMLSlotElement)) return\n const slot = e.target\n const hasContent = slot.assignedElements().length > 0\n if (slot.name === 'prefix') this._hasPrefix = hasContent\n if (slot.name === 'suffix') this._hasSuffix = hasContent\n }\n\n private handleInput(e: Event) {\n if (this._isDisabled || this.readonly) return\n if (!(e.target instanceof HTMLInputElement)) return\n this._value = e.target.value\n this._formAssociation.sync()\n }\n\n // 原生 change 不 composed,被 shadow root 挡住;这里补发 composed 事件,\n // 兑现 $events/README 声明的公共 change 契约。\n private handleNativeChange(e: Event) {\n if (this._isDisabled || this.readonly) return\n if (!(e.target instanceof HTMLInputElement)) return\n this._value = e.target.value\n this._formAssociation.sync()\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n private handleFocus() {\n if (!this._isDisabled) this._focused = true\n }\n\n private handleBlur() {\n this._focused = false\n }\n\n private handleClear() {\n if (this._isDisabled || this.readonly) return\n this._value = ''\n this._formAssociation.sync()\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n }\n\n private preventMouseDownBlur(e: Event) {\n e.preventDefault()\n }\n\n private focusInput() {\n if (this._isDisabled) return\n this.shadowRoot?.querySelector('input')?.focus()\n }\n\n // 公共 focus/blur 与 textarea 对齐:宿主自身没有 tab 位(不可聚焦),\n // 不重定向到内部原生控件的话,调用方拿到的是一次空操作\n override focus(options?: FocusOptions) {\n if (this._isDisabled) return\n this.shadowRoot?.querySelector('input')?.focus(options)\n }\n\n override blur() {\n this.shadowRoot?.querySelector('input')?.blur()\n }\n\n /**\n * 公共 API:全选当前值。\n *\n * `disabled` 时与 `focus()` 一致不产生效果;原生控件未渲染时安全 no-op。\n */\n select() {\n if (this._isDisabled) return\n this.shadowRoot?.querySelector('input')?.select()\n }\n\n override render() {\n // readonly 下不可清除:清空按钮会修改值,与只读语义冲突\n const showClear = this.clearable && this._value && !this.readonly\n\n return html`\n <div class=\"wui-glass wui-input-inner\" @click=${this.focusInput}>\n <slot name=\"prefix\" class=${classMap({ empty: !this._hasPrefix })} @slotchange=${this._onSlotChange}></slot>\n <input\n id=${this._fieldId}\n type=${this.type}\n placeholder=${this.placeholder}\n name=${this.name}\n aria-label=${ifDefined(this.ariaLabel)}\n ?disabled=${this._isDisabled}\n ?readonly=${this.readonly}\n ?required=${this.required}\n .value=${this._value}\n @input=${this.handleInput}\n @change=${this.handleNativeChange}\n @focus=${this.handleFocus}\n @blur=${this.handleBlur}\n />\n ${\n showClear\n ? html`<web-ui-button\n icon\n variant=\"ghost\"\n size=\"20\"\n aria-label=\"清除\"\n @pointerdown=${this.preventMouseDownBlur}\n @click=${this.handleClear}\n >\n <web-ui-icon .icon=${jamCloseCircleF} size=\"14\"></web-ui-icon>\n </web-ui-button>`\n : ''\n }\n <slot name=\"suffix\" class=${classMap({ empty: !this._hasSuffix })} @slotchange=${this._onSlotChange}></slot>\n </div>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n focus: FocusEvent\n blur: FocusEvent\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-input': WebUiInput\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/input/index.ts"],"sourcesContent":["import '@/components/button'\nimport '@/components/icon'\n\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { classMap } from 'lit/directives/class-map.js'\nimport { ifDefined } from 'lit/directives/if-defined.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport { jamCloseCircleF } from '@/icons'\nimport {\n FormAssociated,\n defineFormAssociation,\n forwardInputValidity,\n FormAssociationController\n} from '@/shared/form-association'\nimport { createFieldId } from '@/shared/form-association/field-id'\n\nimport style from './style.css?inline'\n\n@customElement('web-ui-input')\nexport class WebUiInput extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n @property({ type: String, reflect: true }) type = 'text'\n @property({ type: String, reflect: true }) placeholder = ''\n @property({ type: String, reflect: true }) name = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) readonly = false\n @property({ type: Boolean, reflect: true }) required = false\n @property({ type: Boolean, reflect: true }) clearable = false\n @property({ type: Boolean, reflect: true }) full = false\n @property({ type: Boolean, reflect: true }) borderless = false\n @property({ type: String, attribute: 'aria-label' }) override ariaLabel: string | null = null\n\n @state() private _value = ''\n @state() private _focused = false\n @state() private _hasPrefix = false\n @state() private _hasSuffix = false\n\n private readonly _fieldId = createFieldId('web-ui-input')\n\n @property({ type: String, reflect: true })\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 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 syncValidity: () => this._syncValidity()\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('_value')) {\n const input = this.shadowRoot?.querySelector('input')\n if (input && input.value !== this._value) {\n input.value = this._value\n }\n }\n this._syncValidity()\n this.toggleAttribute('focused', this._focused)\n }\n\n private _syncValidity() {\n const input = this.shadowRoot?.querySelector('input')\n const internals = this._formAssociation.getInternals()\n if (!internals || !input || typeof internals.setValidity !== 'function') return\n forwardInputValidity(\n internals,\n input,\n ['valueMissing', 'typeMismatch', 'patternMismatch', 'tooLong', 'tooShort'],\n this._isDisabled\n )\n }\n\n private _onSlotChange(e: Event) {\n if (!(e.target instanceof HTMLSlotElement)) return\n const slot = e.target\n const hasContent = slot.assignedElements().length > 0\n if (slot.name === 'prefix') this._hasPrefix = hasContent\n if (slot.name === 'suffix') this._hasSuffix = hasContent\n }\n\n private handleInput(e: Event) {\n if (this._isDisabled || this.readonly) return\n if (!(e.target instanceof HTMLInputElement)) return\n this._value = e.target.value\n this._formAssociation.sync()\n }\n\n // 原生 change 不 composed,被 shadow root 挡住;这里补发 composed 事件,\n // 兑现 $events/README 声明的公共 change 契约。\n private handleNativeChange(e: Event) {\n if (this._isDisabled || this.readonly) return\n if (!(e.target instanceof HTMLInputElement)) return\n this._value = e.target.value\n this._formAssociation.sync()\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n private handleFocus() {\n if (!this._isDisabled) this._focused = true\n }\n\n private handleBlur() {\n this._focused = false\n }\n\n private handleClear() {\n if (this._isDisabled || this.readonly) return\n this._value = ''\n this._formAssociation.sync()\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n }\n\n private preventMouseDownBlur(e: Event) {\n e.preventDefault()\n }\n\n private focusInput() {\n if (this._isDisabled) return\n this.shadowRoot?.querySelector('input')?.focus()\n }\n\n // 公共 focus/blur 与 textarea 对齐:宿主自身没有 tab 位(不可聚焦),\n // 不重定向到内部原生控件的话,调用方拿到的是一次空操作\n override focus(options?: FocusOptions) {\n if (this._isDisabled) return\n this.shadowRoot?.querySelector('input')?.focus(options)\n }\n\n override blur() {\n this.shadowRoot?.querySelector('input')?.blur()\n }\n\n /**\n * 公共 API:全选当前值。\n *\n * `disabled` 时与 `focus()` 一致不产生效果;原生控件未渲染时安全 no-op。\n */\n select() {\n if (this._isDisabled) return\n this.shadowRoot?.querySelector('input')?.select()\n }\n\n override render() {\n // readonly 下不可清除:清空按钮会修改值,与只读语义冲突\n const showClear = this.clearable && this._value && !this.readonly\n\n return html`\n <div class=\"wui-glass wui-input-inner\" @click=${this.focusInput}>\n <slot name=\"prefix\" class=${classMap({ empty: !this._hasPrefix })} @slotchange=${this._onSlotChange}></slot>\n <input\n id=${this._fieldId}\n type=${this.type}\n placeholder=${this.placeholder}\n name=${this.name}\n aria-label=${ifDefined(this.ariaLabel)}\n ?disabled=${this._isDisabled}\n ?readonly=${this.readonly}\n ?required=${this.required}\n .value=${this._value}\n @input=${this.handleInput}\n @change=${this.handleNativeChange}\n @focus=${this.handleFocus}\n @blur=${this.handleBlur}\n />\n ${\n showClear\n ? html`<web-ui-button\n icon\n variant=\"ghost\"\n size=\"20\"\n aria-label=\"清除\"\n @pointerdown=${this.preventMouseDownBlur}\n @click=${this.handleClear}\n >\n <web-ui-icon .icon=${jamCloseCircleF} size=\"14\"></web-ui-icon>\n </web-ui-button>`\n : ''\n }\n <slot name=\"suffix\" class=${classMap({ empty: !this._hasSuffix })} @slotchange=${this._onSlotChange}></slot>\n </div>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n focus: FocusEvent\n blur: FocusEvent\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-input': WebUiInput\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/input-number/index.ts"],"sourcesContent":["import { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport '@/components/icon'\nimport glass from '@/assets/glass.css?inline'\nimport { lucideMinus, lucidePlus } from '@/icons'\nimport {\n FormAssociated,\n defineFormAssociation,\n forwardInputValidity,\n FormAssociationController\n} from '@/shared/form-association'\nimport { createFieldId } from '@/shared/form-association/field-id'\nimport { normalizeNumber } from '@/shared/normalize'\n\nimport style from './style.css?inline'\n\n@customElement('web-ui-input-number')\nexport class WebUiInputNumber extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n @property({ type: String, reflect: true }) placeholder = ''\n @property({ type: String, reflect: true }) name = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) readonly = false\n @property({ type: Boolean, reflect: true }) required = false\n @property({ type: Number, reflect: true })\n get precision(): number {\n return this._precision\n }\n // 精度变化时重算现有值,避免浮点累积误差\n set precision(v: number) {\n const old = this._precision\n this._precision = v\n const current = this.clamp(this.round(this._value))\n if (current !== this._value) {\n this._value = current\n this._formAssociation.sync()\n }\n this.requestUpdate('precision', old)\n }\n\n @state() private _value = 0\n @state() private _min = 0\n @state() private _max = Infinity\n @state() private _step = 1\n @state() private _focused = false\n private _precision = 0\n private readonly _fieldId = createFieldId('web-ui-input-number')\n\n @property({ type: Number, reflect: true })\n get value(): number {\n return this._value\n }\n\n set value(v: number) {\n const clamped = this.clamp(this.round(v))\n const old = this._value\n this._value = clamped\n this._formAssociation.sync()\n this.requestUpdate('value', old)\n }\n\n private get _isDisabled(): boolean {\n return this.disabled || this._formAssociation.isFormDisabled()\n }\n\n @property({ type: Number, reflect: true })\n get min(): number {\n return this._min\n }\n\n set min(v: unknown) {\n this._min = normalizeNumber(v, -Infinity, Infinity, 0)\n }\n\n @property({ type: Number, reflect: true })\n get max(): number {\n return this._max\n }\n\n set max(v: unknown) {\n this._max = normalizeNumber(v, -Infinity, Infinity, Infinity)\n }\n\n @property({ type: Number, reflect: true })\n get step(): number {\n return this._step\n }\n\n // 步进值,默认 1;若声明了 precision 则以 precision 退化步进\n set step(v: unknown) {\n this._step = normalizeNumber(v, 0, Infinity, 1)\n }\n\n private get _effectiveStep(): number {\n return this._step\n }\n\n private get atMin(): boolean {\n return this._value <= this._min\n }\n\n private get atMax(): boolean {\n return this._value >= this._max\n }\n\n private readonly _formAssociation = defineFormAssociation<number>({\n host: this,\n getState: () => this._value,\n setState: value => {\n this.value = value\n },\n getFormValue: () => String(this._value),\n getFormState: () => String(this._value),\n restoreState: state => {\n if (typeof state !== 'string') return\n const value = Number(state)\n if (Number.isFinite(value)) this.value = value\n },\n syncValidity: () => this._syncValidity()\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n override updated() {\n this._syncValidity()\n this.toggleAttribute('focused', this._focused)\n }\n\n private _syncValidity() {\n const input = this.shadowRoot?.querySelector('input')\n const internals = this._formAssociation.getInternals()\n if (!internals || !input || typeof internals.setValidity !== 'function') return\n forwardInputValidity(\n internals,\n input,\n ['valueMissing', 'rangeUnderflow', 'rangeOverflow', 'stepMismatch'],\n this._isDisabled\n )\n }\n\n private clamp(v: number): number {\n return Math.min(this._max, Math.max(this._min, v))\n }\n\n private round(v: number): number {\n const f = 10 ** this.precision\n return Math.round(v * f) / f\n }\n\n private setValueAndNotify(v: number) {\n this.value = this.clamp(this.round(v))\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n }\n\n private handleStep(direction: 1 | -1) {\n if (this._isDisabled || this.readonly) return\n this.setValueAndNotify(this._value + direction * this._effectiveStep)\n }\n\n private handleInput(e: Event) {\n if (this.readonly) return\n if (!(e.target instanceof HTMLInputElement)) return\n const raw = e.target.value\n if (raw === '' || raw === '-') return\n this._value = this.clamp(this.round(Number(raw)))\n this._formAssociation.sync()\n this._syncValidity()\n }\n\n // 原生 change 不 composed,被 shadow root 挡住;文本失焦提交时补发 composed 事件,\n // 与 ArrowUp/ArrowDown 的 change 行为对齐。\n private handleNativeChange(e: Event) {\n if (this.readonly) return\n if (!(e.target instanceof HTMLInputElement)) return\n const raw = e.target.value\n if (raw === '' || raw === '-') return\n this._value = this.clamp(this.round(Number(raw)))\n this._formAssociation.sync()\n this._syncValidity()\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n private handleKeydown(e: KeyboardEvent) {\n if (this._isDisabled || this.readonly) return\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n this.setValueAndNotify(this._value + this._effectiveStep)\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n this.setValueAndNotify(this._value - this._effectiveStep)\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n }\n\n private handleFocus() {\n if (!this._isDisabled) this._focused = true\n }\n\n private handleBlur() {\n this._focused = false\n }\n\n override render() {\n return html`\n <div class=\"wui-glass wui-input-inner\">\n <button\n class=\"num-btn\"\n aria-label=\"Decrease\"\n ?disabled=${this._isDisabled || this.readonly || this.atMin}\n @click=${() => this.handleStep(-1)}\n >\n <web-ui-icon .icon=${lucideMinus}></web-ui-icon>\n </button>\n <input\n id=${this._fieldId}\n type=\"number\"\n placeholder=${this.placeholder}\n name=${this.name}\n .value=${String(this._value)}\n ?disabled=${this._isDisabled}\n ?readonly=${this.readonly}\n ?required=${this.required}\n @input=${this.handleInput}\n @change=${this.handleNativeChange}\n @keydown=${this.handleKeydown}\n @focus=${this.handleFocus}\n @blur=${this.handleBlur}\n />\n <button\n class=\"num-btn\"\n aria-label=\"Increase\"\n ?disabled=${this._isDisabled || this.readonly || this.atMax}\n @click=${() => this.handleStep(1)}\n >\n <web-ui-icon .icon=${lucidePlus}></web-ui-icon>\n </button>\n </div>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-input-number': WebUiInputNumber\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/input-number/index.ts"],"sourcesContent":["import '@/components/icon'\n\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport { lucideMinus, lucidePlus } from '@/icons'\nimport {\n FormAssociated,\n defineFormAssociation,\n forwardInputValidity,\n FormAssociationController\n} from '@/shared/form-association'\nimport { createFieldId } from '@/shared/form-association/field-id'\nimport { normalizeNumber } from '@/shared/normalize'\n\nimport style from './style.css?inline'\n\n@customElement('web-ui-input-number')\nexport class WebUiInputNumber extends FormAssociated(LitElement) {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n @property({ type: String, reflect: true }) placeholder = ''\n @property({ type: String, reflect: true }) name = ''\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true }) readonly = false\n @property({ type: Boolean, reflect: true }) required = false\n @property({ type: Number, reflect: true })\n get precision(): number {\n return this._precision\n }\n // 精度变化时重算现有值,避免浮点累积误差\n set precision(v: number) {\n const old = this._precision\n this._precision = v\n const current = this.clamp(this.round(this._value))\n if (current !== this._value) {\n this._value = current\n this._formAssociation.sync()\n }\n this.requestUpdate('precision', old)\n }\n\n @state() private _value = 0\n @state() private _min = 0\n @state() private _max = Infinity\n @state() private _step = 1\n @state() private _focused = false\n private _precision = 0\n private readonly _fieldId = createFieldId('web-ui-input-number')\n\n @property({ type: Number, reflect: true })\n get value(): number {\n return this._value\n }\n\n set value(v: number) {\n const clamped = this.clamp(this.round(v))\n const old = this._value\n this._value = clamped\n this._formAssociation.sync()\n this.requestUpdate('value', old)\n }\n\n private get _isDisabled(): boolean {\n return this.disabled || this._formAssociation.isFormDisabled()\n }\n\n @property({ type: Number, reflect: true })\n get min(): number {\n return this._min\n }\n\n set min(v: unknown) {\n this._min = normalizeNumber(v, -Infinity, Infinity, 0)\n }\n\n @property({ type: Number, reflect: true })\n get max(): number {\n return this._max\n }\n\n set max(v: unknown) {\n this._max = normalizeNumber(v, -Infinity, Infinity, Infinity)\n }\n\n @property({ type: Number, reflect: true })\n get step(): number {\n return this._step\n }\n\n // 步进值,默认 1;若声明了 precision 则以 precision 退化步进\n set step(v: unknown) {\n this._step = normalizeNumber(v, 0, Infinity, 1)\n }\n\n private get _effectiveStep(): number {\n return this._step\n }\n\n private get atMin(): boolean {\n return this._value <= this._min\n }\n\n private get atMax(): boolean {\n return this._value >= this._max\n }\n\n private readonly _formAssociation = defineFormAssociation<number>({\n host: this,\n getState: () => this._value,\n setState: value => {\n this.value = value\n },\n getFormValue: () => String(this._value),\n getFormState: () => String(this._value),\n restoreState: state => {\n if (typeof state !== 'string') return\n const value = Number(state)\n if (Number.isFinite(value)) this.value = value\n },\n syncValidity: () => this._syncValidity()\n }).make()\n\n private readonly _formAssociationController = new FormAssociationController(this, this._formAssociation)\n\n override updated() {\n this._syncValidity()\n this.toggleAttribute('focused', this._focused)\n }\n\n private _syncValidity() {\n const input = this.shadowRoot?.querySelector('input')\n const internals = this._formAssociation.getInternals()\n if (!internals || !input || typeof internals.setValidity !== 'function') return\n forwardInputValidity(\n internals,\n input,\n ['valueMissing', 'rangeUnderflow', 'rangeOverflow', 'stepMismatch'],\n this._isDisabled\n )\n }\n\n private clamp(v: number): number {\n return Math.min(this._max, Math.max(this._min, v))\n }\n\n private round(v: number): number {\n const f = 10 ** this.precision\n return Math.round(v * f) / f\n }\n\n private setValueAndNotify(v: number) {\n this.value = this.clamp(this.round(v))\n this.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n }\n\n private handleStep(direction: 1 | -1) {\n if (this._isDisabled || this.readonly) return\n this.setValueAndNotify(this._value + direction * this._effectiveStep)\n }\n\n private handleInput(e: Event) {\n if (this.readonly) return\n if (!(e.target instanceof HTMLInputElement)) return\n const raw = e.target.value\n if (raw === '' || raw === '-') return\n this._value = this.clamp(this.round(Number(raw)))\n this._formAssociation.sync()\n this._syncValidity()\n }\n\n // 原生 change 不 composed,被 shadow root 挡住;文本失焦提交时补发 composed 事件,\n // 与 ArrowUp/ArrowDown 的 change 行为对齐。\n private handleNativeChange(e: Event) {\n if (this.readonly) return\n if (!(e.target instanceof HTMLInputElement)) return\n const raw = e.target.value\n if (raw === '' || raw === '-') return\n this._value = this.clamp(this.round(Number(raw)))\n this._formAssociation.sync()\n this._syncValidity()\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n private handleKeydown(e: KeyboardEvent) {\n if (this._isDisabled || this.readonly) return\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n this.setValueAndNotify(this._value + this._effectiveStep)\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n this.setValueAndNotify(this._value - this._effectiveStep)\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n }\n\n private handleFocus() {\n if (!this._isDisabled) this._focused = true\n }\n\n private handleBlur() {\n this._focused = false\n }\n\n override render() {\n return html`\n <div class=\"wui-glass wui-input-inner\">\n <button\n class=\"num-btn\"\n aria-label=\"Decrease\"\n ?disabled=${this._isDisabled || this.readonly || this.atMin}\n @click=${() => this.handleStep(-1)}\n >\n <web-ui-icon .icon=${lucideMinus}></web-ui-icon>\n </button>\n <input\n id=${this._fieldId}\n type=\"number\"\n placeholder=${this.placeholder}\n name=${this.name}\n .value=${String(this._value)}\n ?disabled=${this._isDisabled}\n ?readonly=${this.readonly}\n ?required=${this.required}\n @input=${this.handleInput}\n @change=${this.handleNativeChange}\n @keydown=${this.handleKeydown}\n @focus=${this.handleFocus}\n @blur=${this.handleBlur}\n />\n <button\n class=\"num-btn\"\n aria-label=\"Increase\"\n ?disabled=${this._isDisabled || this.readonly || this.atMax}\n @click=${() => this.handleStep(1)}\n >\n <web-ui-icon .icon=${lucidePlus}></web-ui-icon>\n </button>\n </div>\n `\n }\n\n declare readonly $events: {\n input: Event\n change: Event\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-input-number': WebUiInputNumber\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/layout/index.ts"],"sourcesContent":["import { html, LitElement, nothing, 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 glass from '@/assets/glass.css?inline'\nimport '@/components/icon'\nimport '@/components/button'\nimport '@/components/drawer'\nimport { radixIconsPanelLeftMinimized } from '@/icons'\nimport { attachDragGesture, clamp, type DragGestureHandle } from '@/shared/gesture'\nimport { defineVisibleAreaTracker, VisibleAreaController } from '@/shared/visible-area'\n\nimport style from './style.css?inline'\n\n/**\n * 移动端断点的媒体查询。\n *\n * 必须与 `style.css` 里那份 `@media (width <= 640px)` 是同一条条件:CSS 用它在窄视口隐藏桌面\n * aside,TS 用同一条件决定渲染哪一套树,两者错开就会出现「树还在桌面、aside 已经被藏起来」\n * 的空档。两份字面量的相等由 `components/drawer/__tests__/breakpoint-parity.spec.ts` 守着。\n */\nconst MOBILE_VIEWPORT_QUERY = '(width <= 640px)'\n\n/**\n * Layout 组件 - 提供页面布局框架,支持可折叠侧边栏\n *\n * @fires sidebar-collapsed-change - 桌面端侧边栏折叠状态变更请求时触发\n * @fires sidebar-open-change - 移动端侧边栏 Drawer 开关请求时触发\n * @fires sidebar-width-change - 桌面端侧边栏拖拽调宽结束时触发,携带最终宽度(受控请求)\n * @fires mobile-change - 视口跨越移动端断点、`mobile` 翻转时触发(派生状态通知)\n * @slot banner - 顶部 Banner 区域(全宽,随页面滚动,可选)\n * @slot header - 内容区顶部 header(sticky)\n * @slot sidebar - 侧边栏内容;Consumer 决定其内部固定区域与滚动容器\n * @slot tabbar - 底部 tabbar 区域\n * @slot default - 主内容区\n */\n@customElement('web-ui-layout')\nexport class WebUiLayout extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n /**\n * 桌面端 Sidebar 的持久布局偏好。\n *\n * `true` 只会将桌面 Sidebar 收窄到 `collapsedWidth`,不会影响移动端 Drawer。\n * 它不与 `sidebarOpen` 合并:前者是跨视口保留的密度选择,后者是移动端瞬时的 modal 可见性。\n *\n * 两处渲染的是同一份 `sidebar` slot 内容,所以本属性只负责桌面 aside 的几何:Consumer\n * 若让自己的 slot 内容跟着它变形态(隐藏标签、改排布),必须按 `mobile` 收窄那个条件\n * (例如 `collapsed && !layout.mobile`,或 CSS 的 `web-ui-layout[mobile]`),\n * 否则折叠态会一路渲染进移动端 Drawer(issue #195)。\n */\n @property({ type: Boolean, attribute: 'sidebar-collapsed', reflect: true })\n sidebarCollapsed = false\n\n @property({ type: String, attribute: 'sidebar-width', reflect: true })\n sidebarWidth = '240px'\n\n @property({ type: String, attribute: 'collapsed-width', reflect: true })\n collapsedWidth = '68px'\n\n /** 启用桌面端 Sidebar 拖拽调宽(折叠态隐藏 handle)。 */\n @property({ type: Boolean, attribute: 'sidebar-resizable', reflect: true })\n sidebarResizable = false\n\n /**\n * 拖拽调宽的下限(px,其他单位不解析);未设置时回退为 `collapsedWidth`,\n * 防止拖到不可用宽度。上限 `sidebar-max-width` 受内置 50vw 硬上限钳制。\n */\n @property({ type: String, attribute: 'sidebar-min-width', reflect: true })\n sidebarMinWidth = ''\n\n /** 拖拽调宽的上限(px,其他单位不解析);始终受内置 50vw 硬上限钳制。 */\n @property({ type: String, attribute: 'sidebar-max-width', reflect: true })\n sidebarMaxWidth = ''\n\n /**\n * 移动端 web-ui-drawer 的瞬时可见性。\n *\n * Drawer Toggle、Escape 和遮罩关闭都通过 `sidebar-open-change` 请求更新它;它不会改变桌面端的折叠偏好。\n * 因而不与 `sidebarCollapsed` 合并,避免响应式切换时将一次移动端交互误写为桌面端布局选择。\n */\n @property({ type: Boolean, attribute: 'sidebar-open', reflect: true })\n sidebarOpen = false\n\n @property({ type: Boolean, attribute: 'header-glow', reflect: true })\n headerGlow = false\n\n /*\n * `mobile` 是派生输出,不是第二个输入:像 `web-ui-theme` 的 `resolved-appearance` 一样由组件\n * 独占写入,读取取 `MOBILE_VIEWPORT_QUERY`。外部写 attribute 会在同一次 reaction 内被恢复,\n * 写 property 不生效(只读 accessor,严格模式下抛错)——两种写法都不构成本属性之外的\n * 第三个视口来源。\n */\n @property({ type: Boolean, attribute: 'mobile', reflect: true })\n private _mobile = false\n\n /**\n * 当前视口是否落在移动端分支(即 `MOBILE_VIEWPORT_QUERY` 命中)。\n *\n * 与组件渲染哪一套树、以及 `mobile-change` 的派发是同一个值:翻转瞬间三者一起变,\n * Consumer 不必自己写断点判断,也不会与 layout 内部的树切换错开半拍。派生状态只读。\n */\n get mobile(): boolean {\n return this._mobile\n }\n\n private _mobileQuery: MediaQueryList | null = null\n\n private readonly _onMobileQueryChange = (event: MediaQueryListEvent) => {\n this._syncMobile(event.matches)\n }\n\n /*\n * 本方法由媒体查询翻转驱动,也被连接时的初次求值调用;attribute 的恢复路径见\n * `attributeChangedCallback`,那里不改判、也不派发通知,所以 `mobile-change` 只在\n * 视口真正跨断点时出现一次(窄视口挂载时那次算第一次)。\n */\n private _syncMobile(next: boolean) {\n if (next === this._mobile) return\n this._mobile = next\n // 跨断点会卸载桌面 layout(resize handle 随之消失);必须先终结进行中的拖拽,\n // 否则手势悬挂,切回桌面后所有新拖拽都在入口被 `_isResizing()` 拦下。\n if (this._isResizing()) this._resetActiveResize()\n this.toggleAttribute('mobile', next)\n this.dispatchEvent(new CustomEvent('mobile-change', { detail: { mobile: next }, bubbles: true, composed: true }))\n }\n\n override attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {\n super.attributeChangedCallback(name, oldValue, newValue)\n // super 已把外部值写进 backing field,所以这里要按媒体查询重算,不能拿字段当真相。\n if (name !== 'mobile') return\n // 无 matchMedia 的环境没有视口可判,`mobile` 恒为 false(见 connectedCallback)。\n const derived = this._mobileQuery?.matches ?? false\n if (this._mobile !== derived) this._mobile = derived\n /*\n * 布尔 attribute 只论存在性,但值本身也要归位:`setAttribute('mobile', 'false')` 在移动端\n * 会让 backing field 与 attribute 的**存在性**一致、值却留在宿主上(`getAttribute` 会回读\n * 到 consumer 写的那串)。组件写的形态是空串,这里按同一形态恢复,主题的\n * `resolved-appearance` 先例同理。\n */\n if (derived) {\n if (this.getAttribute('mobile') !== '') this.setAttribute('mobile', '')\n return\n }\n if (this.hasAttribute('mobile')) this.removeAttribute('mobile')\n }\n\n private readonly _visibleBanner = defineVisibleAreaTracker({\n onVisibleAreaChange: area => {\n this.style.setProperty('--wui-layout-visible-banner-height', `${area.height}px`)\n }\n }).make()\n\n private readonly _visibleBannerController = new VisibleAreaController(this, this._visibleBanner)\n\n private _toggleSidebar() {\n if (this._mobile) {\n this._emitSidebarOpenChange(!this.sidebarOpen)\n return\n }\n\n this._emitSidebarCollapsedChange(!this.sidebarCollapsed)\n }\n\n private _emitSidebarCollapsedChange(collapsed: boolean) {\n this.dispatchEvent(\n new CustomEvent('sidebar-collapsed-change', {\n detail: { collapsed },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _emitSidebarOpenChange(open: boolean) {\n this.dispatchEvent(\n new CustomEvent('sidebar-open-change', {\n detail: { open },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _onDrawerChange(e: CustomEvent<{ open: boolean }>) {\n e.stopPropagation()\n this._emitSidebarOpenChange(e.detail.open)\n }\n\n private _resizeGesture: DragGestureHandle | null = null\n private _resizeStartWidth = 0\n // 拖拽中的临时宽度;由 render 的 styleMap 统一写入,避免与 Lit 样式管理竞争。\n @state() private _resizeWidth: string | null = null\n\n private _parsePx(value: string, fallback: number): number {\n const parsed = Number.parseFloat(value)\n return Number.isFinite(parsed) ? parsed : fallback\n }\n\n private _resolveSidebarMinWidth(): number {\n return this.sidebarMinWidth\n ? this._parsePx(this.sidebarMinWidth, this._parsePx(this.collapsedWidth, 68))\n : this._parsePx(this.collapsedWidth, 68)\n }\n\n private _resolveSidebarMaxWidth(): number {\n // 组件内置硬上限:Sidebar 最多占视口一半(无需调用方设置)。\n // 调用方 sidebar-max-width 在该上限内生效,防止拖拽占据整个视口。\n const halfViewport = Math.floor(window.innerWidth / 2)\n const configured = this.sidebarMaxWidth ? this._parsePx(this.sidebarMaxWidth, halfViewport) : halfViewport\n const upper = Math.min(configured, halfViewport)\n return Math.max(upper, this._resolveSidebarMinWidth())\n }\n\n private _isResizing(): boolean {\n return this._resizeGesture?.isDragging() ?? false\n }\n\n private _handleResizePointerDown(e: PointerEvent) {\n if (!this.sidebarResizable || this.sidebarCollapsed || this._mobile || this._isResizing()) return\n // jsdom 等环境的 PointerEvent 可能缺失 isPrimary,仅在明确为 false(多点触控副指针)时拒绝。\n if (e.isPrimary === false) return\n\n const aside = this.renderRoot.querySelector('aside')\n if (!aside) return\n\n // 拖拽起点以 computed width 为准,并丢弃键盘未提交的调整(Enter 才提交、\n // Escape 撤回;抓取隐式放弃),保证零位移松手不会派发陈旧宽度。\n this._resizeStartWidth = this._parsePx(window.getComputedStyle(aside).width, this._parsePx(this.sidebarWidth, 240))\n this._resizeWidth = null\n aside.classList.add('is-resizing')\n\n this._resizeGesture = attachDragGesture(e, {\n axis: 'x',\n onMove: info => {\n // 向右拖增宽、向左拖收窄(handle 在右缘);钳制在 [min, max] 与视口内。\n const next = this._resizeStartWidth + info.deltaX\n const clamped = clamp(next, this._resolveSidebarMinWidth(), this._resolveSidebarMaxWidth())\n this._resizeWidth = `${Math.round(clamped)}px`\n },\n onEnd: () => {\n // 零位移松手(点击而非拖拽)不视为调宽动作:按 cancel 语义静默收尾。\n if (this._resizeWidth === null) {\n this._resetActiveResize()\n return\n }\n const width = this._resizeWidth\n this._resetActiveResize()\n // 受控契约:松手派发请求并交还 prop 管辖,由 Consumer 回写 sidebar-width。\n this.dispatchEvent(\n new CustomEvent('sidebar-width-change', {\n detail: { width },\n bubbles: true,\n composed: true\n })\n )\n },\n onCancel: () => {\n this._resetActiveResize()\n }\n })\n }\n\n // 终结进行中的拖拽:清手势状态、移除跟手 class,宽度过渡回 prop 管辖,不派发事件。\n // pointer cancel 与视口跨越移动端断点(handle 随桌面 layout 卸载)共用此收尾。\n private _resetActiveResize() {\n this._resizeGesture?.destroy()\n this._resizeGesture = null\n this.renderRoot.querySelector('aside')?.classList.remove('is-resizing')\n this._resizeWidth = null\n }\n\n // 键盘调宽(WAI-ARIA splitter 模式):方向键步进 16px(Shift 加速 64px)调整\n // 临时宽度,Enter 以受控请求派发 `sidebar-width-change`(与指针松手同语义),\n // Escape 撤回临时宽度交还 prop 管辖,Home/End 直接到 min/max。\n private _handleResizeKeydown(e: KeyboardEvent) {\n if (!this.sidebarResizable || this.sidebarCollapsed || this._mobile || this._isResizing()) return\n\n const min = this._resolveSidebarMinWidth()\n const max = this._resolveSidebarMaxWidth()\n const aside = this.renderRoot.querySelector('aside')\n const current = this._resizeWidth\n ? this._parsePx(this._resizeWidth, min)\n : this._parsePx(aside ? window.getComputedStyle(aside).width : this.sidebarWidth, min)\n const step = e.shiftKey ? 64 : 16\n let next: number | null = null\n\n switch (e.key) {\n case 'ArrowLeft':\n next = Math.max(min, current - step)\n break\n case 'ArrowRight':\n next = Math.min(max, current + step)\n break\n case 'Home':\n next = min\n break\n case 'End':\n next = max\n break\n case 'Enter':\n // 无未提交调整时不派发(焦点停留时的回车噪声)\n if (this._resizeWidth !== null) {\n const width = this._resizeWidth\n this._resizeWidth = null\n this.dispatchEvent(\n new CustomEvent('sidebar-width-change', { detail: { width }, bubbles: true, composed: true })\n )\n }\n e.preventDefault()\n return\n case 'Escape':\n this._resizeWidth = null\n e.preventDefault()\n return\n default:\n return\n }\n e.preventDefault()\n this._resizeWidth = `${Math.round(next)}px`\n }\n\n /*\n * 视口判定订阅媒体查询,而不是监听 window resize:跨断点每次只派发一次 change,没有\n * resize 事件流要挡。判定、CSS 的 `@media (width <= 640px)` 与对外暴露的 `mobile` 因此\n * 在同一时刻翻转,不会留下「树还在桌面、aside 已被 CSS 藏起来」的空档。\n */\n private _syncMobileViewportSubscription() {\n this._mobileQuery?.removeEventListener('change', this._onMobileQueryChange)\n // jsdom 等没有 matchMedia 的环境没有视口可判:`mobile` 停在 false,订阅与渲染照常。\n this._mobileQuery = typeof window.matchMedia === 'function' ? window.matchMedia(MOBILE_VIEWPORT_QUERY) : null\n this._mobileQuery?.addEventListener('change', this._onMobileQueryChange)\n this._syncMobile(this._mobileQuery?.matches ?? false)\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._syncMobileViewportSubscription()\n void this.updateComplete.then(() => {\n if (this.isConnected) this._syncBannerPresence()\n })\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._resetActiveResize()\n this._mobileQuery?.removeEventListener('change', this._onMobileQueryChange)\n this._mobileQuery = null\n }\n\n override firstUpdated() {\n this._syncBannerPresence()\n }\n\n private _onBannerSlotChange() {\n this._syncBannerPresence()\n }\n\n private _syncBannerPresence() {\n const bannerSlot = this.renderRoot.querySelector<HTMLSlotElement>('slot[name=\"banner\"]')\n const hasBanner =\n bannerSlot\n ?.assignedNodes()\n .some(\n node =>\n node.nodeType === Node.ELEMENT_NODE || (node.nodeType === Node.TEXT_NODE && !!node.textContent?.trim())\n ) ?? false\n const banner = this.shadowRoot?.querySelector<HTMLElement>('.layout-banner') ?? null\n this._visibleBanner.setTarget(hasBanner ? banner : null)\n }\n\n override render() {\n const isSidebarOpen = !this.sidebarCollapsed\n // 拖拽中的临时宽度优先于 prop 宽度;移动端宽度由 Drawer 内部变量管理。\n const sidebarStyle = this._mobile\n ? {}\n : { width: this._resizeWidth ?? (this.sidebarCollapsed ? this.collapsedWidth : this.sidebarWidth) }\n\n const toggleLabel = isSidebarOpen ? '折叠侧边栏' : '展开侧边栏'\n\n const desktopToggle = html`\n <web-ui-button\n class=\"sidebar-toggle\"\n icon\n variant=\"secondary\"\n @click=\"${this._toggleSidebar}\"\n aria-label=\"${toggleLabel}\"\n >\n <web-ui-icon .icon=${radixIconsPanelLeftMinimized}></web-ui-icon>\n </web-ui-button>\n `\n\n const sidebarViewport = html`\n <div class=\"sidebar-viewport\">\n <slot name=\"sidebar\"></slot>\n </div>\n `\n\n const desktopLayout = html`\n <div class=\"layout-body\">\n <div class=\"sidebar-wrapper\">\n <aside\n class=\"${classMap({\n collapsed: !isSidebarOpen\n })}\"\n style=\"${styleMap(sidebarStyle)}\"\n >\n <div class=\"aside-panel wui-glass\">\n ${sidebarViewport}\n <div class=\"sidebar-toggle-area\">${desktopToggle}</div>\n </div>\n ${\n this.sidebarResizable && !this.sidebarCollapsed\n ? html`\n <div\n class=\"sidebar-resize-handle\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n aria-label=\"调整侧边栏宽度\"\n @pointerdown=${this._handleResizePointerDown}\n @keydown=${this._handleResizeKeydown}\n ></div>\n `\n : nothing\n }\n </aside>\n </div>\n <div class=\"layout-content\">\n <header>\n <slot name=\"header\"></slot>\n </header>\n <main><slot></slot></main>\n <footer><slot name=\"tabbar\"></slot></footer>\n </div>\n </div>\n `\n\n const mobileLayout = html`\n <div class=\"layout-content\">\n <header>\n <web-ui-button\n class=\"mobile-toggle\"\n icon\n variant=\"glass\"\n @click=\"${this._toggleSidebar}\"\n aria-label=\"打开导航菜单\"\n >\n <web-ui-icon .icon=${radixIconsPanelLeftMinimized}></web-ui-icon>\n </web-ui-button>\n <slot name=\"header\"></slot>\n </header>\n <main><slot></slot></main>\n <footer><slot name=\"tabbar\"></slot></footer>\n </div>\n\n <web-ui-drawer\n placement=\"left\"\n ?open=\"${this.sidebarOpen}\"\n controlled\n draggable\n dialog-label=\"主导航\"\n @open-change=\"${this._onDrawerChange}\"\n style=\"${styleMap({\n '--wui-drawer-width': this.sidebarWidth,\n '--wui-drawer-radius': 'var(--wui-layout-sidebar-radius, 24px)'\n })}\"\n >\n ${sidebarViewport}\n </web-ui-drawer>\n `\n\n return html`\n <div class=\"layout-page\">\n <div class=\"layout-banner\">\n <slot name=\"banner\" @slotchange=\"${this._onBannerSlotChange}\"></slot>\n </div>\n ${this._mobile ? mobileLayout : desktopLayout}\n </div>\n `\n }\n\n declare readonly $events: {\n 'sidebar-collapsed-change': CustomEvent<{ collapsed: boolean }>\n 'sidebar-open-change': CustomEvent<{ open: boolean }>\n 'sidebar-width-change': CustomEvent<{ width: string }>\n 'mobile-change': CustomEvent<{ mobile: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-layout': WebUiLayout\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsBA,IAAM,IAAwB,oBAgBjB,IAAN,cAA0B,EAAW;;EA2JK,aA5I5B,KAAA,mBAAA,IAGJ,KAAA,eAAA,SAGE,KAAA,iBAAA,QAIE,KAAA,mBAAA,IAOD,KAAA,kBAAA,IAIA,KAAA,kBAAA,IASJ,KAAA,cAAA,IAGD,KAAA,aAAA,IASK,KAAA,UAAA,IAY4B,KAAA,eAAA,MAEL,KAAA,wBAAA,MAA+B;GACtE,KAAK,YAAY,EAAM,OAAO;EAChC,GAqCkC,KAAA,iBAAA,EAAyB,EACzD,sBAAqB,MAAQ;GAC3B,KAAK,MAAM,YAAY,sCAAsC,GAAG,EAAK,OAAO,GAAG;EACjF,EACF,CAAC,CAAC,CAAC,KAAK,GAEoC,KAAA,2BAAA,IAAI,EAAsB,MAAM,KAAK,cAAc,GAoC5C,KAAA,iBAAA,MACvB,KAAA,oBAAA,GAEmB,KAAA,eAAA;;;EA1JtB,KAAA,SAAA,CAAC,EAAU,CAAK,GAAG,EAAU,CAAK,CAAC;;CAgE5D,IAAI,SAAkB;EACpB,OAAO,KAAK;CACd;CAaA,YAAoB,GAAe;EAC7B,MAAS,KAAK,YAClB,KAAK,UAAU,GAGX,KAAK,YAAY,KAAG,KAAK,mBAAmB,GAChD,KAAK,gBAAgB,UAAU,CAAI,GACnC,KAAK,cAAc,IAAI,YAAY,iBAAiB;GAAE,QAAQ,EAAE,QAAQ,EAAK;GAAG,SAAS;GAAM,UAAU;EAAK,CAAC,CAAC;CAClH;CAEA,yBAAkC,GAAc,GAAyB,GAAyB;EAGhG,IAFA,MAAM,yBAAyB,GAAM,GAAU,CAAQ,GAEnD,MAAS,UAAU;EAEvB,IAAM,IAAU,KAAK,cAAc,WAAW;EAQ9C,IAPI,KAAK,YAAY,MAAS,KAAK,UAAU,IAOzC,GAAS;GACX,AAAI,KAAK,aAAa,QAAQ,MAAM,MAAI,KAAK,aAAa,UAAU,EAAE;GACtE;EACF;EACA,AAAI,KAAK,aAAa,QAAQ,KAAG,KAAK,gBAAgB,QAAQ;CAChE;CAUA,iBAAyB;EACvB,IAAI,KAAK,SAAS;GAChB,KAAK,uBAAuB,CAAC,KAAK,WAAW;GAC7C;EACF;EAEA,KAAK,4BAA4B,CAAC,KAAK,gBAAgB;CACzD;CAEA,4BAAoC,GAAoB;EACtD,KAAK,cACH,IAAI,YAAY,4BAA4B;GAC1C,QAAQ,EAAE,aAAU;GACpB,SAAS;GACT,UAAU;EACZ,CAAC,CACH;CACF;CAEA,uBAA+B,GAAe;EAC5C,KAAK,cACH,IAAI,YAAY,uBAAuB;GACrC,QAAQ,EAAE,QAAK;GACf,SAAS;GACT,UAAU;EACZ,CAAC,CACH;CACF;CAEA,gBAAwB,GAAmC;EAEzD,AADA,EAAE,gBAAgB,GAClB,KAAK,uBAAuB,EAAE,OAAO,IAAI;CAC3C;CAOA,SAAiB,GAAe,GAA0B;EACxD,IAAM,IAAS,OAAO,WAAW,CAAK;EACtC,OAAO,OAAO,SAAS,CAAM,IAAI,IAAS;CAC5C;CAEA,0BAA0C;EACxC,OAAO,KAAK,kBACR,KAAK,SAAS,KAAK,iBAAiB,KAAK,SAAS,KAAK,gBAAgB,EAAE,CAAC,IAC1E,KAAK,SAAS,KAAK,gBAAgB,EAAE;CAC3C;CAEA,0BAA0C;EAGxC,IAAM,IAAe,KAAK,MAAM,OAAO,aAAa,CAAC,GAC/C,IAAa,KAAK,kBAAkB,KAAK,SAAS,KAAK,iBAAiB,CAAY,IAAI;EAE9F,OAAO,KAAK,IADE,KAAK,IAAI,GAAY,CACnB,GAAO,KAAK,wBAAwB,CAAC;CACvD;CAEA,cAA+B;EAC7B,OAAO,KAAK,gBAAgB,WAAW,KAAK;CAC9C;CAEA,yBAAiC,GAAiB;EAGhD,IAFI,CAAC,KAAK,oBAAoB,KAAK,oBAAoB,KAAK,WAAW,KAAK,YAAY,KAEpF,EAAE,cAAc,IAAO;EAE3B,IAAM,IAAQ,KAAK,WAAW,cAAc,OAAO;EAC9C,MAIL,KAAK,oBAAoB,KAAK,SAAS,OAAO,iBAAiB,CAAK,CAAC,CAAC,OAAO,KAAK,SAAS,KAAK,cAAc,GAAG,CAAC,GAClH,KAAK,eAAe,MACpB,EAAM,UAAU,IAAI,aAAa,GAEjC,KAAK,iBAAiB,EAAkB,GAAG;GACzC,MAAM;GACN,SAAQ,MAAQ;IAEd,IAAM,IAAO,KAAK,oBAAoB,EAAK,QACrC,IAAU,EAAM,GAAM,KAAK,wBAAwB,GAAG,KAAK,wBAAwB,CAAC;IAC1F,KAAK,eAAe,GAAG,KAAK,MAAM,CAAO,EAAE;GAC7C;GACA,aAAa;IAEX,IAAI,KAAK,iBAAiB,MAAM;KAC9B,KAAK,mBAAmB;KACxB;IACF;IACA,IAAM,IAAQ,KAAK;IAGnB,AAFA,KAAK,mBAAmB,GAExB,KAAK,cACH,IAAI,YAAY,wBAAwB;KACtC,QAAQ,EAAE,SAAM;KAChB,SAAS;KACT,UAAU;IACZ,CAAC,CACH;GACF;GACA,gBAAgB;IACd,KAAK,mBAAmB;GAC1B;EACF,CAAC;CACH;CAIA,qBAA6B;EAI3B,AAHA,KAAK,gBAAgB,QAAQ,GAC7B,KAAK,iBAAiB,MACtB,KAAK,WAAW,cAAc,OAAO,CAAC,EAAE,UAAU,OAAO,aAAa,GACtE,KAAK,eAAe;CACtB;CAKA,qBAA6B,GAAkB;EAC7C,IAAI,CAAC,KAAK,oBAAoB,KAAK,oBAAoB,KAAK,WAAW,KAAK,YAAY,GAAG;EAE3F,IAAM,IAAM,KAAK,wBAAwB,GACnC,IAAM,KAAK,wBAAwB,GACnC,IAAQ,KAAK,WAAW,cAAc,OAAO,GAC7C,IAAU,KAAK,eACjB,KAAK,SAAS,KAAK,cAAc,CAAG,IACpC,KAAK,SAAS,IAAQ,OAAO,iBAAiB,CAAK,CAAC,CAAC,QAAQ,KAAK,cAAc,CAAG,GACjF,IAAO,EAAE,WAAW,KAAK,IAC3B,IAAsB;EAE1B,QAAQ,EAAE,KAAV;GACE,KAAK;IACH,IAAO,KAAK,IAAI,GAAK,IAAU,CAAI;IACnC;GACF,KAAK;IACH,IAAO,KAAK,IAAI,GAAK,IAAU,CAAI;IACnC;GACF,KAAK;IACH,IAAO;IACP;GACF,KAAK;IACH,IAAO;IACP;GACF,KAAK;IAEH,IAAI,KAAK,iBAAiB,MAAM;KAC9B,IAAM,IAAQ,KAAK;KAEnB,AADA,KAAK,eAAe,MACpB,KAAK,cACH,IAAI,YAAY,wBAAwB;MAAE,QAAQ,EAAE,SAAM;MAAG,SAAS;MAAM,UAAU;KAAK,CAAC,CAC9F;IACF;IACA,EAAE,eAAe;IACjB;GACF,KAAK;IAEH,AADA,KAAK,eAAe,MACpB,EAAE,eAAe;IACjB;GACF,SACE;EACJ;EAEA,AADA,EAAE,eAAe,GACjB,KAAK,eAAe,GAAG,KAAK,MAAM,CAAI,EAAE;CAC1C;CAOA,kCAA0C;EAKxC,AAJA,KAAK,cAAc,oBAAoB,UAAU,KAAK,oBAAoB,GAE1E,KAAK,eAAe,OAAO,OAAO,cAAe,aAAa,OAAO,WAAW,CAAqB,IAAI,MACzG,KAAK,cAAc,iBAAiB,UAAU,KAAK,oBAAoB,GACvE,KAAK,YAAY,KAAK,cAAc,WAAW,EAAK;CACtD;CAEA,oBAA6B;EAG3B,AAFA,MAAM,kBAAkB,GACxB,KAAK,gCAAgC,GACrC,KAAU,eAAe,WAAW;GAClC,AAAI,KAAK,eAAa,KAAK,oBAAoB;EACjD,CAAC;CACH;CAEA,uBAAgC;EAI9B,AAHA,MAAM,qBAAqB,GAC3B,KAAK,mBAAmB,GACxB,KAAK,cAAc,oBAAoB,UAAU,KAAK,oBAAoB,GAC1E,KAAK,eAAe;CACtB;CAEA,eAAwB;EACtB,KAAK,oBAAoB;CAC3B;CAEA,sBAA8B;EAC5B,KAAK,oBAAoB;CAC3B;CAEA,sBAA8B;EAE5B,IAAM,IADa,KAAK,WAAW,cAA+B,uBAEhE,CAAA,EACI,cAAc,CAAC,CAChB,MACC,MACE,EAAK,aAAa,KAAK,gBAAiB,EAAK,aAAa,KAAK,aAAa,CAAC,CAAC,EAAK,aAAa,KAAK,CACzG,KAAK,IACH,IAAS,KAAK,YAAY,cAA2B,gBAAgB,KAAK;EAChF,KAAK,eAAe,UAAU,IAAY,IAAS,IAAI;CACzD;CAEA,SAAkB;EAChB,IAAM,IAAgB,CAAC,KAAK,kBAEtB,IAAe,KAAK,UACtB,CAAC,IACD,EAAE,OAAO,KAAK,iBAAiB,KAAK,mBAAmB,KAAK,iBAAiB,KAAK,cAAc,GAE9F,IAAc,IAAgB,UAAU,SAExC,IAAgB,CAAI;;;;;kBAKZ,KAAK,eAAe;sBAChB,EAAY;;6BAEL,EAA6B;;OAIhD,IAAkB,CAAI;;;;OAMtB,IAAgB,CAAI;;;;qBAIT,EAAS,EAChB,WAAW,CAAC,EACd,CAAC,EAAE;qBACM,EAAS,CAAY,EAAE;;;gBAG5B,EAAgB;iDACiB,EAAc;;cAGjD,KAAK,oBAAoB,CAAC,KAAK,mBAC3B,CAAI;;;;;;;qCAOe,KAAK,yBAAyB;iCAClC,KAAK,qBAAqB;;sBAGzC,EACL;;;;;;;;;;;OAaH,IAAe,CAAI;;;;;;;sBAOP,KAAK,eAAe;;;iCAGT,EAA6B;;;;;;;;;;iBAU7C,KAAK,YAAY;;;;wBAIV,KAAK,gBAAgB;iBAC5B,EAAS;GAChB,sBAAsB,KAAK;GAC3B,uBAAuB;EACzB,CAAC,EAAE;;UAED,EAAgB;;;EAItB,OAAO,CAAI;;;6CAG8B,KAAK,oBAAoB;;UAE5D,KAAK,UAAU,IAAe,EAAc;;;CAGpD;AAQF;AArbG,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,WAAW;CAAqB,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,oBAAA,KAAA,CAAA,GAGzE,EAAA,CAAA,EAAS;CAAE,MAAM;CAAQ,WAAW;CAAiB,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,gBAAA,KAAA,CAAA,GAGpE,EAAA,CAAA,EAAS;CAAE,MAAM;CAAQ,WAAW;CAAmB,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,kBAAA,KAAA,CAAA,GAItE,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,WAAW;CAAqB,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,oBAAA,KAAA,CAAA,GAOzE,EAAA,CAAA,EAAS;CAAE,MAAM;CAAQ,WAAW;CAAqB,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,mBAAA,KAAA,CAAA,GAIxE,EAAA,CAAA,EAAS;CAAE,MAAM;CAAQ,WAAW;CAAqB,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,mBAAA,KAAA,CAAA,GASxE,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,WAAW;CAAgB,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,eAAA,KAAA,CAAA,GAGpE,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,WAAW;CAAe,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,cAAA,KAAA,CAAA,GASnE,EAAA,CAAA,EAAS;CAAE,MAAM;CAAS,WAAW;CAAU,SAAS;AAAK,CAAC,CAAA,GAAA,EAAA,WAAA,WAAA,KAAA,CAAA,GAmG9D,EAAA,CAAA,EAAM,CAAA,GAAA,EAAA,WAAA,gBAAA,KAAA,CAAA,GA5JR,IAAA,EAAA,CAAA,EAAc,eAAe,CAAA,GAAA,CAAA"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/layout/index.ts"],"sourcesContent":["import '@/components/button'\nimport '@/components/drawer'\nimport '@/components/icon'\n\nimport { html, LitElement, nothing, 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 glass from '@/assets/glass.css?inline'\nimport { radixIconsPanelLeftMinimized } from '@/icons'\nimport { attachDragGesture, clamp, type DragGestureHandle } from '@/shared/gesture'\nimport { defineVisibleAreaTracker, VisibleAreaController } from '@/shared/visible-area'\n\nimport style from './style.css?inline'\n\n/**\n * 移动端断点的媒体查询。\n *\n * 必须与 `style.css` 里那份 `@media (width <= 640px)` 是同一条条件:CSS 用它在窄视口隐藏桌面\n * aside,TS 用同一条件决定渲染哪一套树,两者错开就会出现「树还在桌面、aside 已经被藏起来」\n * 的空档。两份字面量的相等由 `components/drawer/__tests__/breakpoint-parity.spec.ts` 守着。\n */\nconst MOBILE_VIEWPORT_QUERY = '(width <= 640px)'\n\n/**\n * Layout 组件 - 提供页面布局框架,支持可折叠侧边栏\n *\n * @fires sidebar-collapsed-change - 桌面端侧边栏折叠状态变更请求时触发\n * @fires sidebar-open-change - 移动端侧边栏 Drawer 开关请求时触发\n * @fires sidebar-width-change - 桌面端侧边栏拖拽调宽结束时触发,携带最终宽度(受控请求)\n * @fires mobile-change - 视口跨越移动端断点、`mobile` 翻转时触发(派生状态通知)\n * @slot banner - 顶部 Banner 区域(全宽,随页面滚动,可选)\n * @slot header - 内容区顶部 header(sticky)\n * @slot sidebar - 侧边栏内容;Consumer 决定其内部固定区域与滚动容器\n * @slot tabbar - 底部 tabbar 区域\n * @slot default - 主内容区\n */\n@customElement('web-ui-layout')\nexport class WebUiLayout extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n /**\n * 桌面端 Sidebar 的持久布局偏好。\n *\n * `true` 只会将桌面 Sidebar 收窄到 `collapsedWidth`,不会影响移动端 Drawer。\n * 它不与 `sidebarOpen` 合并:前者是跨视口保留的密度选择,后者是移动端瞬时的 modal 可见性。\n *\n * 两处渲染的是同一份 `sidebar` slot 内容,所以本属性只负责桌面 aside 的几何:Consumer\n * 若让自己的 slot 内容跟着它变形态(隐藏标签、改排布),必须按 `mobile` 收窄那个条件\n * (例如 `collapsed && !layout.mobile`,或 CSS 的 `web-ui-layout[mobile]`),\n * 否则折叠态会一路渲染进移动端 Drawer(issue #195)。\n */\n @property({ type: Boolean, attribute: 'sidebar-collapsed', reflect: true })\n sidebarCollapsed = false\n\n @property({ type: String, attribute: 'sidebar-width', reflect: true })\n sidebarWidth = '240px'\n\n @property({ type: String, attribute: 'collapsed-width', reflect: true })\n collapsedWidth = '68px'\n\n /** 启用桌面端 Sidebar 拖拽调宽(折叠态隐藏 handle)。 */\n @property({ type: Boolean, attribute: 'sidebar-resizable', reflect: true })\n sidebarResizable = false\n\n /**\n * 拖拽调宽的下限(px,其他单位不解析);未设置时回退为 `collapsedWidth`,\n * 防止拖到不可用宽度。上限 `sidebar-max-width` 受内置 50vw 硬上限钳制。\n */\n @property({ type: String, attribute: 'sidebar-min-width', reflect: true })\n sidebarMinWidth = ''\n\n /** 拖拽调宽的上限(px,其他单位不解析);始终受内置 50vw 硬上限钳制。 */\n @property({ type: String, attribute: 'sidebar-max-width', reflect: true })\n sidebarMaxWidth = ''\n\n /**\n * 移动端 web-ui-drawer 的瞬时可见性。\n *\n * Drawer Toggle、Escape 和遮罩关闭都通过 `sidebar-open-change` 请求更新它;它不会改变桌面端的折叠偏好。\n * 因而不与 `sidebarCollapsed` 合并,避免响应式切换时将一次移动端交互误写为桌面端布局选择。\n */\n @property({ type: Boolean, attribute: 'sidebar-open', reflect: true })\n sidebarOpen = false\n\n @property({ type: Boolean, attribute: 'header-glow', reflect: true })\n headerGlow = false\n\n /*\n * `mobile` 是派生输出,不是第二个输入:像 `web-ui-theme` 的 `resolved-appearance` 一样由组件\n * 独占写入,读取取 `MOBILE_VIEWPORT_QUERY`。外部写 attribute 会在同一次 reaction 内被恢复,\n * 写 property 不生效(只读 accessor,严格模式下抛错)——两种写法都不构成本属性之外的\n * 第三个视口来源。\n */\n @property({ type: Boolean, attribute: 'mobile', reflect: true })\n private _mobile = false\n\n /**\n * 当前视口是否落在移动端分支(即 `MOBILE_VIEWPORT_QUERY` 命中)。\n *\n * 与组件渲染哪一套树、以及 `mobile-change` 的派发是同一个值:翻转瞬间三者一起变,\n * Consumer 不必自己写断点判断,也不会与 layout 内部的树切换错开半拍。派生状态只读。\n */\n get mobile(): boolean {\n return this._mobile\n }\n\n private _mobileQuery: MediaQueryList | null = null\n\n private readonly _onMobileQueryChange = (event: MediaQueryListEvent) => {\n this._syncMobile(event.matches)\n }\n\n /*\n * 本方法由媒体查询翻转驱动,也被连接时的初次求值调用;attribute 的恢复路径见\n * `attributeChangedCallback`,那里不改判、也不派发通知,所以 `mobile-change` 只在\n * 视口真正跨断点时出现一次(窄视口挂载时那次算第一次)。\n */\n private _syncMobile(next: boolean) {\n if (next === this._mobile) return\n this._mobile = next\n // 跨断点会卸载桌面 layout(resize handle 随之消失);必须先终结进行中的拖拽,\n // 否则手势悬挂,切回桌面后所有新拖拽都在入口被 `_isResizing()` 拦下。\n if (this._isResizing()) this._resetActiveResize()\n this.toggleAttribute('mobile', next)\n this.dispatchEvent(new CustomEvent('mobile-change', { detail: { mobile: next }, bubbles: true, composed: true }))\n }\n\n override attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {\n super.attributeChangedCallback(name, oldValue, newValue)\n // super 已把外部值写进 backing field,所以这里要按媒体查询重算,不能拿字段当真相。\n if (name !== 'mobile') return\n // 无 matchMedia 的环境没有视口可判,`mobile` 恒为 false(见 connectedCallback)。\n const derived = this._mobileQuery?.matches ?? false\n if (this._mobile !== derived) this._mobile = derived\n /*\n * 布尔 attribute 只论存在性,但值本身也要归位:`setAttribute('mobile', 'false')` 在移动端\n * 会让 backing field 与 attribute 的**存在性**一致、值却留在宿主上(`getAttribute` 会回读\n * 到 consumer 写的那串)。组件写的形态是空串,这里按同一形态恢复,主题的\n * `resolved-appearance` 先例同理。\n */\n if (derived) {\n if (this.getAttribute('mobile') !== '') this.setAttribute('mobile', '')\n return\n }\n if (this.hasAttribute('mobile')) this.removeAttribute('mobile')\n }\n\n private readonly _visibleBanner = defineVisibleAreaTracker({\n onVisibleAreaChange: area => {\n this.style.setProperty('--wui-layout-visible-banner-height', `${area.height}px`)\n }\n }).make()\n\n private readonly _visibleBannerController = new VisibleAreaController(this, this._visibleBanner)\n\n private _toggleSidebar() {\n if (this._mobile) {\n this._emitSidebarOpenChange(!this.sidebarOpen)\n return\n }\n\n this._emitSidebarCollapsedChange(!this.sidebarCollapsed)\n }\n\n private _emitSidebarCollapsedChange(collapsed: boolean) {\n this.dispatchEvent(\n new CustomEvent('sidebar-collapsed-change', {\n detail: { collapsed },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _emitSidebarOpenChange(open: boolean) {\n this.dispatchEvent(\n new CustomEvent('sidebar-open-change', {\n detail: { open },\n bubbles: true,\n composed: true\n })\n )\n }\n\n private _onDrawerChange(e: CustomEvent<{ open: boolean }>) {\n e.stopPropagation()\n this._emitSidebarOpenChange(e.detail.open)\n }\n\n private _resizeGesture: DragGestureHandle | null = null\n private _resizeStartWidth = 0\n // 拖拽中的临时宽度;由 render 的 styleMap 统一写入,避免与 Lit 样式管理竞争。\n @state() private _resizeWidth: string | null = null\n\n private _parsePx(value: string, fallback: number): number {\n const parsed = Number.parseFloat(value)\n return Number.isFinite(parsed) ? parsed : fallback\n }\n\n private _resolveSidebarMinWidth(): number {\n return this.sidebarMinWidth\n ? this._parsePx(this.sidebarMinWidth, this._parsePx(this.collapsedWidth, 68))\n : this._parsePx(this.collapsedWidth, 68)\n }\n\n private _resolveSidebarMaxWidth(): number {\n // 组件内置硬上限:Sidebar 最多占视口一半(无需调用方设置)。\n // 调用方 sidebar-max-width 在该上限内生效,防止拖拽占据整个视口。\n const halfViewport = Math.floor(window.innerWidth / 2)\n const configured = this.sidebarMaxWidth ? this._parsePx(this.sidebarMaxWidth, halfViewport) : halfViewport\n const upper = Math.min(configured, halfViewport)\n return Math.max(upper, this._resolveSidebarMinWidth())\n }\n\n private _isResizing(): boolean {\n return this._resizeGesture?.isDragging() ?? false\n }\n\n private _handleResizePointerDown(e: PointerEvent) {\n if (!this.sidebarResizable || this.sidebarCollapsed || this._mobile || this._isResizing()) return\n // jsdom 等环境的 PointerEvent 可能缺失 isPrimary,仅在明确为 false(多点触控副指针)时拒绝。\n if (e.isPrimary === false) return\n\n const aside = this.renderRoot.querySelector('aside')\n if (!aside) return\n\n // 拖拽起点以 computed width 为准,并丢弃键盘未提交的调整(Enter 才提交、\n // Escape 撤回;抓取隐式放弃),保证零位移松手不会派发陈旧宽度。\n this._resizeStartWidth = this._parsePx(window.getComputedStyle(aside).width, this._parsePx(this.sidebarWidth, 240))\n this._resizeWidth = null\n aside.classList.add('is-resizing')\n\n this._resizeGesture = attachDragGesture(e, {\n axis: 'x',\n onMove: info => {\n // 向右拖增宽、向左拖收窄(handle 在右缘);钳制在 [min, max] 与视口内。\n const next = this._resizeStartWidth + info.deltaX\n const clamped = clamp(next, this._resolveSidebarMinWidth(), this._resolveSidebarMaxWidth())\n this._resizeWidth = `${Math.round(clamped)}px`\n },\n onEnd: () => {\n // 零位移松手(点击而非拖拽)不视为调宽动作:按 cancel 语义静默收尾。\n if (this._resizeWidth === null) {\n this._resetActiveResize()\n return\n }\n const width = this._resizeWidth\n this._resetActiveResize()\n // 受控契约:松手派发请求并交还 prop 管辖,由 Consumer 回写 sidebar-width。\n this.dispatchEvent(\n new CustomEvent('sidebar-width-change', {\n detail: { width },\n bubbles: true,\n composed: true\n })\n )\n },\n onCancel: () => {\n this._resetActiveResize()\n }\n })\n }\n\n // 终结进行中的拖拽:清手势状态、移除跟手 class,宽度过渡回 prop 管辖,不派发事件。\n // pointer cancel 与视口跨越移动端断点(handle 随桌面 layout 卸载)共用此收尾。\n private _resetActiveResize() {\n this._resizeGesture?.destroy()\n this._resizeGesture = null\n this.renderRoot.querySelector('aside')?.classList.remove('is-resizing')\n this._resizeWidth = null\n }\n\n // 键盘调宽(WAI-ARIA splitter 模式):方向键步进 16px(Shift 加速 64px)调整\n // 临时宽度,Enter 以受控请求派发 `sidebar-width-change`(与指针松手同语义),\n // Escape 撤回临时宽度交还 prop 管辖,Home/End 直接到 min/max。\n private _handleResizeKeydown(e: KeyboardEvent) {\n if (!this.sidebarResizable || this.sidebarCollapsed || this._mobile || this._isResizing()) return\n\n const min = this._resolveSidebarMinWidth()\n const max = this._resolveSidebarMaxWidth()\n const aside = this.renderRoot.querySelector('aside')\n const current = this._resizeWidth\n ? this._parsePx(this._resizeWidth, min)\n : this._parsePx(aside ? window.getComputedStyle(aside).width : this.sidebarWidth, min)\n const step = e.shiftKey ? 64 : 16\n let next: number | null = null\n\n switch (e.key) {\n case 'ArrowLeft':\n next = Math.max(min, current - step)\n break\n case 'ArrowRight':\n next = Math.min(max, current + step)\n break\n case 'Home':\n next = min\n break\n case 'End':\n next = max\n break\n case 'Enter':\n // 无未提交调整时不派发(焦点停留时的回车噪声)\n if (this._resizeWidth !== null) {\n const width = this._resizeWidth\n this._resizeWidth = null\n this.dispatchEvent(\n new CustomEvent('sidebar-width-change', { detail: { width }, bubbles: true, composed: true })\n )\n }\n e.preventDefault()\n return\n case 'Escape':\n this._resizeWidth = null\n e.preventDefault()\n return\n default:\n return\n }\n e.preventDefault()\n this._resizeWidth = `${Math.round(next)}px`\n }\n\n /*\n * 视口判定订阅媒体查询,而不是监听 window resize:跨断点每次只派发一次 change,没有\n * resize 事件流要挡。判定、CSS 的 `@media (width <= 640px)` 与对外暴露的 `mobile` 因此\n * 在同一时刻翻转,不会留下「树还在桌面、aside 已被 CSS 藏起来」的空档。\n */\n private _syncMobileViewportSubscription() {\n this._mobileQuery?.removeEventListener('change', this._onMobileQueryChange)\n // jsdom 等没有 matchMedia 的环境没有视口可判:`mobile` 停在 false,订阅与渲染照常。\n this._mobileQuery = typeof window.matchMedia === 'function' ? window.matchMedia(MOBILE_VIEWPORT_QUERY) : null\n this._mobileQuery?.addEventListener('change', this._onMobileQueryChange)\n this._syncMobile(this._mobileQuery?.matches ?? false)\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._syncMobileViewportSubscription()\n void this.updateComplete.then(() => {\n if (this.isConnected) this._syncBannerPresence()\n })\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._resetActiveResize()\n this._mobileQuery?.removeEventListener('change', this._onMobileQueryChange)\n this._mobileQuery = null\n }\n\n override firstUpdated() {\n this._syncBannerPresence()\n }\n\n private _onBannerSlotChange() {\n this._syncBannerPresence()\n }\n\n private _syncBannerPresence() {\n const bannerSlot = this.renderRoot.querySelector<HTMLSlotElement>('slot[name=\"banner\"]')\n const hasBanner =\n bannerSlot\n ?.assignedNodes()\n .some(\n node =>\n node.nodeType === Node.ELEMENT_NODE || (node.nodeType === Node.TEXT_NODE && !!node.textContent?.trim())\n ) ?? false\n const banner = this.shadowRoot?.querySelector<HTMLElement>('.layout-banner') ?? null\n this._visibleBanner.setTarget(hasBanner ? banner : null)\n }\n\n override render() {\n const isSidebarOpen = !this.sidebarCollapsed\n // 拖拽中的临时宽度优先于 prop 宽度;移动端宽度由 Drawer 内部变量管理。\n const sidebarStyle = this._mobile\n ? {}\n : { width: this._resizeWidth ?? (this.sidebarCollapsed ? this.collapsedWidth : this.sidebarWidth) }\n\n const toggleLabel = isSidebarOpen ? '折叠侧边栏' : '展开侧边栏'\n\n const desktopToggle = html`\n <web-ui-button\n class=\"sidebar-toggle\"\n icon\n variant=\"secondary\"\n @click=\"${this._toggleSidebar}\"\n aria-label=\"${toggleLabel}\"\n >\n <web-ui-icon .icon=${radixIconsPanelLeftMinimized}></web-ui-icon>\n </web-ui-button>\n `\n\n const sidebarViewport = html`\n <div class=\"sidebar-viewport\">\n <slot name=\"sidebar\"></slot>\n </div>\n `\n\n const desktopLayout = html`\n <div class=\"layout-body\">\n <div class=\"sidebar-wrapper\">\n <aside\n class=\"${classMap({\n collapsed: !isSidebarOpen\n })}\"\n style=\"${styleMap(sidebarStyle)}\"\n >\n <div class=\"aside-panel wui-glass\">\n ${sidebarViewport}\n <div class=\"sidebar-toggle-area\">${desktopToggle}</div>\n </div>\n ${\n this.sidebarResizable && !this.sidebarCollapsed\n ? html`\n <div\n class=\"sidebar-resize-handle\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n aria-label=\"调整侧边栏宽度\"\n @pointerdown=${this._handleResizePointerDown}\n @keydown=${this._handleResizeKeydown}\n ></div>\n `\n : nothing\n }\n </aside>\n </div>\n <div class=\"layout-content\">\n <header>\n <slot name=\"header\"></slot>\n </header>\n <main><slot></slot></main>\n <footer><slot name=\"tabbar\"></slot></footer>\n </div>\n </div>\n `\n\n const mobileLayout = html`\n <div class=\"layout-content\">\n <header>\n <web-ui-button\n class=\"mobile-toggle\"\n icon\n variant=\"glass\"\n @click=\"${this._toggleSidebar}\"\n aria-label=\"打开导航菜单\"\n >\n <web-ui-icon .icon=${radixIconsPanelLeftMinimized}></web-ui-icon>\n </web-ui-button>\n <slot name=\"header\"></slot>\n </header>\n <main><slot></slot></main>\n <footer><slot name=\"tabbar\"></slot></footer>\n </div>\n\n <web-ui-drawer\n placement=\"left\"\n ?open=\"${this.sidebarOpen}\"\n controlled\n draggable\n dialog-label=\"主导航\"\n @open-change=\"${this._onDrawerChange}\"\n style=\"${styleMap({\n '--wui-drawer-width': this.sidebarWidth,\n '--wui-drawer-radius': 'var(--wui-layout-sidebar-radius, 24px)'\n })}\"\n >\n ${sidebarViewport}\n </web-ui-drawer>\n `\n\n return html`\n <div class=\"layout-page\">\n <div class=\"layout-banner\">\n <slot name=\"banner\" @slotchange=\"${this._onBannerSlotChange}\"></slot>\n </div>\n ${this._mobile ? mobileLayout : desktopLayout}\n </div>\n `\n }\n\n declare readonly $events: {\n 'sidebar-collapsed-change': CustomEvent<{ collapsed: boolean }>\n 'sidebar-open-change': CustomEvent<{ open: boolean }>\n 'sidebar-width-change': CustomEvent<{ width: string }>\n 'mobile-change': CustomEvent<{ mobile: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-layout': WebUiLayout\n }\n}\n"],"mappings":"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"}