fix(mobile): incorrect height of keyboard toolbar in ios (#8653)
### What Changes: - Fix keyboard height calculation with the `KeyboardToolBarConfig.useScreenHeight = true`. More detials in: https://github.com/toeverything/blocksuite/pull/8645 - Add safe bottom padding for shrinked keyboard toolbar - Add `VirtualKeyboard` polyfill. Close [AF-1573](https://linear.app/affine-design/issue/AF-1573/virtualkeyboard-polyfill-with-capacitor) - Since the `@capacitor/keyboard` dose not implement the `Keyboard.show()` method, this polyfill is not enabled now.
This commit is contained in:
@@ -13,6 +13,7 @@ def capacitor_pods
|
|||||||
pod 'CapacitorCordova', :path => '../../../../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../../../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorApp', :path => '../../../../../node_modules/@capacitor/app'
|
pod 'CapacitorApp', :path => '../../../../../node_modules/@capacitor/app'
|
||||||
pod 'CapacitorBrowser', :path => '../../../../../node_modules/@capacitor/browser'
|
pod 'CapacitorBrowser', :path => '../../../../../node_modules/@capacitor/browser'
|
||||||
|
pod 'CapacitorKeyboard', :path => '../../../../../node_modules/@capacitor/keyboard'
|
||||||
end
|
end
|
||||||
|
|
||||||
target 'App' do
|
target 'App' do
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ PODS:
|
|||||||
- CapacitorBrowser (6.0.3):
|
- CapacitorBrowser (6.0.3):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorCordova (6.1.2)
|
- CapacitorCordova (6.1.2)
|
||||||
|
- CapacitorKeyboard (6.0.2):
|
||||||
|
- Capacitor
|
||||||
- CryptoSwift (1.8.3)
|
- CryptoSwift (1.8.3)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
@@ -13,6 +15,7 @@ DEPENDENCIES:
|
|||||||
- "CapacitorApp (from `../../../../../node_modules/@capacitor/app`)"
|
- "CapacitorApp (from `../../../../../node_modules/@capacitor/app`)"
|
||||||
- "CapacitorBrowser (from `../../../../../node_modules/@capacitor/browser`)"
|
- "CapacitorBrowser (from `../../../../../node_modules/@capacitor/browser`)"
|
||||||
- "CapacitorCordova (from `../../../../../node_modules/@capacitor/ios`)"
|
- "CapacitorCordova (from `../../../../../node_modules/@capacitor/ios`)"
|
||||||
|
- "CapacitorKeyboard (from `../../../../../node_modules/@capacitor/keyboard`)"
|
||||||
- CryptoSwift (~> 1.8.3)
|
- CryptoSwift (~> 1.8.3)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
@@ -28,14 +31,17 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../../../../../node_modules/@capacitor/browser"
|
:path: "../../../../../node_modules/@capacitor/browser"
|
||||||
CapacitorCordova:
|
CapacitorCordova:
|
||||||
:path: "../../../../../node_modules/@capacitor/ios"
|
:path: "../../../../../node_modules/@capacitor/ios"
|
||||||
|
CapacitorKeyboard:
|
||||||
|
:path: "../../../../../node_modules/@capacitor/keyboard"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Capacitor: 679f9673fdf30597493a6362a5d5bf233d46abc2
|
Capacitor: 679f9673fdf30597493a6362a5d5bf233d46abc2
|
||||||
CapacitorApp: 0bc633b4eae40a1f32cd2834788fad3bc42da6a1
|
CapacitorApp: 0bc633b4eae40a1f32cd2834788fad3bc42da6a1
|
||||||
CapacitorBrowser: aab1ed943b01c0365c4810538a8b3477e2d9f72e
|
CapacitorBrowser: aab1ed943b01c0365c4810538a8b3477e2d9f72e
|
||||||
CapacitorCordova: f48c89f96c319101cd2f0ce8a2b7449b5fb8b3dd
|
CapacitorCordova: f48c89f96c319101cd2f0ce8a2b7449b5fb8b3dd
|
||||||
|
CapacitorKeyboard: 2700f9b18687be021e28b5a09b59eb151a46d5e0
|
||||||
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
|
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
|
||||||
|
|
||||||
PODFILE CHECKSUM: 763e3dac392c17bcf42dab97a9225ea234e8416a
|
PODFILE CHECKSUM: 1b0d3fe81862c0e9ce712ddd0c5a0accd0097698
|
||||||
|
|
||||||
COCOAPODS: 1.15.2
|
COCOAPODS: 1.16.1
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { CapacitorConfig } from '@capacitor/cli';
|
import type { CapacitorConfig } from '@capacitor/cli';
|
||||||
|
import { KeyboardResize } from '@capacitor/keyboard';
|
||||||
|
|
||||||
const config: CapacitorConfig = {
|
const config: CapacitorConfig = {
|
||||||
appId: 'app.affine.pro',
|
appId: 'app.affine.pro',
|
||||||
@@ -17,6 +18,9 @@ const config: CapacitorConfig = {
|
|||||||
CapacitorHttp: {
|
CapacitorHttp: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
Keyboard: {
|
||||||
|
resize: KeyboardResize.Native,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"@capacitor/browser": "^6.0.3",
|
"@capacitor/browser": "^6.0.3",
|
||||||
"@capacitor/core": "^6.1.2",
|
"@capacitor/core": "^6.1.2",
|
||||||
"@capacitor/ios": "^6.1.2",
|
"@capacitor/ios": "^6.1.2",
|
||||||
|
"@capacitor/keyboard": "^6.0.2",
|
||||||
"@sentry/react": "^8.0.0",
|
"@sentry/react": "^8.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
import '@affine/core/bootstrap/browser';
|
import '@affine/core/bootstrap/browser';
|
||||||
import '@affine/component/theme';
|
import '@affine/component/theme';
|
||||||
import '@affine/core/mobile/styles/mobile.css';
|
import '@affine/core/mobile/styles/mobile.css';
|
||||||
|
|
||||||
|
// TODO(@L-Sun) Uncomment this when the `show` method implement by `@capacitor/keyboard` in ios
|
||||||
|
// import './virtual-keyboard';
|
||||||
|
|||||||
152
packages/frontend/apps/ios/src/virtual-keyboard.ts
Normal file
152
packages/frontend/apps/ios/src/virtual-keyboard.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import type { PluginListenerHandle } from '@capacitor/core/types/definitions';
|
||||||
|
import { Keyboard } from '@capacitor/keyboard';
|
||||||
|
|
||||||
|
type VirtualKeyboardCallback =
|
||||||
|
| (<K extends keyof VirtualKeyboardEventMap>(
|
||||||
|
this: VirtualKeyboard,
|
||||||
|
ev: VirtualKeyboardEventMap[K]
|
||||||
|
) => any)
|
||||||
|
| EventListenerOrEventListenerObject;
|
||||||
|
|
||||||
|
class NavigatorVirtualKeyboard implements VirtualKeyboard {
|
||||||
|
private readonly _boundingRect = new DOMRect();
|
||||||
|
|
||||||
|
private readonly _overlaysContent = false;
|
||||||
|
|
||||||
|
private _listeners = new Map<
|
||||||
|
string,
|
||||||
|
Set<{
|
||||||
|
cb: VirtualKeyboardCallback;
|
||||||
|
options?: boolean | AddEventListenerOptions;
|
||||||
|
}>
|
||||||
|
>();
|
||||||
|
|
||||||
|
private _capacitorListenerHandles: PluginListenerHandle[] = [];
|
||||||
|
|
||||||
|
private async _bindListener() {
|
||||||
|
const updateBoundingRect = (info?: { keyboardHeight: number }) => {
|
||||||
|
this.boundingRect.x = 0;
|
||||||
|
this.boundingRect.y = info ? window.innerHeight - info.keyboardHeight : 0;
|
||||||
|
this.boundingRect.width = window.innerWidth;
|
||||||
|
this.boundingRect.height = info ? info.keyboardHeight : 0;
|
||||||
|
this.dispatchEvent(new Event('geometrychange'));
|
||||||
|
};
|
||||||
|
|
||||||
|
this._capacitorListenerHandles = [
|
||||||
|
await Keyboard.addListener('keyboardDidShow', updateBoundingRect),
|
||||||
|
await Keyboard.addListener('keyboardDidHide', updateBoundingRect),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchEvent = (event: Event) => {
|
||||||
|
const listeners = this._listeners.get(event.type);
|
||||||
|
if (listeners) {
|
||||||
|
for (const l of listeners) {
|
||||||
|
if (typeof l.cb === 'function') {
|
||||||
|
l.cb.call(this, event);
|
||||||
|
} else {
|
||||||
|
l.cb.handleEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !(event.cancelable && event.defaultPrevented);
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._bindListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this._capacitorListenerHandles.forEach(handle => handle.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
get boundingRect(): DOMRect {
|
||||||
|
return this._boundingRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
get overlaysContent(): boolean {
|
||||||
|
return this._overlaysContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
set overlaysContent(_: boolean) {
|
||||||
|
console.warn(
|
||||||
|
'overlaysContent is read-only in polyfill based on @capacitor/keyboard'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
Keyboard.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
show() {
|
||||||
|
Keyboard.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
ongeometrychange: ((this: VirtualKeyboard, ev: Event) => any) | null = null;
|
||||||
|
|
||||||
|
addEventListener<K extends keyof VirtualKeyboardEventMap>(
|
||||||
|
type: K,
|
||||||
|
listener: VirtualKeyboardCallback,
|
||||||
|
options?: boolean | AddEventListenerOptions
|
||||||
|
) {
|
||||||
|
if (!this._listeners.has(type)) {
|
||||||
|
this._listeners.set(type, new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
const listeners = this._listeners.get(type);
|
||||||
|
if (!listeners) return;
|
||||||
|
|
||||||
|
listeners.add({ cb: listener, options });
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEventListener<K extends keyof VirtualKeyboardEventMap>(
|
||||||
|
type: K,
|
||||||
|
listener: VirtualKeyboardCallback,
|
||||||
|
options?: boolean | EventListenerOptions
|
||||||
|
) {
|
||||||
|
const listeners = this._listeners.get(type);
|
||||||
|
if (!listeners) return;
|
||||||
|
|
||||||
|
const sameCapture = (
|
||||||
|
a?: boolean | AddEventListenerOptions,
|
||||||
|
b?: boolean | EventListenerOptions
|
||||||
|
) => {
|
||||||
|
if (a === undefined && b === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof a === 'boolean' && typeof b === 'boolean') {
|
||||||
|
return a === b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof a === 'object' && typeof b === 'object') {
|
||||||
|
return a.capture === b.capture;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof a === 'object' && typeof b === 'boolean') {
|
||||||
|
return a.capture === b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof a === 'boolean' && typeof b === 'object') {
|
||||||
|
return a === b.capture;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let target = null;
|
||||||
|
for (const l of listeners) {
|
||||||
|
if (l.cb === listener && sameCapture(l.options, options)) {
|
||||||
|
target = l;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
listeners.delete(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error polyfill
|
||||||
|
navigator.virtualKeyboard = new NavigatorVirtualKeyboard();
|
||||||
@@ -10,6 +10,11 @@ import {
|
|||||||
LifeCycleWatcher,
|
LifeCycleWatcher,
|
||||||
StdIdentifier,
|
StdIdentifier,
|
||||||
} from '@blocksuite/affine/block-std';
|
} from '@blocksuite/affine/block-std';
|
||||||
|
import type {
|
||||||
|
RootBlockConfig,
|
||||||
|
TelemetryEventMap,
|
||||||
|
ThemeExtension,
|
||||||
|
} from '@blocksuite/affine/blocks';
|
||||||
import {
|
import {
|
||||||
ColorScheme,
|
ColorScheme,
|
||||||
EdgelessBuiltInManager,
|
EdgelessBuiltInManager,
|
||||||
@@ -18,9 +23,7 @@ import {
|
|||||||
EditorSettingExtension,
|
EditorSettingExtension,
|
||||||
FontLoaderService,
|
FontLoaderService,
|
||||||
PageRootBlockSpec,
|
PageRootBlockSpec,
|
||||||
type TelemetryEventMap,
|
|
||||||
TelemetryProvider,
|
TelemetryProvider,
|
||||||
type ThemeExtension,
|
|
||||||
ThemeExtensionIdentifier,
|
ThemeExtensionIdentifier,
|
||||||
} from '@blocksuite/affine/blocks';
|
} from '@blocksuite/affine/blocks';
|
||||||
import {
|
import {
|
||||||
@@ -39,6 +42,7 @@ import { combineLatest, map } from 'rxjs';
|
|||||||
|
|
||||||
import { getFontConfigExtension } from '../font-extension';
|
import { getFontConfigExtension } from '../font-extension';
|
||||||
import { createDatabaseOptionsConfig } from './database-block';
|
import { createDatabaseOptionsConfig } from './database-block';
|
||||||
|
import { createKeyboardToolbarConfig } from './widgets/keyboard-toolbar';
|
||||||
import { createLinkedWidgetConfig } from './widgets/linked';
|
import { createLinkedWidgetConfig } from './widgets/linked';
|
||||||
import { createToolbarMoreMenuConfig } from './widgets/toolbar';
|
import { createToolbarMoreMenuConfig } from './widgets/toolbar';
|
||||||
|
|
||||||
@@ -144,7 +148,8 @@ function getEditorConfigExtension(
|
|||||||
linkedWidget: createLinkedWidgetConfig(framework),
|
linkedWidget: createLinkedWidgetConfig(framework),
|
||||||
toolbarMoreMenu: createToolbarMoreMenuConfig(framework),
|
toolbarMoreMenu: createToolbarMoreMenuConfig(framework),
|
||||||
databaseOptions: createDatabaseOptionsConfig(framework),
|
databaseOptions: createDatabaseOptionsConfig(framework),
|
||||||
}),
|
keyboardToolbar: createKeyboardToolbarConfig(),
|
||||||
|
} satisfies RootBlockConfig),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { globalVars } from '@affine/core/mobile/styles/mobile.css';
|
||||||
|
import { type KeyboardToolbarConfig } from '@blocksuite/affine/blocks';
|
||||||
|
|
||||||
|
export function createKeyboardToolbarConfig(): Partial<KeyboardToolbarConfig> {
|
||||||
|
return {
|
||||||
|
// TODO(@L-Sun): check android following the PR
|
||||||
|
// https://github.com/toeverything/blocksuite/pull/8645
|
||||||
|
useScreenHeight: BUILD_CONFIG.isIOS,
|
||||||
|
safeBottomPadding: BUILD_CONFIG.isIOS ? globalVars.appTabHeight : '0px',
|
||||||
|
};
|
||||||
|
}
|
||||||
10
yarn.lock
10
yarn.lock
@@ -606,6 +606,7 @@ __metadata:
|
|||||||
"@capacitor/cli": "npm:^6.1.2"
|
"@capacitor/cli": "npm:^6.1.2"
|
||||||
"@capacitor/core": "npm:^6.1.2"
|
"@capacitor/core": "npm:^6.1.2"
|
||||||
"@capacitor/ios": "npm:^6.1.2"
|
"@capacitor/ios": "npm:^6.1.2"
|
||||||
|
"@capacitor/keyboard": "npm:^6.0.2"
|
||||||
"@sentry/react": "npm:^8.0.0"
|
"@sentry/react": "npm:^8.0.0"
|
||||||
"@types/react": "npm:^18.2.75"
|
"@types/react": "npm:^18.2.75"
|
||||||
"@types/react-dom": "npm:^18.2.24"
|
"@types/react-dom": "npm:^18.2.24"
|
||||||
@@ -2991,6 +2992,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@capacitor/keyboard@npm:^6.0.2":
|
||||||
|
version: 6.0.2
|
||||||
|
resolution: "@capacitor/keyboard@npm:6.0.2"
|
||||||
|
peerDependencies:
|
||||||
|
"@capacitor/core": ^6.0.0
|
||||||
|
checksum: 10/cef4cce5c703fd67ceed3f04736a6513e05423006a5b4571d88d07a5849b79fc9f1ec98759606b7498ec1b604e5ee7987e5e30e61e9835538a29238ffbe3b036
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@chromatic-com/storybook@npm:^3.0.0":
|
"@chromatic-com/storybook@npm:^3.0.0":
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
resolution: "@chromatic-com/storybook@npm:3.0.0"
|
resolution: "@chromatic-com/storybook@npm:3.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user