ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Open-Pencil SDK 中的 useToolbar:读取编辑器工具栏上下文的无头原语指南

Open-Pencil SDK 中的 useToolbar:读取编辑器工具栏上下文的无头原语指南 前端桌面应用AI 应用MCP 服务【免费下载链接】open-pencilAI-native design editor. Open-source Figma alternative.项目地址https://gitcode.com/gh_mirrors/op/open-pencil点击查看免费下载useToolbar()是 Open-Pencil Vue SDKopen-pencil/vue为编辑器工具栏提供的一个上下文读取原语context helper它从最近的ToolbarRoot中读取完整的工具栏上下文供其所有后代组件使用。本文基于仓库中 use-toolbar 参考文档及其 法文版本展开并结合packages/vue/src/primitives/Toolbar/下的真实源码讲解上下文的数据结构、底层 provide/inject 实现、与ToolbarItem、useToolbarState的协作方式以及在实际自定义工具栏中的典型用法。读完本文后你将能够在自己的编辑器外壳中构建完全自定义样式的工具栏并正确获取工具列表、当前激活工具与工具切换能力。一、useToolbar()是什么根据官方文档的原始定义useToolbar()renvoie le contexte duToolbarRootle plus proche.useToolbar()返回最近的ToolbarRoot的上下文。useToolbar()是一个无头headless工具它不渲染任何 DOM只负责读取由上层ToolbarRoot通过 Vue 的依赖注入provide/inject机制提供的上下文对象。官方文档同时明确了它的适用场景Utilisez-le dans les descendants qui ont besoin des outils, de létat actif ou de laction de changement doutil.在需要获取工具列表、当前激活状态或切换工具动作的后代组件中使用它。即它的典型消费者是ToolbarRoot内部的任意层级后代——无论是直接子组件还是嵌套在菜单、弹层、分组容器深处的孙组件只要它们需要访问工具栏的状态与动作都可以调用useToolbar()拿到同一份上下文而无需通过 props 层层透传。从实现上看它对应 context.ts 中一个非常简洁的函数export function useToolbar(): ToolbarContext { const ctx inject(TOOLBAR_KEY) if (!ctx) throw new Error([open-pencil] useToolbar() called outside ToolbarRoot) return ctx }二、useToolbar()返回的上下文结构useToolbar()的返回值类型为ToolbarContext该接口完整定义在 context.tsexport interface ToolbarContext { editor: Editor tools: EditorToolDef[] activeTool: ComputedRefTool flyoutSelections: ReadonlyMapTool, Tool expandedFlyout: RefTool | null setTool: (tool: Tool) void toggleFlyout: (tool: Tool) void closeFlyout: () void }各字段的职责如下成员类型说明editorEditor底层编辑器实例可用于进一步访问编辑器的其余状态与命令toolsEditorToolDef[]当前工具栏可用的工具定义列表activeToolComputedRefTool当前激活工具的响应式引用随用户切换工具自动更新flyoutSelectionsReadonlyMapTool, Tool每个带飞出的父工具flyout最近一次选择的子工具记录expandedFlyoutRefTool \| null当前展开的飞出菜单对应的工具null表示全部收起setTool(tool: Tool) void切换激活工具的动作函数toggleFlyout(tool: Tool) void展开/收起某个工具的飞出菜单closeFlyout() void关闭当前展开的飞出菜单其中activeTool、expandedFlyout分别是ComputedRef与Ref在 Vue 模板或computed中需要以.value读取这也是文档中强调状态是响应式的的原因——工具切换后所有调用useToolbar()的后代组件都会自动重新渲染。三、底层原理provide/inject 与越界保护useToolbar()之所以能找到最近的ToolbarRoot靠的是 Vue 的依赖注入机制。在 context.ts 中export const TOOLBAR_KEY: InjectionKeyToolbarContext Symbol(toolbar) export function provideToolbar(ctx: ToolbarContext) { provide(TOOLBAR_KEY, ctx) }使用Symbol(toolbar)作为注入键避免与其他上下文如编辑器上下文冲突ToolbarRoot挂载时调用provideToolbar(...)向子树注入上下文useToolbar()则通过inject(TOOLBAR_KEY)向上查找最近的注入源。值得注意的是一层越界保护如果useToolbar()在ToolbarRoot之外被调用inject返回undefined函数会直接抛出错误[open-pencil] useToolbar() called outside ToolbarRoot。这意味着你无法在ToolbarRoot外部凭空获取工具栏状态任何使用都必须先建立ToolbarRoot容器错误信息给出了明确的修复方向便于排查自定义工具栏外壳中的组件放置问题。四、ToolbarRoot如何构建并注入这份上下文要理解useToolbar()读到的数据从哪来需要看它的提供方 ToolbarRoot.vue。核心流程可概括为四点1. 工具列表可注入缺省用全部编辑器工具const { tools EDITOR_TOOLS } defineProps{ tools?: EditorToolDef[] }()ToolbarRoot接受一个可选的toolsprop默认值为EDITOR_TOOLS来自open-pencil/core/editor的全部编辑器工具定义。因此你既可以用默认全集也可以传入自定义工具列表来裁剪工具栏内容。2. 激活工具直接源自编辑器状态const editor useEditor() const activeTool computed(() editor.state.activeTool)activeTool不是ToolbarRoot自己维护的本地状态而是对编辑器全局状态editor.state.activeTool的响应式投影。useToolbar()的后代拿到的activeTool始终与编辑器当前真实激活的工具保持一致——这也是文档中état actif激活状态的含义。3. 切换工具即调用编辑器命令function setTool(tool: Tool) { editor.setTool(tool) expandedFlyout.value null }setTool最终落到editor.setTool(tool)并在切换成功后自动收起所有展开的飞出菜单保证 UI 状态与编辑器工具状态同步。4. 飞出菜单flyout状态集中管理const expandedFlyout refTool | null(null) const flyoutSelections reactive(new MapTool, Tool()) watch( activeTool, (currentTool) { for (const tool of tools) { if (tool.flyout?.includes(currentTool)) { flyoutSelections.set(tool.key, currentTool) } } }, { immediate: true } )expandedFlyout记录当前展开的飞出菜单flyoutSelections记录每个含飞出的父工具最近一次选中的子工具便于下次展开时保持上次选择watch监听activeTool变化当激活工具属于某父工具的flyout列表时自动更新该父工具的记忆选择。ToolbarRoot的模板是一个纯插槽slot组件将tools、active-tool、flyout-selections、expanded-flyout与actions一并暴露给插槽同时调用provideToolbar注入上下文。这也印证了官方文档中的定位无头headless——结构、状态与动作由 SDK 提供样式与布局完全由你的应用自己定义。五、与ToolbarItem的协作共享的工具选择接线useToolbar()最直接的消费方是 ToolbarItem.vue。它的实现完整展示了如何用useToolbar()组装单个工具按钮script setup langts import { computed } from vue import type { Tool } from open-pencil/core/editor import { useToolbar } from #vue/primitives/Toolbar/context const { tool } defineProps{ tool: Tool }() const { activeTool, setTool } useToolbar() const isActive computed(() activeTool.value tool) const actions { select: () setTool(tool) } /script template slot :activeisActive :actionsactions :tooltool / /template这里ToolbarItem通过useToolbar()解构出activeTool与setTool用activeTool.value tool计算当前工具是否激活isActive用setTool(tool)封装出actions.select选择动作再把active、actions、tool通过作用域插槽交给应用自定义的按钮渲染。因此你在ToolbarRoot内部可以完全不使用 SDK 预设的按钮样式而是ToolbarRoot template #default{ tools } div classmy-toolbar ToolbarItem v-fortool in tools :keytool.key :tooltool.key template #default{ active, actions } button :class[my-btn, { is-active: active }] clickactions.select {{ tool.key }} /button /template /ToolbarItem /div /template /ToolbarRoot这正是文档所说afficher son propre bouton et réutiliser la logique partagée du SDK显示自己的按钮、复用 SDK 共享逻辑的落地方式按钮的视觉与布局是自己的激活判定与选择动作是共享的。六、useToolbar()与useToolbarState()的分工同属工具栏家族的还有 useToolbarState其实现位于 useToolbarState.ts。两者的定位有明显区别useToolbar()读取结构性上下文。必须在ToolbarRoot后代中调用返回ToolbarContext编辑器、工具列表、激活工具、切换动作、飞出菜单控制useToolbarState()面向展示presentation-oriented的本地工具状态。它不依赖ToolbarRoot直接返回移动端分类翻页状态与goPrev()/goNext()等辅助函数源码注释明确写它是responsive toolbar UI state for mobile category paging用来在移动端把工具栏按 3 个分类分页滑动展示。useToolbarState中还内置了两个与激活判定有关的纯函数可用于自定义工具栏的展示逻辑export function isToolbarToolActive(tool: EditorToolDef, activeTool: Tool): boolean { return tool.key activeTool || (tool.flyout?.includes(activeTool) ?? false) } export function getToolbarToolSelection( tool: EditorToolDef, activeTool: Tool, flyoutSelections?: ReadonlyMapTool, Tool ): Tool { if (tool.flyout?.includes(activeTool)) return activeTool return flyoutSelections?.get(tool.key) ?? tool.key }isToolbarToolActive判定一个工具是否处于激活态——要么自身就是激活工具要么激活工具属于它的飞出子工具列表getToolbarToolSelection计算父工具当前应展示的选择——若激活的是飞出子工具则返回该子工具否则返回flyoutSelections中记忆的最近选择缺省为父工具自身。典型组合方式是用ToolbarRootuseToolbar()提供数据与动作用useToolbarState()处理移动端翻页和激活高亮计算。所有原语统一从 primitives/Toolbar/index.ts 导出ToolbarRoot、ToolbarItem、useToolbar及ToolbarContext类型均可从open-pencil/vue直接引入。七、典型用法在自定义工具栏中读取上下文假设你要在工具栏内部渲染一个自定义按钮组同时在一个侧边弹出菜单中展示更多工具。核心模式是在ToolbarRoot的插槽内部调用useToolbar()script setup langts import { useToolbar } from open-pencil/vue // 仅限 ToolbarRoot 后代使用越界会抛出 // [open-pencil] useToolbar() called outside ToolbarRoot const { tools, activeTool, setTool, toggleFlyout, closeFlyout, expandedFlyout } useToolbar() /script template div button v-fortool in tools :keytool.key :class{ active: activeTool.value tool.key } clicksetTool(tool.key) {{ tool.key }} /button !-- 带飞出菜单的工具点击切换展开再点关闭 -- div v-ifexpandedFlyout button clickcloseFlyout关闭/button !-- 渲染飞出子工具… -- /div /div /template几点实践建议保持activeTool.value的响应式读取在computed、模板或watch中使用.value解包确保工具切换时 UI 自动更新优先复用ToolbarItem如果只是渲染单个工具按钮直接用ToolbarItem的插槽即可获得现成的active与actions.select不必手动解构useToolbar()飞出菜单的统一管理展开状态集中在expandedFlyout配合toggleFlyout/closeFlyout可以做到同一时刻只有一个飞出菜单展开不要越过ToolbarRoot使用useToolbar()依赖注入链脱离ToolbarRoot会抛错——这是设计上的约束而非缺陷它保证了工具栏状态来源单一。八、相关 API 导航useToolbar()不是孤立存在的它与整个工具栏原语家族配合工作官方文档Voir aussi另见一节给出了三条入口在仓库中的对应文档如下ToolbarRoot工具栏根容器提供上下文实现见 ToolbarRoot.vueToolbarItem单个工具栏工具内部即调用 useToolbar实现见 ToolbarItem.vueuseToolbarState面向移动端分类翻页的展示态工具实现见 useToolbarState.ts。此外若要理解工具栏之外的编辑器能力还可参考同一 SDK 文档体系中的 useEditorCommands 与 useSelectionCapabilitiesToolbarRoot文档的关联 API。当你要在自定义编辑器外壳中整合工具栏时custom-editor-shell 指南 提供了更完整的落地上下文。赞分享前端桌面应用AI 应用MCP 服务【免费下载链接】open-pencilAI-native design editor. Open-source Figma alternative.项目地址https://gitcode.com/gh_mirrors/op/open-pencil点击查看免费下载相关推荐open-pencil 工具栏编程指南useToolbar 上下文与 ToolbarRoot 组合式 API 实战open pencil 工具栏编程指南useToolbar 上下文与 ToolbarRoot 组合式 API 实战 useToolbar 是 open pen前端桌面应用AI 应用MCP 服务open-pencil open-pencil/vue 中的 ToolbarRoot为设计编辑器构建 Headless 工具栏原语open pencil open pencil/vue 中的 ToolbarRoot为设计编辑器构建 Headless 工具栏原语 ToolbarRoot前端桌面应用AI 应用MCP 服务open-pencil 无头工具栏原语 ToolbarItem工具状态与选择逻辑的复用方案open pencil 无头工具栏原语 ToolbarItem工具状态与选择逻辑的复用方案 ToolbarItem 是 open pencilAI nati前端桌面应用AI 应用MCP 服务创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表