【免费下载链接】pomotroid:tomato: Simple and visually-pleasing Pomodoro timer项目地址https://gitcode.com/gh_mirrors/po/pomotroid点击查看免费下载导读Pomotroid 的计时器窗口大量使用纯图标按钮如 Restart Round、Skip、Reset、Mute设置窗口中的开关行也只有简短标签用户往往无法仅凭界面判断这些控件的具体行为或平台差异。本文以仓库内openspec/changes/archive/2026-03-26-ui-tooltips/变更包为主线完整讲解 Pomotroid 如何通过自研的Tooltip.svelte与TooltipInfo.svelte组件、两档延迟交互模型、视口边缘翻转定位以及全 8 语言 i18n 接入为计时器窗口与设置窗口的每个目标控件补齐即时上下文。读完本文你将掌握该组件的 props 契约、定位算法、无障碍接线方式以及从 spec 到真实源码的完整落地路径。一、为什么需要 Tooltip问题背景与设计目标Pomotroid 的计时器窗口主窗口为了保持界面简洁多数控制按钮是纯图标形态仅靠aria-label提供可访问性描述设置窗口的开关行则使用简短标签诸如Countdown DialVerbose Logging等术语对新手并不直观。变更提案 proposal.md 指出Tooltip 的价值是在用户需要的那一刻、在 UI 原位上提供上下文从而降低新用户的学习摩擦并能在最相关的时刻呈现平台注意事项例如 GNOME 下托盘图标需要 AppIndicator 扩展这一关键提示。本次变更的边界非常清晰proposal 的 Impact 一节前端新增Tooltip.svelte组件修改计时器页面与设置分区组件i18n在全部 8 个语言文件src/messages/*.json中新增所有 tooltip 字符串并需要重新生成 Paraglide 消息无后端改动Tooltip 纯属前端关注点无新增依赖行为完全用 CSS Svelte 实现不引入 Floating UI、Popper.js 等第三方库。设计文档 design.md 明确了目标与非目标目标非目标可在两个窗口任意位置复用的Tooltip.svelteTooltip 内支持富内容HTML、图片——仅纯文本两种交互模式延迟按钮/开关与即时info 图标触屏/移动端支持——Pomotroid 是纯桌面应用CSS 驱动定位 JS 兜底翻转近视口边缘时上下翻转引入第三方 Tooltip 库——保持依赖面平坦全部 tooltip 字符串走 Paraglide 完整国际化—用 info 图标 即时 tooltip 替换 Linux 下 GNOME 托盘的静态.note段落—二、核心组件契约Tooltip.svelte任务清单 tasks.md 第 1 节定义了组件基础能力而 spec 文件 specs/tooltips/spec.md 给出了精确的 props 契约。2.1 Props 接口组件接受以下 propssrc/lib/components/Tooltip.svelteinterface Props { text: string; // tooltip 文本由调用方通过 Paraglide 翻译 delay?: number; // 悬停后出现 tooltip 的毫秒数默认 600 placement?: above | below; // 首选放置方向默认 above children: Snippet; // 触发器元素通过默认 slot 传入 }关键行为对应 spec 中的场景延迟出现鼠标悬停触发器至少delay毫秒后显示 tooltipspec 场景 Tooltip appears after delay立即消失鼠标移出触发器时 tooltip 立即隐藏场景 Tooltip disappears on mouse leave即时显示设置delay{0}时悬停即显场景 Instant tooltip with delay0边缘翻转触发器位置导致 tooltip 在首选方向溢出视口时改向反方向渲染场景 Tooltip flips placement near viewport edge。2.2 事件模型与延迟计时器源码中的显示逻辑使用show()/hide()两个函数async function show() { if (visible || timer ! undefined) return; const run async () { timer undefined; visible true; await tick(); updatePosition(); }; if (delay 0) { await run(); } else { timer setTimeout(run, delay); } } function hide() { clearTimeout(timer); timer undefined; visible false; positioned false; flipped false; tooltipStyle ; }触发器同时绑定onpointerenter与onpointermove后者让鼠标在触发区内小幅移动时不至于因pointerenter未触发而丢失显示离开时onpointerleave立即隐藏隐藏时会清理延迟计时器并复位positioned/flipped/tooltipStyle确保下次显示重新计算定位tooltip 的 DOM id 用Math.random().toString(36).slice(2, 9)生成格式为tooltip-xxxxxxx供aria-describedby引用。2.3 视口边缘检测与翻转算法updatePosition()是定位核心对应任务 1.2on show, checkgetBoundingClientRect()and flip placement if tooltip would overflowfunction updatePosition() { if (!wrapper || !tooltipEl) return; const wRect wrapper.getBoundingClientRect(); const tRect tooltipEl.getBoundingClientRect(); const centerX wRect.left wRect.width / 2; const pad 8; // 垂直方向若触发器距顶部过近且首选 above则翻转到下方 flipped wRect.top 70 placement above; let top: number; if (actualPlacement below) { top wRect.bottom 8; } else { top wRect.top - 8 - tRect.height; } // 水平方向水平居中于触发器并夹在视口内 let left centerX - tRect.width / 2; if (left pad) left pad; if (left tRect.width window.innerWidth - pad) { left window.innerWidth - pad - tRect.width; } // 箭头偏移始终指向触发器的水平中心 const arrowLeft centerX - left; tooltipStyle top:${top}px;left:${left}px;--arrow-left:${arrowLeft}px; positioned true; }算法要点垂直翻转阈值当触发器的getBoundingClientRect().top 70px且首选方向为above时翻转为below70px大致对应标题栏高度加上 tooltip 自身高度余量这对应验证任务 6.2把窗口调小、确认靠近顶部的 tooltip 翻转到下方水平夹取tooltip 相对触发器水平居中但左右各保留8px视口边距超宽时贴边显示箭头跟随通过 CSS 自定义属性--arrow-left把箭头::after伪元素始终对准触发器的水平中心防闪烁tooltip 默认visibility: hidden只有 JS 完成定位.positioned类后才可见避免首帧闪现在左上角源码注释明确说明这一设计。2.4 渲染结构与 ARIA 接线模板部分对应任务 1.3Wirearia-describedbyspan classtooltip-wrapper bind:this{wrapper} onpointerenter{show} onpointermove{show} onpointerleave{hide} aria-describedby{visible ? tooltipId : undefined} {render children()} {#if visible} span bind:this{tooltipEl} classtooltip class:below{actualPlacement below} class:positioned style{tooltipStyle} id{tooltipId} roletooltip{text}/span {/if} /span无障碍契约spec Accessibility 一节tooltip可见时触发器元素获得aria-describedby指向 tooltip 元素的id屏幕阅读器会把 tooltip 文本与控件标签一并朗读场景 Screen reader announces tooltip texttooltip隐藏时aria-describedby属性被移除源码中visible ? tooltipId : undefined确保读屏不读到已隐藏的内容tooltip 元素本身带roletooltip。2.5 样式主题感知与箭头样式契约spec Tooltip styling 一节与实现对照spec 要求源码实现Tooltip.svelte的style块背景--color-background-light主题未定义时用深色叠加--tooltip-bg: var(--color-background-light, color-mix(in oklch, var(--color-foreground) 10%, var(--color-background)))文字色--color-foregroundcolor: var(--color-foreground)字号0.75rem实现为0.72rem、line-height: 1.4实现较 spec 微调圆角4pxborder-radius: 4px最大宽度240px、文字换行max-width: 240px; white-space: normal; text-align: center轻微阴影box-shadow: 0 2px 8px color-mix(in oklch, black 30%, transparent)外加1px前景色 12% 透明描边指向触发器的小箭头.tooltip::after使用 5px 边框三角.tooltip.below::after反向主题适配通过 CSS 自定义属性完成--color-*由 src/lib/stores/theme.ts 的applyTheme()在切换主题时写入:root因此任何可见的 tooltip 都会立即反映新主题spec 场景 Tooltip uses active theme colors。全仓 40 套主题 JSON见 static/themes都定义这些颜色变量。三、信息图标组件TooltipInfo.svelte任务 2.1 要求创建TooltipInfo.svelte作为Tooltip的薄封装spec 定义了其契约渲染一个样式化的ⓘ图标悬停时立即显示 tooltipdelay{0}接受单一textprop用于设置窗口开关行旁展示上下文备注而不增加布局负担。源码实现src/lib/components/TooltipInfo.svelteTooltip {text} delay{0} span classinfo-icon roleimg aria-labelMore information onclick{(e) e.stopPropagation()} svg width13 height13 viewBox0 0 13 13 fillnone aria-hiddentrue circle cx6.5 cy6.5 r5.75 strokecurrentColor stroke-width1.5 / circle cx6.5 cy4 r0.9 fillcurrentColor / line x16.5 y16 x26.5 y210 strokecurrentColor stroke-width1.6 stroke-linecapround / /svg /span /Tooltip值得注意的实现细节必须是span而非button因为TooltipInfo经常被渲染在button classrow开关整行内部span是button中合法的 phrasing content源码注释明确说明onclick必须stopPropagation()防止点击 info 图标时触发父级按钮的onclick而意外切换开关视觉层级rest 状态opacity: 0.55、color: var(--color-foreground)hover 时opacity: 1——对应 spec 场景Info icon is visually distinct but unobtrusivespec 原文以--color-foreground-darker描述 rest 状态实现上通过降低透明度达到次要于相邻标签的同等效果。四、i18n14 个 tooltip 键与 8 语言覆盖4.1 键与英文文案本地化 spec specs/localization/spec.md 列出全部 14 个新增键。以下英文值均可在 src/messages/en.json 中核实i18n 键英文值tooltip_settingsOpen Settingstooltip_statisticsOpen Statisticstooltip_restart_roundRestart the current round from the beginning.tooltip_skipSkip to the next round.tooltip_resetReset the timer to the first work round. Current session progress will be cleared.tooltip_muteMute alert soundstooltip_unmuteUnmute alert soundstooltip_verbose_loggingEnables detailed debug logging. Use when reporting issues. Log files are accessible via Open Log Folder in Advanced settings.tooltip_dial_countdownWhen enabled, the progress arc counts down to zero instead of filling up.tooltip_auto_start_workAutomatically start the next work round when a break ends.tooltip_auto_start_breakAutomatically start the break when a work round completes.tooltip_websocketEnables a local WebSocket server for external integrations such as stream overlays. Disabled by default.tooltip_round_counterCurrent work round out of the total rounds before a long break.tooltip_round_counter_sessionContinuous session round count. Resets only when the timer is reset.补充说明spec 原文明确system_tray_gnome_hint是已有键被 Linux 专属的 System Tray 开关 TooltipInfo 直接复用无需新增。4.2 多语言落地任务 3.1/3.2 要求en 之外fr.json、de.json、es.json、pt.json、zh.json、ja.json、tr.json全部补齐 14 个键仓库搜索确认全部 8 个语言文件都包含tooltip_websocket、tooltip_auto_start_work、tooltip_dial_countdown等键每个文件命中 3 处中文示例可核对 src/messages/zh.json如tooltip_reset: 将计时器重置到第一个工作轮次。当前会话进度将被清除。、tooltip_websocket: 启用本地 WebSocket 服务器用于直播覆盖层等外部集成。默认禁用。spec 注明非英语翻译初始版本可以机器翻译后续由社区改进调用方式统一为 Paraglide 函数m.tooltip_restart_round()等消息编译命令为npm run paraglide:compile见 package.json 的 scripts完整类型检查见下文第七节。五、计时器窗口7 个 tooltip 目标的落地spec Tooltip inventory — timer window 给出了完整清单全部 600 ms 延迟与源码逐一对应元素位置i18n 键源码位置Settings 按钮Titlebartooltip_settingsTitlebar.svelte 的settingsBtn()snippetStatistics 按钮Titlebartooltip_statisticsTitlebar.svelte 的statsBtn()snippetRestart Round 按钮Timer controlstooltip_restart_roundTimer.svelte 的Back: restart current roundSkip 按钮Timer controlstooltip_skipTimer.svelte 的Skip: advance to next roundReset 按钮Footertooltip_resetTimerFooter.svelteMute/Unmute 按钮Footertooltip_mute/tooltip_unmuteTimerFooter.svelteRound indicatorFootertooltip_round_counter/tooltip_round_counter_sessionTimerFooter.svelte5.1 TitlebarSettings 与 StatisticsTitlebar.svelte用 Svelte 5 snippet 组织两组带 tooltip 的图标按钮{#snippet settingsBtn()} Tooltip text{m.tooltip_settings()} button classbtn-icon onclick{openSettings} aria-labelSettings !-- 齿轮 SVG -- /button /Tooltip {/snippet}注意平台差异非 macOS 时这两个按钮渲染在标题栏左侧macOS 时渲染在右侧绕过系统红绿灯区域但 tooltip 包装完全一致。5.2 TimerRestart Round 与 SkipTimer.svelte中 Restart Round回到当前轮起点与 Skip跳到下一轮按钮都被Tooltip包裹分别对应 spec 场景Reset tooltip communicates consequence悬停 Reset 600 ms 后显示tooltip_reset说明计时器回到第一个工作轮次并清除会话进度Skip tooltip describes the action显示tooltip_skip说明跳过当前轮并开始下一轮。5.3 TimerFooterReset、Mute/Unmute、Round indicatorTimerFooter.svelte是状态相关 tooltip 最密集的组件Reset任务 4.3Tooltip text{m.tooltip_reset()} button classbtn-text onclick{timerReset} aria-label{m.timer_reset()} {m.timer_reset()} /button /TooltipMute/Unmute任务 4.4tooltip 文本跟随静音状态实时切换对应 spec 场景 Mute tooltip reflects current stateTooltip text{localVolume 0 ? m.tooltip_unmute() : m.tooltip_mute()} button classbtn-icon onclick{toggleMute} aria-label{localVolume 0 ? Unmute : Mute} !-- 音量图标静音态画叉 -- /button /TooltiptoggleMute()还会记住静音前的音量premuteVolume取消静音时恢复。Round indicator任务 4.5按长休息是否启用切换文案对应 spec 场景 Round indicator tooltip reflects long-break modeTooltip text{$settings.long_breaks_enabled ? m.tooltip_round_counter() : m.tooltip_round_counter_session()} span classrounds {#if $settings.long_breaks_enabled} {snap.work_round_number} nbsp;|nbsp; {snap.work_rounds_total} {:else} {m.timer_session_round({ n: snap.session_work_count })} {/if} /span /Tooltip即开启长休息时显示当前工作轮 / 总轮数如2 | 4并解释这是长休息前的第几轮关闭长休息时显示Round 3式的连续会话计数并说明只有重置计时器才会清零。六、设置窗口6 个 TooltipInfo 的落地spec Tooltip inventory — settings window 清单全部即时显示设置项分区i18n 键源码位置Show in System Tray仅 LinuxSystemsystem_tray_gnome_hint已有键复用SystemSection.svelteVerbose LoggingSystemLoggingtooltip_verbose_loggingSystemSection.svelteDial CountdownTimertooltip_dial_countdownTimerSection.svelteAuto-start WorkTimertooltip_auto_start_workTimerSection.svelteAuto-start BreakTimertooltip_auto_start_breakTimerSection.svelteWebSocket ServerSystemIntegrationstooltip_websocketSystemSection.svelte6.1 SettingsToggle统一的 tooltip 注入点这些开关全部经由SettingsToggle.svelte渲染该组件新增了可选tooltip?: stringprop有值时在标签旁渲染TooltipInfobutton classrow {onclick} span classtext span classlabel {label}{#if tooltip}TooltipInfo text{tooltip} /{/if}/span {#if description} span classdesc{description}/span {/if} /span span classtoggle class:on{checked} aria-checked{checked} roleswitch/span /button这也解释了TooltipInfo内层必须用span且stopPropagation()的原因——它嵌套在整行button内。6.2 GNOME 提示Linux 专属 info 图标任务 5.1 是本变更最特别的点此前 GNOME AppIndicator 提示以静态.note段落显示在所有平台上design.md 的 Context 明确批评了这一点本次改为仅 Linux 渲染的 info 图标 即时 tooltip并移除.note段落。SettingsToggle label{m.system_toggle_show_tray()} description{m.system_toggle_show_tray_desc()} tooltip{isLinux ? m.system_tray_gnome_hint() : undefined} checked{$settings.tray_icon_enabled} onclick{() toggle(tray_icon_enabled, $settings.tray_icon_enabled)} /isLinux来自 src/lib/utils/platform.ts/Linux/.test(navigator.userAgent)spec 场景 GNOME hint shown only on LinuxLinux 上显示该 info 图标macOS/Windows 上该开关不附带任何 info 图标英文文案提示用户GNOME 上托盘图标需要 AppIndicator 扩展如gnome-shell-extension-appindicator安装后需注销并重新登录另外整个托盘分组还被trayAvailable门控——Linux 下运行时探测libayatana-appindicator3库缺失时整个分组隐藏SystemSection.svelte的onMount。6.3 其余设置项Verbose Loggingtooltip_verbose_logging解释启用详细调试日志用于问题上报日志文件通过 Settings → About 的 Open Log Folder 访问WebSocket Servertooltip_websocket解释启用本地 WebSocket 服务器供外部集成如直播覆盖层使用、默认禁用。SystemSection.svelte中启用后还会显示端口输入行与ws://127.0.0.1:{port}/ws的.note说明这部分是 WebSocket 功能原有文案不属于本次 tooltip 变更Dial Countdowntooltip_dial_countdown解释进度弧从满倒计至零而非从零填充Auto-start Work / Auto-start Breaktooltip_auto_start_work/tooltip_auto_start_break分别解释休息结束后自动开始下一工作轮、工作轮结束后自动开始休息。任务 5.4–5.6 中 Auto-start 与 Dial Countdown 的注入点在 TimerSection.svelte。由于仓库中TimerSection.svelte的这些开关通过SettingsToggle渲染tooltipprop 的传入位置在组件调用处。七、验证流程与质量保障任务清单第 6 节定义了验证标准其中部分可以从源码直接佐证6.1 两窗口内 tooltip 正确显示与消失由show()/hide()的事件模型保证onpointerenter/onpointermove/onpointerleave6.2 视口边缘翻转将窗口调小、确认靠近顶部的 tooltip 翻转到下方——由updatePosition()中wRect.top 70 placement above的逻辑保证CHANGELOGCHANGELOG.md 第 71 行附近也记载了实现最终采用position: fixed以避免窗口边缘裁剪6.3 GNOME 提示仅 Linux 出现由tooltip{isLinux ? m.system_tray_gnome_hint() : undefined}保证6.4 类型检查执行npm run checkparaglide-js compile --project ./project.inlang --outdir ./src/paraglide svelte-kit sync svelte-check --tsconfig ./tsconfig.json确保新增消息键编译后无 TypeScript 错误。另外design.md 记录了三个明确的权衡理解它们有助于在扩展 tooltip 时避免踩坑溢出裁剪风险若某父容器overflow: hiddentooltip 可能被裁剪。缓解手段是审计父容器、必要时改为overflow: visible或对该实例改用 portal 方案紧凑模式compact modeTimer.svelte中isCompact时隐藏 footer 与 controls渲染MiniControls这些元素上的 tooltip 自然永远不会触发——无需处理元素不存在即可翻译质量7 个非英语语言的 tooltip 字符串初始版本允许机器翻译后续由社区打磨。还有一个未决问题Play/Pause 按钮是否加 tooltipdesign.md 的 Open Questions 倾向不加——它是纯图标但功能普遍易懂避免对显而易见的控件过度 tooltip留待用户反馈再定。八、小结一套零依赖、可复用的即时上下文方案从 tasks.md 的 6 组任务到 specs/tooltips/spec.md 的完整契约Pomotroid 的 tooltip 体系最终落地为Tooltip.svelte——slot 包裹式复用组件text/delay/placement三 props600 ms 默认延迟、delay{0}即时模式、getBoundingClientRect()视口翻转、aria-describedby无障碍接线、CSS 自定义属性主题适配TooltipInfo.svelte——delay{0}的即时ⓘ图标封装专用于设置开关行7 个计时器窗口目标 6 个设置窗口目标——状态条件文本Mute/Unmute、Round counter与平台条件渲染Linux GNOME 提示是两处最具代表性的用法14 个新 i18n 键 × 8 语言——Paraglide 全量接入npm run check把关。该方案未引入任何第三方依赖纯 CSS/Svelte 实现符合 Pomotroid 保持依赖面平坦的一贯风格。对于希望在自家 Tauri/Svelte 应用中实现轻量 tooltip 的开发者上述组件契约、定位算法与无障碍模式都可以直接作为参考蓝图。延伸阅读均位于本仓库变更总览见 proposal.md 与 design.md组件源码见 Tooltip.svelte 与 TooltipInfo.svelte消息文件见 src/messages。赞分享【免费下载链接】pomotroid:tomato: Simple and visually-pleasing Pomodoro timer项目地址https://gitcode.com/gh_mirrors/po/pomotroid点击查看免费下载相关推荐Material Design Lite 组件全指南Badges 到 Tooltips 的 MDL 组件体系详解Material Design Lite 组件全指南Badges 到 Tooltips 的 MDL 组件体系详解 Material Design LiteM前端UI组件Naive UI Drawer 抽屉组件完全指南从 Props 到源码实现Naive UI Drawer 抽屉组件完全指南从 Props 到源码实现 抽屉Drawer是 Naive UI 中与 Modal 对话框 https:前端UI组件cube-ui ActionSheet 组件完全指南从 API 调用到源码实现cube ui ActionSheet 组件完全指南从 API 调用到源码实现 本篇指南围绕 cube ui基于 Vue 的移动端 UI 组件库中的 Ac前端UI组件移动开发创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考