mirror of
https://github.com/dream-num/univer.git
synced 2026-09-24 15:32:18 +08:00
feat(ui): export shortcut facade API (#4324)
This commit is contained in:
@@ -53,8 +53,8 @@ import '@univerjs/sheets-filter/facade';
|
||||
import '@univerjs/sheets-formula/facade';
|
||||
import '@univerjs/sheets-numfmt/facade';
|
||||
import '@univerjs/sheets-hyper-link-ui/facade';
|
||||
import '@univerjs/sheets-thread-comment/facade';
|
||||
|
||||
import '@univerjs/sheets-thread-comment/facade';
|
||||
import '@univerjs/sheets-conditional-formatting/facade';
|
||||
import '../global.css';
|
||||
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export function onLoad(univerAPI) {
|
||||
let mode = 'normal';
|
||||
|
||||
let label;
|
||||
function setMode(m) {
|
||||
mode = m;
|
||||
if (!label) {
|
||||
label = document.createElement('div');
|
||||
label.style.position = 'fixed';
|
||||
label.style.bottom = '36px';
|
||||
label.style.left = '0';
|
||||
label.style.borderTopRightRadius = '4px';
|
||||
label.style.backgroundColor = 'rgb(255, 255, 255)';
|
||||
label.style.zIndex = '1000000';
|
||||
label.style.paddingLeft = '8px';
|
||||
label.style.width = '120px';
|
||||
label.style.fontSize = '14px';
|
||||
label.style.fontFamily = 'monospace';
|
||||
|
||||
document.body.append(label);
|
||||
}
|
||||
|
||||
label.textContent = `Vim: ${mode}`;
|
||||
switch (mode) {
|
||||
case 'normal':
|
||||
label.style.color = 'rgb(115, 141, 242)';
|
||||
break;
|
||||
case 'insert':
|
||||
label.style.color = 'rgb(223, 93, 0)';
|
||||
break;
|
||||
case 'visual':
|
||||
label.style.color = 'rgb(139, 187, 17)';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setMode('normal');
|
||||
|
||||
univerAPI.getShortcut().disableShortcut();
|
||||
|
||||
univerAPI.onCommandExecuted((command) => {
|
||||
if (command.id.startsWith('sheet.operation.set-cell-edit-visible')) {
|
||||
if (command.params.visible) {
|
||||
setMode('insert');
|
||||
} else {
|
||||
setMode('normal');
|
||||
}
|
||||
} else if (command.id === 'sheet.operation.set-selections') {
|
||||
if (command.params.selections.length > 1) {
|
||||
setMode('visual');
|
||||
return;
|
||||
}
|
||||
|
||||
const length = command.params.selections.length;
|
||||
const range = command.params.selections[length - 1].range;
|
||||
const primary = command.params.selections[length - 1].primary;
|
||||
|
||||
if (
|
||||
range.startRow !== primary.startRow
|
||||
|| range.startColumn !== primary.startColumn
|
||||
|| range.endRow !== primary.endRow
|
||||
|| range.endColumn !== primary.endColumn
|
||||
) {
|
||||
setMode('visual');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (mode === 'normal') {
|
||||
if (e.key === 'i') {
|
||||
setMode('insert');
|
||||
|
||||
univerAPI.getShortcut().enableShortcut();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'j') {
|
||||
univerAPI.executeCommand('sheet.command.move-selection', { direction: 2 });
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'k') {
|
||||
univerAPI.executeCommand('sheet.command.move-selection', { direction: 0 });
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'l') {
|
||||
univerAPI.executeCommand('sheet.command.move-selection', { direction: 1 });
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'h') {
|
||||
univerAPI.executeCommand('sheet.command.move-selection', { direction: 3 });
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'v') {
|
||||
setMode('visual');
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'y') {
|
||||
univerAPI.executeCommand('univer.command.copy');
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'd') {
|
||||
univerAPI.executeCommand('univer.command.cut');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'p') {
|
||||
univerAPI.executeCommand('univer.command.paste');
|
||||
setMode('normal');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!univerAPI.getShortcut().dispatchShortcutEvent(e)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
if (mode === 'insert' && e.key === 'Escape') {
|
||||
setMode('normal');
|
||||
|
||||
univerAPI.getShortcut().disableShortcut();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (mode === 'visual') {
|
||||
if (e.key === 'j') {
|
||||
univerAPI.executeCommand('sheet.command.expand-selection', { direction: 2 });
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (e.key === 'k') {
|
||||
univerAPI.executeCommand('sheet.command.expand-selection', { direction: 0 });
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (e.key === 'l') {
|
||||
univerAPI.executeCommand('sheet.command.expand-selection', { direction: 1 });
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (e.key === 'h') {
|
||||
univerAPI.executeCommand('sheet.command.expand-selection', { direction: 3 });
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (e.key === 'y') {
|
||||
univerAPI.executeCommand('univer.command.copy');
|
||||
|
||||
setMode('normal');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'd') {
|
||||
univerAPI.executeCommand('univer.command.cut');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'p') {
|
||||
univerAPI.executeCommand('univer.command.paste');
|
||||
setMode('normal');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
setMode('normal');
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!univerAPI.getShortcut().dispatchShortcutEvent(e)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -14,16 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Direction } from '@univerjs/core';
|
||||
import type { IShortcutItem } from '@univerjs/ui';
|
||||
import { KeyCode, MetaKeys } from '@univerjs/ui';
|
||||
|
||||
import type {
|
||||
IExpandSelectionCommandParams,
|
||||
IMoveSelectionCommandParams,
|
||||
IMoveSelectionEnterAndTabCommandParams,
|
||||
ISelectAllCommandParams,
|
||||
} from '../../commands/commands/set-selection.command';
|
||||
import { Direction } from '@univerjs/core';
|
||||
import { KeyCode, MetaKeys } from '@univerjs/ui';
|
||||
import {
|
||||
ExpandSelectionCommand,
|
||||
JumpOver,
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { toDisposable } from '@univerjs/core';
|
||||
import type { IDisposable } from '@univerjs/core';
|
||||
import { toDisposable } from '@univerjs/core';
|
||||
|
||||
export function fromGlobalEvent<K extends keyof WindowEventMap>(
|
||||
type: K,
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IDisposable } from '@univerjs/core';
|
||||
import type { IShortcutItem } from '@univerjs/ui';
|
||||
import { FBase, Inject, Injector } from '@univerjs/core';
|
||||
import { IShortcutService } from '@univerjs/ui';
|
||||
|
||||
export class FShortcut extends FBase {
|
||||
private _forceEscapeDisposable: IDisposable | null = null;
|
||||
|
||||
constructor(
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@IShortcutService protected readonly _shortcutService: IShortcutService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
enableShortcut(): void {
|
||||
this._forceEscapeDisposable?.dispose();
|
||||
this._forceEscapeDisposable = null;
|
||||
}
|
||||
|
||||
disableShortcut(): void {
|
||||
if (!this._forceEscapeDisposable) {
|
||||
this._forceEscapeDisposable = this._shortcutService.forceEscape();
|
||||
}
|
||||
}
|
||||
|
||||
dispatchShortcutEvent(e: KeyboardEvent): IShortcutItem<object> | undefined {
|
||||
return this._shortcutService.dispatch(e);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import type { IFacadeMenuItem, IFacadeSubmenuItem } from './f-menu-builder';
|
||||
import { FUniver } from '@univerjs/core';
|
||||
import { ComponentManager, CopyCommand, IDialogService, IMessageService, ISidebarService, PasteCommand } from '@univerjs/ui';
|
||||
import { FMenu, FSubmenu } from './f-menu-builder';
|
||||
import { FShortcut } from './f-shortcut';
|
||||
|
||||
export interface IFUniverUIMixin {
|
||||
/**
|
||||
@@ -28,6 +29,10 @@ export interface IFUniverUIMixin {
|
||||
* @returns the [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) object
|
||||
*/
|
||||
getURL(): URL;
|
||||
/**
|
||||
* Get the Shortcut handler to interact with Univer's shortcut functionalities.
|
||||
*/
|
||||
getShortcut(): FShortcut;
|
||||
/**
|
||||
* Copy the current selected content of the currently focused unit into your system clipboard.
|
||||
*/
|
||||
@@ -115,6 +120,10 @@ export class FUniverUIMixin extends FUniver implements IFUniverUIMixin {
|
||||
return new URL(window.location.href);
|
||||
}
|
||||
|
||||
override getShortcut(): FShortcut {
|
||||
return this._injector.createInstance(FShortcut);
|
||||
}
|
||||
|
||||
override copy(): Promise<boolean> {
|
||||
return this._commandService.executeCommand(CopyCommand.id);
|
||||
}
|
||||
|
||||
@@ -100,6 +100,11 @@ export interface IShortcutService {
|
||||
*/
|
||||
forceEscape(): IDisposable;
|
||||
|
||||
/**
|
||||
* Dispatch a keyboard event to the shortcut service and check if there is a shortcut that matches the event.
|
||||
* @param e - the keyboard event to be dispatched.
|
||||
*/
|
||||
dispatch(e: KeyboardEvent): IShortcutItem<object> | undefined;
|
||||
/**
|
||||
* Register a shortcut item to the shortcut service.
|
||||
* @param {IShortcutItem} shortcut - the shortcut item to be registered.
|
||||
@@ -235,6 +240,14 @@ export class ShortcutService extends Disposable implements IShortcutService {
|
||||
}
|
||||
|
||||
private _resolveKeyboardEvent(e: KeyboardEvent): void {
|
||||
const candidate = this.dispatch(e);
|
||||
if (candidate) {
|
||||
this._commandService.executeCommand(candidate.id, candidate.staticParameters);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(e: KeyboardEvent): IShortcutItem<object> | undefined {
|
||||
// Should get the container element of the Univer instance and see if
|
||||
// the event target is a descendant of the container element.
|
||||
// Also we should check through escape list and force catching list.
|
||||
@@ -247,34 +260,21 @@ export class ShortcutService extends Disposable implements IShortcutService {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldPreventDefault = this._dispatch(e);
|
||||
if (shouldPreventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private _dispatch(e: KeyboardEvent): boolean {
|
||||
const binding = this._deriveBindingFromEvent(e);
|
||||
if (binding === null) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const shortcuts = this._shortCutMapping.get(binding);
|
||||
if (shortcuts === undefined) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const shouldTrigger = Array.from(shortcuts)
|
||||
const candidateShortcut = Array.from(shortcuts)
|
||||
.sort((s1, s2) => (s2.priority ?? 0) - (s1.priority ?? 0))
|
||||
.find((s) => s.preconditions?.(this._contextService) ?? true);
|
||||
|
||||
if (shouldTrigger) {
|
||||
this._commandService.executeCommand(shouldTrigger.id, shouldTrigger.staticParameters);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return candidateShortcut;
|
||||
}
|
||||
|
||||
private _getBindingFromItem(item: IShortcutItem): number {
|
||||
|
||||
Reference in New Issue
Block a user