mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 10:22:55 +08:00
fix(editor): android keyboard can not be opened (#10502)
Close [BS-2674](https://linear.app/affine-design/issue/BS-2674/[android]-%E6%96%87%E6%9C%AC%E7%BC%96%E8%BE%91%E5%8C%BA%E5%9F%9F%E7%82%B9%E5%87%BB%E5%90%8E%E6%97%A0%E6%B3%95%E6%BF%80%E6%B4%BB%E9%94%AE%E7%9B%98) [BS-2609](https://linear.app/affine-design/issue/BS-2609/[android]-%E8%BE%93%E5%85%A5%E7%9A%84-toolbar-%E6%B2%A1%E6%9C%89%E4%BA%86)
This commit is contained in:
@@ -113,12 +113,6 @@ import {
|
||||
|
||||
export type KeyboardToolbarConfig = {
|
||||
items: KeyboardToolbarItem[];
|
||||
/**
|
||||
* @description Whether to use the screen height as the keyboard height when the virtual keyboard API is not supported.
|
||||
* It is useful when the app is running in a webview and the keyboard is not overlaid on the content.
|
||||
* @default false
|
||||
*/
|
||||
useScreenHeight?: boolean;
|
||||
};
|
||||
|
||||
export type KeyboardToolbarItem =
|
||||
@@ -1106,5 +1100,4 @@ export const defaultKeyboardToolbarConfig: KeyboardToolbarConfig = {
|
||||
},
|
||||
},
|
||||
],
|
||||
useScreenHeight: false,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getDocTitleByEditorHost } from '@blocksuite/affine-components/doc-title';
|
||||
import type { RootBlockModel } from '@blocksuite/affine-model';
|
||||
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
||||
import { WidgetComponent } from '@blocksuite/block-std';
|
||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||
import { assertType } from '@blocksuite/global/utils';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import { html, nothing } from 'lit';
|
||||
|
||||
@@ -20,10 +20,10 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
|
||||
> {
|
||||
private readonly _close = (blur: boolean) => {
|
||||
if (blur) {
|
||||
if (document.activeElement === this._docTitle) {
|
||||
this._docTitle?.blur();
|
||||
if (document.activeElement === this._docTitle?.inlineEditorContainer) {
|
||||
this._docTitle?.inlineEditor?.setInlineRange(null);
|
||||
} else if (document.activeElement === this.block.rootComponent) {
|
||||
this.block.rootComponent?.blur();
|
||||
this.std.selection.clear();
|
||||
}
|
||||
}
|
||||
this._show$.value = false;
|
||||
@@ -31,12 +31,8 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
|
||||
|
||||
private readonly _show$ = signal(false);
|
||||
|
||||
private get _docTitle(): HTMLDivElement | null {
|
||||
const docTitle = this.std.host
|
||||
.closest('.affine-page-viewport')
|
||||
?.querySelector('doc-title rich-text .inline-editor');
|
||||
assertType<HTMLDivElement | null>(docTitle);
|
||||
return docTitle;
|
||||
private get _docTitle() {
|
||||
return getDocTitleByEditorHost(this.std.host);
|
||||
}
|
||||
|
||||
get config() {
|
||||
@@ -61,10 +57,11 @@ export class AffineKeyboardToolbarWidget extends WidgetComponent<
|
||||
}
|
||||
|
||||
if (this._docTitle) {
|
||||
this.disposables.addFromEvent(this._docTitle, 'focus', () => {
|
||||
const { inlineEditorContainer } = this._docTitle;
|
||||
this.disposables.addFromEvent(inlineEditorContainer, 'focus', () => {
|
||||
this._show$.value = true;
|
||||
});
|
||||
this.disposables.addFromEvent(this._docTitle, 'blur', () => {
|
||||
this.disposables.addFromEvent(inlineEditorContainer, 'blur', () => {
|
||||
this._show$.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import {
|
||||
VirtualKeyboardController,
|
||||
type VirtualKeyboardControllerConfig,
|
||||
} from '@blocksuite/affine-components/virtual-keyboard';
|
||||
import { getSelectedModelsCommand } from '@blocksuite/affine-shared/commands';
|
||||
import { VirtualKeyboardProvider } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
PropTypes,
|
||||
requiredProperties,
|
||||
@@ -17,20 +14,21 @@ import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import { PageRootBlockComponent } from '../../page/page-root-block.js';
|
||||
import { PageRootBlockComponent } from '../../page/page-root-block';
|
||||
import type {
|
||||
KeyboardIconType,
|
||||
KeyboardToolbarConfig,
|
||||
KeyboardToolbarContext,
|
||||
KeyboardToolbarItem,
|
||||
KeyboardToolPanelConfig,
|
||||
} from './config.js';
|
||||
import { keyboardToolbarStyles, TOOLBAR_HEIGHT } from './styles.js';
|
||||
} from './config';
|
||||
import { PositionController } from './position-controller';
|
||||
import { keyboardToolbarStyles } from './styles';
|
||||
import {
|
||||
isKeyboardSubToolBarConfig,
|
||||
isKeyboardToolBarActionItem,
|
||||
isKeyboardToolPanelConfig,
|
||||
} from './utils.js';
|
||||
} from './utils';
|
||||
|
||||
export const AFFINE_KEYBOARD_TOOLBAR = 'affine-keyboard-toolbar';
|
||||
|
||||
@@ -43,11 +41,28 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
) {
|
||||
static override styles = keyboardToolbarStyles;
|
||||
|
||||
/** This field records the panel static height same as the virtual keyboard height */
|
||||
panelHeight$ = signal(0);
|
||||
|
||||
positionController = new PositionController(this);
|
||||
|
||||
get std() {
|
||||
return this.rootComponent.std;
|
||||
}
|
||||
|
||||
get keyboard() {
|
||||
return this._context.std.get(VirtualKeyboardProvider);
|
||||
}
|
||||
|
||||
get panelOpened() {
|
||||
return this._currentPanelIndex$.value !== -1;
|
||||
}
|
||||
|
||||
private readonly _closeToolPanel = () => {
|
||||
if (!this._isPanelOpened) return;
|
||||
if (!this.panelOpened) return;
|
||||
|
||||
this._currentPanelIndex$.value = -1;
|
||||
this._keyboardController.show();
|
||||
this.keyboard.show();
|
||||
};
|
||||
|
||||
private readonly _currentPanelIndex$ = signal(-1);
|
||||
@@ -55,7 +70,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
private readonly _goPrevToolbar = () => {
|
||||
if (!this._isSubToolbarOpened) return;
|
||||
|
||||
if (this._isPanelOpened) this._closeToolPanel();
|
||||
if (this.panelOpened) this._closeToolPanel();
|
||||
|
||||
this._path$.value = this._path$.value.slice(0, -1);
|
||||
};
|
||||
@@ -75,31 +90,25 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
this._closeToolPanel();
|
||||
} else {
|
||||
this._currentPanelIndex$.value = index;
|
||||
this._keyboardController.hide();
|
||||
this.scrollCurrentBlockIntoView();
|
||||
this.keyboard.hide();
|
||||
this._scrollCurrentBlockIntoView();
|
||||
}
|
||||
}
|
||||
this._lastActiveItem$.value = item;
|
||||
};
|
||||
|
||||
private readonly _keyboardController = new VirtualKeyboardController(this);
|
||||
|
||||
private readonly _lastActiveItem$ = signal<KeyboardToolbarItem | null>(null);
|
||||
|
||||
/** This field records the panel static height, which dose not aim to control the panel opening */
|
||||
private readonly _panelHeight$ = signal(0);
|
||||
|
||||
private readonly _path$ = signal<number[]>([]);
|
||||
|
||||
private readonly scrollCurrentBlockIntoView = () => {
|
||||
const { std } = this.rootComponent;
|
||||
std.command
|
||||
private readonly _scrollCurrentBlockIntoView = () => {
|
||||
this.std.command
|
||||
.chain()
|
||||
.pipe(getSelectedModelsCommand)
|
||||
.pipe(({ selectedModels }) => {
|
||||
if (!selectedModels?.length) return;
|
||||
|
||||
const block = std.view.getBlock(selectedModels[0].id);
|
||||
const block = this.std.view.getBlock(selectedModels[0].id);
|
||||
if (!block) return;
|
||||
|
||||
const { y: y1 } = this.getBoundingClientRect();
|
||||
@@ -118,7 +127,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
|
||||
private get _context(): KeyboardToolbarContext {
|
||||
return {
|
||||
std: this.rootComponent.std,
|
||||
std: this.std,
|
||||
rootComponent: this.rootComponent,
|
||||
closeToolbar: (blur = false) => {
|
||||
this.close(blur);
|
||||
@@ -130,7 +139,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
}
|
||||
|
||||
private get _currentPanelConfig(): KeyboardToolPanelConfig | null {
|
||||
if (!this._isPanelOpened) return null;
|
||||
if (!this.panelOpened) return null;
|
||||
|
||||
const result = this._currentToolbarItems[this._currentPanelIndex$.value];
|
||||
|
||||
@@ -139,9 +148,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
|
||||
private get _currentToolbarItems(): KeyboardToolbarItem[] {
|
||||
let items = this.config.items;
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let i = 0; i < this._path$.value.length; i++) {
|
||||
const index = this._path$.value[i];
|
||||
for (const index of this._path$.value) {
|
||||
if (isKeyboardSubToolBarConfig(items[index])) {
|
||||
items = items[index].items;
|
||||
} else {
|
||||
@@ -156,21 +163,10 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
);
|
||||
}
|
||||
|
||||
private get _isPanelOpened() {
|
||||
return this._currentPanelIndex$.value !== -1;
|
||||
}
|
||||
|
||||
private get _isSubToolbarOpened() {
|
||||
return this._path$.value.length > 0;
|
||||
}
|
||||
|
||||
get virtualKeyboardControllerConfig(): VirtualKeyboardControllerConfig {
|
||||
return {
|
||||
useScreenHeight: this.config.useScreenHeight ?? false,
|
||||
inputElement: this.rootComponent,
|
||||
};
|
||||
}
|
||||
|
||||
private _renderIcon(icon: KeyboardIconType) {
|
||||
return typeof icon === 'function' ? icon(this._context) : icon;
|
||||
}
|
||||
@@ -252,35 +248,13 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
if (this._keyboardController.opened) {
|
||||
this._panelHeight$.value = this._keyboardController.keyboardHeight;
|
||||
} else if (this._isPanelOpened && this._panelHeight$.peek() === 0) {
|
||||
this._panelHeight$.value = 260;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
if (this._keyboardController.opened && !this.config.useScreenHeight) {
|
||||
document.body.style.paddingBottom = `${this._keyboardController.keyboardHeight + TOOLBAR_HEIGHT}px`;
|
||||
} else if (this._isPanelOpened) {
|
||||
document.body.style.paddingBottom = `${this._panelHeight$.value + TOOLBAR_HEIGHT}px`;
|
||||
} else {
|
||||
document.body.style.paddingBottom = '';
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.disposables.add(
|
||||
effect(() => {
|
||||
const std = this.rootComponent.std;
|
||||
std.selection.value;
|
||||
// wait cursor updated
|
||||
requestAnimationFrame(() => {
|
||||
this.scrollCurrentBlockIntoView();
|
||||
this._scrollCurrentBlockIntoView();
|
||||
});
|
||||
})
|
||||
);
|
||||
@@ -328,24 +302,14 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
);
|
||||
}
|
||||
|
||||
override disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
document.body.style.paddingBottom = '';
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
// workaround for the virtual keyboard showing transition animation
|
||||
setTimeout(() => {
|
||||
this.scrollCurrentBlockIntoView();
|
||||
this._scrollCurrentBlockIntoView();
|
||||
}, 700);
|
||||
}
|
||||
|
||||
override render() {
|
||||
this.style.bottom =
|
||||
this.config.useScreenHeight && this._keyboardController.opened
|
||||
? `${-this._panelHeight$.value}px`
|
||||
: '0px';
|
||||
|
||||
return html`
|
||||
<div class="keyboard-toolbar">
|
||||
${this._renderItems()}
|
||||
@@ -355,7 +319,7 @@ export class AffineKeyboardToolbar extends SignalWatcher(
|
||||
<affine-keyboard-tool-panel
|
||||
.config=${this._currentPanelConfig}
|
||||
.context=${this._context}
|
||||
height=${this._panelHeight$.value}
|
||||
height=${this.panelHeight$.value}
|
||||
></affine-keyboard-tool-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { type VirtualKeyboardProvider } from '@blocksuite/affine-shared/services';
|
||||
import type { BlockStdScope, ShadowlessElement } from '@blocksuite/block-std';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import { effect, type Signal } from '@preact/signals-core';
|
||||
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
||||
|
||||
import { TOOLBAR_HEIGHT } from './styles';
|
||||
|
||||
/**
|
||||
* This controller is used to control the keyboard toolbar position
|
||||
*/
|
||||
export class PositionController implements ReactiveController {
|
||||
private readonly _disposables = new DisposableGroup();
|
||||
|
||||
host: ReactiveControllerHost &
|
||||
ShadowlessElement & {
|
||||
std: BlockStdScope;
|
||||
panelHeight$: Signal<number>;
|
||||
keyboard: VirtualKeyboardProvider;
|
||||
panelOpened: boolean;
|
||||
};
|
||||
|
||||
constructor(host: PositionController['host']) {
|
||||
(this.host = host).addController(this);
|
||||
}
|
||||
|
||||
hostConnected() {
|
||||
const { keyboard, panelOpened } = this.host;
|
||||
|
||||
this._disposables.add(
|
||||
effect(() => {
|
||||
if (keyboard.visible$.value) {
|
||||
this.host.panelHeight$.value = keyboard.height$.value;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.host.style.bottom = '0px';
|
||||
this._disposables.add(
|
||||
effect(() => {
|
||||
if (keyboard.visible$.value) {
|
||||
document.body.style.paddingBottom = `${keyboard.height$.value + TOOLBAR_HEIGHT}px`;
|
||||
} else if (panelOpened) {
|
||||
document.body.style.paddingBottom = `${this.host.panelHeight$.peek() + TOOLBAR_HEIGHT}px`;
|
||||
} else {
|
||||
document.body.style.paddingBottom = '';
|
||||
}
|
||||
})
|
||||
);
|
||||
this._disposables.add(() => {
|
||||
document.body.style.paddingBottom = '';
|
||||
});
|
||||
}
|
||||
|
||||
hostDisconnected() {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,6 @@ export interface LinkedWidgetConfig {
|
||||
) => string | null;
|
||||
|
||||
mobile: {
|
||||
useScreenHeight?: boolean;
|
||||
/**
|
||||
* The linked doc menu widget will scroll the container to make sure the input cursor is visible in viewport.
|
||||
* It accepts a selector string, HTMLElement or Window
|
||||
|
||||
@@ -214,7 +214,6 @@ export class AffineLinkedDocWidget extends WidgetComponent<
|
||||
convertTriggerKey: true,
|
||||
getMenus,
|
||||
mobile: {
|
||||
useScreenHeight: false,
|
||||
scrollContainer: getViewportElement(this.std.host) ?? window,
|
||||
scrollTopOffset: 46,
|
||||
},
|
||||
|
||||
@@ -2,10 +2,7 @@ import {
|
||||
cleanSpecifiedTail,
|
||||
getTextContentFromInlineRange,
|
||||
} from '@blocksuite/affine-components/rich-text';
|
||||
import {
|
||||
VirtualKeyboardController,
|
||||
type VirtualKeyboardControllerConfig,
|
||||
} from '@blocksuite/affine-components/virtual-keyboard';
|
||||
import { VirtualKeyboardProvider } from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
createKeydownObserver,
|
||||
getViewportElement,
|
||||
@@ -43,8 +40,6 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
|
||||
private _firstActionItem: LinkedMenuItem | null = null;
|
||||
|
||||
private readonly _keyboardController = new VirtualKeyboardController(this);
|
||||
|
||||
private readonly _linkedDocGroup$ = signal<LinkedMenuGroup[]>([]);
|
||||
|
||||
private readonly _renderGroup = (group: LinkedMenuGroup) => {
|
||||
@@ -159,11 +154,8 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
);
|
||||
}
|
||||
|
||||
get virtualKeyboardControllerConfig(): VirtualKeyboardControllerConfig {
|
||||
return {
|
||||
useScreenHeight: this.context.config.mobile.useScreenHeight ?? false,
|
||||
inputElement: this.rootComponent,
|
||||
};
|
||||
get keyboard() {
|
||||
return this.context.std.get(VirtualKeyboardProvider);
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
@@ -230,8 +222,8 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
if (!this._keyboardController.opened) {
|
||||
this._keyboardController.show();
|
||||
if (!this.keyboard.visible$.value) {
|
||||
this.keyboard.show();
|
||||
}
|
||||
this._scrollInputToTop();
|
||||
}
|
||||
@@ -244,11 +236,7 @@ export class AffineMobileLinkedDocMenu extends SignalWatcher(
|
||||
|
||||
this._firstActionItem = resolveSignal(groups[0].items)[0];
|
||||
|
||||
this.style.bottom =
|
||||
this.context.config.mobile.useScreenHeight &&
|
||||
this._keyboardController.opened
|
||||
? '0px'
|
||||
: `max(0px, ${this._keyboardController.keyboardHeight}px)`;
|
||||
this.style.bottom = `${this.keyboard.height$.value}px`;
|
||||
|
||||
return html`
|
||||
${join(groups.map(this._renderGroup), html`<div class="divider"></div>`)}
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"./date-picker": "./src/date-picker/index.ts",
|
||||
"./drop-indicator": "./src/drop-indicator/index.ts",
|
||||
"./filterable-list": "./src/filterable-list/index.ts",
|
||||
"./virtual-keyboard": "./src/virtual-keyboard/index.ts",
|
||||
"./toggle-button": "./src/toggle-button/index.ts",
|
||||
"./toggle-switch": "./src/toggle-switch/index.ts",
|
||||
"./notification": "./src/notification/index.ts",
|
||||
|
||||
@@ -153,6 +153,10 @@ export class DocTitle extends WithDisposable(ShadowlessElement) {
|
||||
return this._richTextElement.inlineEditor;
|
||||
}
|
||||
|
||||
get inlineEditorContainer() {
|
||||
return this._richTextElement.inlineEditorContainer;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
import { IS_IOS } from '@blocksuite/global/env';
|
||||
import type * as GlobalTypes from '@blocksuite/global/types';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
||||
|
||||
declare type _GLOBAL_ = typeof GlobalTypes;
|
||||
|
||||
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 readonly _disposables = new DisposableGroup();
|
||||
|
||||
private readonly _keyboardHeight$ = signal(0);
|
||||
|
||||
private readonly _keyboardOpened$ = signal(false);
|
||||
|
||||
private readonly _storeInitialInputElementAttributes = () => {
|
||||
const { inputElement } = this.config;
|
||||
if (navigator.virtualKeyboard) {
|
||||
const { overlaysContent } = navigator.virtualKeyboard;
|
||||
const { virtualKeyboardPolicy } = inputElement;
|
||||
|
||||
this._disposables.add(() => {
|
||||
if (!navigator.virtualKeyboard) return;
|
||||
navigator.virtualKeyboard.overlaysContent = overlaysContent;
|
||||
inputElement.virtualKeyboardPolicy = virtualKeyboardPolicy;
|
||||
});
|
||||
} else if (visualViewport) {
|
||||
const { inputMode } = inputElement;
|
||||
this._disposables.add(() => {
|
||||
inputElement.inputMode = inputMode;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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 -
|
||||
(IS_IOS ? 0 : visualViewport.offsetTop);
|
||||
} else {
|
||||
notSupportedWarning();
|
||||
}
|
||||
};
|
||||
|
||||
hide = () => {
|
||||
if (navigator.virtualKeyboard) {
|
||||
navigator.virtualKeyboard.hide();
|
||||
} else {
|
||||
this.config.inputElement.inputMode = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
host: ReactiveControllerHost & {
|
||||
virtualKeyboardControllerConfig: VirtualKeyboardControllerConfig;
|
||||
hasUpdated: boolean;
|
||||
};
|
||||
|
||||
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() {
|
||||
this._storeInitialInputElementAttributes();
|
||||
|
||||
const { inputElement } = this.config;
|
||||
|
||||
if (navigator.virtualKeyboard) {
|
||||
navigator.virtualKeyboard.overlaysContent = true;
|
||||
this.config.inputElement.virtualKeyboardPolicy = 'manual';
|
||||
|
||||
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(inputElement, 'focus', this.show);
|
||||
this._disposables.addFromEvent(inputElement, 'blur', this.hide);
|
||||
|
||||
this._updateKeyboardHeight();
|
||||
}
|
||||
|
||||
hostDisconnected() {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './controller.js';
|
||||
@@ -18,3 +18,4 @@ export * from './quick-search-service';
|
||||
export * from './sidebar-service';
|
||||
export * from './telemetry-service';
|
||||
export * from './theme-service';
|
||||
export * from './virtual-keyboard-service';
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
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');
|
||||
Reference in New Issue
Block a user