fix: guard IME composition state before early returns in RangeBinding#14841
fix: guard IME composition state before early returns in RangeBinding#14841Xuan4781 wants to merge 2 commits into
Conversation
📝 WalkthroughWalkthroughThe changes address two separate concerns: (1) improving table command insertion logic to properly traverse parent list blocks when computing the target anchor, and (2) fixing IME composition state tracking by ensuring explicit state transitions occur at event boundaries rather than conditional checks. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
blocksuite/affine/blocks/table/src/commands.ts (1)
23-54:⚠️ Potential issue | 🟡 Minor
removeEmptyLinecleanup no longer targets the originally selected block.After the new while-loop reassigns
targetModelto an ancestor when the selection lives inside anaffine:list, theremoveEmptyLinebranch at Line 52–54 now inspectstargetModel.text?.lengthon that ancestor (a list block, which typically has notext) instead of the originally selected empty line. As a result:
- The intended cleanup of the empty anchor line inside a list is silently skipped (regression vs. prior behavior when the selection was a list item).
- If an ancestor ever does satisfy
text?.length === 0,deleteBlockwould be called on the wrong node.Preserve the original selection for the cleanup check while using the climbed model only as the sibling anchor.
🔧 Proposed fix
- let targetModel = + const originalTargetModel = place === 'before' ? selectedModels[0] : selectedModels[selectedModels.length - 1]; - if (!targetModel) return; + if (!originalTargetModel) return; + let targetModel: BlockModel = originalTargetModel; let parent = std.store.getParent(targetModel); while (parent && parent.flavour === 'affine:list') { targetModel = parent; parent = std.store.getParent(targetModel); } @@ - if (removeEmptyLine && targetModel.text?.length === 0) { - std.store.deleteBlock(targetModel); + if (removeEmptyLine && originalTargetModel.text?.length === 0) { + std.store.deleteBlock(originalTargetModel); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blocksuite/affine/blocks/table/src/commands.ts` around lines 23 - 54, Save the originally selected anchor before climbing the parent chain (e.g., store selectedModels[0] or the initially computed targetModel into a variable like originalTargetModel) and continue to use the climbed targetModel only for finding the sibling insertion point and calling std.store.addSiblingBlocks; then, when handling removeEmptyLine, check originalTargetModel.text?.length and call std.store.deleteBlock(originalTargetModel) so the cleanup targets the original selected block rather than the ancestor.
🧹 Nitpick comments (1)
blocksuite/framework/std/src/inline/range/range-binding.ts (1)
87-98: LGTM — flag transitions now align to event boundaries.Setting
isComposing = trueat the top of_onCompositionStart(Line 98) correctly guards against_onNativeSelectionChangedreconciling the DOM during IME composition in the single-block cursor case (to == null), which was the root cause of the hyperlink+IME freeze. Likewise, unconditionally clearing the flag at the top of_onCompositionEnd(Line 88) ensures the flag is never left stucktrueif the callback path isn't taken. The cross-block path viainline-range-provider.ts(which readsbinding?.isComposing) continues to work since the flag is still set during composition.Minor note: the redundant
this.isComposing = falseat Line 121 inside_compositionStartCallbackis now dead (already cleared at Line 88 before the callback runs at Line 92). Safe to leave, but could be removed for clarity.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blocksuite/framework/std/src/inline/range/range-binding.ts` around lines 87 - 98, Remove the now-dead assignment that clears the composition flag inside the composition callback: locate the _compositionStartCallback implementation (referenced as _compositionStartCallback) within range-binding.ts and delete the inner "this.isComposing = false" statement (the redundant clear inside the callback) because isComposing is already set to false at the top of _onCompositionEnd and clearing it inside the callback is unnecessary and confusing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@blocksuite/affine/blocks/table/src/commands.ts`:
- Around line 23-54: Save the originally selected anchor before climbing the
parent chain (e.g., store selectedModels[0] or the initially computed
targetModel into a variable like originalTargetModel) and continue to use the
climbed targetModel only for finding the sibling insertion point and calling
std.store.addSiblingBlocks; then, when handling removeEmptyLine, check
originalTargetModel.text?.length and call
std.store.deleteBlock(originalTargetModel) so the cleanup targets the original
selected block rather than the ancestor.
---
Nitpick comments:
In `@blocksuite/framework/std/src/inline/range/range-binding.ts`:
- Around line 87-98: Remove the now-dead assignment that clears the composition
flag inside the composition callback: locate the _compositionStartCallback
implementation (referenced as _compositionStartCallback) within range-binding.ts
and delete the inner "this.isComposing = false" statement (the redundant clear
inside the callback) because isComposing is already set to false at the top of
_onCompositionEnd and clearing it inside the callback is unnecessary and
confusing.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f9ca97f5-1af6-4809-9b82-150294f52b45
📒 Files selected for processing (2)
blocksuite/affine/blocks/table/src/commands.tsblocksuite/framework/std/src/inline/range/range-binding.ts
3403237 to
78a9942
Compare
Fixes #14533
Issue
After inserting a hyperlink and typing Chinese (or any IME language)
directly after it, the line would freeze and become unselectable. The
cursor would appear briefly then disappear on click. English input was
unaffected, and adding a space after the hyperlink before typing would
avoid the bug.
Fix
isComposingwas only set totrueafter theif (!to) returnearlyreturn in
_onCompositionStart, so single-block cursor positions (e.g.typing right after a hyperlink) never set the flag which left
_onNativeSelectionChangedfiring and reconciling the DOM mid-IME compositionthis.isComposing = trueto the top of_onCompositionStartbeforeany early returns
this.isComposing = falseunconditionally at the top of_onCompositionEndso the flag is always clearedScreenshot Verification
Summary by CodeRabbit