fix(ios): can not open keyboard in editor (#11401)

Close [BS-2917](https://linear.app/affine-design/issue/BS-2917/【移动端】ios-唤起键盘的edge-case)

This PR fixes an issue where the keyboard cannot be re-triggered on iOS devices after the keyboard toolbar is hidden or executing some actions in keyboard toolbar.

Key changes:
- Preserve and restore the initial input mode when keyboard toolbar shows/hides
- Improve virtual keyboard service interface to better handle keyboard state
- Add proper cleanup of input mode state in component lifecycle
This commit is contained in:
L-Sun
2025-04-03 01:51:56 +00:00
parent 2026f12daa
commit 5109ceccec
6 changed files with 71 additions and 42 deletions

View File

@@ -1,9 +1,13 @@
import { getDocTitleByEditorHost } from '@blocksuite/affine-fragment-doc-title';
import type { RootBlockModel } from '@blocksuite/affine-model';
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
import {
FeatureFlagService,
VirtualKeyboardProvider,
type VirtualKeyboardProviderWithAction,
} from '@blocksuite/affine-shared/services';
import { IS_MOBILE } from '@blocksuite/global/env';
import { WidgetComponent } from '@blocksuite/std';
import { signal } from '@preact/signals-core';
import { effect, signal } from '@preact/signals-core';
import { html, nothing } from 'lit';
import type { PageRootBlockComponent } from '../../page/page-root-block.js';
@@ -22,6 +26,7 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
if (blur) {
if (document.activeElement === this._docTitle?.inlineEditorContainer) {
this._docTitle?.inlineEditor?.setInlineRange(null);
this._docTitle?.inlineEditor?.eventSource?.blur();
} else if (document.activeElement === this.block?.rootComponent) {
this.std.selection.clear();
}
@@ -31,6 +36,27 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
private readonly _show$ = signal(false);
private _initialInputMode: string = '';
get keyboard(): VirtualKeyboardProviderWithAction {
return {
// fallback keyboard actions
show: () => {
const rootComponent = this.block?.rootComponent;
if (rootComponent && rootComponent === document.activeElement) {
rootComponent.inputMode = this._initialInputMode;
}
},
hide: () => {
const rootComponent = this.block?.rootComponent;
if (rootComponent && rootComponent === document.activeElement) {
rootComponent.inputMode = 'none';
}
},
...this.std.get(VirtualKeyboardProvider),
};
}
private get _docTitle() {
return getDocTitleByEditorHost(this.std.host);
}
@@ -48,12 +74,25 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
const rootComponent = this.block?.rootComponent;
if (rootComponent) {
this._initialInputMode = rootComponent.inputMode;
this.disposables.add(() => {
rootComponent.inputMode = this._initialInputMode;
});
this.disposables.addFromEvent(rootComponent, 'focus', () => {
this._show$.value = true;
});
this.disposables.addFromEvent(rootComponent, 'blur', () => {
this._show$.value = false;
});
this.disposables.add(
effect(() => {
// recover input mode when keyboard toolbar is hidden
if (!this._show$.value) {
rootComponent.inputMode = this._initialInputMode;
}
})
);
}
if (this._docTitle) {
@@ -84,10 +123,11 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
return html`<blocksuite-portal
.shadowDom=${false}
.template=${html`<affine-keyboard-toolbar
.keyboard=${this.keyboard}
.config=${this.config}
.rootComponent=${this.block.rootComponent}
.close=${this._close}
></affine-keyboard-toolbar> `}
></affine-keyboard-toolbar>`}
></blocksuite-portal>`;
}
}

View File

@@ -1,5 +1,5 @@
import { getSelectedModelsCommand } from '@blocksuite/affine-shared/commands';
import { VirtualKeyboardProvider } from '@blocksuite/affine-shared/services';
import { type VirtualKeyboardProviderWithAction } from '@blocksuite/affine-shared/services';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import { ArrowLeftBigIcon, KeyboardIcon } from '@blocksuite/icons/lit';
import {
@@ -50,10 +50,6 @@ export class AffineKeyboardToolbar extends SignalWatcher(
return this.rootComponent.std;
}
get keyboard() {
return this._context.std.get(VirtualKeyboardProvider);
}
get panelOpened() {
return this._currentPanelIndex$.value !== -1;
}
@@ -324,6 +320,9 @@ export class AffineKeyboardToolbar extends SignalWatcher(
`;
}
@property({ attribute: false })
accessor keyboard!: VirtualKeyboardProviderWithAction;
@property({ attribute: false })
accessor close: (blur: boolean) => void = () => {};

View File

@@ -230,9 +230,6 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
}
override firstUpdated() {
if (!this.keyboard.visible$.value) {
this.keyboard.show();
}
this._scrollInputToTop();
}

View File

@@ -2,11 +2,16 @@ import { createIdentifier } from '@blocksuite/global/di';
import type { ReadonlySignal } from '@preact/signals-core';
export interface VirtualKeyboardProvider {
show: () => void;
hide: () => void;
readonly visible$: ReadonlySignal<boolean>;
readonly height$: ReadonlySignal<number>;
}
export const VirtualKeyboardProvider =
createIdentifier<VirtualKeyboardProvider>('VirtualKeyboardProvider');
export interface VirtualKeyboardProviderWithAction
extends VirtualKeyboardProvider {
show: () => void;
hide: () => void;
}
export const VirtualKeyboardProvider = createIdentifier<
VirtualKeyboardProvider | VirtualKeyboardProviderWithAction
>('VirtualKeyboardProvider');