From 916887e9dc829bb363599f93a26bfbdf0d35630b Mon Sep 17 00:00:00 2001 From: L-Sun Date: Fri, 1 Aug 2025 12:27:45 +0800 Subject: [PATCH] fix(editor): virtual keyboard closes unexpectedly when backspace is pressed after a block (#13386) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Close [AF-2764](https://linear.app/affine-design/issue/AF-2764/移动端没法删除图片和其他非文本block) #### PR Dependency Tree * **PR #13386** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## Summary by CodeRabbit * **New Features** * Improved virtual keyboard handling on mobile devices to prevent unexpected keyboard closure during certain editing actions. * Added new signals for keyboard height and safe area, enhancing UI responsiveness and adaptability to keyboard state. * **Refactor** * Streamlined keyboard toolbar logic for more reliable panel height calculation and smoother panel open/close transitions. * Simplified and modernized the approach to toolbar visibility and input mode restoration. * **Style** * Updated keyboard toolbar and panel styling for better positioning and layout consistency across devices. * **Bug Fixes** * Fixed an issue where the virtual keyboard could be incorrectly reported as visible when a physical keyboard is connected. --- .../paragraph/src/utils/merge-with-prev.ts | 16 ++++++-- .../keyboard-toolbar/src/keyboard-toolbar.ts | 2 +- .../widgets/keyboard-toolbar/src/widget.ts | 41 +++++++++---------- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/blocksuite/affine/blocks/paragraph/src/utils/merge-with-prev.ts b/blocksuite/affine/blocks/paragraph/src/utils/merge-with-prev.ts index 26af657b26..b96de60532 100644 --- a/blocksuite/affine/blocks/paragraph/src/utils/merge-with-prev.ts +++ b/blocksuite/affine/blocks/paragraph/src/utils/merge-with-prev.ts @@ -24,6 +24,7 @@ import { getPrevContentBlock, matchModels, } from '@blocksuite/affine-shared/utils'; +import { IS_MOBILE } from '@blocksuite/global/env'; import { BlockSelection, type EditorHost } from '@blocksuite/std'; import type { BlockModel, Text } from '@blocksuite/store'; @@ -91,10 +92,17 @@ export function mergeWithPrev(editorHost: EditorHost, model: BlockModel) { ...EMBED_BLOCK_MODEL_LIST, ]) ) { - const selection = editorHost.selection.create(BlockSelection, { - blockId: prevBlock.id, - }); - editorHost.selection.setGroup('note', [selection]); + // due to create a block selection will clear text selection, which lead + // the virtual keyboard to be auto closed on mobile. This behavior breaks + // the user experience. + if (!IS_MOBILE) { + const selection = editorHost.selection.create(BlockSelection, { + blockId: prevBlock.id, + }); + editorHost.selection.setGroup('note', [selection]); + } else { + doc.deleteBlock(prevBlock); + } if (model.text?.length === 0) { doc.deleteBlock(model, { diff --git a/blocksuite/affine/widgets/keyboard-toolbar/src/keyboard-toolbar.ts b/blocksuite/affine/widgets/keyboard-toolbar/src/keyboard-toolbar.ts index d3e4db782d..19a7e28554 100644 --- a/blocksuite/affine/widgets/keyboard-toolbar/src/keyboard-toolbar.ts +++ b/blocksuite/affine/widgets/keyboard-toolbar/src/keyboard-toolbar.ts @@ -221,7 +221,7 @@ export class AffineKeyboardToolbar extends SignalWatcher( } private _renderItems() { - if (document.activeElement !== this.rootComponent) + if (!this.std.event.active$.value) return html`
`; const goPrevToolbarAction = when( diff --git a/blocksuite/affine/widgets/keyboard-toolbar/src/widget.ts b/blocksuite/affine/widgets/keyboard-toolbar/src/widget.ts index 7dda4385c9..41485b1d57 100644 --- a/blocksuite/affine/widgets/keyboard-toolbar/src/widget.ts +++ b/blocksuite/affine/widgets/keyboard-toolbar/src/widget.ts @@ -61,29 +61,26 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent override connectedCallback(): void { super.connectedCallback(); - const rootComponent = this.block?.rootComponent; - if (rootComponent) { - this.disposables.addFromEvent(rootComponent, 'focus', () => { - this._show$.value = true; - }); - this.disposables.addFromEvent(rootComponent, 'blur', () => { - this._show$.value = false; - }); + this.disposables.add( + effect(() => { + this._show$.value = this.std.event.active$.value; + }) + ); - if (this.keyboard.fallback) { - this._initialInputMode = rootComponent.inputMode; - this.disposables.add(() => { - rootComponent.inputMode = this._initialInputMode; - }); - this.disposables.add( - effect(() => { - // recover input mode when keyboard toolbar is hidden - if (!this._show$.value) { - rootComponent.inputMode = this._initialInputMode; - } - }) - ); - } + const rootComponent = this.block?.rootComponent; + if (rootComponent && this.keyboard.fallback) { + this._initialInputMode = rootComponent.inputMode; + this.disposables.add(() => { + rootComponent.inputMode = this._initialInputMode; + }); + this.disposables.add( + effect(() => { + // recover input mode when keyboard toolbar is hidden + if (!this._show$.value) { + rootComponent.inputMode = this._initialInputMode; + } + }) + ); } if (this._docTitle) {