尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Tiptap BubbleMenu 扩展 v3 演进全解析:从 tippy.js 迁移到 Floating UI 的 API 与内部实现

Tiptap BubbleMenu 扩展 v3 演进全解析:从 tippy.js 迁移到 Floating UI 的 API 与内部实现 Tiptap BubbleMenu 扩展 v3 演进全解析从 tippy.js 迁移到 Floating UI 的 API 与内部实现【免费下载链接】tiptapThe headless rich text editor framework for web artisans.项目地址: https://gitcode.com/GitHub_Trending/ti/tiptap导读tiptap/extension-bubble-menu是 Tiptap 编辑器中随选区浮起的经典工具条扩展它的版本变更史记录了 Tiptap v3 在浮层技术栈、定位管线、显示控制与多实例隔离上的完整演进。本文以 packages/extension-bubble-menu/CHANGELOG.md 为主线结合同目录源码与测试梳理该扩展从 v2 到 v3.30.3 的关键变化帮助读者理解options对象、事务元数据transaction meta控制协议以及菜单定位的底层原理。一、CHANGELOG 说明了什么一个包的演进骨架该 CHANGELOG 覆盖了从 2021 年2.0.0-beta.1到当前3.30.3的全部发布记录。去掉大量纯依赖同步tiptap/core、tiptap/pm的版本跟随条目后剩下的核心信息可分为五类破坏性架构迁移v3 起始3.0.0-next.0/3.0.1用 Floating UI 替换 tippy.jsAPI 面扩展appendTo、scrollTarget、shouldShow、options、生命周期回调等的引入与完善程序化控制协议通过事务元数据控制菜单显示/隐藏/更新定位多实例隔离以pluginKey作为元数据键解决实例间互相干扰定位正确性修复覆盖文本选区、节点选区、表格单元格选区、滚动与 resize、销毁等边界场景。这些类别恰好对应着扩展的三个源文件src/bubble-menu.ts扩展本体、src/bubble-menu-plugin.tsProseMirror 插件与视图类、src/index.ts出口以及tests/bubble-menu-plugin.spec.ts 中的行为测试。下文将逐条展开。二、v3 起点移除 tippy.js迁移到 Floating UICHANGELOG 在3.0.0-next.0、3.0.0-next.6与3.0.1中连续记录了同一项Major Change构建工具改用 tsup不再支持 UMD 构建同时移除了 tippy.js改用 Floating UI当时被描述为更新、更轻量、更可定制的浮层库。这是一次破坏性变更波及的包包括tiptap/extension-floating-menutiptap/extension-bubble-menutiptap/extension-mentiontiptap/suggestiontiptap/reacttiptap/vue-2tiptap/vue-3迁移要求如下原文档以代码块给出此处原样继承npm install floating-ui/dom^1.6.0同时必须从FloatingMenu/BubbleMenu组件中删除旧的tippyOptions改用新的options对象。这一点在源码中得到印证BubbleMenuView内部维护floatingUIOptions并通过computePosition完成定位package.json 的依赖中已将floating-ui/dom^1.0.0列为核心依赖。对于需要手动迁移的既有实现核心动作是将原来传给 tippy 的placement、offset等配置映射到新的options对象——其键与 Floating UI 的 middleware 一一对应详见下文第五节。三、扩展结构从配置到浮层定位的调用链结合源码可以将 BubbleMenu 的运行链路还原为三层扩展层bubble-menu.tsBubbleMenu Extension.createBubbleMenuOptions默认选项为element: null菜单 DOM 元素pluginKey: bubbleMenuupdateDelay: undefinedappendTo: undefinedshouldShow: null当且仅当传入的element存在时扩展才通过addProseMirrorPlugins()注册BubbleMenuPlugin否则不产生任何副作用。插件层BubbleMenuPlugin把element、editor与各选项包装为一个 ProseMirrorPlugin其view回调实例化BubbleMenuView字符串形式的pluginKey会被转换为new PluginKey(...)实例。视图层bubble-menu-plugin.ts 中的BubbleMenuView负责何时显示、定位到哪、如何更新这是整个扩展最核心的类。关键默认值与节流策略BubbleMenuView构造函数给出了 CHANGELOG 之外的实现细节pluginKey默认bubbleMenuupdateDelay默认250毫秒——当存在有效选区selection.from ! selection.to时菜单更新会被防抖避免高频输入或协同光标造成的性能问题resizeDelay默认60毫秒——窗口resize与滚动监听走独立防抖this.element.tabIndex 0使菜单可聚焦对应 CHANGELOG 中Addedtab-index0to menu wrappers。update()内部逻辑为若存在有效选区且updateDelay 0走handleDebouncedUpdate且只有 selection 或 doc 真正变化时才触发否则即时updateHandler。updateHandler中还会跳过输入法组合输入view.composing与无变化的事务。四、显示判定shouldShow 与默认过滤逻辑CHANGELOG 在3.0.5记录Make shouldShow optional on bubbleMenu and floatingMenu options将shouldShow变为可选。若未提供源码中的默认实现如下计算选区所有 range 的起止取最小from与最大to兼容表格CellSelection过滤空文本块!doc.textBetween(from, to).length isTextSelection并注释说明有时仅判断empty不够——双击空段落会得到节点尺寸 2对应 v2 时代空段落起点选中不显示菜单等修复过滤失焦hasEditorFocus view.hasFocus() || isChildOfMenu其中isChildOfMenu判断document.activeElement是否位于菜单元素内部——这保证用户点击菜单按钮时编辑器blur不会立刻收起菜单过滤不可编辑!this.editor.isEditable时不显示。开发者传入自定义shouldShow即可覆盖上述默认行为其回调签名包含editor/element/view/state/oldState/from/to上下文见 bubble-menu-plugin.ts 的类型定义。五、options 对象透传 Floating UI 的完整定位配置这是 tippy 迁移后最重要的 API。BubbleMenuPluginProps.options同时支持两类键1. 定位与策略strategy: absolute | fixedplacementtop、bottom、left、right及其-start/-end组合共 12 个取值2. Floating UI middleware 开关与参数flip、shift、offset、arrow、size、autoPlacement、hide、inline。每个键可传false关闭或传对应 middleware 的选项对象。源码get middlewares()会按固定顺序组装[flip, shift, offset, arrow, size, autoPlacement, hide, inline]默认值集中在floatingUIOptions字段bubble-menu-plugin.ts{ strategy: absolute, placement: top, offset: 8, flip: {}, shift: {}, arrow: false, size: false, autoPlacement: false, hide: false, inline: false, onShow / onHide / onUpdate / onDestroy: undefined, }其中inline的加入与 CHANGELOG3.0.7的修复相关——该版本修复了 inline 选项与缺少getClientRects的虚拟元素的兼容问题从源码看虚拟元素会同时实现getBoundingClientRect与getClientRects正是为支持 Floating UI 的 inline middleware。3. 生命周期回调CHANGELOG 在3.0.0-beta.11记录Added missingonShow,onUpdate,onHideandonDestroyoptions这四个回调保留至今并纳入类型show()触发onShowhide()触发onHide每次定位计算成功后触发onUpdatedestroy()触发onDestroy。六、定位虚拟元素文本、节点与表格单元格选区BubbleMenuView.virtualElement的取值优先级为若配置了getReferencedVirtualElement则直接使用其返回值——这在菜单需要相对某个特定 DOM 元素定位时非常有用3.0.0-beta期间引入的getReferencedVirtualElementAPI默认基于选区posToDOMRect(view, from, to)生成虚拟元素若是NodeSelection优先使用节点的[data-node-view-wrapper]包裹层做锚点修复节点选区下菜单位置非法3.1.0并支持无文本内容的原子节点v2 的#1446修复若是表格CellSelection跨单元格时用combineDOMRects合并首尾单元格的包围盒对应 v2#6472d2c与3.0.1中修复表格单元格选区不会正确定位气泡菜单的记录。定位本身由 Floating UI 的computePosition(virtualElement, this.element, { placement, strategy, middleware })完成成功后写入element的内联样式position、left、top并保持width: max-content。七、程序化控制事务元数据协议与多实例隔离这是 v3 后期版本的核心增强也是源码注释中明确建议的用法。1. 首次出现的 updateBubbleMenuPosition 命令3.5.03.5.0增加了updateBubbleMenuPosition命令用于在菜单自身尺寸变化等事件后程序化刷新定位。但3.6.0随即将其移除——原因记录得很直白该命令在 React、Vue 组件版 BubbleMenu 中不可用只在原生扩展中生效容易造成困惑。同一版本还要求把transactionHandler写成箭头函数保证this始终指向BubbleMenuView实例。2. 事务元数据成为统一控制通道3.20.0、3.22.23.20.0修复了BubbleMenu/FloatingMenu的事务元数据键问题——改用pluginKey作为元数据键使多实例可独立更新互不干扰。源码transactionHandler中通过tr.getMeta(this.pluginKey)读取控制指令支持四种值updatePosition立即重算位置{ type: updateOptions, options }运行时更新选项调用updateOptionshide隐藏菜单show刷新位置并显示。3.22.2正式将其作为公共 API 记录可通过transaction.setMeta(menuKey, show)与transaction.setMeta(menuKey, hide)程序化显隐气泡/浮层菜单。标准调用形如// 显示默认 pluginKey 的菜单 editor.view.dispatch(editor.state.tr.setMeta(bubbleMenu, show)) // 隐藏 editor.view.dispatch(editor.state.tr.setMeta(bubbleMenu, hide)) // 仅刷新位置例如外部改变了菜单宽度 editor.view.dispatch(editor.state.tr.setMeta(bubbleMenu, updatePosition)) // 运行时更新配置 editor.view.dispatch( editor.state.tr.setMeta(bubbleMenu, { type: updateOptions, options: { updateDelay: 500 }, }), )3. 多实例互不污染的测试证据tests/bubble-menu-plugin.spec.ts 中专门设计了 cross-contamination实例间串扰测试组覆盖字符串pluginKeybubbleMenu1vsbubbleMenu2下只有目标实例收到updateOptionsPluginKey实例customBubbleA/customBubbleB同样被正确隔离updatePosition、show、hide均只作用于自身实例两个实例updateDelay被分别改写为 999/777 互不影响未显式指定pluginKey时兼容默认bubbleMenu键向后兼容性回归测试。4. 运行时可更新 props3.18.03.18.0修复了BubbleMenu 与 FloatingMenu 初始化后 props 不更新的问题。对应实现即上文updateOptionsupdateDelay、resizeDelay、appendTo、getReferencedVirtualElement、shouldShow与整个options都可在运行时替换其中scrollTarget变化时会先移除旧监听再绑定新监听。八、滚动、隐藏与销毁边界情况的修复脉络这类修复贯穿 v2 至 v3可直接指导真实项目中的疑难排查版本变更要点对应源码机制2.0.0-beta.16修复插件全局 resize 处理器在销毁时未注销destroy()中对称removeEventListener2.1.0-rc.1 / 2.0.3修复 debounce 在协同/协作光标下失效基于selectionChanged/docChanged判断后再 debounce3.4.2新增可选scrollTarget替代默认window监听滚动并保证清理构造时监听this.scrollTarget销毁时移除3.6.2销毁编辑器时插件清理过程抛错blurHandler中先判断editor.isDestroyed再destroy()3.6.6创建时shouldShow为真但菜单位置未更新构造函数末尾getShouldShow()通过则show()updatePosition()3.17.0防止销毁时Cannot read properties of null (reading domFromPos)销毁防护3.17.1正确消费 hide middleware 数据引用元素滚出视口时隐藏菜单updatePosition()检查middlewareData.hide.referenceHidden / escaped3.22.0修复隐藏菜单在滚动/resize 期间复活定位仅对已显示菜单执行且迟到的定位回调被丢弃3.22.4修复依赖安装后 peer 依赖解析冲突package.json 同步其中3.22.0与隐藏后是否复活的行为在测试中有两条直接验证已隐藏的菜单调用updatePosition()后仍保持visibility: hidden、left/top为空should not make a hidden menu visible先show()再updatePosition()再hide()迟到的computePositionresolve 不会把已隐藏的菜单重新点亮should ignore late position updates。3.17.1对应的机制位于updatePosition()的回调里一旦 Floating UI 的hidemiddleware 报告referenceHidden或escaped就置visibility: hidden并直接返回而不是继续写入坐标。九、appendTo控制菜单挂载的父容器3.0.9引入appendTo选项v2 时代add appendTo option的延续默认值为编辑器父元素this.view.dom.parentElement。其典型动机是出于无障碍、裁剪overflow clipping或 z-index 层级问题菜单需要挂到编辑器之外的 DOM 上下文。3.6.3进一步允许appendTo传回调函数要求同步返回一个元素从而支持动态创建的目标容器。源码在show()中统一处理两者const appendToElement typeof this.appendTo function ? this.appendTo() : this.appendTo ;(appendToElement ?? this.view.dom.parentElement)?.appendChild(this.element)值得注意的是show()/hide()采用插入/移除节点 切换visibility/opacity而非 display 切换配合element.isConnected判断避免对已脱离文档的节点写入坐标。十、交互细节与版本节奏拖拽隐藏dragstartHandler会在用户拖拽选中内容时隐藏菜单对应 v2#1443hide bubble menu on drag。焦点守卫mousedownHandler以捕获阶段监听菜单内按下事件并置preventHide true配合blurHandler中对relatedTarget落在菜单父级或编辑器 DOM 内的放行形成完整的失焦不隐藏逻辑。当前版本截至 CHANGELOG 顶部包版本为3.30.3与tiptap/core、tiptap/pm完全同步发布构建入口为 ESM CJS 双格式见 package.json 的exports字段。结语透过这份 CHANGELOG 可以清晰看到 Tiptap BubbleMenu 扩展的两条主线一是技术栈收敛——从 tippy.js 到 Floating UI把定位能力开放为与 middleware 一一对应的options并补齐生命周期回调二是控制协议稳定化——以pluginKey为命名空间的事务元数据成为唯一的程序化控制通道配合updateOptions实现运行时热更新同时用完整的测试bubble-menu-plugin.spec.ts锁住多实例隔离与隐藏态不复活等关键行为。对于正在迁移到 v3 或希望深度定制浮层菜单的开发者将 CHANGELOG 条目与 bubble-menu-plugin.ts 的BubbleMenuView对照阅读是最快的上手路径。【免费下载链接】tiptapThe headless rich text editor framework for web artisans.项目地址: https://gitcode.com/GitHub_Trending/ti/tiptap创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表