首页/新闻资讯/正文详情

OpenPencil Vue SDK:usePropScrub 拖动式属性更新与 Undo 感知提交机制

发布时间:2026/9/25 4:18:48 来源:云帆数科 栏目:资讯中心
OpenPencil Vue SDK:usePropScrub 拖动式属性更新与 Undo 感知提交机制
前端桌面应用AI 应用MCP 服务【免费下载链接】open-pencilAI-native design editor. Open-source Figma alternative.项目地址https://gitcode.com/gh_mirrors/op/open-pencil点击查看免费下载本文围绕 OpenPencil 的usePropScrubComposable 展开介绍它如何在 Vue 属性面板中实现拖动即预览、松手才提交的数值属性编辑updateProp在拖动过程中实时刷新画布上的节点属性交互结束后commitProp将最终值写入 Undo 历史cancelProp则可整体回滚。读完本文你可以基于该 API 自定义数字输入控件并理解其底层的节点预览NodePreview与撤销历史衔接机制。一、API 概览usePropScrub(editor)usePropScrub(editor)接收一个编辑器实例Editor类型来自open-pencil/core/editor协调scrubbing按住拖动连续调节数值期间的实时属性更新并在交互结束时提交一个 Undo 感知的变更。它适用于构建通过拖动直接修改选中节点数值属性如 x/y、width/height、rotation 等的数字控件。源码实现位于 prop-scrub/use.tsimport type { Editor } from open-pencil/core/editor import type { NumericNodeProperty, SceneNode } from open-pencil/scene-graph import { useNodePreview } from #vue/controls/node-preview/use export function usePropScrub(editor: Editor) { const preview useNodePreview(editor) function updateProp(nodes: SceneNode[], key: NumericNodeProperty, value: number) { preview.update( nodes.map((node) node.id), { [key]: value }, Change ${key} ) } function commitProp( _nodes: SceneNode[], _key: NumericNodeProperty, _value: number, _previous: number ) { preview.commit() } function cancelProp(_nodes: SceneNode[], _key: NumericNodeProperty) { preview.cancel() } return { updateProp, commitProp, cancelProp } }可以看到usePropScrub本身是一个薄封装它把三个方法全部委托给 node-preview/use.ts 中的useNodePreview。三个方法的参数说明方法参数说明updateProp(nodes, key, value)nodes: SceneNode[]要更新的节点数组支持多选场景下对多个节点同时赋值key: NumericNodeProperty场景图的数值属性键例如x、width、rotationvalue: number拖动过程中写入的当前数值commitProp(nodes, key, value, previous)四个参数均为SceneNode[]/ 属性键 / 当前值 / 拖动前的原值实现中仅调用preview.commit()参数带_前缀表示未使用保留完整签名是为了与控件层的提交回调约定保持一致cancelProp(nodes, key)节点数组与属性键实现中仅调用preview.cancel()整体回滚未提交的预览值得注意的两点Undo 标签由updateProp内的Change ${key}生成即撤销面板中显示的操作名会带上属性名如 Change width首次updateProp调用时才会通过editor.beginNodePreview(label)开启一次预览会话后续调用复用同一次会话保证整段拖动只产生一条撤销记录。二、中间层useNodePreview 如何固定编辑目标useNodePreview 是usePropScrub与编辑器核心之间的桥接层其注释写明了设计意图Keeps an edits original targets until its terminal event, even after selection changes——即使选择发生变化也保留本次编辑最初的编辑目标直到收到终止事件提交或取消。关键实现export function useNodePreview(editor: Editor) { let preview: NodePreview | undefined let targets: readonly string[] [] function update(ids: readonly string[], changes: PartialSceneNode, label: string) { if (!preview) { if (!ids.length) return targets [...ids] preview editor.beginNodePreview(label) } try { for (const id of targets) preview.update(id, changes) } catch (error) { cancel() throw error } } // takePreview / commit / cancel 略 tryOnScopeDispose(cancel) if (getCurrentInstance()) onDeactivated(cancel) return { update, commit, cancel } }这段代码解决了一个典型的 UI 交互隐患预览创建后update始终作用于首次记录的targetsfor (const id of targets)不会因为中途改变选择而把值写错到别的节点上通过tryOnScopeDispose(cancel)与onDeactivated(cancel)注册兜底取消当组件被销毁或从keep-alive中停用时未提交的预览会被自动回滚不会把脏数据留在场景图上。三、底层机制NodePreview 与撤销历史的衔接真正做脏值跟踪与 Undo 写入的是编辑器核心的 node-preview.ts。createNodePreviewActions导出的NodePreview接口只有四个成员export interface NodePreview { readonly closed: boolean update: (id: string, changes: PartialSceneNode) void commit: () void cancel: () void }其内部工作流可以概括为四步捕获原值capture。update(id, changes)在写入前先通过capture把该节点尚未记录过的字段做一次structuredClone快照存入originals。同时处于graph.isApplyingLayout过程中被改动的节点会被标记为layoutOnly即只是布局联动产生的衍生变更为提交时的回放方式做准备。实时预览写入。真正的属性写入走graph.runPreviewUpdates(() updateNode(id, changes), capture)属于预览性质的更新画布会重绘ctx.requestRepaint()但不会触发已提交变更的对外事件也不会进入 Undo 栈。提交commit。先对所有目标节点assertNodeEditable然后为每个节点生成{ id, before, after }编辑对before取自快照、after取自当前节点值仅保留被改动的键。若before与after相等则跳过。最后ctx.undo.push({ label, forward: () apply(after), inverse: () apply(before) }) apply(after)这一步把整次拖动压缩成一条带前后镜像的 Undo 记录——无论拖动过程中调用过多少次updatePropCtrlZ一次即可回到拖动前的状态CtrlShiftZ一次即可恢复。layoutOnly节点在回放时使用graph.withLayoutMutations包裹保证布局联动变更被正确重放。取消cancel。用originals快照逐节点执行graph.updateNodePreview(id, previous)还原原值且不向 Undo 栈写入任何记录注释明确cancellation publishes no committed node edits。此外beginNodePreview会为预览订阅几个编辑器事件selection:changed、page:changed、graph:replaced直接触发preview.cancel()node:deleted在删除的节点属于编辑目标时同样取消。这意味着一旦用户在拖动期间改变了选择或切换了页面进行中的预览会被安全回滚防止数值串到其他节点上。四、实战用法在属性面板中接住一次拖动1. 通过 usePosition 直接调用仓库中 position/use.ts 给出了标准接法usePosition()内部组合了useNodeProps()提供当前选中的nodes与usePropScrub(editor)再对外暴露收窄了上下文的三个方法const { nodes } useNodeProps() const { updateProp: _updateProp, commitProp: _commitProp, cancelProp: _cancelProp } usePropScrub(editor) function updateProp(key: NumericNodeProperty, value: number) { _updateProp(nodes.value, key, value) } function commitProp(key: NumericNodeProperty, value: number, previous: number) { _commitProp(nodes.value, key, value, previous) }2. 通过 PositionControls 原语消费 actionsPositionControlsRoot.vue 把usePosition()的updateProp / commitProp / cancelProp打包成actions通过插槽下发给自定义控件PositionControlsRoot template #default{ xValue, wValue, hValue, actions } NumberField v-modelxValue labelX updateactions.updateProp(x, $event) commitactions.commitProp(x, $event.value, $event.previous) cancelactions.cancelProp(x) / /template /PositionControlsRoot这套原语组件提供状态与 actions插槽内控件负责交互事件的分层使得NumberField这类输入控件可以完全复用同一套预览/提交管线。3. 完整交互时序一次典型的拖动调节可归纳为用户按下并拖动 NumberField └─ 每帧: updateProp(nodes, width, v) // 预览写入画布实时重绘不进 Undo 用户松开交互结束 └─ commitProp(nodes, width, v, v0) // 快照对比 → ctx.undo.push → 单条 Undo 记录 或 用户按 Esc / 选择变化 / 组件销毁 └─ cancelProp(nodes, width) // 按快照还原不产生任何提交记录五、测试佐证目标固定、选择变化自动取消、作用域销毁自动回滚tests/engine/vue/controls/property-preview.test.ts 的 pins targets through selection changes and releases previews on scope disposal 用例覆盖了这套机制的三个关键行为const scrub scope.run(() usePropScrub(editor)) scrub.updateProp([first], x, 50) // 开启预览first.x 预览为 50 editor.select([second.id]) // 选择变化 → 预览被自动 cancelfirst.x 还原为 10 scrub.updateProp([second], x, 80) // 旧预览已 closed更新成为 no-op scrub.commitProp([second], x, 80, 30) // closed 预览的 commit 不产生 Undo expect([first.x, second.x]).toEqual([10, 30]) expect(editor.undo.canUndo).toBe(false) // 确认没有脏的 Undo 记录 scrub.updateProp([second], x, 90) scope.stop() // 作用域销毁 → 未提交预览被自动回滚 expect(second.x).toBe(30) expect(editor.isInteractiveEditing()).toBe(false)该用例印证了前文两个结论选择变化会触发核心层自动取消预览作用域销毁组件卸载/停用的兜底保证预览一定被释放且interactive editing状态随之结束。同文件后续的 size/layout 用例还验证了预览会话能拥有拖动过程中派生的隐式变更例如 HUG 尺寸被拖成 FIXED 的转换提交或取消时一并生效/回滚。六、边界与注意事项一次拖动 一条 Undo 记录。多次updateProp不会累积多条撤销项只有commitProp时before/after有差异才会真正ctx.undo.pushedits.length 0时仅请求重绘。编辑目标在预览开始时即固定。中途改变选择不会把值写到新选中的节点而是直接取消本次预览——自定义控件若希望换目标后继续拖需要重新走一次updateProp开启新预览。不可编辑节点会中断预览。update与commit内部都会assertNodeEditable失败时自动cancel()后重新抛出异常调用方无需额外处理。参数签名与实现的分离。commitProp/cancelProp的参数在实现中未使用源码中以_前缀标注但控件层回调约定仍按(nodes, key, value, previous)传递保持了对上层控件的统一接口。相关 APINumberField数字输入控件原语usePropScrub的典型搭配对象参见 advanced 文档索引 中的 NumberField 条目useNodeProps提供当前选中节点集合的 Composable通常与usePropScrub组合使用node-preview/use.ts 与 node-preview.ts预览/提交/取消管线的完整实现packages/vue/src/index.tsopen-pencil/vue的公开导出入口usePropScrub等 Composable 从该包对外提供。赞分享前端桌面应用AI 应用MCP 服务【免费下载链接】open-pencilAI-native design editor. Open-source Figma alternative.项目地址https://gitcode.com/gh_mirrors/op/open-pencil点击查看免费下载相关推荐aider 的 Git 集成机制详解自动提交、/undo 撤销与提交归属配置Qwen3-Coder 评估工具链实战aider 的 Git 集成机制详解自动提交、/undo 撤销与提交归属配置Qwen3 Coder 评估工具链实战 本指南以 aider 的官方 Git大模型代码模型微调模型评测强化学习open-pencil SDK 深度解析useNodeProps 属性面板 Composable 的混合值检测、多选更新与撤销感知提交open pencil SDK 深度解析useNodeProps 属性面板 Composable 的混合值检测、多选更新与撤销感知提交 useNodeProp前端桌面应用AI 应用MCP 服务深入 OpenPencil Vue SDK 的 Locale API低层国际化状态与语言切换机制深入 OpenPencil Vue SDK 的 Locale API低层国际化状态与语言切换机制 本文基于 OpenPencil 官方文档 locale ap前端桌面应用AI 应用MCP 服务上一篇radiobox.css完全指南从安装到自定义解锁纯CSS动画新技能下一篇Money Ruby库类型安全使用RBS签名提升代码质量的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

5G SA现网优化实战:信令三层定位与参数联动调优
5G SA现网优化实战:信令三层定位与参数联动调优

简介:本资源是面向5G网络优化工程师及通信运维人员的实战型技术指导手册,聚焦SA架构下用户低接入率问题的系统性分析与优化。内容覆盖无线接通率三大核心指标(RRC建立成功率、QoS Flow建立成功率、NG信令连接成功率)的定义、根因定… · 2026/9/25 4:18:48

在 Cloudflare Workers 上运行 Next.js Pages Router:vinext 最小示例从零跑通
在 Cloudflare Workers 上运行 Next.js Pages Router:vinext 最小示例从零跑通

后端Web框架SSR 【免费下载链接】vinext Vite plugin that reimplements the Next.js API surface — deploy anywhere 项目地址: https://gitcode.com/gh_mirrors/vi/vinext 点击查看 免费下载 本指南以仓库中的 pages-router-cloudflare 示例为完整蓝本&#xff… · 2026/9/25 4:18:48

WebPlotDigitizer曲线数据提取全攻略:从坐标轴标定到自动提取的完整流程
WebPlotDigitizer曲线数据提取全攻略:从坐标轴标定到自动提取的完整流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:18:42

华为MDE岗位:模型驱动工程在通信研发中的落地实践
华为MDE岗位:模型驱动工程在通信研发中的落地实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:30

STM32调试失败的5个底层真相:BOOT0、NRST与SWD链路深度解析
STM32调试失败的5个底层真相:BOOT0、NRST与SWD链路深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:30

ESP32-S3四路麦克风阵列回声消除实战:从硬件配置到算法调优
ESP32-S3四路麦克风阵列回声消除实战:从硬件配置到算法调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:29

SEED-labs网络安全实验环境搭建:Ubuntu 20.04 + VirtualBox + Docker
SEED-labs网络安全实验环境搭建:Ubuntu 20.04 + VirtualBox + Docker

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:29

Android阅读器翻页交互优化:ViewPager2与Glide实战解析
Android阅读器翻页交互优化:ViewPager2与Glide实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:29

Claude Code嵌入式开发实战:权限控制、寄存器配置与Skills技能管理
Claude Code嵌入式开发实战:权限控制、寄存器配置与Skills技能管理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:57:22

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码