fix(editor): virtual keyboard closes unexpectedly when backspace is pressed after a block (#13386)

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)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## 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.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
L-Sun
2025-08-01 12:27:45 +08:00
committed by GitHub
parent 3c9fe48c6c
commit 916887e9dc
3 changed files with 32 additions and 27 deletions
@@ -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, {
@@ -221,7 +221,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
}
private _renderItems() {
if (document.activeElement !== this.rootComponent)
if (!this.std.event.active$.value)
return html`<div class="item-container"></div>`;
const goPrevToolbarAction = when(
@@ -61,29 +61,26 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel>
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) {