十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

plate 表格 Shift+Arrow 急切选择:在 keydown 阶段接管单格跨单元格扩展,消除原生选区闪烁

plate 表格 Shift+Arrow 急切选择:在 keydown 阶段接管单格跨单元格扩展,消除原生选区闪烁 plate 表格 ShiftArrow 急切选择在 keydown 阶段接管单格跨单元格扩展消除原生选区闪烁【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate导读本文基于 plate 表格插件的一次键盘交互专项修复深入讲解当用户按住Shift并按方向键让选区从一个表格单元格跨入相邻单元格时如何避免浏览器先绘制一段原生文本选区、再被表格逻辑修补成单元格选区而产生的肉眼可见闪烁。读完本文你将理解 plate 表格插件中ShiftArrow的完整选择移动链路、keydown阶段与apply阶段所有权ownership的划分原理以及如何用共享视觉行边界 helper 让普通方向键与Shift方向键使用同一套边界判定规则。问题背景多格与单格的ShiftArrow走了两条不同的路径在修复之前plate 表格插件的ShiftArrow跨单元格扩展实际上存在两条执行路径二者行为不一致多格ShiftArrow即已经选中多个单元格后再扩展已经由 onKeyDownTable.ts 在keydown阶段同步接管浏览器原生选择不会先发生单格跨格扩展光标只在一个单元格内ShiftArrow首次把选区扩展到相邻单元格却仍然依赖set_selection变更 setTimeout异步等待浏览器会先绘制一次原生文本选区表格逻辑在下一个 tick 才把选区修复成单元格选区。以ShiftDown和ShiftRight最为明显用户看到的是先闪出一段文本高亮随后才变成单元格选择框。最终选区是正确的但中间态肉眼可见属于典型的选择时序timing seam缺陷。这一问题在 solution 文档 中被归类为async_timing根因、logic_error问题类型。修复目标本次计划的核心目标非常明确移除ShiftArrow从一个表格单元格扩展到另一个单元格时瞬时原生文本选区native text-range flash的出现。要做到这一点不能继续在apply阶段事后修补而必须在keydown阶段、在原生选择生效之前就把单格跨边界的移动路径也纳入表格插件自己的选择移动逻辑。实施计划与完成状态原计划将工作拆分为以下几个可验证的步骤且均已标记完成为急切eager的单格ShiftArrow扩展添加keydown级别的回归测试在原生选择生效前将单格ShiftArrow路由到表格自有的选择移动逻辑即onKeyDownTable移除withApplyTable中的 apply 时修复行为并删除overrideSelectionFromCell及其专属测试更新覆盖率文档与相关文档明确该行为仅由keydown阶段拥有运行聚焦测试、包构建、类型检查与 lint。核心实现在 onKeyDownTable 中急切拦截单格跨边界移动1. 单格边界判定shouldMoveSingleCellSelection修复的核心代码位于 onKeyDownTable.ts。它先通过Hotkeys.isExtendDownward / isExtendBackward / isExtendForward / isExtendUpward识别四个方向的Shift方向键组合键然后对每个命中的键执行两段式处理const handled moveSelectionFromCell(editor, { edge: (KEY_SHIFT_EDGES as any)[key], reverse: key shiftup, }) || (shouldMoveSingleCellSelection(editor, key as keyof typeof KEY_SHIFT_EDGES) moveSelectionFromCell(editor, { at: editor.selection!, edge: (KEY_SHIFT_EDGES as any)[key], fromOneCell: true, reverse: key shiftup, }));第一段moveSelectionFromCell处理多格扩展既有路径保持不变第二段则是新增的单格急切路径先由shouldMoveSingleCellSelection判断焦点边缘是否即将离开当前单元格若判定成立立即以fromOneCell: true调用moveSelectionFromCell完成跨格扩展。只要handled为真就同步执行event.preventDefault()与event.stopPropagation()把原生行为完全挡在门外。shouldMoveSingleCellSelection的四个方向判定逻辑如下ShiftLeft只有当焦点已位于单元格文本起点editor.api.isStart(point, cellPath)时才拦截ShiftRight只有当焦点已位于单元格文本终点editor.api.isEnd(point, cellPath)时才拦截ShiftUp/ShiftDown先检查单元格内是否存在相邻块hasAdjacentBlockInCell若光标所在块在垂直方向上还有相邻块则放行原生行为让选区继续在当前单元格内扩展否则复用shouldMoveSelectionFromCell的视觉行边界判定。水平方向左右只做行首/行尾判定是因为跨格移动的语义边界非常明确垂直方向上下则必须考虑单元格内可能存在的多块结构例如单元格内多个段落因此需要更复杂的视觉行判定。2. 共享视觉行边界 helpershouldMoveSelectionFromCell.ts原计划特别强调提取视觉行边界检查为共享 helper使普通方向键与 Shift 方向键使用同一条垂直边缘规则。这一诉求在 shouldMoveSelectionFromCell.ts 中得到落实该文件集中了三个导出getTableMoveSelectionContext以当前焦点默认为editor.selection?.anchor为入参校验光标确实位于表格单元格内并返回{ blockPath, cellPath, point }上下文hasAdjacentBlockInCell沿blockPath向前/向后查找相邻块并用PathApi.isAncestor(cellPath, adjacentBlock[1])判断该相邻块是否仍属于当前单元格shouldMoveSelectionFromCell视觉行边界判定。它通过editor.api.toDOMRange同时取得光标矩形caret rect与当前块矩形block rect然后比较const VISUAL_LINE_TOLERANCE 1; return reverse ? caretRect.top boundary VISUAL_LINE_TOLERANCE : caretRect.bottom boundary - VISUAL_LINE_TOLERANCE;其中boundary是块矩形顶部的最小值反向/向上或块矩形底部的最大值正向/向下。VISUAL_LINE_TOLERANCE 1是 1 像素的容差用于容忍浮点布局误差。这段逻辑与普通方向键移动moveLine共用同一规则从根源上避免两套边界判定各自漂移drift。3. moveSelectionFromCell 的 fromOneCell 模式跨格移动的最终执行者是 moveSelectionFromCell.ts。该函数有两种形态edge 展开形态当传入edge: bottom | left | right | top时通过getTableGridAbove获取网格内的单元格条目并把锚点/焦点路径向对应方向推进一格后调用editor.tf.select。fromOneCell: true时minCell 0即允许仅选中一个单元格也能向外扩展默认minCell 1要求至少两个单元格cell 移动形态未传edge时以单元格为单位整体移动选区并在表格首尾处通过editor.tf.withoutNormalizing包裹的select move处理边界回绕。方向与KEY_SHIFT_EDGES的映射关系定义在 constants.tsexport const KEY_SHIFT_EDGES { shiftdown: bottom, shiftleft: left, shiftright: right, shiftup: top, };4. 移除 overrideSelectionFromCell 与 apply 时回退修复的另一半是减法删除 apply 时修复路径。原先 withApplyTable.ts 会在apply阶段拦截set_selection操作当新选区横跨表格边界时改写op.newProperties.focus把浏览器已经移动过的原生选区再修补回来——这正是闪烁的来源。现在overrideSelectionFromCell及其专属测试被整体删除withApplyTable仅保留与单元格索引清理相关的职责如remove_node/move_node时调用computeCellIndices重建索引选择移动的所有权完全收归keydown阶段。测试覆盖keydown 级别的回归证据新增与更新的测试集中在 onKeyDownTable.spec.tsx全部以jsxt声明式编辑器结构构造输入与期望选区测试用例输入场景期望结果eagerly expands ShiftDown from one cell into the next cell光标位于第一行第二格末尾preventDefault与stopPropagation各调用一次选区锚点/焦点分别落在两行的单元格起点keeps ShiftDown native while focus can still extend inside the current cell光标位于单元格内首段下方还有段落不调用preventDefault选区保持不变原生扩展继续eagerly expands ShiftRight from one cell into the next cell光标位于第一格文本起点同步跨格焦点落在相邻格起点eagerly expands ShiftUp from one cell into the previous cell光标位于第二行单元格起点反向跨格焦点落在上一格起点eagerly expands ShiftLeft from one cell into the previous cell光标位于第二格文本起点反向跨格焦点落在前一格起点extends an existing multi-cell selection with ShiftRight已选中两格再向右扩展多格路径继续生效焦点推进到第三格其中第二个用例尤其关键它证明修复没有矫枉过正——当光标在单元格内还有扩展空间多块单元格的块间移动时行为保持原生只有真正即将跨出单元格边界时才急切接管。此外moveSelectionFromCell.spec.tsx 中补充了fromOneCell: true的单格向上/向右扩展用例以及未传fromOneCell时单格不可扩展返回undefined的守卫用例。验证命令计划文档列出的完整验证流程如下聚焦于packages/table包bun test packages/table/src/react/onKeyDownTable.spec.tsx bun test packages/table/src/react/onKeyDownTable.spec.tsx packages/table/src/lib/withApplyTable.spec.ts packages/table/src/lib/withTable.spec.tsx packages/table/src/lib/transforms/moveSelectionFromCell.spec.tsx pnpm install pnpm turbo build --filter./packages/table pnpm turbo typecheck --filter./packages/table pnpm lint:fix bun test packages/table/src/react/onKeyDownTable.spec.tsx bun test packages/table/src/lib/withTable.spec.tsx packages/table/src/lib/transforms/shouldMoveSelectionFromCell.spec.ts packages/table/src/lib/transforms/moveSelectionFromCell.spec.tsx packages/table/src/lib/withApplyTable.spec.ts可以看出验证分两条线第一条是修复落地后的聚焦回归onKeyDownTable 相关模块第二条是在后续共享 helper 重构完成后把shouldMoveSelectionFromCell.spec.ts纳入测试集确认提取共享 helper这一重构没有破坏任何行为。经验与预防原则从这次修复中可以提炼出三条可复用的工程原则solution 文档 Prevention 部分 亦有记录凡是键盘交互只要不该出现中间原生选区状态就不要在apply阶段事后修补而应在keydown这一接缝seam处抢先拥有own该交互避免维护一条可能随时间漂移的二次修复路径当普通方向键与 Shift 方向键共享同一视觉边界规则时把边界检查收敛到一个 helper 中如shouldMoveSelectionFromCell让两条接缝共用同一实现从结构上杜绝判定逻辑分叉同步preventDefault是急切接管的落点判定成立后立即阻止原生事件并完成editor.tf.select原生选区根本没有机会绘制闪烁自然消失。小结本次修复将表格ShiftArrow的选择移动所有权完整收归keydown阶段多格扩展保持原有路径单格跨边界扩展通过shouldMoveSingleCellSelection的边界判定 moveSelectionFromCell({ fromOneCell: true })在原生选择生效前同步完成同时删除 apply 时修复路径overrideSelectionFromCell并让普通方向键与 Shift 方向键共享同一视觉行边界 helper。最终以 keydown 级回归测试、包级构建、类型检查与 lint 全链路验证收尾为同类选择时序类缺陷提供了可复用的解决范式。相关参考计划文档 | 问题与修复记录 | 核心实现 onKeyDownTable.ts | 边界判定 shouldMoveSelectionFromCell.ts【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表