- 前端
- UI组件
【免费下载链接】zag
Build your design system in React, Solid, Vue, Svelte or Vanilla. Powered by finite state machines
本文以
@zag-js/tour包的 CHANGELOG.md 为时间主线,结合 tour.machine.ts、tour.types.ts、tour.connect.ts 等源码,系统梳理 Zag.js 引导式走查组件从 0.37.0 诞生到 1.46.0 的完整演进,并深入讲解其状态机设计、步骤类型、动作系统与各版本修复背后的工程细节。读完本文,你将能够理解 Tour 状态机的内部工作机制,掌握tooltip / dialog / wait / floating四种步骤类型与next / prev / dismiss / skip / goto动作系统的正确用法,并学会规避历史版本中踩过的定位、焦点、无障碍等常见坑。
一、@zag-js/tour 是什么
@zag-js/tour是 Zag.js 中以有限状态机(finite state machine)实现的引导式走查(guided tour / product tour / walkthrough)组件核心逻辑,用于在页面上一步步高亮目标元素、配合遮罩(backdrop)与聚光灯(spotlight)引导用户完成操作流程。包自身定位为"core logic for the tour widget implemented as a state machine"(见 package.json)。
从依赖关系看,Tour 机器直接复用了 Zag.js 生态的多个底层能力(见 package.json 的dependencies):
| 依赖包 | 在 Tour 中的职责 |
|---|---|
@zag-js/core | 状态机运行时(createMachine) |
@zag-js/anatomy | 组件各部分(parts)的 DOM 结构声明 |
@zag-js/popper | 步骤浮层定位(placement、offset、arrow) |
@zag-js/dismissable | 可关闭分支管理(dismissable branch) |
@zag-js/interact-outside | 点击/聚焦外部时的关闭行为 |
@zag-js/focus-trap | 步骤内容与聚光灯目标的焦点陷阱 |
@zag-js/dom-query/@zag-js/utils/@zag-js/types | DOM 查询、工具函数与类型定义 |
这些依赖在整个 CHANGELOG 中反复以 "Updated dependencies" 出现,是理解 Tour 行为的重要线索:Tour 的许多修复(定位抖动、焦点限制、外部交互关闭等)实际受益于底层包的同步升级。
二、版本演进时间线:从诞生到 1.46.0
CHANGELOG 完整记录了 Tour 机器从引入到稳定的历程,可按三个阶段理解:
2.1 诞生期(0.37.0 → 0.71.0):从"新组件"到"不再携带源码"
- 0.37.0(引入):通过 PR #1253 引入全新的 guided tour 组件,同时依赖
@zag-js/dismissable、@zag-js/popper、@zag-js/anatomy、@zag-js/core等包。这是 Tour 的起点。 - 0.37.1:暴露
splitProps函数,便于在 Zag.js 之上创建自定义组件。 - 0.58.0:所有机器统一导出
Service类型(用于基于 Lit 的组件),并确保invalid、required、readOnly等表单相关属性的应用一致。 - 0.71.0:包不再发布
src文件("We no longer shipsrcfiles in the packages"),转向只发布构建产物,这是发布策略的一次收敛。
2.2 重构期(0.72.0 → 0.82.x):面向健壮性的重新设计
- 0.72.0(Minor):这是 Tour 演进中最关键的一次重构——"Redesign tour to be more robust and support programmatic control in wait steps"(CHANGELOG)。从此 Tour 支持在
wait步骤中以编程方式控制流程,后续版本的大量修复都围绕这次重构展开。 - 0.81.1:移除元素上的
zIndex设置,改为交由用户层控制,避免层叠上下文冲突。 - 0.82.0:修复 dialog 步骤的 z-index 与内容不同步的问题。
2.3 稳定期(1.0.0 → 1.46.0):细节打磨与可访问性修复
1.0.0 之后 Tour 进入语义化版本阶段,修复集中在定位、焦点、无障碍、滚动与类型导出等方向,具体见下文各节。
三、状态机架构:TourSchema 与四个运行子状态
Tour 的状态机定义在 tour.machine.ts,其状态类型在 tour.types.ts 的TourSchema中声明:
export interface TourSchema { tag: "open" | "closed" state: "tourInactive" | "running.resolving" | "running.scrolling" | "running.waiting" | "running.active" props: RequiredBy<TourProps, PropsWithDefault> context: PrivateContext refs: Refs computed: ComputedContext event: EventObject action: string guard: string effect: string }顶层状态为tourInactive(标签closed)与running。running内又划分为四个子状态,形成 Tour 的核心工作流:
running ├── resolving (等待/解析目标元素,标签 closed) ├── scrolling (滚动到目标,标签 open) ├── waiting (wait 步骤,等待 effect 触发下一步,标签 closed) └── active (步骤展示中,标签 open)各子状态的职责与切换逻辑(见 tour.machine.ts):
| 子状态 | 标签 | 进入时的 effects | 说明 |
|---|---|---|---|
resolving | closed | waitForTarget、waitForTargetTimeout | 通过MutationObserver等待目标元素出现;3 秒超时后发送TARGET.NOT_FOUND |
scrolling | open | scrollToTarget等 7 个 effects | 滚动到目标,等待SCROLL.END后进入active |
waiting | closed | 无 | wait 步骤专用,由effect通过next()等编程控制 |
active | open | 6 个 effects | 展示步骤内容,含焦点陷阱、placement 跟踪、外部交互跟踪等 |
状态切换通过事件驱动,核心事件包括:
START:从tourInactive进入running,执行clearStep→setInitialStep→invokeOnStartSTEP.SET/STEP.NEXT/STEP.PREV:在running内部切换步骤STEP.CHANGED/STEP.ROUTE:内部/外部步骤变更的统一路由TARGET.NOT_FOUND/TARGET.RESOLVED:目标解析结果SCROLL.END:滚动完成DISMISS/SKIP:关闭或跳过(DISMISS 在最后一步时额外触发invokeOnComplete)
注意STEP.CHANGED与STEP.ROUTE的区别(源码注释明确说明):STEP.CHANGED用于外部stepId 变化(由watch回调触发),会先cleanupStepEffect;STEP.ROUTE用于内部步骤切换(performStepTransition/show触发),此时 effect 清理已由performStepTransition完成,无需重复执行。这正对应 CHANGELOG 0.43.0 中"从步骤的effect中 dismiss tour 时跳过清理、可能漏掉 completed 状态"的修复——effect 清理时机是 Tour 状态机设计中最精细的部分之一。
running状态还统一定义了DISMISS的两个分支(tour.machine.ts):
DISMISS: [ { guard: "isLastStep", target: "tourInactive", actions: ["cleanupAll", "invokeOnDismiss", "invokeOnComplete"] }, { target: "tourInactive", actions: ["cleanupAll", "invokeOnDismiss"] }, ],即在最后一步dismiss 时,除了dismissed状态回调外,还会额外触发completed回调。
四、步骤模型:四种类型与参数全解
4.1 步骤类型(StepType)
StepType在 tour.types.ts 中定义为"tooltip" | "dialog" | "wait" | "floating"。normalizeStep(见 utils/step.ts)会根据步骤配置自动补全默认值:
- tooltip:有
target、未指定类型(或显式tooltip)时的默认类型,默认arrow: true、backdrop: true,浮层通过 popper 定位在目标元素周围。 - dialog:无
target或显式dialog时使用,默认placement: "center"、backdrop: true,内容居中展示(模态)。 - wait:不展示 UI(标签 closed),进入
running.waiting状态,由effect挂载事件监听,通过回调(如next())编程推进流程。计算进度时会通过getEffectiveSteps将wait步骤排除。 - floating:无遮罩、无箭头,默认
placement: "bottom-end",用于轻量浮层提示。
对应 CHANGELOG 0.72.0 "support programmatic control in wait steps" 与 1.26.5 "Fix issue where wait step doesn't work correctly" 的修复记录,wait 步骤的编程控制正是重构后的核心能力。
4.2 步骤字段(StepBaseDetails / StepDetails)
StepDetails的完整字段(tour.types.ts):
| 字段 | 类型 | 默认/说明 |
|---|---|---|
id | string | 步骤唯一标识(必填) |
type | StepType | 步骤类型;无 target 时视为模态步骤 |
target | () => HTMLElement \| null | 返回要高亮的元素 |
title/description | any | 步骤标题与描述 |
placement | StepPlacement(Placement \| "center") | 浮层位置,默认"bottom" |
offset | { mainAxis?, crossAxis? } | 内容与目标之间的偏移 |
meta | Record<string, any> | 附加元数据 |
backdrop | boolean | 是否显示遮罩 |
arrow | boolean | 是否显示箭头 |
actions | StepAction[] | 步骤动作列表 |
effect | (args: StepEffectArgs) => StepEffectCleanup | 步骤展示前执行的副作用,返回清理函数 |
4.3 步骤动作(StepAction)与动作映射
StepAction结构(tour.types.ts):
export interface StepAction { label: string action?: StepActionType | StepActionFn | undefined // "next" | "prev" | "dismiss" | "skip" 或自定义函数 attrs?: Record<string, any> | undefined // 传递给触发按钮的属性 }StepActionMap为自定义动作函数提供完整控制能力(tour.types.ts):
export interface StepActionMap { next: VoidFunction prev: VoidFunction dismiss: VoidFunction skip: VoidFunction goto: (id: string) => void }getActionTriggerProps(tour.connect.ts)按动作类型生成按钮属性:
next:data-type: "next",无下一步时disabled+data-disabledprev:data-type: "prev",无上一步时disabled+data-disableddismiss:data-type: "close"skip:data-type: "skip"- 自定义函数:
data-type: "custom",点击时以actionMap为参数调用函数
CHANGELOG 中与动作系统相关的关键修复:
- 1.43.0:修复
action: "skip"的步骤动作点击后无效果的问题(此前 skip 动作被忽略)。 - 1.18.1:修复
StepActionMap中goto函数传入步骤 ID(字符串)时不生效的问题。 - 1.46.0:修复动作触发器被翻译文本(如 "Got it" 被读作 "close tour")而非其可见标签宣告的问题;对仅图标的触发器,可通过
attrs: { "aria-label": ... }命名,这正是StepAction.attrs字段的用途。
4.4 完整步骤示例
结合 examples/next-ts/pages/tour/step-types.tsx 与 examples/next-ts/pages/tour/wait-step.tsx,一个覆盖四种类型的步骤数组示例:
const steps: tour.StepDetails[] = [ { id: "dialog-intro", type: "dialog", title: "Dialog Step", description: "This is a centered dialog step with no target element.", actions: [{ label: "Next", action: "next" }], }, { id: "tooltip-feature", type: "tooltip", target: () => document.querySelector<HTMLElement>("#feature-btn"), title: "Tooltip Step", description: "This tooltip points to a specific element on the page.", placement: "bottom", actions: [ { label: "Prev", action: "prev" }, { label: "Next", action: "next" }, ], }, { id: "wait-action", type: "wait", effect({ next }) { const btn = document.querySelector<HTMLElement>("#continue-btn") if (!btn) return const handler = () => next() btn.addEventListener("click", handler, { once: true }) return () => btn.removeEventListener("click", handler) }, }, { id: "floating-end", type: "floating", title: "Floating Step", description: "A floating step with no backdrop or arrow.", actions: [{ label: "Done", action: "dismiss" }], }, ]4.5 步骤的 effect 与清理
StepEffectArgs(tour.types.ts)提供 effect 内可用的控制句柄:
export interface StepEffectArgs { next: VoidFunction goto: (id: string) => void dismiss: VoidFunction show: VoidFunction update: (data: Partial<StepBaseDetails>) => void target?: (() => HTMLElement | null) | undefined }executeStepEffect(tour.machine.ts)的要点:
effect的返回值会存入refs.effectCleanup,在步骤切换(cleanupStepEffect)或整体关闭(cleanupAll)时执行;effect抛错会被捕获并console.error,不会导致状态机崩溃;- 若步骤类型为
wait,effect 执行后自动调用show()完成展示。
这正是 CHANGELOG 1.26.5 "Fix issue where effects were not cleanup correct"、0.43.0 "dismissing a tour from a step'seffectskipped cleanup and could miss firing the completed status" 等修复的落点:effect 清理的可靠性直接决定 Tour 关闭时状态回调的完整性。
五、机器 Props 与组件 API
5.1 TourProps 参数
TourProps完整定义于 tour.types.ts,createProps声明于 tour.props.ts。核心参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
steps | StepDetails[] | [] | 步骤列表 |
stepId | string \| null | — | 当前高亮步骤 id(受控) |
onStepChange | (details: StepChangeDetails) => void | — | 高亮步骤变化回调(含stepId、stepIndex、totalSteps、complete、progress) |
onStepsChange | (details: StepsChangeDetails) => void | — | 步骤列表变化回调 |
onStatusChange | (details: StatusChangeDetails) => void | — | 状态变化回调(idle / started / skipped / completed / dismissed / not-found) |
closeOnInteractOutside | boolean | true | 点击外部是否关闭 |
closeOnEscape | boolean | true | 按 Esc 是否关闭 |
keyboardNavigation | boolean | true | 是否允许左右方向键切换步骤 |
preventInteraction | boolean | false | 是否阻止与页面其余部分交互(对目标元素设置inert) |
spotlightOffset | Point | { x: 10, y: 10 } | 聚光灯外扩偏移 |
spotlightRadius | number | 4 | 聚光灯圆角半径(clip-path 圆角) |
translations | IntlTranslations | 见下方 | 文案翻译 |
ids | ElementIds | — | 各部分 DOM id(用于组合) |
dir | "ltr" \| "rtl" | — | 文本方向,影响键盘导航方向 |
默认翻译(tour.connect.ts):
const defaultTranslations = { nextStep: "next step", prevStep: "previous step", close: "close tour", progressText: ({ current, total }) => `${current + 1} of ${total}`, skip: "skip tour", }5.2 TourApi 组件 API
TourApi(tour.types.ts)提供状态查询与操作:
- 状态查询:
open、totalSteps、stepIndex、step、hasNextStep、hasPrevStep、firstStep、lastStep - 步骤管理:
addStep、removeStep、updateStep、setSteps、setStep、isValidStep、isCurrentStep - 流程控制:
start(id?)(可从指定步骤启动)、next()、prev() - 进度:
getProgressText()、getProgressPercent() - DOM Props:
getBackdropProps、getSpotlightProps、getProgressTextProps、getPositionerProps、getArrowProps、getArrowTipProps、getContentProps、getTitleProps、getDescriptionProps、getCloseTriggerProps、getActionTriggerProps
典型用法(examples/next-ts/pages/tour/basic.tsx):
const service = useMachine(tour.machine, { id: useId(), steps: tourData }) const api = tour.connect(service, normalizeProps) return ( <> <button onClick={() => api.start()}>Start Tour</button> {api.step && api.open && ( <Portal> {api.step.backdrop && <div {...api.getBackdropProps()} />} <div {...api.getSpotlightProps()} /> <div {...api.getPositionerProps()}> <div {...api.getContentProps()}> {api.step.arrow && ( <div {...api.getArrowProps()}><div {...api.getArrowTipProps()} /></div> )} <p {...api.getTitleProps()}>{api.step.title}</p> <div {...api.getDescriptionProps()}>{api.step.description}</div> <div {...api.getProgressTextProps()}>{api.getProgressText()}</div> {api.step.actions?.map((action) => ( <button key={action.label} {...api.getActionTriggerProps({ action })}>{action.label}</button> ))} <button {...api.getCloseTriggerProps()}><X /></button> </div> </div> </Portal> )} </> )与start(id)相关的修复记录:1.12.3 修复api.start(<id>)传步骤 id 不生效的问题;0.38.1 修复next、prev、setStep在 Tour 未激活时仍触发、绕过start流程的问题——这正对应状态机只在running状态响应STEP.NEXT/STEP.PREV/STEP.SET的设计。
六、关键实现细节:遮罩、聚光灯与定位
6.1 组件各部分(anatomy)
anatomy.ts 声明了 11 个 parts:
export const anatomy = createAnatomy("tour").parts( "content", "actionTrigger", "closeTrigger", "progressText", "title", "description", "positioner", "arrow", "arrowTip", "backdrop", "spotlight", )6.2 遮罩与聚光灯(backdrop / spotlight)
- backdrop:
getBackdropProps通过clip-path实现"挖洞"效果——tooltip 步骤时用getClipPath(utils/clip-path.ts)以目标矩形 +spotlightRadius圆角生成裁剪路径;dialog 步骤时position: fixed铺满视口。 - spotlight:
getSpotlightProps使用 CSS 变量--spotlight-x/y/width/height定位,borderRadius: var(--spotlight-radius),pointerEvents: none。suppressSpotlightTransition为true时transitionDuration: 0ms,即聚光灯只在步骤切换之间动画,resize/滚动重定位时直接跳转(对应 CHANGELOG 1.45.0 "The spotlight now only animates between steps")。
6.3 placement 与>getPlacement(anchorEl, positionerEl, { defer: true, placement: step.placement ?? "bottom", strategy: "absolute", gutter: 10, offset: step.offset, restoreStyles: false, applyStyles: false, getAnchorRect(el) { if (!isHTMLElement(el)) return null const rect = el.getBoundingClientRect() return offset(rect, prop("spotlightOffset")) }, onComplete(data) { /* 更新 targetRect、currentPlacement、floatingOffset */ }, })
其中restoreStyles是 CHANGELOG 1.38.1 的重要变更:popper 包将cleanupStyles选项重命名为restoreStyles,同时修复了"从 tooltip 步骤切换到 dialog/非 tooltip 步骤时 popper 样式未清理"的问题。在 Tour 中restoreStyles: false,样式清理由 Tour 自己管理(floatingOffset在关闭后保留,以便 Presence 退出动画保持锚定——见 tour.types.ts 中floatingOffset的注释)。
另外getAnchorRect对目标矩形应用spotlightOffset外扩,因此遮罩挖洞区域与聚光灯视觉区域保持一致。visualViewport的容错处理(1.45.0 修复 jsdom 等无visualViewport环境下的崩溃)可在trackBoundarySize中看到:win.visualViewport?.width ?? win.innerWidth。
七、焦点与无障碍:从 1.32.0 到 1.46.0 的持续修复
Tour 的焦点管理由trapFocuseffect 实现(tour.machine.ts):
return trapFocus([contentEl, targetEl], { escapeDeactivates: false, allowOutsideClick: true, preventScroll: true, returnFocusOnDeactivate: false, getShadowRoot: true, })CHANGELOG 中与焦点/无障碍直接相关的修复:
- 1.32.0:修复聚光灯目标内的输入元素不可交互的问题——focus trap 现在将 tour 内容和聚光灯目标都作为容器,允许用户与高亮区域内的元素交互。这正是上面
trapFocus([contentEl, targetEl], ...)传两个容器的原因。 - 1.27.0:所有 focus trap 组件(dialog、popover、tour 等)改进对shadow DOM的支持(
getShadowRoot: true)。 - 1.43.1:给
getCloseTriggerProps()增加type="button"。此前按钮默认type="submit",点击会提交祖先表单——这是表单场景下的经典陷阱。 - 1.46.0:修复动作触发器被翻译文本(而非可见标签)宣告的问题,可通过
attrs: { "aria-label": ... }为图标按钮命名。
内容容器本身也具备完整的无障碍属性(tour.connect.ts):role: "alertdialog"、aria-modal: "true"、aria-live: "polite"、aria-atomic: "true"、aria-labelledby指向标题、aria-describedby指向描述、tabIndex: -1。
键盘导航方面,getContentProps的onKeyDown支持左右方向键切换步骤(keyboardNavigation为true时),并在 RTL 模式下自动反转方向:
case "ArrowRight": send({ type: isRtl ? "STEP.PREV" : "STEP.NEXT", src: "keydown" })八、视口、滚动与动态目标:1.45.0 的大修
1.45.0 集中修复了三类视觉问题,对应三个独立 PR:
- 步骤卡片、聚光灯与遮罩在目标元素被替换(replaced)时坍缩到左上角(PR #3341)——
trackPlacement中anchorEl每次更新都会重新解析目标:若step.target()返回的新元素与resolvedTarget不同,则更新 context 并重新同步目标属性(见 tour.machine.ts 的anchorEl注释 "Re-resolved per update: the target can be replaced mid-tour, and a detached node measures zero")。 - 窗口 resize 或内容加载时,遮罩漏掉部分页面、聚光灯滑动到新位置(PR #3340)——通过
suppressSpotlightTransition机制:在onComplete中对比prevSpotlightRect与当前rects.reference,若 rect 变化且prevSpotlightStepId为空或等于当前 stepId(即非步骤切换的重定位),则抑制过渡(suppressSpotlightTransition = true),聚光灯直接跳到新位置而非动画滑动。 - iframe 或自定义 root 中的 Tour 错误测量外部页面,且在没有
visualViewport的环境(如 jsdom)挂载时崩溃——trackBoundarySize使用scope.getWin()/scope.getDoc()(作用域窗口,iframe 场景下即 iframe 自身窗口),宽度取visualViewport?.width ?? innerWidth,高度取documentElement.scrollHeight,并通过 rAF 轮询(注释说明:height: 100%这类场景下溢出变化不会触发 root 边框尺寸的 resize 事件)。
与此相关的还有 1.33.0 的滚动优化:将scrollIntoView从block: "center"改为block: "nearest",页面只在目标未可见时才滚动,消除步骤间切换的抖动(源码中scrollToTarget使用{ behavior: "instant", block: "nearest", inline: "nearest" })。
九、工程实践:从 CHANGELOG 提炼的避坑清单
综合整个 CHANGELOG 与源码,以下工程要点值得在集成@zag-js/tour时牢记:
- 按钮类型陷阱:所有
getActionTriggerProps/getCloseTriggerProps输出的按钮均已带type="button"(1.43.1),集成时不要覆盖此属性,避免嵌套在表单中触发提交。 - effect 必须返回清理函数:从 0.43.0、1.26.5 的修复看,effect 清理是状态回调(
completed/dismissed)可靠触发的关键。effect返回的 cleanup 会在步骤切换与 Tour 关闭时执行。 - 目标元素可能被替换:1.45.0 起
anchorEl会在每次定位时重新解析目标,若目标被 React/Vue 等框架替换,Tour 会自适应;但target回调应始终返回最新的元素引用。 - wait 步骤不计入进度:
getProgressText/getProgressPercent基于getEffectiveSteps(排除wait类型)计算,wait 步骤只是流程中的"等待哨兵"。 - 图标按钮需要显式无障碍名:1.46.0 起可用
attrs: { "aria-label": "..." }为纯图标动作按钮命名,否则屏幕阅读器会读翻译文案。 - z-index 由用户层控制:0.81.1 起 Tour 不再设置
zIndex,通过 CSS 变量--tour-layer(backdrop=0、spotlight=1、positioner=2)与--tour-z-index组合实现层级,用户需自行保证容器层叠上下文正确。 - 避免步骤 id 重复:
validateSteps会在挂载时校验(tour.machine.ts),重复 id 会直接抛错:[zag-js/tour] Duplicate step id: ...;同时每个步骤必须提供target或type之一。
十、依赖协同:Tour 如何受益于生态升级
CHANGELOG 中几乎每个版本都带 "Updated dependencies",这些不是例行公事,而是 Tour 行为改进的直接来源:
@zag-js/focus-trap的升级带来 shadow DOM 支持(1.27.0)、目标容器纳入陷阱(1.32.0);@zag-js/popper的升级带来restoreStyles重命名与样式清理修复(1.38.1)、data-side支持(1.41.0);@zag-js/dismissable/@zag-js/interact-outside的升级支撑了点击外部关闭、dismissable branch 的稳定性;@zag-js/types的升级(1.43.3)将可选属性改为显式?: T | undefined,修复了Partial导出遮蔽内置类型、破坏@vue/compiler-sfc的interface X extends Partial<Y>的问题——这是类型层面对 Vue 生态的兼容性修复。
结语
从 0.37.0 引入到 1.46.0,@zag-js/tour的 CHANGELOG 浓缩了一个真实生产组件的打磨历程:状态机从resolving → scrolling → waiting → active的清晰分层,到 wait 步骤的编程化重构(0.72.0),再到聚光灯过渡抑制(1.45.0)、目标替换自适应(1.45.0)、焦点陷阱扩展(1.32.0)与无障碍细节(1.43.1、1.46.0)的持续收敛。结合 tour.machine.ts 与 tour.connect.ts 的源码阅读,你可以将这套状态机设计思路直接迁移到自己的引导式走查实现中,并在集成 Zag.js Tour 时避开历史上沉淀下来的全部已知坑点。
- 前端
- UI组件
【免费下载链接】zag
Build your design system in React, Solid, Vue, Svelte or Vanilla. Powered by finite state machines
相关推荐
@zag-js/tour 源码解析:有限状态机驱动的产品引导(Tour)组件全指南
@zag js/tour 源码解析:有限状态机驱动的产品引导(Tour)组件全指南 导读 本文以 Zag 仓库中 @zag js/tour https://li
前端UI组件@zag-js/toggle 状态机组件全解析:从 CHANGELOG 看 toggle 机器的演进与实现
@zag js/toggle 状态机组件全解析:从 CHANGELOG 看 toggle 机器的演进与实现 本文以 packages/machines/togg
前端UI组件Zag.js Hover Card 状态机演进全解:从 CHANGELOG 到源码级实现剖析
Zag.js Hover Card 状态机演进全解:从 CHANGELOG 到源码级实现剖析 导读 Hover Card(悬停卡片)是 Zag.js 中一个典型
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考