Files
AFFiNE-Mirror/blocksuite/affine/widgets/keyboard-toolbar/src/widget.ts
T
L-Sun 916887e9dc 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 -->
2025-08-01 04:27:45 +00:00

133 lines
3.8 KiB
TypeScript

import { getDocTitleByEditorHost } from '@blocksuite/affine-fragment-doc-title';
import type { RootBlockModel } from '@blocksuite/affine-model';
import {
FeatureFlagService,
isVirtualKeyboardProviderWithAction,
VirtualKeyboardProvider,
type VirtualKeyboardProviderWithAction,
} from '@blocksuite/affine-shared/services';
import { IS_MOBILE } from '@blocksuite/global/env';
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/std';
import { effect, signal } from '@preact/signals-core';
import { html, nothing } from 'lit';
import { literal, unsafeStatic } from 'lit/static-html.js';
import {
defaultKeyboardToolbarConfig,
KeyboardToolbarConfigExtension,
} from './config.js';
export const AFFINE_KEYBOARD_TOOLBAR_WIDGET = 'affine-keyboard-toolbar-widget';
export class AffineKeyboardToolbarWidget extends WidgetComponent<RootBlockModel> {
private readonly _show$ = signal(false);
private _initialInputMode: string = '';
get keyboard(): VirtualKeyboardProviderWithAction & { fallback?: boolean } {
const provider = this.std.get(VirtualKeyboardProvider);
if (isVirtualKeyboardProviderWithAction(provider)) return provider;
return {
// fallback keyboard actions
fallback: true,
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';
}
},
...provider,
};
}
private get _docTitle() {
return getDocTitleByEditorHost(this.std.host);
}
get config() {
return {
...defaultKeyboardToolbarConfig,
...this.std.getOptional(KeyboardToolbarConfigExtension.identifier),
};
}
override connectedCallback(): void {
super.connectedCallback();
this.disposables.add(
effect(() => {
this._show$.value = this.std.event.active$.value;
})
);
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) {
const { inlineEditorContainer } = this._docTitle;
this.disposables.addFromEvent(inlineEditorContainer, 'focus', () => {
this._show$.value = true;
});
this.disposables.addFromEvent(inlineEditorContainer, 'blur', () => {
this._show$.value = false;
});
}
}
override render() {
if (
this.store.readonly ||
!IS_MOBILE ||
!this.store
.get(FeatureFlagService)
.getFlag('enable_mobile_keyboard_toolbar')
)
return nothing;
if (!this._show$.value) return nothing;
if (!this.block?.rootComponent) return nothing;
return html`<blocksuite-portal
.shadowDom=${false}
.template=${html`<affine-keyboard-toolbar
.keyboard=${this.keyboard}
.config=${this.config}
.rootComponent=${this.block.rootComponent}
></affine-keyboard-toolbar>`}
></blocksuite-portal>`;
}
}
export const keyboardToolbarWidget = WidgetViewExtension(
'affine:page',
AFFINE_KEYBOARD_TOOLBAR_WIDGET,
literal`${unsafeStatic(AFFINE_KEYBOARD_TOOLBAR_WIDGET)}`
);
declare global {
interface HTMLElementTagNameMap {
[AFFINE_KEYBOARD_TOOLBAR_WIDGET]: AffineKeyboardToolbarWidget;
}
}