@greypan/web-ui 6.4.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.CN.md +134 -40
- package/README.md +145 -51
- package/dist/assets/glass.js +1 -1
- package/dist/assets/glass.js.map +1 -1
- package/dist/assets/selection-control.js +6 -0
- package/dist/assets/selection-control.js.map +1 -0
- package/dist/assets/toast-containers.js.map +1 -1
- package/dist/components/autocomplete/index.d.ts +2 -0
- package/dist/components/autocomplete/index.js +19 -20
- package/dist/components/autocomplete/index.js.map +1 -1
- package/dist/components/autocomplete/style.js.map +1 -1
- package/dist/components/badge/style.js.map +1 -1
- package/dist/components/button/style.js.map +1 -1
- package/dist/components/checkbox/index.d.ts +9 -2
- package/dist/components/checkbox/index.js +50 -26
- package/dist/components/checkbox/index.js.map +1 -1
- package/dist/components/checkbox/style.js +1 -1
- package/dist/components/checkbox/style.js.map +1 -1
- package/dist/components/checkbox-group/index.d.ts +3 -0
- package/dist/components/checkbox-group/index.js +24 -13
- package/dist/components/checkbox-group/index.js.map +1 -1
- package/dist/components/checkbox-group/style.js +1 -1
- package/dist/components/checkbox-group/style.js.map +1 -1
- package/dist/components/collapse/index.js.map +1 -1
- package/dist/components/context-menu/index.d.ts +10 -0
- package/dist/components/context-menu/index.js +48 -34
- package/dist/components/context-menu/index.js.map +1 -1
- package/dist/components/dialog/index.d.ts +6 -0
- package/dist/components/dialog/index.js +45 -20
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/dialog/style.js +1 -1
- package/dist/components/dialog/style.js.map +1 -1
- package/dist/components/drawer/index.d.ts +22 -4
- package/dist/components/drawer/index.js +134 -106
- package/dist/components/drawer/index.js.map +1 -1
- package/dist/components/drawer/style.js +1 -1
- package/dist/components/drawer/style.js.map +1 -1
- package/dist/components/dropdown/index.d.ts +21 -1
- package/dist/components/dropdown/index.js +74 -51
- package/dist/components/dropdown/index.js.map +1 -1
- package/dist/components/image-preview/index.js +86 -66
- package/dist/components/image-preview/index.js.map +1 -1
- package/dist/components/image-preview/style.js +1 -1
- package/dist/components/image-preview/style.js.map +1 -1
- package/dist/components/index.js +29 -29
- package/dist/components/input/index.js +2 -2
- package/dist/components/input/index.js.map +1 -1
- package/dist/components/input/style.js.map +1 -1
- package/dist/components/layout/index.js +2 -2
- package/dist/components/layout/index.js.map +1 -1
- package/dist/components/layout/style.js +1 -1
- package/dist/components/layout/style.js.map +1 -1
- package/dist/components/popover/index.d.ts +8 -2
- package/dist/components/popover/index.js +47 -52
- package/dist/components/popover/index.js.map +1 -1
- package/dist/components/radio/index.d.ts +1 -1
- package/dist/components/radio/index.js +14 -13
- package/dist/components/radio/index.js.map +1 -1
- package/dist/components/radio/style.js +1 -1
- package/dist/components/radio/style.js.map +1 -1
- package/dist/components/radio-group/index.d.ts +3 -0
- package/dist/components/radio-group/index.js +24 -13
- package/dist/components/radio-group/index.js.map +1 -1
- package/dist/components/radio-group/style.js +1 -1
- package/dist/components/radio-group/style.js.map +1 -1
- package/dist/components/segmented/style.js +1 -1
- package/dist/components/segmented/style.js.map +1 -1
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.js +20 -19
- package/dist/components/select/index.js.map +1 -1
- package/dist/components/slider/index.js.map +1 -1
- package/dist/components/slider/style.js +1 -1
- package/dist/components/slider/style.js.map +1 -1
- package/dist/components/svg-draw-lines/index.d.ts +13 -2
- package/dist/components/svg-draw-lines/index.js +44 -31
- package/dist/components/svg-draw-lines/index.js.map +1 -1
- package/dist/components/switch/index.js.map +1 -1
- package/dist/components/switch/style.js.map +1 -1
- package/dist/components/textarea/index.js +2 -2
- package/dist/components/textarea/index.js.map +1 -1
- package/dist/components/textarea/style.js.map +1 -1
- package/dist/components/theme/index.d.ts +17 -0
- package/dist/components/theme/index.js +180 -20
- package/dist/components/theme/index.js.map +1 -1
- package/dist/components/theme/style.js +1 -1
- package/dist/components/theme/style.js.map +1 -1
- package/dist/components/toast/index.d.ts +1 -1
- package/dist/components/toast/manager.d.ts +1 -3
- package/dist/components/toast/manager.js +92 -49
- package/dist/components/toast/manager.js.map +1 -1
- package/dist/components/toast/style.js.map +1 -1
- package/dist/components/toast/toast.d.ts +37 -0
- package/dist/components/toast/toast.js +61 -8
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/components/toast/types.d.ts +0 -4
- package/dist/components/tooltip/index.d.ts +6 -1
- package/dist/components/tooltip/index.js +11 -12
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/shared/gesture/drag-gesture.js +66 -64
- package/dist/shared/gesture/drag-gesture.js.map +1 -1
- package/dist/shared/gesture/physics.js +7 -29
- package/dist/shared/gesture/physics.js.map +1 -1
- package/dist/shared/menu-behavior/index.js +3 -4
- package/dist/shared/menu-behavior/index.js.map +1 -1
- package/dist/shared/option-portal/combobox-shell.js +7 -8
- package/dist/shared/option-portal/combobox-shell.js.map +1 -1
- package/dist/shared/overlay/anchored-panel.js +36 -31
- package/dist/shared/overlay/anchored-panel.js.map +1 -1
- package/dist/shared/overlay/native-dialog-presence.js +16 -0
- package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
- package/dist/shared/overlay/nested-drawer-layers.js +7 -4
- package/dist/shared/overlay/nested-drawer-layers.js.map +1 -1
- package/dist/shared/overlay/open-overlay.js +182 -0
- package/dist/shared/overlay/open-overlay.js.map +1 -0
- package/dist/shared/overlay/presence.js +19 -22
- package/dist/shared/overlay/presence.js.map +1 -1
- package/dist/shared/theme/duration.js +9 -0
- package/dist/shared/theme/duration.js.map +1 -0
- package/dist/shared/theme/reduced-motion.js +15 -0
- package/dist/shared/theme/reduced-motion.js.map +1 -0
- package/package.json +1 -1
- package/dist/shared/overlay/composition.js +0 -57
- package/dist/shared/overlay/composition.js.map +0 -1
- package/dist/shared/overlay/lifecycle.js +0 -41
- package/dist/shared/overlay/lifecycle.js.map +0 -1
- package/dist/vite.svg +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","names":[],"sources":["../../../src/components/toast/manager.ts"],"sourcesContent":["import { getFallbackOverlayRoot } from '@/shared/overlay/overlay-root'\nimport { findNearestTheme, findRootTheme } from '@/shared/overlay/theme-overlay-scope'\n\nimport type { WebUiToast } from './toast'\nimport type {\n ToastCloseReason,\n ToastInstanceOptions,\n ToastMessageUpdateOptions,\n ToastOptions,\n ToastPosition\n} from './types'\n\nlet toastIdCounter = 0\n\nconst toastContainers = new Set<HTMLElement>()\nconst visibleToasts = new Map<string, WebUiToast>()\n\nlet pendingBatch: Array<{ id: string; options: ToastInstanceOptions; container: HTMLElement }> | undefined\nlet isBatchScheduled = false\n\ntype ToastShortcutOptions = Omit<ToastOptions, 'message' | 'type'> & Pick<ToastInstanceOptions, 'position'>\n\nfunction generateId(): string {\n return `toast-${++toastIdCounter}`\n}\n\nfunction ensureContainer(position: ToastPosition, root: HTMLElement): HTMLElement {\n const existing = Array.from(root.children).find(\n (child): child is HTMLElement => child instanceof HTMLElement && child.dataset.wuiToastPosition === position\n )\n if (existing) return existing\n\n const container = document.createElement('div')\n container.className = `wui-toast-container wui-toast-${position}`\n container.dataset.wuiToastPosition = position\n container.setAttribute('role', 'log')\n container.setAttribute('aria-live', 'polite')\n container.setAttribute('aria-relevant', 'additions')\n root.appendChild(container)\n toastContainers.add(container)\n\n container.addEventListener('pointerenter', () => (container.dataset.hovered = 'true'))\n container.addEventListener('pointerleave', () => {\n delete container.dataset.hovered\n scrollToBottom(container)\n })\n return container\n}\n\nfunction scrollToBottom(container: HTMLElement) {\n if (container.dataset.hovered === 'true') return\n requestAnimationFrame(() => {\n container.scrollTop = container.scrollHeight\n })\n}\n\nfunction scheduleBatchFlush() {\n if (isBatchScheduled) return\n isBatchScheduled = true\n void Promise.resolve().then(flushBatch)\n}\n\nfunction flushBatch() {\n isBatchScheduled = false\n const batch = pendingBatch\n pendingBatch = undefined\n if (!batch) return\n\n const containersToScroll = new Set<HTMLElement>()\n for (const item of batch) {\n mountToast(item.id, item.options, item.container)\n containersToScroll.add(item.container)\n }\n for (const container of containersToScroll) scrollToBottom(container)\n}\n\nfunction createToast(options: ToastInstanceOptions): string {\n const id = options.id || generateId()\n if (visibleToasts.has(id)) return id\n\n const position = options.position || 'top-right'\n const targetTheme = options.target ? findNearestTheme(options.target) : findRootTheme()\n const root = options.container ?? targetTheme?.getOverlayRoot() ?? getFallbackOverlayRoot()\n const container = ensureContainer(position, root)\n\n if (!pendingBatch) pendingBatch = []\n pendingBatch.push({ id, options, container })\n scheduleBatchFlush()\n return id\n}\n\nfunction mountToast(id: string, options: ToastInstanceOptions, container: HTMLElement) {\n const el = document.createElement('web-ui-toast')\n el.toastId = id\n el.type = options.type || 'info'\n el.position = options.position || 'top-right'\n el.heading = options.heading || ''\n el.message = options.message\n el.duration = options.duration ?? 3000\n el.noCloseButton = options.closable === false\n\n el.addEventListener('toast-close', (e: Event) => {\n const detail = (e as CustomEvent<{ id: string; reason: ToastCloseReason }>).detail\n removeToast(detail.id)\n })\n\n container.appendChild(el)\n visibleToasts.set(id, el)\n requestAnimationFrame(() => el.show())\n}\n\nfunction removeToast(id: string) {\n const el = visibleToasts.get(id)\n if (!el) return\n el.remove()\n visibleToasts.delete(id)\n}\n\nfunction close(id: string) {\n visibleToasts.get(id)?.dismiss('programmatic')\n}\n\nfunction clear() {\n for (const el of visibleToasts.values()) el.dismiss('clear')\n}\n\n/**\n * 更新指定 Toast 的消息与可选标题,不影响其自动关闭倒计时。\n * 尚未完成批量挂载的 Toast 也会更新其初始内容。\n */\nfunction updateMessage(id: string, options: ToastMessageUpdateOptions) {\n const visibleToast = visibleToasts.get(id)\n if (visibleToast) {\n visibleToast.message = options.message\n if (options.heading !== undefined) visibleToast.heading = options.heading\n return\n }\n\n const pendingToast = pendingBatch?.find(item => item.id === id)\n if (!pendingToast) return\n pendingToast.options.message = options.message\n if (options.heading !== undefined) pendingToast.options.heading = options.heading\n}\n\nfunction toast(options: ToastInstanceOptions): string {\n return createToast(options)\n}\n\ntoast.success = (message: string, options?: ToastShortcutOptions) =>\n createToast({ ...options, message, type: 'success' })\ntoast.info = (message: string, options?: ToastShortcutOptions) => createToast({ ...options, message, type: 'info' })\ntoast.warning = (message: string, options?: ToastShortcutOptions) =>\n createToast({ ...options, message, type: 'warning' })\ntoast.error = (message: string, options?: ToastShortcutOptions) =>\n createToast({ ...options, message, type: 'error', duration: options?.duration ?? 5000 })\ntoast.close = close\ntoast.clear = clear\ntoast.updateMessage = updateMessage\n\n// 获取当前可见 toast 数量(测试用)\ntoast._visibleCount = () => visibleToasts.size\n\n// 重置全部状态(测试用)\ntoast._reset = () => {\n pendingBatch = undefined\n isBatchScheduled = false\n for (const el of visibleToasts.values()) el.remove()\n visibleToasts.clear()\n for (const container of toastContainers) container.remove()\n toastContainers.clear()\n}\n\nexport { toast }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"manager.js","names":[],"sources":["../../../src/components/toast/manager.ts"],"sourcesContent":["import { getFallbackOverlayRoot } from '@/shared/overlay/overlay-root'\nimport { findNearestTheme, findRootTheme } from '@/shared/overlay/theme-overlay-scope'\n\nimport type { WebUiToast } from './toast'\nimport type { ToastInstanceOptions, ToastOptions, ToastPosition } from './types'\n\nlet toastIdCounter = 0\n\nconst toastContainers = new Set<HTMLElement>()\nconst visibleToasts = new Map<string, WebUiToast>()\n\nlet pendingBatch: Array<{ id: string; options: ToastInstanceOptions; root: HTMLElement }> | undefined\nlet isBatchScheduled = false\n\ntype ToastShortcutOptions = Omit<ToastOptions, 'message' | 'type'> & Pick<ToastInstanceOptions, 'position'>\n\n// moveBefore 尚未进入 Baseline,DOM lib 未必声明;按能力探测调用,缺失时走降级分支。\ntype MaybeMoveBefore = HTMLElement & { moveBefore?: (node: Node, child: Node | null) => void }\n\nfunction generateId(): string {\n return `toast-${++toastIdCounter}`\n}\n\nfunction resolveRoot(options: ToastInstanceOptions): HTMLElement {\n const targetTheme = options.target ? findNearestTheme(options.target) : findRootTheme()\n return options.container ?? targetTheme?.getOverlayRoot() ?? getFallbackOverlayRoot()\n}\n\nfunction ensureContainer(position: ToastPosition, root: HTMLElement): HTMLElement {\n const existing = Array.from(root.children).find(\n (child): child is HTMLElement => child instanceof HTMLElement && child.dataset.wuiToastPosition === position\n )\n if (existing) return existing\n\n const container = document.createElement('div')\n container.className = `wui-toast-container wui-toast-${position}`\n container.dataset.wuiToastPosition = position\n container.setAttribute('role', 'log')\n container.setAttribute('aria-live', 'polite')\n container.setAttribute('aria-relevant', 'additions')\n root.appendChild(container)\n toastContainers.add(container)\n\n container.addEventListener('pointerenter', () => (container.dataset.hovered = 'true'))\n container.addEventListener('pointerleave', () => {\n delete container.dataset.hovered\n scrollToBottom(container)\n })\n return container\n}\n\nfunction pruneContainer(container: HTMLElement | null) {\n if (!container || container.children.length > 0) return\n container.remove()\n toastContainers.delete(container)\n}\n\nfunction scrollToBottom(container: HTMLElement) {\n if (container.dataset.hovered === 'true') return\n requestAnimationFrame(() => {\n container.scrollTop = container.scrollHeight\n })\n}\n\nfunction scheduleBatchFlush() {\n if (isBatchScheduled) return\n isBatchScheduled = true\n void Promise.resolve().then(flushBatch)\n}\n\nfunction flushBatch() {\n isBatchScheduled = false\n const batch = pendingBatch\n pendingBatch = undefined\n if (!batch) return\n\n const containersToScroll = new Set<HTMLElement>()\n for (const item of batch) {\n // 容器在挂载时才解析:待挂载条目被 patch 改了 position 也能落到正确容器。\n const container = ensureContainer(item.options.position || 'top-right', item.root)\n mountToast(item.id, item.options, container)\n containersToScroll.add(container)\n }\n for (const container of containersToScroll) scrollToBottom(container)\n}\n\n/**\n * 命令式 API 的 upsert 语义:指定同一个 id 的多次调用收敛到同一条 toast。\n * 给出的字段覆盖,未给出的保持原值;`container` 与 `target` 以首次为准,换 overlay root 牵连更广。\n */\nfunction mergePatch(base: ToastInstanceOptions, patch: ToastInstanceOptions): ToastInstanceOptions {\n return {\n ...base,\n message: patch.message ?? base.message,\n heading: patch.heading ?? base.heading,\n type: patch.type ?? base.type,\n duration: patch.duration ?? base.duration,\n closable: patch.closable ?? base.closable,\n position: patch.position ?? base.position,\n container: base.container,\n target: base.target\n }\n}\n\nfunction applyPatch(el: WebUiToast, options: ToastInstanceOptions) {\n if (options.message !== undefined) el.message = options.message\n if (options.heading !== undefined) el.heading = options.heading\n if (options.type !== undefined) el.type = options.type\n if (options.closable !== undefined) el.noCloseButton = options.closable === false\n // duration 只有显式传入才重启计时;message/heading/type 都是 Lit 属性,赋值不碰 _closeTimer。\n // 悬停暂停期间交给元素自己裁决:setDuration() 只记新值不点火,否则鼠标还压在 toast 上\n // 时倒计时就被重启,「悬停暂停」在这条路径上会静默失效。\n if (options.duration !== undefined) el.setDuration(options.duration)\n if (options.position !== undefined && options.position !== el.position) relocateToast(el, options.position)\n}\n\nfunction relocateToast(el: WebUiToast, position: ToastPosition) {\n const prev = el.parentElement\n const root = prev?.parentElement\n if (!root) return\n const next = ensureContainer(position, root)\n\n const movable = next as MaybeMoveBefore\n if (typeof movable.moveBefore === 'function') {\n // 不摘出节点,disconnected/connected 都不触发:计时、焦点与动画状态原样保留。\n movable.moveBefore(el, null)\n } else {\n el.pauseAutoClose()\n next.appendChild(el)\n el.resumeAutoClose()\n }\n\n el.position = position\n pruneContainer(prev)\n scrollToBottom(next)\n}\n\nfunction createToast(options: ToastInstanceOptions): string {\n const id = options.id || generateId()\n\n const mounted = visibleToasts.get(id)\n if (mounted) {\n /*\n * 「已挂载但 show() 还没跑」的窗口(mountToast 之后、rAF 里的 show() 之前)仍复用同一条元素;\n * 正在退场、或被宿主摘出 DOM 的元素则不再接受更新。后者的补丁只会落在一条即将(或已经)\n * 离开文档的元素上:调用方拿到同一个 id,却没有任何可见通知,`relocateToast` 也会因\n * 找不到 root 而静默吞掉 position。因此摘掉映射、改走新建路径。\n * 退场元素仍在 DOM 里,由它自己延迟派发的 `toast-close` 收尾(按元素身份解绑,见 removeToastElement)。\n */\n if (mounted.isConnected && !mounted.dismissing) {\n applyPatch(mounted, options)\n return id\n }\n visibleToasts.delete(id)\n }\n\n const pending = pendingBatch?.find(item => item.id === id)\n if (pending) {\n pending.options = mergePatch(pending.options, options)\n return id\n }\n\n if (!pendingBatch) pendingBatch = []\n pendingBatch.push({ id, options, root: resolveRoot(options) })\n scheduleBatchFlush()\n return id\n}\n\nfunction mountToast(id: string, options: ToastInstanceOptions, container: HTMLElement) {\n const el = document.createElement('web-ui-toast')\n const type = options.type || 'info'\n el.toastId = id\n el.type = type\n el.position = options.position || 'top-right'\n el.heading = options.heading || ''\n el.message = options.message\n // error 的 5000 只在挂载时兜底,不写进 options:写进去 applyPatch 就会把它当成「显式传入\n // duration」而每次 upsert 都重启倒计时,与「duration 只有显式传入才重启」的契约冲突。\n el.duration = options.duration ?? (type === 'error' ? 5000 : 3000)\n el.noCloseButton = options.closable === false\n\n el.addEventListener('toast-close', () => removeToastElement(el))\n\n container.appendChild(el)\n visibleToasts.set(id, el)\n requestAnimationFrame(() => {\n // close()/clear() 可能已经在这段窗口里收走了它(见 dismiss 的无退场动画分支):\n // 不再入场,否则一条孤儿元素会在 duration 之后补派一次同 id 的 toast-close。\n if (el.dismissing) return\n el.show()\n })\n}\n\n/**\n * 按元素身份解绑并移除。退场中的旧元素会在稍后派发 `toast-close`,而此刻同 id 可能已经有\n * 新元素(退场窗口内的 upsert 会新建一条);若按 `detail.id` 删映射,就会把刚建好的那条\n * 一起删掉 —— 与「孤儿元素收不走」是同一类缺陷,只是换了个入口。\n */\nfunction removeToastElement(el: WebUiToast) {\n if (visibleToasts.get(el.toastId) === el) visibleToasts.delete(el.toastId)\n const container = el.parentElement\n el.remove()\n pruneContainer(container)\n}\n\n/**\n * id 不在 Map 里时仍要清掉同 id 的残留节点:早先的按 id 删除路径在这里直接 return,\n * 重复挂载留下的孤儿 toast 因此连自关闭、close() 和 clear() 都收不走,会永久留在\n * role=\"log\" 容器里。强删不派发 `toast-close`,但对不可从管理器触达的元素来说没有消费方状态可言。\n */\nfunction removeStray(id: string) {\n for (const container of toastContainers) {\n for (const child of Array.from(container.children)) {\n if (child.tagName !== 'WEB-UI-TOAST') continue\n if ((child as Partial<WebUiToast>).toastId === id) child.remove()\n }\n }\n for (const container of Array.from(toastContainers)) pruneContainer(container)\n}\n\n/**\n * 关闭一个 id 时,「还没开始显示」的两种状态都要能取消,否则 close() 会静默失效:\n *\n * - 同 tick 仍在 `pendingBatch` 里:还不是 DOM 元素,`removeStray` 扫不到 —— 出队即取消挂载。\n * - 已 `flushBatch`、rAF 里的 `show()` 未跑:元素在映射里但 `visible` 仍是 false,\n * `dismiss()` 现在对这段窗口立即派发(无退场动画可播),由 toast-close 监听摘除。\n *\n * 已在退场的元素不必也不该再动:它自己延迟派发的 toast-close 会收尾,重复 close 不改变结果。\n */\nfunction close(id: string) {\n const queued = pendingBatch\n if (queued) {\n const index = queued.findIndex(item => item.id === id)\n if (index >= 0) {\n queued.splice(index, 1)\n return\n }\n }\n\n const el = visibleToasts.get(id)\n if (el) {\n el.dismiss('programmatic')\n return\n }\n removeStray(id)\n}\n\n/**\n * 队列条目与已挂载元素一起取消。二者都会在同一个微任务内落地,只遍历已挂载元素会让\n * 「clear() 之后仍冒出一条」成为唯一的例外 —— 与 close() 的可用范围对齐。\n */\nfunction clear() {\n pendingBatch = undefined\n for (const el of visibleToasts.values()) el.dismiss('clear')\n}\n\nfunction toast(options: ToastInstanceOptions): string {\n return createToast(options)\n}\n\ntoast.success = (message: string, options?: ToastShortcutOptions) =>\n createToast({ ...options, message, type: 'success' })\ntoast.info = (message: string, options?: ToastShortcutOptions) => createToast({ ...options, message, type: 'info' })\ntoast.warning = (message: string, options?: ToastShortcutOptions) =>\n createToast({ ...options, message, type: 'warning' })\n// 不注入 duration:error 的 5000 默认值在 mountToast 兜底,否则 upsert 每次都判定为\n// 「显式传入 duration」而重启倒计时(success/info/warning 都没有这个不对称)。\ntoast.error = (message: string, options?: ToastShortcutOptions) => createToast({ ...options, message, type: 'error' })\ntoast.close = close\ntoast.clear = clear\n\n// 重置全部状态(测试用)\ntoast._reset = () => {\n pendingBatch = undefined\n isBatchScheduled = false\n for (const el of visibleToasts.values()) el.remove()\n visibleToasts.clear()\n for (const container of toastContainers) container.remove()\n toastContainers.clear()\n}\n\nexport { toast }\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style.js","names":[],"sources":["../../../src/components/toast/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n display: block;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n/*
|
|
1
|
+
{"version":3,"file":"style.js","names":[],"sources":["../../../src/components/toast/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n display: block;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n/* 单层玻璃:.toast 自身承担背景/阴影/backdrop-filter。blur(0px) ↔ blur(4px) 平滑\n 插值(none↔blur 不可插值才是当年生硬跳变的根因),own opacity 过渡不禁用自身\n blur,因此 toast 直接 opacity + backdrop-filter + transform 同步过渡,无需双层。 */\n.toast {\n --wui-internal-glass-shadow: var(--wui-shadow-panel, 0 3px 9px rgb(0 0 0 / 0.27));\n --wui-glass-corner-radius: var(--wui-radius-menu, 18px);\n position: relative;\n transform: translate(var(--toast-slide-x, 0), var(--toast-slide-y, -8px)) scale(var(--wui-scale-enter, 0.95));\n\n display: flex;\n gap: 8px;\n\n width: min(320px, calc(100dvw - 2 * var(--wui-toast-viewport-gap, 16px)));\n min-height: 36px;\n padding: 8px;\n border-radius: var(--wui-radius-menu, 18px);\n\n font-size: var(--wui-font-size, 14px);\n line-height: 1.5;\n color: var(--wui-color-text, #1b1b1b);\n\n opacity: 0;\n background-color: var(--wui-color-surface-overlay, rgb(246 246 246 / 0.82));\n backdrop-filter: blur(0) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\n\n /* transition 列表需手写 -webkit-backdrop-filter:lightningcss 不会在 transition\n value 内追加 -webkit 项,iOS 16.4 上 blur 过渡依赖它(属性前缀会自动生成)。 */\n transition:\n opacity var(--wui-duration-toast-enter, 280ms) var(--wui-ease-enter, cubic-bezier(0.23, 1, 0.32, 1)),\n -webkit-backdrop-filter var(--wui-duration-toast-enter, 280ms) var(--wui-ease-enter, cubic-bezier(0.23, 1, 0.32, 1)),\n backdrop-filter var(--wui-duration-toast-enter, 280ms) var(--wui-ease-enter, cubic-bezier(0.23, 1, 0.32, 1)),\n transform var(--wui-duration-toast-enter, 280ms) var(--wui-ease-enter, cubic-bezier(0.23, 1, 0.32, 1));\n}\n\n:host([visible]) .toast {\n transform: translate(0, 0) scale(1);\n opacity: 1;\n backdrop-filter: blur(4px) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\n}\n\n:host(:not([visible])) .toast {\n transition-duration: var(--wui-duration-toast-exit, 200ms);\n}\n\n/* ===== 关闭按钮(放在 hover 规则之前,避免 specificity 降序) ===== */\n\nweb-ui-button.toast-close-btn {\n pointer-events: none;\n cursor: default;\n\n position: absolute;\n top: -8px;\n right: -8px;\n\n opacity: 0;\n\n transition: opacity var(--wui-duration-trigger, 160ms) ease;\n\n /* 内层原生按钮获得键盘焦点时宿主同样可见 */\n &:focus-within {\n pointer-events: auto;\n opacity: 1;\n }\n}\n\n@media (hover: hover) and (pointer: fine) {\n web-ui-button.toast-close-btn:hover,\n .toast:hover web-ui-button.toast-close-btn {\n pointer-events: auto;\n opacity: 1;\n }\n}\n\n/* secondary 变体默认用文本色,恢复原关闭按钮的 muted 观感 */\n.toast web-ui-button[icon] {\n --wui-button-color: var(--wui-color-text-secondary, #6a6a6a);\n}\n\n.toast-icon {\n display: flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n\n width: 20px;\n height: 20px;\n margin-bottom: auto;\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n}\n\n.toast.success .toast-icon {\n color: var(--wui-color-success, #16a34a);\n background-color: color-mix(in srgb, var(--wui-color-success, #16a34a) 10%, transparent);\n}\n\n.toast.info .toast-icon {\n color: var(--wui-color-info, #2563eb);\n background-color: color-mix(in srgb, var(--wui-color-info, #2563eb) 10%, transparent);\n}\n\n.toast.warning .toast-icon {\n color: var(--wui-color-warning, #d97706);\n background-color: color-mix(in srgb, var(--wui-color-warning, #d97706) 10%, transparent);\n}\n\n.toast.error .toast-icon {\n color: var(--wui-color-danger, #dc2626);\n background-color: color-mix(in srgb, var(--wui-color-danger, #dc2626) 10%, transparent);\n}\n\n.toast-body {\n flex: 1;\n min-width: 0;\n}\n\n.toast-heading {\n overflow: hidden;\n\n font-size: var(--wui-font-size, 14px);\n font-weight: 600;\n line-height: 1.4;\n color: var(--wui-color-text, #1b1b1b);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.toast-message {\n overflow: hidden;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 3;\n\n font-size: calc(var(--wui-font-size, 14px) - 1px);\n line-height: 1.4;\n color: color-mix(in srgb, var(--wui-color-text, #1b1b1b) 55%, transparent);\n word-break: break-all;\n}\n\n.toast-time {\n font-size: 12px;\n color: color-mix(in srgb, var(--wui-color-text, #1b1b1b) 30%, transparent);\n white-space: nowrap;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .toast,\n :host([visible]) .toast {\n transform: none;\n transition-duration: var(--wui-duration-feedback, 100ms);\n }\n}\n"],"mappings":""}
|
|
@@ -11,16 +11,53 @@ export declare class WebUiToast extends LitElement {
|
|
|
11
11
|
noCloseButton: boolean;
|
|
12
12
|
visible: boolean;
|
|
13
13
|
private _closeTimer?;
|
|
14
|
+
private _deadline;
|
|
15
|
+
/**
|
|
16
|
+
* 暂停自动关闭时记下的剩余时间。`undefined` 表示暂停时本来就没有计时器(`duration` 为 0、
|
|
17
|
+
* 或元素尚未 `show()`),与「已到期」(剩余 0)是两件事,不能共用一个 0。
|
|
18
|
+
*/
|
|
19
|
+
private _pausedRemaining?;
|
|
20
|
+
/** 指针停在 toast 上:计时处于悬停暂停态,只有指针离开才能续跑。 */
|
|
21
|
+
private _hoverPaused;
|
|
22
|
+
private _dismissing;
|
|
23
|
+
/**
|
|
24
|
+
* 退场已开始、`toast-close` 尚未派发。这段窗口内元素仍连接在 DOM、也仍被 manager 的映射
|
|
25
|
+
* 持有,但已不再接受属性更新:manager 的 upsert 据此走新建路径,否则「更新」会写进一条
|
|
26
|
+
* 正在消失的元素里 —— 调用方拿到同一个 id,却看不到任何可见通知。
|
|
27
|
+
*/
|
|
28
|
+
get dismissing(): boolean;
|
|
29
|
+
/** 指针仍停在 toast 上,自动关闭计时处于暂停态。 */
|
|
30
|
+
get hoverPaused(): boolean;
|
|
14
31
|
connectedCallback(): void;
|
|
15
32
|
disconnectedCallback(): void;
|
|
33
|
+
/**
|
|
34
|
+
* 定义该回调后,`Element.moveBefore()` 搬迁节点不再触发 disconnected/connected,
|
|
35
|
+
* 自动关闭计时与动画状态得以保留;未定义时旧引擎会走摘出重插,计时被 `_clearTimer()` 清掉。
|
|
36
|
+
*/
|
|
37
|
+
connectedMoveCallback(): void;
|
|
16
38
|
protected updated(changed: PropertyValues): void;
|
|
17
39
|
private _applySlideDirection;
|
|
18
40
|
startAutoClose(): void;
|
|
41
|
+
/**
|
|
42
|
+
* 由 manager 调用:显式 `duration` 更新。悬停暂停期间只记新值、不点火 —— 否则指针还压在
|
|
43
|
+
* toast 上时计时就被重启,「悬停暂停」在这条路径上直接失效。恢复时按新 duration 计满:
|
|
44
|
+
* 调用方显式改了生命周期,续跑按旧时长算出的剩余没有意义。`0` 表示不自动关闭,记成
|
|
45
|
+
* 「没有计时器」,否则恢复时会被当成「已到期」立即退场。
|
|
46
|
+
*/
|
|
47
|
+
setDuration(ms: number): void;
|
|
48
|
+
pauseAutoClose(): void;
|
|
49
|
+
resumeAutoClose(): void;
|
|
19
50
|
show(): void;
|
|
20
51
|
dismiss(reason?: ToastCloseReason): void;
|
|
21
52
|
private _clearTimer;
|
|
22
53
|
private _onPointerEnter;
|
|
23
54
|
private _onPointerLeave;
|
|
55
|
+
private _onDocumentPointerOver;
|
|
56
|
+
private _onDocumentPointerOut;
|
|
57
|
+
private _onDocumentPointerLeave;
|
|
58
|
+
private _resumeFromHover;
|
|
59
|
+
private _attachHoverFallback;
|
|
60
|
+
private _detachHoverFallback;
|
|
24
61
|
private _onCloseClick;
|
|
25
62
|
private _dispatchClose;
|
|
26
63
|
render(): import('lit').TemplateResult<1>;
|
|
@@ -24,10 +24,16 @@ function g() {
|
|
|
24
24
|
}
|
|
25
25
|
var _ = class extends l {
|
|
26
26
|
constructor(...e) {
|
|
27
|
-
super(...e), this.toastId = "", this.type = "info", this.position = "top-right", this.heading = "", this.message = "", this.duration = 3e3, this.noCloseButton = !1, this.visible = !1, this._onPointerEnter = (e) => {
|
|
28
|
-
e.pointerType !== "touch" && this.
|
|
27
|
+
super(...e), this.toastId = "", this.type = "info", this.position = "top-right", this.heading = "", this.message = "", this.duration = 3e3, this.noCloseButton = !1, this.visible = !1, this._deadline = 0, this._hoverPaused = !1, this._dismissing = !1, this._onPointerEnter = (e) => {
|
|
28
|
+
e.pointerType !== "touch" && (this._hoverPaused || (this._hoverPaused = !0, this.pauseAutoClose(), this._attachHoverFallback()));
|
|
29
29
|
}, this._onPointerLeave = (e) => {
|
|
30
|
-
e.pointerType !== "touch" && this.
|
|
30
|
+
e.pointerType !== "touch" && this._resumeFromHover();
|
|
31
|
+
}, this._onDocumentPointerOver = (e) => {
|
|
32
|
+
e.pointerType !== "touch" && (e.composedPath().includes(this) || this._resumeFromHover());
|
|
33
|
+
}, this._onDocumentPointerOut = (e) => {
|
|
34
|
+
e.pointerType !== "touch" && e.relatedTarget === null && this._resumeFromHover();
|
|
35
|
+
}, this._onDocumentPointerLeave = (e) => {
|
|
36
|
+
e.pointerType !== "touch" && this._resumeFromHover();
|
|
31
37
|
}, this._onCloseClick = () => {
|
|
32
38
|
this.dismiss("manual");
|
|
33
39
|
};
|
|
@@ -35,12 +41,19 @@ var _ = class extends l {
|
|
|
35
41
|
static {
|
|
36
42
|
this.styles = [f(o), f(c)];
|
|
37
43
|
}
|
|
44
|
+
get dismissing() {
|
|
45
|
+
return this._dismissing;
|
|
46
|
+
}
|
|
47
|
+
get hoverPaused() {
|
|
48
|
+
return this._hoverPaused;
|
|
49
|
+
}
|
|
38
50
|
connectedCallback() {
|
|
39
|
-
super.connectedCallback(), this.addEventListener("pointerenter", this._onPointerEnter), this.addEventListener("pointerleave", this._onPointerLeave);
|
|
51
|
+
super.connectedCallback(), this.addEventListener("pointerenter", this._onPointerEnter), this.addEventListener("pointerleave", this._onPointerLeave), this._hoverPaused && this._attachHoverFallback();
|
|
40
52
|
}
|
|
41
53
|
disconnectedCallback() {
|
|
42
|
-
super.disconnectedCallback(), this.removeEventListener("pointerenter", this._onPointerEnter), this.removeEventListener("pointerleave", this._onPointerLeave), this._clearTimer();
|
|
54
|
+
super.disconnectedCallback(), this.removeEventListener("pointerenter", this._onPointerEnter), this.removeEventListener("pointerleave", this._onPointerLeave), this._detachHoverFallback(), this._clearTimer();
|
|
43
55
|
}
|
|
56
|
+
connectedMoveCallback() {}
|
|
44
57
|
updated(e) {
|
|
45
58
|
e.has("position") && this._applySlideDirection();
|
|
46
59
|
}
|
|
@@ -49,15 +62,46 @@ var _ = class extends l {
|
|
|
49
62
|
this.position.includes("left") ? e = -20 : this.position.includes("right") && (e = 20), this.position.startsWith("top") ? t = -20 : this.position.startsWith("bottom") && (t = 20), this.style.setProperty("--toast-slide-x", `${e}px`), this.style.setProperty("--toast-slide-y", `${t}px`);
|
|
50
63
|
}
|
|
51
64
|
startAutoClose() {
|
|
52
|
-
this._clearTimer(), this.duration > 0 && (this._closeTimer = setTimeout(() => {
|
|
65
|
+
this._clearTimer(), this.duration > 0 && (this._deadline = Date.now() + this.duration, this._closeTimer = setTimeout(() => {
|
|
53
66
|
this.dismiss("auto");
|
|
54
67
|
}, this.duration));
|
|
55
68
|
}
|
|
69
|
+
setDuration(e) {
|
|
70
|
+
if (this.duration = e, this._hoverPaused) {
|
|
71
|
+
this._pausedRemaining = e > 0 ? e : void 0;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.startAutoClose();
|
|
75
|
+
}
|
|
76
|
+
pauseAutoClose() {
|
|
77
|
+
(this._closeTimer !== void 0 || this._pausedRemaining === void 0) && (this._pausedRemaining = this._closeTimer === void 0 ? void 0 : Math.max(0, this._deadline - Date.now()), this._clearTimer());
|
|
78
|
+
}
|
|
79
|
+
resumeAutoClose() {
|
|
80
|
+
if (this._hoverPaused) return;
|
|
81
|
+
let e = this._pausedRemaining;
|
|
82
|
+
if (this._pausedRemaining = void 0, e !== void 0) {
|
|
83
|
+
if (this._clearTimer(), e <= 0) {
|
|
84
|
+
this.dismiss("auto");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this._deadline = Date.now() + e, this._closeTimer = setTimeout(() => {
|
|
88
|
+
this.dismiss("auto");
|
|
89
|
+
}, e);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
56
92
|
show() {
|
|
57
|
-
this.visible = !0, this.
|
|
93
|
+
if (this._dismissing = !1, this.visible = !0, this._hoverPaused) {
|
|
94
|
+
this._pausedRemaining = this.duration > 0 ? this.duration : void 0;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.startAutoClose();
|
|
58
98
|
}
|
|
59
99
|
dismiss(e = "programmatic") {
|
|
60
|
-
if (
|
|
100
|
+
if (this._dismissing) return;
|
|
101
|
+
if (this._dismissing = !0, this._hoverPaused = !1, this._detachHoverFallback(), !this.visible) {
|
|
102
|
+
this._dispatchClose(e);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
61
105
|
this.visible = !1, this._clearTimer();
|
|
62
106
|
let t = () => {
|
|
63
107
|
this.removeEventListener("transitionend", t), this._dispatchClose(e);
|
|
@@ -69,6 +113,15 @@ var _ = class extends l {
|
|
|
69
113
|
_clearTimer() {
|
|
70
114
|
this._closeTimer !== void 0 && (clearTimeout(this._closeTimer), this._closeTimer = void 0);
|
|
71
115
|
}
|
|
116
|
+
_resumeFromHover() {
|
|
117
|
+
this._hoverPaused && (this._hoverPaused = !1, this._detachHoverFallback(), this.resumeAutoClose());
|
|
118
|
+
}
|
|
119
|
+
_attachHoverFallback() {
|
|
120
|
+
document.addEventListener("pointerover", this._onDocumentPointerOver), document.addEventListener("pointerout", this._onDocumentPointerOut), document.addEventListener("pointerleave", this._onDocumentPointerLeave);
|
|
121
|
+
}
|
|
122
|
+
_detachHoverFallback() {
|
|
123
|
+
document.removeEventListener("pointerover", this._onDocumentPointerOver), document.removeEventListener("pointerout", this._onDocumentPointerOut), document.removeEventListener("pointerleave", this._onDocumentPointerLeave);
|
|
124
|
+
}
|
|
72
125
|
_dispatchClose(e) {
|
|
73
126
|
this.dispatchEvent(new CustomEvent("toast-close", {
|
|
74
127
|
detail: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.js","names":[],"sources":["../../../src/components/toast/toast.ts"],"sourcesContent":["import type { IconifyIcon } from '@iconify/types'\nimport { html, LitElement, nothing, type PropertyValues, unsafeCSS } from 'lit'\n\nimport '@/components/icon'\n// web-ui-button 复用 icon 变体的焦点环与统一样式(secondary 变体,浅灰底贴合 toast 角落)\nimport '@/components/button'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport { lucideCheck, lucideInfo, lucideTriangleAlert, lucideCircleAlert, heroiconsXMark16Solid } from '@/icons'\nimport { getTransitionDuration } from '@/shared/overlay/presence'\n\nimport style from './style.css?inline'\nimport type { ToastCloseReason, ToastPosition, ToastType } from './types'\n\nconst TYPE_ICONS: Record<ToastType, IconifyIcon> = {\n success: lucideCheck,\n info: lucideInfo,\n warning: lucideTriangleAlert,\n error: lucideCircleAlert\n}\n\nfunction _formatTime(): string {\n const now = new Date()\n const h = now.getHours().toString().padStart(2, '0')\n const m = now.getMinutes().toString().padStart(2, '0')\n return `${h}:${m}`\n}\n\n@customElement('web-ui-toast')\nexport class WebUiToast extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n @property({ type: String, attribute: false }) toastId = ''\n @property({ type: String, reflect: true }) type: ToastType = 'info'\n @property({ type: String, reflect: true }) position: ToastPosition = 'top-right'\n @property({ type: String }) heading = ''\n @property({ type: String }) message = ''\n @property({ type: Number }) duration = 3000\n @property({ type: Boolean, reflect: true, attribute: 'no-close-button' }) noCloseButton = false\n @property({ type: Boolean, reflect: true }) visible = false\n\n private _closeTimer?: ReturnType<typeof setTimeout>\n\n override connectedCallback() {\n super.connectedCallback()\n this.addEventListener('pointerenter', this._onPointerEnter)\n this.addEventListener('pointerleave', this._onPointerLeave)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('pointerenter', this._onPointerEnter)\n this.removeEventListener('pointerleave', this._onPointerLeave)\n this._clearTimer()\n }\n\n protected override updated(changed: PropertyValues) {\n if (changed.has('position')) {\n this._applySlideDirection()\n }\n }\n\n // 根据 position 设置入场/退场滑动方向\n private _applySlideDirection() {\n const SLIDE_OFFSET = 20\n let x = 0\n let y = 0\n\n if (this.position.includes('left')) x = -SLIDE_OFFSET\n else if (this.position.includes('right')) x = SLIDE_OFFSET\n\n if (this.position.startsWith('top')) y = -SLIDE_OFFSET\n else if (this.position.startsWith('bottom')) y = SLIDE_OFFSET\n\n this.style.setProperty('--toast-slide-x', `${x}px`)\n this.style.setProperty('--toast-slide-y', `${y}px`)\n }\n\n // 启动自动关闭计时器\n startAutoClose() {\n this._clearTimer()\n if (this.duration > 0) {\n this._closeTimer = setTimeout(() => {\n this.dismiss('auto')\n }, this.duration)\n }\n }\n\n // 由 manager 调用:播放入场动画后自动开始计时\n show() {\n this.visible = true\n this.startAutoClose()\n }\n\n // 含退场动画,结束由 transitionend 驱动后派发 close。\n dismiss(reason: ToastCloseReason = 'programmatic') {\n if (!this.visible) return\n this.visible = false\n this._clearTimer()\n const onEnd = () => {\n this.removeEventListener('transitionend', onEnd)\n this._dispatchClose(reason)\n }\n this.addEventListener('transitionend', onEnd)\n // fallback: transitionend 不触发时(display:none 等)\n const toast = this.shadowRoot?.querySelector<HTMLElement>('.toast')\n setTimeout(onEnd, getTransitionDuration(toast ?? this) + 80)\n }\n\n private _clearTimer() {\n if (this._closeTimer !== undefined) {\n clearTimeout(this._closeTimer)\n this._closeTimer = undefined\n }\n }\n\n private _onPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n this._clearTimer()\n }\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n this.startAutoClose()\n }\n\n private _onCloseClick = () => {\n this.dismiss('manual')\n }\n\n private _dispatchClose(reason: ToastCloseReason) {\n this.dispatchEvent(\n new CustomEvent('toast-close', {\n detail: { id: this.toastId, reason },\n bubbles: true,\n composed: true\n })\n )\n }\n\n override render() {\n const icon = TYPE_ICONS[this.type]\n const ariaLive = this.type === 'error' ? 'assertive' : 'polite'\n // role=\"alert\" 会强制隐式 aria-live=\"assertive\",覆盖掉上方 aria-live 设置,\n // 因此仅 error toast 使用 alert(本就应 assertive);其余类型交给容器 role=\"log\" 的 polite 播报。\n const role = this.type === 'error' ? 'alert' : nothing\n\n return html`\n <div class=\"toast wui-glass ${this.type}\" role=${role} aria-live=${ariaLive} aria-atomic=\"true\">\n <span class=\"toast-icon\" aria-hidden=\"true\">\n <web-ui-icon .icon=${icon} size=\"14\"></web-ui-icon>\n </span>\n <div class=\"toast-body\">\n ${this.heading ? html`<div class=\"toast-heading\">${this.heading}</div>` : nothing}\n <div class=\"toast-message\">${this.message}</div>\n </div>\n <span class=\"toast-time\">${_formatTime()}</span>\n ${\n !this.noCloseButton\n ? html`\n <web-ui-button\n class=\"toast-close-btn\"\n icon\n variant=\"secondary\"\n size=\"24\"\n aria-label=\"关闭\"\n @click=${this._onCloseClick}\n >\n <web-ui-icon .icon=${heroiconsXMark16Solid} size=\"16\"></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n }\n </div>\n `\n }\n\n declare readonly $events: {\n 'toast-close': CustomEvent<{ id: string; reason: ToastCloseReason }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-toast': WebUiToast\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toast.js","names":[],"sources":["../../../src/components/toast/toast.ts"],"sourcesContent":["import type { IconifyIcon } from '@iconify/types'\nimport { html, LitElement, nothing, type PropertyValues, unsafeCSS } from 'lit'\n\nimport '@/components/icon'\n// web-ui-button 复用 icon 变体的焦点环与统一样式(secondary 变体,浅灰底贴合 toast 角落)\nimport '@/components/button'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport { lucideCheck, lucideInfo, lucideTriangleAlert, lucideCircleAlert, heroiconsXMark16Solid } from '@/icons'\nimport { getTransitionDuration } from '@/shared/overlay/presence'\n\nimport style from './style.css?inline'\nimport type { ToastCloseReason, ToastPosition, ToastType } from './types'\n\nconst TYPE_ICONS: Record<ToastType, IconifyIcon> = {\n success: lucideCheck,\n info: lucideInfo,\n warning: lucideTriangleAlert,\n error: lucideCircleAlert\n}\n\nfunction _formatTime(): string {\n const now = new Date()\n const h = now.getHours().toString().padStart(2, '0')\n const m = now.getMinutes().toString().padStart(2, '0')\n return `${h}:${m}`\n}\n\n@customElement('web-ui-toast')\nexport class WebUiToast extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(style)]\n\n @property({ type: String, attribute: false }) toastId = ''\n @property({ type: String, reflect: true }) type: ToastType = 'info'\n @property({ type: String, reflect: true }) position: ToastPosition = 'top-right'\n @property({ type: String }) heading = ''\n @property({ type: String }) message = ''\n @property({ type: Number }) duration = 3000\n @property({ type: Boolean, reflect: true, attribute: 'no-close-button' }) noCloseButton = false\n @property({ type: Boolean, reflect: true }) visible = false\n\n private _closeTimer?: ReturnType<typeof setTimeout>\n private _deadline = 0\n /**\n * 暂停自动关闭时记下的剩余时间。`undefined` 表示暂停时本来就没有计时器(`duration` 为 0、\n * 或元素尚未 `show()`),与「已到期」(剩余 0)是两件事,不能共用一个 0。\n */\n private _pausedRemaining?: number\n /** 指针停在 toast 上:计时处于悬停暂停态,只有指针离开才能续跑。 */\n private _hoverPaused = false\n private _dismissing = false\n\n /**\n * 退场已开始、`toast-close` 尚未派发。这段窗口内元素仍连接在 DOM、也仍被 manager 的映射\n * 持有,但已不再接受属性更新:manager 的 upsert 据此走新建路径,否则「更新」会写进一条\n * 正在消失的元素里 —— 调用方拿到同一个 id,却看不到任何可见通知。\n */\n get dismissing(): boolean {\n return this._dismissing\n }\n\n /** 指针仍停在 toast 上,自动关闭计时处于暂停态。 */\n get hoverPaused(): boolean {\n return this._hoverPaused\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this.addEventListener('pointerenter', this._onPointerEnter)\n this.addEventListener('pointerleave', this._onPointerLeave)\n // 悬停中被搬迁(不支持 moveBefore 的引擎走摘出重插)时暂停态要连兜底一起带回来,\n // 否则新位置上既没有 document 监听、也不会再补发 pointerenter,这条 toast 又变成永久停留。\n if (this._hoverPaused) this._attachHoverFallback()\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('pointerenter', this._onPointerEnter)\n this.removeEventListener('pointerleave', this._onPointerLeave)\n this._detachHoverFallback()\n this._clearTimer()\n }\n\n /**\n * 定义该回调后,`Element.moveBefore()` 搬迁节点不再触发 disconnected/connected,\n * 自动关闭计时与动画状态得以保留;未定义时旧引擎会走摘出重插,计时被 `_clearTimer()` 清掉。\n */\n connectedMoveCallback() {}\n\n protected override updated(changed: PropertyValues) {\n if (changed.has('position')) {\n this._applySlideDirection()\n }\n }\n\n private _applySlideDirection() {\n const SLIDE_OFFSET = 20\n let x = 0\n let y = 0\n\n if (this.position.includes('left')) x = -SLIDE_OFFSET\n else if (this.position.includes('right')) x = SLIDE_OFFSET\n\n if (this.position.startsWith('top')) y = -SLIDE_OFFSET\n else if (this.position.startsWith('bottom')) y = SLIDE_OFFSET\n\n this.style.setProperty('--toast-slide-x', `${x}px`)\n this.style.setProperty('--toast-slide-y', `${y}px`)\n }\n\n startAutoClose() {\n this._clearTimer()\n if (this.duration > 0) {\n this._deadline = Date.now() + this.duration\n this._closeTimer = setTimeout(() => {\n this.dismiss('auto')\n }, this.duration)\n }\n }\n\n /**\n * 由 manager 调用:显式 `duration` 更新。悬停暂停期间只记新值、不点火 —— 否则指针还压在\n * toast 上时计时就被重启,「悬停暂停」在这条路径上直接失效。恢复时按新 duration 计满:\n * 调用方显式改了生命周期,续跑按旧时长算出的剩余没有意义。`0` 表示不自动关闭,记成\n * 「没有计时器」,否则恢复时会被当成「已到期」立即退场。\n */\n setDuration(ms: number) {\n this.duration = ms\n if (this._hoverPaused) {\n this._pausedRemaining = ms > 0 ? ms : undefined\n return\n }\n this.startAutoClose()\n }\n\n // 不支持 moveBefore 的引擎上搬迁节点前调用:记下剩余时间,搬完由 resumeAutoClose() 续跑。\n pauseAutoClose() {\n // 已经在暂停态时保留既有剩余:悬停暂停期间若发生搬迁,降级路径也会走到这里,无条件\n // 重算会把悬停记下的剩余抹成「没有计时器」,指针离开后这条 toast 就再也不会关闭。\n if (this._closeTimer === undefined && this._pausedRemaining !== undefined) return\n // 计时器仍在跑时剩余时间可能算出 0:deadline 已过、回调还排在队列里没执行。这一支与\n // 「本来就没有计时器」必须分开记(undefined vs 0),否则续跑无法区分两者。\n this._pausedRemaining = this._closeTimer === undefined ? undefined : Math.max(0, this._deadline - Date.now())\n this._clearTimer()\n }\n\n // 与 pauseAutoClose() 成对:暂停时没有计时器就保持原状,不重启满时长;\n // 已到期则立即退场 —— 而不是把「已到期」当成「从来没有计时器」放过,那样这条 toast 会永不关闭。\n resumeAutoClose() {\n // 悬停还没结束时只交还剩余时间、不点火:搬迁降级路径也会走到这里,真正的续跑等指针\n // 离开(_resumeFromHover)再开始,否则鼠标还停着就已经在倒计时了。\n if (this._hoverPaused) return\n const remaining = this._pausedRemaining\n this._pausedRemaining = undefined\n if (remaining === undefined) return\n // 装新计时器前先拆掉可能仍在跑的旧计时器:pause/resume 一旦不再严格成对(调用序被重排),\n // 直接覆盖 `_closeTimer` 会留下两个 deadline 同时倒计时,先到的那个把 toast 提前关掉。\n this._clearTimer()\n if (remaining <= 0) {\n this.dismiss('auto')\n return\n }\n this._deadline = Date.now() + remaining\n this._closeTimer = setTimeout(() => {\n this.dismiss('auto')\n }, remaining)\n }\n\n // 由 manager 调用:播放入场动画后自动开始计时\n show() {\n this._dismissing = false\n this.visible = true\n // 挂载时指针已经压在上面(光标停在 toast 将要出现的位置):入场后直接进入暂停,\n // 不能点火 —— 那条计时会在指针还停着的时候把 toast 关掉。\n if (this._hoverPaused) {\n this._pausedRemaining = this.duration > 0 ? this.duration : undefined\n return\n }\n this.startAutoClose()\n }\n\n // 含退场动画,结束由 transitionend 驱动后派发 close。\n dismiss(reason: ToastCloseReason = 'programmatic') {\n if (this._dismissing) return\n this._dismissing = true\n // 退场优先于悬停:暂停态不再有意义,document 兜底监听也要收掉,否则退场中的\n // 元素会继续持有「指针还在上面」的状态。\n this._hoverPaused = false\n this._detachHoverFallback()\n if (!this.visible) {\n // 尚未 show()(manager 挂载之后、rAF 之前)或从未显示过:没有退场动画可播,立即派发,\n // 由 manager 的 toast-close 监听摘除元素。若在这里早退,close()/clear() 落在这段窗口\n // 里就会静默失效 —— 元素照常入场,调用方却以为已经关掉了。\n this._dispatchClose(reason)\n return\n }\n this.visible = false\n this._clearTimer()\n const onEnd = () => {\n this.removeEventListener('transitionend', onEnd)\n this._dispatchClose(reason)\n }\n this.addEventListener('transitionend', onEnd)\n // fallback: transitionend 不触发时(display:none 等)\n const toast = this.shadowRoot?.querySelector<HTMLElement>('.toast')\n setTimeout(onEnd, getTransitionDuration(toast ?? this) + 80)\n }\n\n private _clearTimer() {\n if (this._closeTimer !== undefined) {\n clearTimeout(this._closeTimer)\n this._closeTimer = undefined\n }\n }\n\n private _onPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this._hoverPaused) return\n this._hoverPaused = true\n this.pauseAutoClose()\n // pointerleave 是唯一的恢复入口时,漏掉一次(指针拖出窗口、元素在悬停期间被搬迁或\n // 摘出、光标静止时布局把 toast 移走)这条 toast 就永久停在屏幕上。document 级兜底\n // 补上这个缺口:指针落到别的元素上、或离开文档,都判定悬停结束。三个信号互相兜底,\n // 因为单一信号在不同引擎上都有漏派发的场景。\n this._attachHoverFallback()\n }\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n this._resumeFromHover()\n }\n\n // 兜底路径:指针移到别的元素上(冒泡到 document)。路径不含本元素说明指针已经不在\n // toast 上;composedPath() 覆盖 shadow DOM,指针在 toast 内部节点之间移动不算离开。\n private _onDocumentPointerOver = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (e.composedPath().includes(this)) return\n this._resumeFromHover()\n }\n\n // 兜底路径:指针移出窗口。部分引擎不保证 document 上的 pointerleave,用 relatedTarget\n // 为空的 pointerout 补上 —— 悬停中的 toast 被摘除时同样会派发这种 pointerout。\n private _onDocumentPointerOut = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (e.relatedTarget !== null) return\n this._resumeFromHover()\n }\n\n // 兜底路径:指针离开整个文档(移出窗口)。文档内的元素间移动不会派发到 document。\n private _onDocumentPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n this._resumeFromHover()\n }\n\n private _resumeFromHover() {\n if (!this._hoverPaused) return\n this._hoverPaused = false\n this._detachHoverFallback()\n this.resumeAutoClose()\n }\n\n private _attachHoverFallback() {\n document.addEventListener('pointerover', this._onDocumentPointerOver)\n document.addEventListener('pointerout', this._onDocumentPointerOut)\n document.addEventListener('pointerleave', this._onDocumentPointerLeave)\n }\n\n private _detachHoverFallback() {\n document.removeEventListener('pointerover', this._onDocumentPointerOver)\n document.removeEventListener('pointerout', this._onDocumentPointerOut)\n document.removeEventListener('pointerleave', this._onDocumentPointerLeave)\n }\n\n private _onCloseClick = () => {\n this.dismiss('manual')\n }\n\n private _dispatchClose(reason: ToastCloseReason) {\n this.dispatchEvent(\n new CustomEvent('toast-close', {\n detail: { id: this.toastId, reason },\n bubbles: true,\n composed: true\n })\n )\n }\n\n override render() {\n const icon = TYPE_ICONS[this.type]\n const ariaLive = this.type === 'error' ? 'assertive' : 'polite'\n // role=\"alert\" 会强制隐式 aria-live=\"assertive\",覆盖掉上方 aria-live 设置,\n // 因此仅 error toast 使用 alert(本就应 assertive);其余类型交给容器 role=\"log\" 的 polite 播报。\n const role = this.type === 'error' ? 'alert' : nothing\n\n return html`\n <div class=\"toast wui-glass ${this.type}\" role=${role} aria-live=${ariaLive} aria-atomic=\"true\">\n <span class=\"toast-icon\" aria-hidden=\"true\">\n <web-ui-icon .icon=${icon} size=\"14\"></web-ui-icon>\n </span>\n <div class=\"toast-body\">\n ${this.heading ? html`<div class=\"toast-heading\">${this.heading}</div>` : nothing}\n <div class=\"toast-message\">${this.message}</div>\n </div>\n <span class=\"toast-time\">${_formatTime()}</span>\n ${\n !this.noCloseButton\n ? html`\n <web-ui-button\n class=\"toast-close-btn\"\n icon\n variant=\"secondary\"\n size=\"24\"\n aria-label=\"关闭\"\n @click=${this._onCloseClick}\n >\n <web-ui-icon .icon=${heroiconsXMark16Solid} size=\"16\"></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n }\n </div>\n `\n }\n\n declare readonly $events: {\n 'toast-close': CustomEvent<{ id: string; reason: ToastCloseReason }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-toast': WebUiToast\n }\n}\n"],"mappings":"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"}
|
|
@@ -26,7 +26,12 @@ export declare class WebUiTooltip extends LitElement {
|
|
|
26
26
|
private readonly _userOpenChange;
|
|
27
27
|
private _shouldOpenInstantly;
|
|
28
28
|
private _portal?;
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* 实例作用域的帧事务入口。`requestClose` 是关闭入口的唯一接线:面板的
|
|
31
|
+
* `arbitration: 'none'` 让 tooltip 在组合树里但永不成候选,因此这条**现在不会被
|
|
32
|
+
* 模块调用**。保留它是不想把「怎么关」藏起来,也让仲裁策略变化时依然成立。
|
|
33
|
+
*/
|
|
34
|
+
private readonly _overlay;
|
|
30
35
|
private readonly _panel;
|
|
31
36
|
get isOpen(): boolean;
|
|
32
37
|
connectedCallback(): void;
|
|
@@ -4,9 +4,9 @@ import n from "../../assets/overlay-motion.js";
|
|
|
4
4
|
import { normalizeLiteral as r, normalizeNumber as i } from "../../shared/normalize/index.js";
|
|
5
5
|
import { dispatchOpenChangeEvent as a } from "../../shared/open-state/index.js";
|
|
6
6
|
import { defineAnchoredPanel as o } from "../../shared/overlay/anchored-panel.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { defineOpenOverlay as s } from "../../shared/overlay/open-overlay.js";
|
|
8
|
+
import { defineOverlayPortal as c } from "../../shared/overlay/portal.js";
|
|
9
|
+
import { UserChangeController as l } from "../../shared/events/user-change.js";
|
|
10
10
|
import { FLOATING_PLACEMENTS as u } from "../../shared/overlay/placement-props.js";
|
|
11
11
|
import d from "./style.js";
|
|
12
12
|
import { LitElement as f, html as p, unsafeCSS as m } from "lit";
|
|
@@ -14,10 +14,7 @@ import { customElement as h, property as g } from "lit/decorators.js";
|
|
|
14
14
|
//#region src/components/tooltip/index.ts
|
|
15
15
|
var _ = 0, v = class extends f {
|
|
16
16
|
constructor(...e) {
|
|
17
|
-
super(...e), this._placement = "top", this._showDelay = 200, this._hideDelay = 100, this.disabled = !1, this._offset = 6, this.content = "", this.open = !1, this.portal = !1, this._isCountedVisible = !1, this._userOpenChange = new
|
|
18
|
-
isConnected: () => this.isConnected,
|
|
19
|
-
isOpen: () => this.open
|
|
20
|
-
}), this._panel = o().make({
|
|
17
|
+
super(...e), this._placement = "top", this._showDelay = 200, this._hideDelay = 100, this.disabled = !1, this._offset = 6, this.content = "", this.open = !1, this.portal = !1, this._isCountedVisible = !1, this._userOpenChange = new l(), this._shouldOpenInstantly = !0, this._overlay = s().make({ requestClose: () => this._hide() }), this._panel = o().make({
|
|
21
18
|
getAnchor: () => this.shadowRoot?.querySelector(".tooltip-trigger") ?? null,
|
|
22
19
|
getLocalPanel: () => this.shadowRoot?.querySelector(".tooltip-panel") ?? null,
|
|
23
20
|
getPositioning: () => ({
|
|
@@ -26,7 +23,9 @@ var _ = 0, v = class extends f {
|
|
|
26
23
|
strategy: this.portal ? "fixed" : "absolute"
|
|
27
24
|
}),
|
|
28
25
|
isPortal: () => this.portal,
|
|
29
|
-
createPortal: () => this._createPortal()
|
|
26
|
+
createPortal: () => this._createPortal(),
|
|
27
|
+
openOverlay: this._overlay,
|
|
28
|
+
arbitration: "none"
|
|
30
29
|
}), this._onPointerEnter = (e) => {
|
|
31
30
|
if (e.pointerType === "touch" || this.disabled) return;
|
|
32
31
|
clearTimeout(this._hideTimer);
|
|
@@ -79,16 +78,16 @@ var _ = 0, v = class extends f {
|
|
|
79
78
|
return this.open;
|
|
80
79
|
}
|
|
81
80
|
connectedCallback() {
|
|
82
|
-
super.connectedCallback(), this.
|
|
81
|
+
super.connectedCallback(), this._overlay.resume(), this.addEventListener("pointerenter", this._onPointerEnter), this.addEventListener("pointerleave", this._onPointerLeave), this.addEventListener("focusin", this._onFocusIn), this.addEventListener("focusout", this._onFocusOut);
|
|
83
82
|
}
|
|
84
83
|
disconnectedCallback() {
|
|
85
|
-
super.disconnectedCallback(), this.removeEventListener("pointerenter", this._onPointerEnter), this.removeEventListener("pointerleave", this._onPointerLeave), this.removeEventListener("focusin", this._onFocusIn), this.removeEventListener("focusout", this._onFocusOut), clearTimeout(this._showTimer), clearTimeout(this._hideTimer), this.
|
|
84
|
+
super.disconnectedCallback(), this.removeEventListener("pointerenter", this._onPointerEnter), this.removeEventListener("pointerleave", this._onPointerLeave), this.removeEventListener("focusin", this._onFocusIn), this.removeEventListener("focusout", this._onFocusOut), clearTimeout(this._showTimer), clearTimeout(this._hideTimer), this._overlay.suspend(), this._syncVisibleTooltipCount(!1), this._panel.dispose();
|
|
86
85
|
}
|
|
87
86
|
firstUpdated() {
|
|
88
87
|
this.open && (this._openOverlay(this._shouldOpenInstantly), this._shouldOpenInstantly = !0);
|
|
89
88
|
}
|
|
90
89
|
updated(e) {
|
|
91
|
-
e.has("portal") || e.has("overlayContainer") ? (this.
|
|
90
|
+
e.has("portal") || e.has("overlayContainer") ? (this._overlay.invalidate(), this._overlay.scheduleFrame(() => this._reconfigureOverlay())) : (e.has("placement") || e.has("offset")) && requestAnimationFrame(() => this._panel.updatePosition()), e.has("open") && (this._syncVisibleTooltipCount(this.open), this.open ? (this._openOverlay(this._shouldOpenInstantly), this._shouldOpenInstantly = !0) : (this._overlay.invalidate(), this._closeOverlay()), this._userOpenChange.consume() && this._dispatchChange(this.open)), e.has("content") && this._syncPortalContent();
|
|
92
91
|
}
|
|
93
92
|
_show(e = !1) {
|
|
94
93
|
this.open ||= (this._shouldOpenInstantly = e, this._userOpenChange.mark(), !0);
|
|
@@ -100,7 +99,7 @@ var _ = 0, v = class extends f {
|
|
|
100
99
|
this._panel.open(e);
|
|
101
100
|
}
|
|
102
101
|
_createPortal() {
|
|
103
|
-
let e =
|
|
102
|
+
let e = c().make({
|
|
104
103
|
container: this.overlayContainer,
|
|
105
104
|
target: this,
|
|
106
105
|
style: `${t}\n${n}\n${d}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/tooltip/index.ts"],"sourcesContent":["import type { Placement } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport overlayMotion from '@/assets/overlay-motion.css?inline'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { normalizeLiteral, normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineAnchoredPanel } from '@/shared/overlay/anchored-panel'\nimport { defineOverlayLifecycle } from '@/shared/overlay/lifecycle'\nimport { FLOATING_PLACEMENTS } from '@/shared/overlay/placement-props'\nimport { defineOverlayPortal } from '@/shared/overlay/portal'\nimport type { OverlayContainer, OverlayPortal } from '@/shared/overlay/portal'\n\nimport style from './style.css?inline'\n\nlet visibleTooltipCount = 0\n\n@customElement('web-ui-tooltip')\nexport class WebUiTooltip extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(overlayMotion), unsafeCSS(style)]\n\n @property({ type: String, reflect: true })\n get placement(): Placement {\n return this._placement\n }\n set placement(v: string) {\n const old = this._placement\n this._placement = normalizeLiteral(v, FLOATING_PLACEMENTS, 'top')\n this.requestUpdate('placement', old)\n }\n private _placement: Placement = 'top'\n\n @property({ type: Number, attribute: 'show-delay' })\n get showDelay(): number {\n return this._showDelay\n }\n set showDelay(v: number) {\n const old = this._showDelay\n this._showDelay = normalizeNumber(v, 0, 5000, 200)\n this.requestUpdate('showDelay', old)\n }\n private _showDelay = 200\n\n @property({ type: Number, attribute: 'hide-delay' })\n get hideDelay(): number {\n return this._hideDelay\n }\n set hideDelay(v: number) {\n const old = this._hideDelay\n this._hideDelay = normalizeNumber(v, 0, 5000, 100)\n this.requestUpdate('hideDelay', old)\n }\n private _hideDelay = 100\n\n @property({ type: Boolean, reflect: true }) disabled = false\n\n @property({ type: Number })\n get offset(): number {\n return this._offset\n }\n set offset(v: number) {\n const old = this._offset\n this._offset = normalizeNumber(v, 0, 100, 6)\n this.requestUpdate('offset', old)\n }\n private _offset = 6\n\n @property({ type: String }) content = ''\n\n @property({ type: Boolean, reflect: true }) open = false\n @property({ type: Boolean, reflect: true }) portal = false\n @property({ attribute: false }) overlayContainer?: OverlayContainer\n\n private _showTimer?: ReturnType<typeof setTimeout>\n private _hideTimer?: ReturnType<typeof setTimeout>\n private _isCountedVisible = false\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n private _portal?: OverlayPortal\n private readonly _lifecycle = defineOverlayLifecycle().make({\n isConnected: () => this.isConnected,\n isOpen: () => this.open\n })\n private readonly _panel = defineAnchoredPanel().make({\n getAnchor: () => this.shadowRoot?.querySelector<HTMLElement>('.tooltip-trigger') ?? null,\n getLocalPanel: () => this.shadowRoot?.querySelector<HTMLElement>('.tooltip-panel') ?? null,\n getPositioning: () => ({\n placement: this.placement,\n offset: this.offset,\n strategy: this.portal ? 'fixed' : 'absolute'\n }),\n isPortal: () => this.portal,\n createPortal: () => this._createPortal()\n })\n\n get isOpen(): boolean {\n return this.open\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._lifecycle.resume()\n this.addEventListener('pointerenter', this._onPointerEnter)\n this.addEventListener('pointerleave', this._onPointerLeave)\n this.addEventListener('focusin', this._onFocusIn)\n this.addEventListener('focusout', this._onFocusOut)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('pointerenter', this._onPointerEnter)\n this.removeEventListener('pointerleave', this._onPointerLeave)\n this.removeEventListener('focusin', this._onFocusIn)\n this.removeEventListener('focusout', this._onFocusOut)\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n this._lifecycle.dispose()\n this._syncVisibleTooltipCount(false)\n this._panel.dispose()\n }\n\n override firstUpdated() {\n if (this.open) {\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n }\n }\n\n protected override updated(changed: Map<string, unknown>) {\n if (changed.has('portal') || changed.has('overlayContainer')) {\n // 同帧 open + reconfigure 可能重复建事务;invalidate 保证 reconfigure 是当前唯一帧回调。\n this._lifecycle.invalidate()\n this._lifecycle.scheduleFrame(() => this._reconfigureOverlay())\n } else if (changed.has('placement') || changed.has('offset'))\n requestAnimationFrame(() => this._panel.updatePosition())\n\n if (changed.has('open')) {\n this._syncVisibleTooltipCount(this.open)\n if (this.open) {\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n } else {\n this._lifecycle.invalidate()\n void this._closeOverlay()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this.open)\n }\n if (changed.has('content')) this._syncPortalContent()\n }\n\n private _onPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled) return\n clearTimeout(this._hideTimer)\n // 已有 tooltip 在场时(指针滑到相邻目标):既不等延迟,也不播入场动画。\n const isRepeat = visibleTooltipCount > 0\n this._showTimer = setTimeout(() => this._show(isRepeat), isRepeat ? 0 : this.showDelay)\n }\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled) return\n clearTimeout(this._showTimer)\n this._hideTimer = setTimeout(() => this._hide(), this.hideDelay)\n }\n\n private _onFocusIn = (event: FocusEvent) => {\n if (this.disabled) return\n if (!(event.target instanceof Element) || !event.target.matches(':focus-visible')) return\n\n clearTimeout(this._hideTimer)\n this._show(true)\n }\n\n private _onFocusOut = () => {\n if (this.disabled) return\n clearTimeout(this._showTimer)\n this._hide()\n }\n\n private _show(isInstant = false) {\n if (this.open) return\n this._shouldOpenInstantly = isInstant\n this._userOpenChange.mark()\n this.open = true\n }\n\n private _hide() {\n if (!this.open) return\n this._userOpenChange.mark()\n this.open = false\n }\n\n private _openOverlay(isInstant = false) {\n this._panel.open(isInstant)\n }\n\n private _createPortal(): OverlayPortal {\n const portal = defineOverlayPortal().make({\n container: this.overlayContainer,\n target: this,\n style: `${glass}\\n${overlayMotion}\\n${style}`,\n className: 'tooltip-panel portal wui-glass wui-floating-panel',\n // 已迁移节点被框架删除后的解除追踪是 portal 内建默认行为,这里无需 onContentChange\n // 打开期实时渲染:新增的 slot=content 元素实时迁入面板(注释锚点无法携带\n // slot 属性,天然留在宿主作为框架 patch 的插入基准)。\n migrateAddedNodes: addedNodes =>\n addedNodes.filter(node => node instanceof Element && node.getAttribute('slot') === 'content')\n })\n this._portal = portal\n if (this.content) {\n const text = document.createElement('span')\n text.className = 'tooltip-text'\n text.textContent = this.content\n portal.panel.append(text)\n } else portal.moveContent(Array.from(this.querySelectorAll('[slot=\"content\"]')))\n portal.panel.setAttribute('role', 'tooltip')\n return portal\n }\n\n private async _closeOverlay() {\n await this._panel.close(() => this.open)\n }\n\n private _reconfigureOverlay() {\n this._panel.reconfigure(this.open)\n }\n\n private _syncPortalContent() {\n if (!this.portal || !this.open) return\n const localPanel = this._panel.getPanel()\n if (!localPanel) return\n const panel = this._portal?.panel ?? localPanel\n const text = localPanel.querySelector<HTMLElement>('.tooltip-text')\n if (text) {\n text.textContent = this.content\n return\n }\n if (!this.content) return\n panel.replaceChildren()\n const nextText = document.createElement('span')\n nextText.className = 'tooltip-text'\n nextText.textContent = this.content\n panel.append(nextText)\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _syncVisibleTooltipCount(isVisible: boolean) {\n if (isVisible && !this._isCountedVisible) {\n visibleTooltipCount += 1\n this._isCountedVisible = true\n } else if (!isVisible && this._isCountedVisible) {\n visibleTooltipCount = Math.max(0, visibleTooltipCount - 1)\n this._isCountedVisible = false\n }\n }\n\n override render() {\n return html`\n <div class=\"tooltip-anchor\">\n <div class=\"tooltip-trigger\"><slot></slot></div>\n <div class=\"tooltip-panel wui-floating-panel wui-glass\" hidden role=\"tooltip\">\n ${this.content ? html`<span class=\"tooltip-text\">${this.content}</span>` : html`<slot name=\"content\"></slot>`}\n </div>\n </div>\n `\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-tooltip': WebUiTooltip\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/tooltip/index.ts"],"sourcesContent":["import type { Placement } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport overlayMotion from '@/assets/overlay-motion.css?inline'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { normalizeLiteral, normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineAnchoredPanel } from '@/shared/overlay/anchored-panel'\nimport { defineOpenOverlay } from '@/shared/overlay/open-overlay'\nimport { FLOATING_PLACEMENTS } from '@/shared/overlay/placement-props'\nimport { defineOverlayPortal } from '@/shared/overlay/portal'\nimport type { OverlayContainer, OverlayPortal } from '@/shared/overlay/portal'\n\nimport style from './style.css?inline'\n\nlet visibleTooltipCount = 0\n\n@customElement('web-ui-tooltip')\nexport class WebUiTooltip extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(overlayMotion), unsafeCSS(style)]\n\n @property({ type: String, reflect: true })\n get placement(): Placement {\n return this._placement\n }\n set placement(v: string) {\n const old = this._placement\n this._placement = normalizeLiteral(v, FLOATING_PLACEMENTS, 'top')\n this.requestUpdate('placement', old)\n }\n private _placement: Placement = 'top'\n\n @property({ type: Number, attribute: 'show-delay' })\n get showDelay(): number {\n return this._showDelay\n }\n set showDelay(v: number) {\n const old = this._showDelay\n this._showDelay = normalizeNumber(v, 0, 5000, 200)\n this.requestUpdate('showDelay', old)\n }\n private _showDelay = 200\n\n @property({ type: Number, attribute: 'hide-delay' })\n get hideDelay(): number {\n return this._hideDelay\n }\n set hideDelay(v: number) {\n const old = this._hideDelay\n this._hideDelay = normalizeNumber(v, 0, 5000, 100)\n this.requestUpdate('hideDelay', old)\n }\n private _hideDelay = 100\n\n @property({ type: Boolean, reflect: true }) disabled = false\n\n @property({ type: Number })\n get offset(): number {\n return this._offset\n }\n set offset(v: number) {\n const old = this._offset\n this._offset = normalizeNumber(v, 0, 100, 6)\n this.requestUpdate('offset', old)\n }\n private _offset = 6\n\n @property({ type: String }) content = ''\n\n @property({ type: Boolean, reflect: true }) open = false\n @property({ type: Boolean, reflect: true }) portal = false\n @property({ attribute: false }) overlayContainer?: OverlayContainer\n\n private _showTimer?: ReturnType<typeof setTimeout>\n private _hideTimer?: ReturnType<typeof setTimeout>\n private _isCountedVisible = false\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n private _portal?: OverlayPortal\n /**\n * 实例作用域的帧事务入口。`requestClose` 是关闭入口的唯一接线:面板的\n * `arbitration: 'none'` 让 tooltip 在组合树里但永不成候选,因此这条**现在不会被\n * 模块调用**。保留它是不想把「怎么关」藏起来,也让仲裁策略变化时依然成立。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._hide()\n })\n private readonly _panel = defineAnchoredPanel().make({\n getAnchor: () => this.shadowRoot?.querySelector<HTMLElement>('.tooltip-trigger') ?? null,\n getLocalPanel: () => this.shadowRoot?.querySelector<HTMLElement>('.tooltip-panel') ?? null,\n getPositioning: () => ({\n placement: this.placement,\n offset: this.offset,\n strategy: this.portal ? 'fixed' : 'absolute'\n }),\n isPortal: () => this.portal,\n createPortal: () => this._createPortal(),\n openOverlay: this._overlay,\n // tooltip 在逻辑组合树里(面板迁移后仍要判对「谁在谁里面」),但从不作为 Escape\n // 候选:按键穿过它落到下层浮层,与重构前一致。\n arbitration: 'none'\n })\n\n get isOpen(): boolean {\n return this.open\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._overlay.resume()\n this.addEventListener('pointerenter', this._onPointerEnter)\n this.addEventListener('pointerleave', this._onPointerLeave)\n this.addEventListener('focusin', this._onFocusIn)\n this.addEventListener('focusout', this._onFocusOut)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('pointerenter', this._onPointerEnter)\n this.removeEventListener('pointerleave', this._onPointerLeave)\n this.removeEventListener('focusin', this._onFocusIn)\n this.removeEventListener('focusout', this._onFocusOut)\n clearTimeout(this._showTimer)\n clearTimeout(this._hideTimer)\n // 停止帧调度;撤销登记由 _panel.dispose() 完成(句柄归它持有)。\n this._overlay.suspend()\n this._syncVisibleTooltipCount(false)\n this._panel.dispose()\n }\n\n override firstUpdated() {\n if (this.open) {\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n }\n }\n\n protected override updated(changed: Map<string, unknown>) {\n if (changed.has('portal') || changed.has('overlayContainer')) {\n // 同帧 open + reconfigure 可能重复建事务;invalidate 保证 reconfigure 是当前唯一帧回调。\n this._overlay.invalidate()\n this._overlay.scheduleFrame(() => this._reconfigureOverlay())\n } else if (changed.has('placement') || changed.has('offset'))\n requestAnimationFrame(() => this._panel.updatePosition())\n\n if (changed.has('open')) {\n this._syncVisibleTooltipCount(this.open)\n if (this.open) {\n this._openOverlay(this._shouldOpenInstantly)\n this._shouldOpenInstantly = true\n } else {\n this._overlay.invalidate()\n void this._closeOverlay()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this.open)\n }\n if (changed.has('content')) this._syncPortalContent()\n }\n\n private _onPointerEnter = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled) return\n clearTimeout(this._hideTimer)\n // 已有 tooltip 在场时(指针滑到相邻目标):既不等延迟,也不播入场动画。\n const isRepeat = visibleTooltipCount > 0\n this._showTimer = setTimeout(() => this._show(isRepeat), isRepeat ? 0 : this.showDelay)\n }\n\n private _onPointerLeave = (e: PointerEvent) => {\n if (e.pointerType === 'touch') return\n if (this.disabled) return\n clearTimeout(this._showTimer)\n this._hideTimer = setTimeout(() => this._hide(), this.hideDelay)\n }\n\n private _onFocusIn = (event: FocusEvent) => {\n if (this.disabled) return\n if (!(event.target instanceof Element) || !event.target.matches(':focus-visible')) return\n\n clearTimeout(this._hideTimer)\n this._show(true)\n }\n\n private _onFocusOut = () => {\n if (this.disabled) return\n clearTimeout(this._showTimer)\n this._hide()\n }\n\n private _show(isInstant = false) {\n if (this.open) return\n this._shouldOpenInstantly = isInstant\n this._userOpenChange.mark()\n this.open = true\n }\n\n private _hide() {\n if (!this.open) return\n this._userOpenChange.mark()\n this.open = false\n }\n\n private _openOverlay(isInstant = false) {\n this._panel.open(isInstant)\n }\n\n private _createPortal(): OverlayPortal {\n const portal = defineOverlayPortal().make({\n container: this.overlayContainer,\n target: this,\n style: `${glass}\\n${overlayMotion}\\n${style}`,\n className: 'tooltip-panel portal wui-glass wui-floating-panel',\n // 已迁移节点被框架删除后的解除追踪是 portal 内建默认行为,这里无需 onContentChange\n // 打开期实时渲染:新增的 slot=content 元素实时迁入面板(注释锚点无法携带\n // slot 属性,天然留在宿主作为框架 patch 的插入基准)。\n migrateAddedNodes: addedNodes =>\n addedNodes.filter(node => node instanceof Element && node.getAttribute('slot') === 'content')\n })\n this._portal = portal\n if (this.content) {\n const text = document.createElement('span')\n text.className = 'tooltip-text'\n text.textContent = this.content\n portal.panel.append(text)\n } else portal.moveContent(Array.from(this.querySelectorAll('[slot=\"content\"]')))\n portal.panel.setAttribute('role', 'tooltip')\n return portal\n }\n\n private async _closeOverlay() {\n await this._panel.close(() => this.open)\n }\n\n private _reconfigureOverlay() {\n this._panel.reconfigure(this.open)\n }\n\n private _syncPortalContent() {\n if (!this.portal || !this.open) return\n const localPanel = this._panel.getPanel()\n if (!localPanel) return\n const panel = this._portal?.panel ?? localPanel\n const text = localPanel.querySelector<HTMLElement>('.tooltip-text')\n if (text) {\n text.textContent = this.content\n return\n }\n if (!this.content) return\n panel.replaceChildren()\n const nextText = document.createElement('span')\n nextText.className = 'tooltip-text'\n nextText.textContent = this.content\n panel.append(nextText)\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _syncVisibleTooltipCount(isVisible: boolean) {\n if (isVisible && !this._isCountedVisible) {\n visibleTooltipCount += 1\n this._isCountedVisible = true\n } else if (!isVisible && this._isCountedVisible) {\n visibleTooltipCount = Math.max(0, visibleTooltipCount - 1)\n this._isCountedVisible = false\n }\n }\n\n override render() {\n return html`\n <div class=\"tooltip-anchor\">\n <div class=\"tooltip-trigger\"><slot></slot></div>\n <div class=\"tooltip-panel wui-floating-panel wui-glass\" hidden role=\"tooltip\">\n ${this.content ? html`<span class=\"tooltip-text\">${this.content}</span>` : html`<slot name=\"content\"></slot>`}\n </div>\n </div>\n `\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-tooltip': WebUiTooltip\n }\n}\n"],"mappings":"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"}
|