
Plate 与 Slate v2 热键运行时依赖治理用自研匹配器替换 is-hotkey 的完整 RALPLAN 拆解【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate导读本文围绕 docs/plans/2026-05-03-slate-v2-hotkey-runtime-dependency-ralplan.md 这份规划文档展开讲解 Slate v2以及依赖它的 Plate如何把编辑器键盘热键运行时从已停止维护的第三方包is-hotkey中剥离改为由 Slate 自己持有的一小套热键匹配器。读完本文你将掌握为什么一个 20 KB 的小依赖也值得被编辑器框架收回自持、mod/可选修饰符/shift?等热键语法的精确含义、event.key优先与event.code回退的布局策略、非英语键盘/Dvorak/AltGr 三类高风险场景的证明方法以及先写红测试、再换实现、最后删依赖的硬切割hard cut执行节奏。文中所有结论均可在当前仓库的源码、计划文档与解决方案笔记中交叉验证。一、裁决与边界替换不保留、不分叉、不引入编辑器协议RALPLAN 文档开门见山给出了明确的 VerdictReplaceis-hotkey. Do not keep it, do not fork it as a package, and do not import ProseMirror/Tiptap keymap code.正确形态是一个由 Slate 拥有的小型热键匹配器放在slate-dom内配合围绕现有 SlateHotkeys行为的 parity 测试并附带两项显式升级保留 Slate 今天使用的mod、平台分支、可选修饰符、方向键/删除键/回车键语法借鉴 Lexical 更强的非英语键盘行为先匹配event.key仅在event.key为非 ASCII 且快捷键为单字母时回退到event.code。这样既保持公开HotkeysAPI 稳定又移除了一个过时的热路径依赖。文档随后用 Intent / Boundary Record 明确了边界在范围内slate-dom热键匹配器内部实现slate-react中消费Hotkeys的键盘命令行为当前推荐或直接导入is-hotkey的示例与文档根目录与包级依赖清理聚焦的单元与浏览器证明。非目标不做 ProseMirror 风格的 keymap 插件系统不做 Tiptap 扩展键盘快捷键 API不对Hotkeys做公开破坏性改名不做任意用户可配置快捷键注册表不为了删一个依赖而重写 Slate 核心命令优先级。决策边界仅当测试需要时才允许私有导出若示例确实需要一个通用匹配器可以新增公开isHotkey帮助函数实现不得增加每次 keydown 的分配。这段边界记录是整份计划的宪法后续所有选项取舍、反对账本和硬切割清单都围绕它展开。二、现状证据当前热键语法与依赖关系文档给出了 Slate v2 仓库路径前缀为/Users/zbeyens/git/slate-v2中的证据。在当前 Plate 仓库中等价的实现位于 packages/core/src/lib/utils/hotkeys.ts它至今仍直接依赖is-hotkey第 4、6 行是理解替换前现状的最佳标本。当前语法小而具体分为三层映射通用层HOTKEYSpackages/core/src/lib/utils/hotkeys.tsbold: modb、italic: modi、undo: modz、splitBlock: enter、insertSoftBreak: shiftenter、deleteBackward: shift?backspace、deleteForward: shift?delete、moveWordBackward: ctrlleft、tab: tab、untab: shifttab等。注意shift?表示shift 可有可无的可选修饰符。Apple 专属层APPLE_HOTKEYS同文件moveWordBackward: optleft、deleteLineBackward: cmdshift?backspace、redo: cmdshiftz、transposeCharacter: ctrlt、deleteBackward: [ctrlbackspace, ctrlh]等。Windows 专属层WINDOWS_HOTKEYS同文件redo: [ctrly, ctrlshiftz]、deleteWordBackward: ctrlshift?backspace等。createHotkey在初始化时用isKeyHotkey为每个键位编译一次检查器事件发生时按通用 → Apple若 IS_APPLE→ Windows若非 Apple顺序复用避免每次按键都重新解析同文件。最终对外暴露语义化Hotkeys对象同文件消费方例如 packages/table/src/react/onKeyDownTable.ts 中Hotkeys.isExtendDownward(event)、Hotkeys.isExtendBackward(event)等。依赖关系是真实存在的并非仅文档提及packages/core/package.json 声明is-hotkey: ^0.2.0仓库还带有一个针对它的补丁 patches/is-hotkey0.2.0.patch键盘命令路径依赖Hotkeys完成历史撤销/重做、断行插入、删除单位与光标移动的分类判断。文档还记录了该依赖已经造成过的实际事故docs/plans/2026-03-30-local-is-hotkey-parse-failure.md 显示本地安装时node_modules/.bun/is-hotkey0.2.0/.../lib/index.js被破坏——toKeyCode中注入了一行出现在return之后的死代码、toKeyName前缺少闭合大括号——直接阻塞了apps/www的路由加载最终只能靠完整清空本地环境重建才恢复。这类非版本化安装损坏是驱动本次替换的重要实证。三、参考证据Lexical、ProseMirror、Tiptap 各自偷什么、不偷什么文档逐一考察了三家编辑器生态的做法结论是行为可以偷运行时不能搬Lexical快捷键匹配是编辑器自带的工具函数而非第三方热键包。isExactShortcutMatch先检查修饰符掩码、对event.key做大小写不敏感比较、对 ASCII 重映射布局拒绝code回退、对非英语单字母布局回退到event.code文档引用packages/lexical/src/LexicalUtils.ts:995-1023。其单元测试覆盖小写/大写、Dvorak/重映射 ASCII、非英语回退与特殊键。偷行为与证明风格不偷命令运行时——Slate 有自己的 editing kernel。ProseMirrorprosemirror-keymap规范化Mod-/Cmd-/Ctrl-/Alt-/Shift-名称、预编译绑定表、检测重复规范化键、只用event.key命名并对修改过的字符键做 keyCode 回退同时规避 Windows 上 AltGr 导致的 Ctrl-Alt 误判。偷Mod-语法纪律与预编译映射思想不导入prosemirror-keymap否则会把 ProseMirror 的Plugin/EditorView/命令协议带进 Slate 运行时工具。Tiptap核心 keymap 是产品层的 ProseMirror 组合keyboardShortcut命令会合成一个KeyboardEvent走handleKeyDownnode-range 扩展直接按平台与修饰符布尔值处理Mod。偷Mod是一等公民 DX这一理念不偷扩展级 keymap 设计。is-hotkey现状使用别名与键码、解析shift?、可返回编译检查器默认匹配路径是event.which除非用byKey。文档判断这不是 Slate v2 长期正确的浏览器契约——现代键盘正确性需要event.key优先并配合谨慎的event.code回退。NPM 元数据补充了时效性事实is-hotkey最新版本为0.2.0最后发布于 2020-11-24MIT 许可解包约 20 KBw3c-keyname为 2.2.82023-06-07 发布约 8 KB可作为 ProseMirror 侧的证据但不足以让 Slate 为当前这么小的语法新增依赖。四、决策简报五个选项的取舍矩阵文档用完整的决策简报比较了五个可行方案选项优点缺点裁决1. 保留is-hotkey无需立即改代码运行时依赖过期、默认路径以which为中心、示例直接泄漏、反复出现安装/构建摩擦拒绝2. 把is-hotkeyfork 成包移植量最小为 20 KB 工具制造包维护负担且仍从过时语义起步拒绝3. 换成prosemirror-keymap或 Tiptap keymap生态代码有维护抽象边界错误把 PM 插件/视图/命令假设带进 Slate拒绝4. 引入w3c-keyname再写 Slate 包装key 命名经过 PM 验证对当前小语法仍是新增依赖只需子集Lexical 式回退可直接实现保留为后备5. Slate 自持匹配器借鉴 is-hotkey/Lexical/PM移除过期依赖、HotkeysAPI 稳定、测试完全自持、避免编辑器模型泄漏Slate 从此自己承担浏览器边缘情况采用对应后果is-hotkey与types/is-hotkey从清单与 lockfile 消失示例改用Hotkeys帮助函数或 Slate 提供的通用匹配器非拉丁键盘行为变成显式测试覆盖而非 changelog 传闻未来快捷键 API 工作从 Slate 自持代码起步。五、公开 API 目标Hotkeys 稳定 直调式 isHotkey文档对公开 API 的结论非常具体保持稳定import { Hotkeys } from slate-dom不变继续保留isBold、isItalic、isUndo、isRedo、移动、删除、分割、组合等语义化帮助函数。新增通用帮助函数仅当示例需要import { isHotkey } from slate-dom; if (isHotkey(mods, event)) { // Save. }拒绝把createHotkeyMatcher作为公开 API因为它暴露实现机制且让常见示例更难读const isTabHotkey createHotkeyMatcher(tab)不如isHotkey(tab, event)。拒绝公开柯里化用法const isTabHotkey isHotkey(tab)这种形态对示例而言过度设计会让一个布尔判断帮助函数看起来像匹配器工厂。Slate 保留私有编译匹配器供语义化Hotkeys使用并在内部缓存直调式isHotkey(...)。对照 LexicalLexical 偏好语义化事件帮助函数与键命令isTab(event)、isBold(event)、KEY_TAB_COMMAND不提供公开柯里化匹配器 API。Slate 保留语义化Hotkeys仅对非意见化的自定义快捷键检查提供直调isHotkey(spec, event)不复制 Lexical 的命令分发面。本切片不暴露 ProseMirror/Tiptap 风格 keymap 对象。六、内部运行时目标私有匹配器的契约设计文档给出了建议的私有匹配器类型骨架type HotkeySpec string | readonly string[]; type HotkeyMatchOptions { platform?: apple | windows | other; }; type KeyboardEventLike { key: string; code?: string; altKey?: boolean; ctrlKey?: boolean; metaKey?: boolean; shiftKey?: boolean; getModifierState?: (key: string) boolean; }; function isHotkey(spec: HotkeySpec, event: KeyboardEventLike): boolean; function isHotkey( spec: HotkeySpec, options?: HotkeyMatchOptions, event?: KeyboardEventLike, ): boolean { const matcher getCachedHotkeyMatcher(spec, options); return matcher(event); }匹配器规则对应文档 Matcher rules 一节规格在模块初始化或匹配器创建时解析一次每次 keydown 零正则分配修饰符支持mod、cmd、command、meta、ctrl、control、alt、opt、option、shift可选修饰符后缀shift?更通用的可选修饰符仅在测试证明现有行为需要时引入键别名left/right/up/down、backspace、delete、enter、return、tab、escape、space以及示例用到的标点如匹配顺序先精确修饰符掩码再event.key大小写不敏感比较最后仅在预期键为单字母且event.key为非 ASCII时回退event.codeApple/Windows 平台分支保留在hotkeys.ts或通过注入平台选项供单元测试使用Windows 上 AltGr 不得被误读为 CtrlAlt 快捷键行为。七、布局语义非英语键盘、Dvorak 与 AltGr 三类高风险场景这是整份计划最关键的行为升级部分文档借 Lexical 的测试确立了明确的布局策略非英语键盘期望modz能匹配code: KeyZ、key为非 ASCII 字符的事件——用户在俄语/希伯来语/阿拉伯语等布局下按物理 Z 键浏览器报告的event.key是本地字母但用户期望的仍是撤销。Dvorak/重映射 ASCIIASCII 重映射布局下event.key必须被当作字符键而非物理键处理不得把重映射后的event.key当成物理键来匹配这与非英语回退是严格区分的两条规则。Windows AltGrgetModifierState(AltGraph)为真时必须拦截 CtrlAlt 组合的快捷键匹配因为大量国际键盘用 AltGr 输入普通字符若误触发快捷键会直接污染文本输入。文档后续在 High-Risk Deliberate Mode 的 pre-mortem 中把这三条列为头号失败风险并在回归证明矩阵中为每条都安排了显式测试行。八、回归证明矩阵与浏览器证明策略键盘快捷键虽然不属于 50k 块的性能问题但位于事件热路径。文档规划了三个层面的证明slate-dom单元测试modb在 Apple 上映射 meta、非 Apple 上映射 ctrlcmdshiftz、ctrly、ctrlshiftz的 redo 行与当前平台行为一致shift?backspace、shift?delete带与不带 shift 都要匹配方向键移动与 shift-方向键扩展行保持现状Apple 的 ctrl delete/backspace 别名匹配标点示例mod可用若导出公开通用匹配器非英语回退modz匹配code: KeyZ 非 ASCIIkey但 Dvorak 式 ASCII 重映射event.key不被当作物理键Windows AltGr 守卫CtrlAlt 字符输入不得误触发普通快捷键。slate-react集成测试getEditableCommandFromKeyDown对同样的行仍返回 history、insert-break、delete、movement 命令输入路径不在每个事件上分配或解析规格。示例/浏览器证明富文本示例的 bold/italic/underline/code 快捷键可用iframe 示例移除快捷键导入后行为不变非拉丁布局回退至少有一个合成浏览器契约或 jsdom 单元当 Playwright 无法切换布局时。依赖证明全仓rg is-hotkey|types/is-hotkey只命中历史 changelog 或刻意归档文档包构建不再打印已知的is-hotkey外部警告。九、Plate 与 slate-yjs 的迁移骨干文档单独开辟两节说明依赖方影响Plate 迁移骨干Plate 受益于原始 Slate 自持热键匹配器因为 Plate 插件可以依赖稳定的 Slate 键语义而不是自己携带或再导出is-hotkey。但不要把 Plate 专属快捷键塞进 Slate——迁移骨干是经过测试的原始工具不是 Plate 的产品 keymap。slate-yjs 迁移骨干无直接的协作数据模型影响间接影响在于操作生成前必须有确定的键盘命令检测。移除is-hotkey必须保持 undo/redo/delete/move 命令分类不变否则协作操作流会意外改变。当前仓库中与此呼应的消费方可见 packages/table/src/react/onKeyDownTable.ts 这类依赖Hotkeys语义分类的插件。十、硬切割清单与实施阶段文档给出了不可妥协的 Hard Cuts实现后不再有is-hotkey运行时依赖不再有types/is-hotkey不导入 ProseMirrorkeymap不复制 Tiptap keymap不做基于 effect 或 React state 的快捷键处理不做每次 keydown 的快捷键解析本切片不做公开 keymap 注册表。实施分五个阶段推进每阶段带 Gate 判定Phase 1红测试。在slate-dom添加冻结当前Hotkeys行为以及期望的非英语/Dvorak/AltGr 行为的单元测试。Gate测试对缺失行为的地方以当前或占位实现失败尽可能用公开Hotkeys仅在必要时用私有帮助函数。Phase 2Slate 自持匹配器。新增packages/slate-dom/src/utils/hotkey-match.ts或等价文件。Gatehotkeys.ts不再导入is-hotkey规格只编译一次平台分支可测试无逐事件解析。Phase 3示例/文档迁移。移除示例中的直接is-hotkey导入与文档推荐。Gate示例改用Hotkeys、Slate 自持通用匹配器或更清晰时用本地显式逻辑。Phase 4依赖移除。从根与包清单移除is-hotkey与types/is-hotkey更新 lockfile。Gaterg is-hotkey|types/is-hotkey不再显示活跃的依赖/导入/文档推荐。Phase 5验证。运行文档给出的验证命令组bun test packages/slate-dom bun test packages/slate-react bun --filter slate-dom typecheck bun --filter slate-react typecheck bun --filter slate-dom build bun --filter slate-react build bun check文档特别注明命令以实际仓库脚本为准source-first 的包级检查优先于盲目照搬根构建。浏览器证明聚焦/examples/richtextmark 快捷键与/examples/iframeiframe 热键行为。十一、反对账本与高风险预演维护者反对账本Objection Ledger用变更—反对意见—钢人反方—回答—裁决的表格逐一回应了五条可能的反对为什么要碰老掉牙但能用的键盘代码——该依赖自 2020 年无发布已造成本地解析/安装事故泄漏进示例且默认走which。Slate 可以用测试自持这个微小时运行时。→ keep推进fork 比重写更安全——fork 保留过时语义并制造包维护负担当前语法小到足以用 parity 测试加 Lexical 更强的布局行为重实现。→ keepProseMirror/Tiptap 已经解决 keymap 了——PM 解决的是 PM 视图的 keymap 插件问题Slate 需要的是给 editing kernel 用的匹配器不是 PMPlugin/EditorView协议。→ keep暴露直调 isHotkey 会扩大公开 API——示例需要替代直接is-hotkey用法的东西工厂/柯里化形态不值得公开isHotkey(tab, event)匹配旧库最有用的 DXLexical 也以语义化is*帮助函数而非公开匹配器工厂命名。→ keep加非英语回退可能改变行为——Lexical 对该类问题有精确测试Slate changelog 已声称热键工具支持非拉丁键盘本计划让该契约成真。→ keepHigh-Risk Deliberate Mode 记录了三个 pre-mortem 场景及应对非英语用户丢快捷键对策event.key优先、仅对非 ASCII 单字母布局回退event.codeDvorak/重映射用户得到物理键行为对策Dvorak 测试Windows AltGr 触发 CtrlAlt 快捷键污染输入对策AltGr 守卫测试。Blast radius 明确为packages/slate-dom、packages/slate-react、示例目录、资源文档、根与包清单、bun.lock。回滚答案也很务实若匹配器边缘情况 parity 失败保留公开HotkeysAPI、内部换回原实现、测试保留无需任何公开用户迁移。十二、执行账本硬切割如何在真实仓库落地文档末尾的执行账本记录了该计划在.tmp/slate-v2中的实际落地过程是对照前文各 Gate 的验收证据新增 Slate 自持热键匹配器测试packages/slate-dom/test/hotkeys.ts与内部实现packages/slate-dom/src/utils/hotkey-match.ts重接Hotkeys到自持匹配器同时保持公开Hotkeys表面不变迁移示例与文档从is-hotkey到 Slate 自持热键帮助函数从活跃清单与bun.lock移除is-hotkey与types/is-hotkey验证命令全部通过bun test ./packages/slate-dom/test/hotkeys.ts、bun --filter slate-dom typecheck、bun --filter slate-dom build、bun check、bun lint:fix等/examples/richtext与/examples/iframe浏览器证明通过活跃依赖扫描只剩packages/slate-react/CHANGELOG.md的历史记录。关键规则被沉淀为解决方案笔记 docs/solutions/developer-experience/2026-05-03-slate-hotkey-dependency-hard-cuts-need-owned-matchers-and-layout-contracts.md其中给出了可复用的依赖清扫命令rg -n is-hotkey|types/is-hotkey|isHotkey|isKeyHotkey \ package.json packages site docs bun.lock \ --glob !site/out/** \ --glob !**/node_modules/** \ --glob !**/dist/**后续几条执行记录还展示了回填上游测试的方法论克隆依赖后阅读../is-hotkey/test/index.js把有价值的公开行为行cmd/space/别名、问号与非 ASCII 键匹配、多规格、精确修饰符拒绝、可选修饰符、平台相关mod、仅修饰符 keydown、非法修饰符语法拒绝回填进自持测试公开 API 最终定型为仅直调的isHotkey支持isHotkey(mods, event)与isHotkey(mods, { platform: apple }, event)拒绝公开柯里化形态接受结构化KeyboardEventLikeReact 示例直接传 React 键盘事件而非event.nativeEvent并继续拒绝which/keyCode 模式、byKey、解析器导出、isCodeHotkey与isKeyHotkey。十三、对当前 Plate 仓库的启示最后回到本仓库的现实截至本文写作packages/core/src/lib/utils/hotkeys.ts 中的Hotkeys仍然通过isKeyHotkey依赖is-hotkey^0.2.0见 packages/core/package.json并保留 patches/is-hotkey0.2.0.patch 补丁。也就是说这份 RALPLAN 面向的替换后状态是在 slate-v2 仓库侧完成的对应文档执行账本中的.tmp/slate-v2而 Plate 核心仍在替换前形态。若读者想复现这套治理路径可参照以下顺序先按文档 Phase 1 为Hotkeys的语义行为补齐冻结测试再实现自持匹配器并切换createHotkey的底层随后清理示例与文档中的直接导入最后从 packages/core/package.json 移除依赖并跑通 packages/table/src/react/onKeyDownTable.ts 等消费方的回归测试。整个过程的决策骨架、证明矩阵与硬切割清单都可以直接沿用这份文档。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考