fix(mobile): mobile keyboard padding (#15365)

#### PR Dependency Tree


* **PR #15365** 👈

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

- **Bug Fixes**
- Improved mobile keyboard and toolbar layout handling when the keyboard
overlays or resizes app content.
- Prevented incorrect extra spacing when Android applies keyboard insets
directly.
- Updated toolbar sizing and visibility states for smoother transitions.

- **Style**
- Adjusted mobile bottom spacing to account for keyboard height, safe
areas, and toolbar height.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-07-28 13:02:38 +08:00
committed by GitHub
parent e7ec8a1032
commit b6fc0a2192
6 changed files with 42 additions and 31 deletions
@@ -41,8 +41,6 @@ export class AffineKeyboardToolbar extends SignalWatcher(
) { ) {
static override styles = keyboardToolbarStyles; static override styles = keyboardToolbarStyles;
private readonly _expanded$ = signal(false);
get std() { get std() {
return this.rootComponent.std; return this.rootComponent.std;
} }
@@ -52,13 +50,17 @@ export class AffineKeyboardToolbar extends SignalWatcher(
} }
private get panelHeight() { private get panelHeight() {
return this._expanded$.value if (this.keyboard.visible$.value) {
? `${ return `${this.keyboard.height$.value}px`;
this.keyboard.staticHeight$.value !== 0 }
? this.keyboard.staticHeight$.value if (this.panelOpened) {
: 330 return `${
}px` this.keyboard.staticHeight$.value !== 0
: this.keyboard.appTabSafeArea$.value; ? this.keyboard.staticHeight$.value
: 330
}px`;
}
return this.keyboard.appTabSafeArea$.value;
} }
/** /**
@@ -266,20 +268,13 @@ export class AffineKeyboardToolbar extends SignalWatcher(
super.connectedCallback(); super.connectedCallback();
this.setAttribute(RANGE_SYNC_EXCLUDE_ATTR, 'true'); this.setAttribute(RANGE_SYNC_EXCLUDE_ATTR, 'true');
// There are two cases that `_expanded$` will be true:
// 1. when virtual keyboard is opened, the panel need to be expanded and overlapped by the keyboard,
// so that the toolbar will be on the top of the keyboard.
// 2. the panel is opened, whether the keyboard is closed or not exists (e.g. a physical keyboard connected)
//
// There is one case that `_expanded$` will be false:
// 1. the panel is closed, and the keyboard is closed, the toolbar will be rendered at the bottom of the viewport
this._disposables.add( this._disposables.add(
effect(() => { effect(() => {
if (this.keyboard.visible$.value || this.panelOpened) { this.toggleAttribute(
this._expanded$.value = true; 'data-keyboard-visible',
} else { this.keyboard.visible$.value
this._expanded$.value = false; );
} this.toggleAttribute('data-panel-open', this.panelOpened);
}) })
); );
@@ -149,6 +149,9 @@ framework.impl(VirtualKeyboardProvider, {
// even though the `keyboardWillShow` event is still triggered. // even though the `keyboardWillShow` event is still triggered.
visible: info.keyboardHeight !== 0, visible: info.keyboardHeight !== 0,
height: info.keyboardHeight - navBarHeight, height: info.keyboardHeight - navBarHeight,
// SystemBars applies the IME inset to the WebView parent, so the
// keyboard no longer overlaps the web content on Android.
overlaysContent: false,
}); });
})().catch(console.error); })().catch(console.error);
}), }),
@@ -45,15 +45,19 @@ export function KeyboardToolbarExtension(
override mounted() { override mounted() {
this._disposables.add( this._disposables.add(
affineVirtualKeyboardProvider.onChange(({ visible, height }) => { affineVirtualKeyboardProvider.onChange(
batch(() => { ({ visible, height, overlaysContent }) => {
if (visible && this.staticHeight$.peek() !== height) { const layoutHeight = overlaysContent === false ? 0 : height;
this.staticHeight$.value = height;
} batch(() => {
this.visible$.value = visible; if (visible && this.staticHeight$.peek() !== height) {
this.height$.value = height; this.staticHeight$.value = height;
}); }
}) this.visible$.value = visible;
this.height$.value = layoutHeight;
});
}
)
); );
} }
@@ -3,6 +3,7 @@ import { createIdentifier } from '@toeverything/infra';
interface VirtualKeyboardInfo { interface VirtualKeyboardInfo {
visible: boolean; visible: boolean;
height: number; height: number;
overlaysContent?: boolean;
} }
type VirtualKeyboardAction = { type VirtualKeyboardAction = {
@@ -20,8 +20,10 @@ export class VirtualKeyboardService extends Service {
private _observe() { private _observe() {
this.disposables.push( this.disposables.push(
this.virtualKeyboardProvider.onChange(info => { this.virtualKeyboardProvider.onChange(info => {
const layoutHeight = info.overlaysContent === false ? 0 : info.height;
this.visible$.next(info.visible); this.visible$.next(info.visible);
this.height$.next(info.height); this.height$.next(layoutHeight);
setElementVars(document.body, { setElementVars(document.body, {
[globalVars.appKeyboardHeight]: `${this.height$.value}px`, [globalVars.appKeyboardHeight]: `${this.height$.value}px`,
@@ -22,6 +22,12 @@ globalStyle('body:has(>#app-tabs):not(:has(affine-keyboard-toolbar))', {
paddingBottom: globalVars.appTabSafeArea, paddingBottom: globalVars.appTabSafeArea,
}); });
globalStyle('body:has(affine-keyboard-toolbar)', { globalStyle('body:has(affine-keyboard-toolbar)', {
paddingBottom: `calc(${globalVars.appTabSafeArea} + 46px)`,
});
globalStyle('body:has(affine-keyboard-toolbar[data-keyboard-visible])', {
paddingBottom: `calc(${globalVars.appKeyboardHeight} + 46px)`,
});
globalStyle('body:has(affine-keyboard-toolbar[data-panel-open])', {
paddingBottom: `calc(${globalVars.appKeyboardStaticHeight} + 46px)`, paddingBottom: `calc(${globalVars.appKeyboardStaticHeight} + 46px)`,
}); });
globalStyle('body:has(>#app-tabs) edgeless-toolbar-widget', { globalStyle('body:has(>#app-tabs) edgeless-toolbar-widget', {