- 前端
- 桌面应用
- AI 应用
- MCP 服务
【免费下载链接】open-pencil
AI-native design editor. Open-source Figma alternative.
本文围绕 OpenPencil 前端 SDK(@open-pencil/vue)中的useToolbarState()组合式函数展开,讲解如何基于ToolbarRoot构建在小屏设备上按类别分页切换的响应式工具栏,并深入源码与编辑器内实际调用场景,验证其分类边界、翻页语义与滑动方向动画的工作原理。
一、useToolbarState 的定位与解决的问题
OpenPencil 是一个 AI 原生的开源设计编辑器,其编辑器界面依赖大量 Vue 组合式函数与“无头(headless)”原语(primitive)来组织交互逻辑。在小屏设备上,工具栏空间有限,无法一次性展示所有工具,因此需要将工具按类别分组、分页展示。
useToolbarState()正是为此设计的“移动端分类分页状态助手”,官方 API 文档将其定位为mobile-category paging state(移动端分类分页状态)。它返回:
- 当前分类索引
mobileCategory - 滑动方向
slideDirection - 翻页能力判断
hasPrev/hasNext - 翻页动作
goPrev()/goNext() - 工具激活判定
isActive与工具选择键activeKeyForTool
该组合式函数只负责展示层(presentation-oriented)状态,不直接读取编辑器上下文,因此它天然与 useToolbar(读取ToolbarRoot提供的工具上下文)互补:useToolbar回答“当前有哪些工具、哪个处于激活态”,而useToolbarState回答“在窄屏上当前应该展示哪一页工具、能否翻页”。
二、返回状态与方法的完整语义
源码位于 packages/vue/src/primitives/Toolbar/useToolbarState.ts,其完整返回对象如下:
| 成员 | 类型 | 语义 |
|---|---|---|
mobileCategory | Ref<number> | 当前分类页码,从 0 开始,范围[0, CATEGORY_COUNT - 1] |
slideDirection | Ref<number> | 滑动方向,-1表示向后翻(上一页),1表示向前翻(下一页),用于驱动入场/离场动画 |
hasPrev | ComputedRef<boolean> | 是否存在上一页(mobileCategory > 0) |
hasNext | ComputedRef<boolean> | 是否存在下一页(mobileCategory < CATEGORY_COUNT - 1) |
isActive | function | 判断某个工具定义在当前激活工具下是否处于激活态 |
activeKeyForTool | function | 给定工具定义与激活工具,返回应当显示为“选中”的工具键 |
goPrev() | function | 翻到上一页,同时将slideDirection置为-1 |
goNext() | function | 翻到下一页,同时将slideDirection置为1 |
分类数量边界:CATEGORY_COUNT
源码第 5 行定义了分类总数常量:
const CATEGORY_COUNT = 3这意味着mobileCategory只会在0、1、2三个页面间切换。在 OpenPencil 编辑器内,这三页分别对应:工具页(tools)、编辑动作页(edit)、排列动作页(arrange)(见下文对 MobileToolbar.vue 的分析)。
翻页的边界保护
goPrev()与goNext()均带边界保护,越界调用会被静默忽略:
function goPrev() { if (!hasPrev.value) return slideDirection.value = -1 mobileCategory.value-- } function goNext() { if (!hasNext.value) return slideDirection.value = 1 mobileCategory.value++ }因此即使父组件在最后一页反复触发goNext,页码也不会越界;hasPrev/hasNext可用来渲染禁用态的导航按钮。
滑动方向与动画
slideDirection并不参与页码计算,它专门供动画库读取:MobileToolbar.vue中slideVariants按方向计算位移(下一页从右向左滑入x: 20 → 0,上一页则反向),从而让用户明确感知“前进/后退”的方向语义。
三、激活态与选中态的两个辅助函数
useToolbarState额外暴露两个纯函数形式的辅助方法(它们同时也是模块级导出,可直接按需导入测试)。
isToolbarToolActive:判断工具是否激活
export function isToolbarToolActive(tool: EditorToolDef, activeTool: Tool): boolean { return tool.key === activeTool || (tool.flyout?.includes(activeTool) ?? false) }- 若工具自身的
key等于当前激活工具,返回true; - 若该工具带有
flyout(弹出子工具组)且flyout列表包含当前激活工具,也视为激活; flyout不存在时通过?? false兜底,保证安全比较。
getToolbarToolSelection:解析当前应选中的工具键
export function getToolbarToolSelection( tool: EditorToolDef, activeTool: Tool, flyoutSelections?: ReadonlyMap<Tool, Tool> ): Tool { if (tool.flyout?.includes(activeTool)) return activeTool return flyoutSelections?.get(tool.key) ?? tool.key }- 如果激活工具属于该工具的 flyout 子组,直接返回激活工具(子工具自身高亮);
- 否则优先返回用户在 flyout 中的历史选择(
flyoutSelections映射),没有记录时回退到工具主键tool.key。
这两处逻辑在 MobileToolbar.vue 中被用于同时驱动 flyout 按钮的selected-tool与普通工具按钮的active状态。
四、与相关 API 的组合使用方式
useToolbarState属于工具栏 API 家族中的“展示层”成员,官方文档(use-toolbar-state.md)列出的关联 API 包括:
- ToolbarRoot:无头工具栏结构原语,负责提供工具、激活工具与选择行为的上下文;
- ToolbarItem:单个工具栏工具的无头原语,暴露激活态与选择行为;
- useToolbar:读取
ToolbarRoot注入的本地工具栏上下文。
典型的分工是:ToolbarRoot通过作用域插槽暴露{ tools, activeTool, flyoutSelections, actions },useToolbarState提供分页导航状态,ToolbarItem负责渲染单个工具按钮并接入选择动作。
从@open-pencil/vue的导出面看(packages/vue/src/index.ts),ToolbarRoot、ToolbarItem与useToolbar由 packages/vue/src/primitives/Toolbar/index.ts 统一导出;useToolbarState作为独立组合式函数同包导出,供应用层工具栏直接消费。
五、编辑器内的真实调用场景
组装:Toolbar.vue
OpenPencil 应用层的工具栏组件 src/components/Toolbar/Toolbar.vue 是useToolbarState的实际消费方。它同时基于useViewportKind()区分桌面与移动端渲染:
- 桌面端渲染
DesktopToolbar,无需分页; - 移动端渲染
MobileToolbar,并将useToolbarState()解构出的六个成员传入:
const { mobileCategory, slideDirection, hasPrev, hasNext, goPrev, goNext } = useToolbarState()模板中通过:mobile-category、:slide-direction、:has-prev、:has-next传给子组件,@prev="goPrev"与@next="goNext"绑定导航按钮事件。
分页渲染:MobileToolbar.vue
在 MobileToolbar.vue 内部,三个分类页面通过v-if/v-else-if/v-else分支渲染:
mobileCategory === 0:tools(data-test-id="mobile-toolbar-tools"),渲染全部工具按钮与 flyout;mobileCategory === 1:edit(data-test-id="mobile-toolbar-edit"),渲染编辑动作组editActions;- 其余(即
2):arrange(data-test-id="mobile-toolbar-arrange"),渲染排列动作组arrangeActions。
页面切换由AnimatePresence mode="popLayout" :custom="slideDirection"驱动,配合slideVariants(初始位移动画)完成方向感知的转场:
const slideVariants = { initial: (dir: unknown) => ({ opacity: 0, x: (dir as number) * 20 }), animate: { opacity: 1, x: 0 }, exit: (dir: unknown) => ({ opacity: 0, x: (dir as number) * -20 }) }hasPrev/hasNext同时控制左右导航按钮的禁用与透明度:不可翻页时按钮通过navigationClass(true)获得禁用样式,并通过:animate="{ opacity: hasPrev ? 1 : 0 }"淡出隐藏,对应按钮的data-test-id分别为mobile-toolbar-prev与mobile-toolbar-next。
六、动手实践:构建自己的分页工具栏
useToolbarState不依赖任何编辑器内部上下文,因此可以在自定义工具栏壳层中直接复用。一个最小可运行的分页工具栏骨架如下:
<script setup lang="ts"> import { useToolbarState } from '@open-pencil/vue' import { ToolbarRoot, ToolbarItem } from '@open-pencil/vue' const { mobileCategory, hasPrev, hasNext, goPrev, goNext } = useToolbarState() </script> <template> <div class="toolbar-shell"> <button :disabled="!hasPrev" @click="goPrev">‹</button> <ToolbarRoot v-slot="{ tools, activeTool, actions }"> <ToolbarItem v-for="tool in tools" :key="tool.key" :tool="tool.key" v-slot="{ active, actions }"> <button :class="{ active }" @click="actions.select">{{ tool.key }}</button> </ToolbarItem> </ToolbarRoot> <button :disabled="!hasNext" @click="goNext">›</button> <p>当前分类索引:{{ mobileCategory }}</p> </div> </template>要点:
- 用
hasPrev/hasNext控制导航按钮的禁用态; - 用
mobileCategory决定当前展示哪一组内容(可参照MobileToolbar.vue的三个v-if分支); - 若希望复用编辑器内置的激活态逻辑,直接解构
isActive与activeKeyForTool即可; - 若需要方向动画,将
slideDirection透传给转场动画的custom参数。
七、小结
useToolbarState()是 OpenPencil 移动端工具栏的“分页大脑”:它以CATEGORY_COUNT = 3为边界管理分类页码,通过goPrev()/goNext()与hasPrev/hasNext提供安全翻页,通过slideDirection驱动方向感知的滑动动画,并通过isActive/activeKeyForTool两个辅助函数解决 flyout 子工具与历史选择的激活态解析。
从实现层面看,packages/vue/src/primitives/Toolbar/useToolbarState.ts 是完全展示层的纯状态逻辑,不触碰编辑器 store 或 DOM,因此既易于单元测试,也可在任意基于ToolbarRoot的自定义工具栏壳层中复用。对希望基于 OpenPencil 构建自定义响应式工具栏(或深入理解其编辑器移动端交互)的开发者,useToolbarState是一个值得优先阅读与复用的入口。
- 前端
- 桌面应用
- AI 应用
- MCP 服务
【免费下载链接】open-pencil
AI-native design editor. Open-source Figma alternative.
相关推荐
open-pencil Vue SDK 的 useToolbarState:无 ToolbarRoot 的响应式工具栏状态与移动端分类翻页
open pencil Vue SDK 的 useToolbarState:无 ToolbarRoot 的响应式工具栏状态与移动端分类翻页 useToolbar
前端桌面应用AI 应用MCP 服务open-pencil 工具栏响应式分页状态:useToolbarState 源码解析与实战
open pencil 工具栏响应式分页状态:useToolbarState 源码解析与实战 useToolbarState 是 open pencil(AI
前端桌面应用AI 应用MCP 服务终极响应式导航:vue-element-admin移动端导航栏完美适配指南
终极响应式导航:vue element admin移动端导航栏完美适配指南 vue element admin是一个功能强大的Vue管理系统模板,其内置的响应式
前端企业应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考