From e633a2dea24120397a6c9d5a651ccd2a9972eb0f Mon Sep 17 00:00:00 2001 From: L-Sun Date: Fri, 8 Nov 2024 02:58:21 +0000 Subject: [PATCH] refactor(blocks): move virutal keyboard controller to affine components (#8663) ### What Changes: - Move `VirtualKeyboardController` to `affine-components` for common usage --- packages/affine/components/package.json | 7 +- .../src/virtual-keyboard/controller.ts | 147 ++++++++++++++++++ .../components/src/virtual-keyboard/index.ts | 1 + .../keyboard-toolbar/keyboard-toolbar.ts | 12 +- .../widgets/keyboard-toolbar/utils.ts | 137 ---------------- 5 files changed, 165 insertions(+), 139 deletions(-) create mode 100644 packages/affine/components/src/virtual-keyboard/controller.ts create mode 100644 packages/affine/components/src/virtual-keyboard/index.ts diff --git a/packages/affine/components/package.json b/packages/affine/components/package.json index 89fa5b5f6aca..6bb8fa0ea13f 100644 --- a/packages/affine/components/package.json +++ b/packages/affine/components/package.json @@ -50,7 +50,8 @@ "./caption": "./src/caption/index.ts", "./context-menu": "./src/context-menu/index.ts", "./date-picker": "./src/date-picker/index.ts", - "./drag-indicator": "./src/drag-indicator/index.ts" + "./drag-indicator": "./src/drag-indicator/index.ts", + "./virtual-keyboard": "./src/virtual-keyboard/index.ts" }, "publishConfig": { "access": "public", @@ -104,6 +105,10 @@ "./drag-indicator": { "types": "./dist/drag-indicator/index.d.ts", "import": "./dist/drag-indicator/index.js" + }, + "./virtual-keyboard": { + "types": "./dist/virtual-keyboard/index.d.ts", + "import": "./dist/virtual-keyboard/index.js" } } }, diff --git a/packages/affine/components/src/virtual-keyboard/controller.ts b/packages/affine/components/src/virtual-keyboard/controller.ts new file mode 100644 index 000000000000..7f713594ab34 --- /dev/null +++ b/packages/affine/components/src/virtual-keyboard/controller.ts @@ -0,0 +1,147 @@ +import type { ReactiveController, ReactiveControllerHost } from 'lit'; + +import { DisposableGroup } from '@blocksuite/global/utils'; +import { signal } from '@preact/signals-core'; + +function notSupportedWarning() { + console.warn('VirtualKeyboard API and VisualViewport API are not supported'); +} + +export type VirtualKeyboardControllerConfig = { + useScreenHeight: boolean; + inputElement: HTMLElement; +}; + +export class VirtualKeyboardController implements ReactiveController { + private _disposables = new DisposableGroup(); + + private readonly _keyboardHeight$ = signal(0); + + private readonly _keyboardOpened$ = signal(false); + + private readonly _updateKeyboardHeight = () => { + const { virtualKeyboard } = navigator; + if (virtualKeyboard) { + this._keyboardOpened$.value = virtualKeyboard.boundingRect.height > 0; + this._keyboardHeight$.value = virtualKeyboard.boundingRect.height; + } else if (visualViewport) { + const windowHeight = this.config.useScreenHeight + ? window.screen.height + : window.innerHeight; + + /** + * ┌───────────────┐ - window top + * │ │ + * │ │ + * │ │ + * │ │ + * │ │ + * └───────────────┘ - keyboard top -- + * │ │ │ keyboard height in layout viewport + * └───────────────┘ - page(html) bottom -- + * │ │ │ visualViewport.offsetTop + * └───────────────┘ - window bottom -- + */ + this._keyboardOpened$.value = windowHeight - visualViewport.height > 0; + this._keyboardHeight$.value = + windowHeight - visualViewport.height - visualViewport.offsetTop; + } else { + notSupportedWarning(); + } + }; + + hide = () => { + if (navigator.virtualKeyboard) { + navigator.virtualKeyboard.hide(); + } else { + this.config.inputElement.inputMode = 'none'; + } + }; + + host: ReactiveControllerHost & { + virtualKeyboardControllerConfig: VirtualKeyboardControllerConfig; + }; + + show = () => { + if (navigator.virtualKeyboard) { + navigator.virtualKeyboard.show(); + } else { + this.config.inputElement.inputMode = ''; + } + }; + + toggle = () => { + if (this.opened) { + this.hide(); + } else { + this.show(); + } + }; + + get config() { + return this.host.virtualKeyboardControllerConfig; + } + + /** + * Return the height of keyboard in layout viewport + * see comment in the `_updateKeyboardHeight` method + */ + get keyboardHeight() { + return this._keyboardHeight$.value; + } + + get opened() { + return this._keyboardOpened$.value; + } + + constructor(host: VirtualKeyboardController['host']) { + (this.host = host).addController(this); + } + + hostConnected() { + if (navigator.virtualKeyboard) { + const { overlaysContent } = navigator.virtualKeyboard; + const { virtualKeyboardPolicy } = this.config.inputElement; + + navigator.virtualKeyboard.overlaysContent = true; + this.config.inputElement.virtualKeyboardPolicy = 'manual'; + + this._disposables.add(() => { + if (!navigator.virtualKeyboard) return; + navigator.virtualKeyboard.overlaysContent = overlaysContent; + this.config.inputElement.virtualKeyboardPolicy = virtualKeyboardPolicy; + }); + this._disposables.addFromEvent( + navigator.virtualKeyboard, + 'geometrychange', + this._updateKeyboardHeight + ); + } else if (visualViewport) { + this._disposables.addFromEvent( + visualViewport, + 'resize', + this._updateKeyboardHeight + ); + this._disposables.addFromEvent( + visualViewport, + 'scroll', + this._updateKeyboardHeight + ); + } else { + notSupportedWarning(); + } + + this._disposables.addFromEvent( + this.config.inputElement, + 'focus', + this.show + ); + this._disposables.addFromEvent(this.config.inputElement, 'blur', this.hide); + + this._updateKeyboardHeight(); + } + + hostDisconnected() { + this._disposables.dispose(); + } +} diff --git a/packages/affine/components/src/virtual-keyboard/index.ts b/packages/affine/components/src/virtual-keyboard/index.ts new file mode 100644 index 000000000000..534a0e5917df --- /dev/null +++ b/packages/affine/components/src/virtual-keyboard/index.ts @@ -0,0 +1 @@ +export * from './controller.js'; diff --git a/packages/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts b/packages/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts index b1fb6ab26c48..1b57d90017b8 100644 --- a/packages/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts +++ b/packages/blocks/src/root-block/widgets/keyboard-toolbar/keyboard-toolbar.ts @@ -1,3 +1,7 @@ +import { + VirtualKeyboardController, + type VirtualKeyboardControllerConfig, +} from '@blocksuite/affine-components/virtual-keyboard'; import { PropTypes, requiredProperties } from '@blocksuite/block-std'; import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils'; import { ArrowLeftBigIcon, KeyboardIcon } from '@blocksuite/icons/lit'; @@ -23,7 +27,6 @@ import { isKeyboardToolBarActionItem, isKeyboardToolPanelConfig, scrollCurrentBlockIntoView, - VirtualKeyboardController, } from './utils.js'; export const AFFINE_KEYBOARD_TOOLBAR = 'affine-keyboard-toolbar'; @@ -167,6 +170,13 @@ export class AffineKeyboardToolbar extends SignalWatcher( ); } + get virtualKeyboardControllerConfig(): VirtualKeyboardControllerConfig { + return { + useScreenHeight: this.config.useScreenHeight ?? false, + inputElement: this.rootComponent, + }; + } + private _renderIcon(icon: KeyboardIconType) { return typeof icon === 'function' ? icon(this._context) : icon; } diff --git a/packages/blocks/src/root-block/widgets/keyboard-toolbar/utils.ts b/packages/blocks/src/root-block/widgets/keyboard-toolbar/utils.ts index 56bb97cd701a..ad4f8f660164 100644 --- a/packages/blocks/src/root-block/widgets/keyboard-toolbar/utils.ts +++ b/packages/blocks/src/root-block/widgets/keyboard-toolbar/utils.ts @@ -1,149 +1,12 @@ import type { BlockStdScope } from '@blocksuite/block-std'; -import type { ReactiveController, ReactiveControllerHost } from 'lit'; -import { DisposableGroup } from '@blocksuite/global/utils'; -import { signal } from '@preact/signals-core'; - -import type { PageRootBlockComponent } from '../../page/page-root-block.js'; import type { KeyboardSubToolbarConfig, KeyboardToolbarActionItem, - KeyboardToolbarConfig, KeyboardToolbarItem, KeyboardToolPanelConfig, } from './config.js'; -function notSupportedWarning() { - console.warn('VirtualKeyboard API and VisualViewport API are not supported'); -} - -export class VirtualKeyboardController implements ReactiveController { - private _disposables = new DisposableGroup(); - - private readonly _keyboardHeight$ = signal(0); - - private readonly _keyboardOpened$ = signal(false); - - private readonly _updateKeyboardHeight = () => { - const { virtualKeyboard } = navigator; - if (virtualKeyboard) { - this._keyboardOpened$.value = virtualKeyboard.boundingRect.height > 0; - this._keyboardHeight$.value = virtualKeyboard.boundingRect.height; - } else if (visualViewport) { - const windowHeight = this.host.config.useScreenHeight - ? window.screen.height - : window.innerHeight; - - /** - * ┌───────────────┐ - window top - * │ │ - * │ │ - * │ │ - * │ │ - * │ │ - * └───────────────┘ - keyboard top -- - * │ │ │ keyboard height in layout viewport - * └───────────────┘ - page(html) bottom -- - * │ │ │ visualViewport.offsetTop - * └───────────────┘ - window bottom -- - */ - this._keyboardOpened$.value = windowHeight - visualViewport.height > 0; - this._keyboardHeight$.value = - windowHeight - visualViewport.height - visualViewport.offsetTop; - } else { - notSupportedWarning(); - } - }; - - hide = () => { - if (navigator.virtualKeyboard) { - navigator.virtualKeyboard.hide(); - } else { - this.host.rootComponent.inputMode = 'none'; - } - }; - - host: ReactiveControllerHost & { - rootComponent: PageRootBlockComponent; - config: KeyboardToolbarConfig; - }; - - show = () => { - if (navigator.virtualKeyboard) { - navigator.virtualKeyboard.show(); - } else { - this.host.rootComponent.inputMode = ''; - } - }; - - toggle = () => { - if (this.opened) { - this.hide(); - } else { - this.show(); - } - }; - - /** - * Return the height of keyboard in layout viewport - * see comment in the `_updateKeyboardHeight` method - */ - get keyboardHeight() { - return this._keyboardHeight$.value; - } - - get opened() { - return this._keyboardOpened$.value; - } - - constructor(host: VirtualKeyboardController['host']) { - (this.host = host).addController(this); - } - - hostConnected() { - if (navigator.virtualKeyboard) { - const { overlaysContent } = navigator.virtualKeyboard; - const { virtualKeyboardPolicy } = this.host.rootComponent; - - navigator.virtualKeyboard.overlaysContent = true; - this.host.rootComponent.virtualKeyboardPolicy = 'manual'; - - this._disposables.add(() => { - if (!navigator.virtualKeyboard) return; - navigator.virtualKeyboard.overlaysContent = overlaysContent; - this.host.rootComponent.virtualKeyboardPolicy = virtualKeyboardPolicy; - }); - this._disposables.addFromEvent( - navigator.virtualKeyboard, - 'geometrychange', - this._updateKeyboardHeight - ); - } else if (visualViewport) { - this._disposables.addFromEvent( - visualViewport, - 'resize', - this._updateKeyboardHeight - ); - this._disposables.addFromEvent( - visualViewport, - 'scroll', - this._updateKeyboardHeight - ); - } else { - notSupportedWarning(); - } - - this._disposables.addFromEvent(this.host.rootComponent, 'focus', this.show); - this._disposables.addFromEvent(this.host.rootComponent, 'blur', this.hide); - - this._updateKeyboardHeight(); - } - - hostDisconnected() { - this._disposables.dispose(); - } -} - export function isKeyboardToolBarActionItem( item: KeyboardToolbarItem ): item is KeyboardToolbarActionItem {