mirror of
https://github.com/dream-num/univer.git
synced 2026-10-02 04:24:34 +08:00
revert(ui): restore unit-scoped runtime routing (#7472)
This commit is contained in:
@@ -28,7 +28,6 @@ export const FOCUSING_SHEET = 'FOCUSING_SHEET';
|
||||
export const FOCUSING_DOC = 'FOCUSING_DOC';
|
||||
export const FOCUSING_SLIDE = 'FOCUSING_SLIDE';
|
||||
export const FOCUSING_BOARD = 'FOCUSING_BOARD';
|
||||
export const FOCUSING_BASE = 'FOCUSING_BASE';
|
||||
|
||||
export const EDITOR_ACTIVATED = 'EDITOR_ACTIVATED';
|
||||
export const FOCUSING_EDITOR_INPUT_FORMULA = 'FOCUSING_EDITOR_INPUT_FORMULA';
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { UIRuntimeScopeService } from '../ui-runtime-scope.service';
|
||||
|
||||
describe('UIRuntimeScopeService', () => {
|
||||
it('resolves concurrent runtimes for the same unit from their DOM roots', () => {
|
||||
const service = new UIRuntimeScopeService();
|
||||
const floatingRoot = document.createElement('div');
|
||||
const fullscreenRoot = document.createElement('div');
|
||||
const floatingTarget = document.createElement('textarea');
|
||||
const fullscreenTarget = document.createElement('textarea');
|
||||
floatingRoot.appendChild(floatingTarget);
|
||||
fullscreenRoot.appendChild(fullscreenTarget);
|
||||
|
||||
const floatingScope = {
|
||||
unitId: 'base-1',
|
||||
root: floatingRoot,
|
||||
has: () => false,
|
||||
get<T>(): T {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
};
|
||||
const fullscreenScope = {
|
||||
unitId: 'base-1',
|
||||
root: fullscreenRoot,
|
||||
has: () => false,
|
||||
get<T>(): T {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
};
|
||||
const floatingDisposable = service.register(floatingScope);
|
||||
const fullscreenDisposable = service.register(fullscreenScope);
|
||||
|
||||
expect(service.get('base-1')).toBe(fullscreenScope);
|
||||
expect(service.get('base-1', floatingTarget)).toBe(floatingScope);
|
||||
expect(service.get('base-1', fullscreenTarget)).toBe(fullscreenScope);
|
||||
|
||||
fullscreenDisposable.dispose();
|
||||
expect(service.get('base-1')).toBe(floatingScope);
|
||||
floatingDisposable.dispose();
|
||||
expect(service.get('base-1')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('keeps the unit id as the runtime boundary when resolving an event target', () => {
|
||||
const service = new UIRuntimeScopeService();
|
||||
const baseRoot = document.createElement('div');
|
||||
const sheetRoot = document.createElement('div');
|
||||
const sheetTarget = document.createElement('textarea');
|
||||
sheetRoot.appendChild(sheetTarget);
|
||||
|
||||
const baseScope = {
|
||||
unitId: 'base-1',
|
||||
root: baseRoot,
|
||||
has: () => false,
|
||||
get<T>(): T {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
};
|
||||
const sheetScope = {
|
||||
unitId: 'sheet-1',
|
||||
root: sheetRoot,
|
||||
has: () => false,
|
||||
get<T>(): T {
|
||||
throw new Error('not implemented');
|
||||
},
|
||||
};
|
||||
service.register(baseScope);
|
||||
service.register(sheetScope);
|
||||
|
||||
expect(service.get('base-1', sheetTarget)).toBe(baseScope);
|
||||
expect(service.get('sheet-1', sheetTarget)).toBe(sheetScope);
|
||||
expect(service.get(undefined, sheetTarget)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -14,54 +14,36 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { DependencyIdentifier, IDisposable } from '@univerjs/core';
|
||||
import type { IDisposable } from '@univerjs/core';
|
||||
import { createIdentifier, Disposable, toDisposable } from '@univerjs/core';
|
||||
|
||||
export interface IUIRuntimeScope {
|
||||
unitId: string;
|
||||
root?: HTMLElement;
|
||||
has<T>(identifier: DependencyIdentifier<T>): boolean;
|
||||
get<T>(identifier: DependencyIdentifier<T>): T;
|
||||
has(identifier: unknown): boolean;
|
||||
get<T = unknown>(identifier: unknown): T;
|
||||
}
|
||||
|
||||
export interface IUIRuntimeScopeService {
|
||||
register(scope: IUIRuntimeScope): IDisposable;
|
||||
get(unitId: string | null | undefined, target?: EventTarget | null): IUIRuntimeScope | undefined;
|
||||
get(unitId: string | null | undefined): IUIRuntimeScope | undefined;
|
||||
}
|
||||
|
||||
export const IUIRuntimeScopeService = createIdentifier<IUIRuntimeScopeService>('ui.runtime-scope.service');
|
||||
|
||||
export class UIRuntimeScopeService extends Disposable implements IUIRuntimeScopeService {
|
||||
private readonly _scopes = new Map<string, IUIRuntimeScope[]>();
|
||||
private readonly _scopes = new Map<string, IUIRuntimeScope>();
|
||||
|
||||
register(scope: IUIRuntimeScope): IDisposable {
|
||||
const scopes = this._scopes.get(scope.unitId) ?? [];
|
||||
scopes.push(scope);
|
||||
this._scopes.set(scope.unitId, scopes);
|
||||
this._scopes.set(scope.unitId, scope);
|
||||
|
||||
return toDisposable(() => {
|
||||
const registeredScopes = this._scopes.get(scope.unitId);
|
||||
const index = registeredScopes?.lastIndexOf(scope) ?? -1;
|
||||
if (index >= 0) {
|
||||
registeredScopes?.splice(index, 1);
|
||||
}
|
||||
if (!registeredScopes?.length) {
|
||||
if (this._scopes.get(scope.unitId) === scope) {
|
||||
this._scopes.delete(scope.unitId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get(unitId: string | null | undefined, target?: EventTarget | null): IUIRuntimeScope | undefined {
|
||||
const scopes = unitId ? this._scopes.get(unitId) : undefined;
|
||||
if (scopes && typeof Node !== 'undefined' && target instanceof Node) {
|
||||
for (let index = scopes.length - 1; index >= 0; index--) {
|
||||
const scope = scopes[index];
|
||||
if (scope.root?.contains(target)) {
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scopes?.[scopes.length - 1];
|
||||
get(unitId: string | null | undefined): IUIRuntimeScope | undefined {
|
||||
return unitId ? this._scopes.get(unitId) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
|
||||
import { FOCUSING_BASE, ICommandService, IContextService, Injector } from '@univerjs/core';
|
||||
import { ICommandService, IContextService, Injector } from '@univerjs/core';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { EMBED_CHILD_UNIT_ID_ATTRIBUTE } from '../../../utils/embed-boundary';
|
||||
import { ILayoutService } from '../../layout/layout.service';
|
||||
@@ -304,7 +304,7 @@ describe('ShortcutService', () => {
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
|
||||
expect(getRuntimeScope).toHaveBeenCalledWith('child-board', canvas);
|
||||
expect(getRuntimeScope).toHaveBeenCalledWith('child-board');
|
||||
expect(scopedContextService.getContextValue).toHaveBeenCalledWith('FOCUSING_BOARD');
|
||||
expect(scopedExecuteCommand).toHaveBeenCalledWith('board.operation.grouping-shortcut', undefined);
|
||||
expect(executeCommand).not.toHaveBeenCalled();
|
||||
@@ -312,44 +312,6 @@ describe('ShortcutService', () => {
|
||||
service.dispose();
|
||||
});
|
||||
|
||||
it('uses the runtime that owns the event target when the same child unit has multiple runtimes', () => {
|
||||
const runtime = document.createElement('div');
|
||||
const canvas = document.createElement('canvas');
|
||||
runtime.setAttribute(EMBED_CHILD_UNIT_ID_ATTRIBUTE, 'child-base');
|
||||
runtime.appendChild(canvas);
|
||||
|
||||
const scopedExecuteCommand = vi.fn(() => Promise.resolve(true));
|
||||
const scopedContextService = {
|
||||
getContextValue: vi.fn((key: string) => key === FOCUSING_BASE),
|
||||
};
|
||||
const targetRuntimeScope = {
|
||||
has: vi.fn(() => true),
|
||||
get: vi.fn((identifier: unknown) => identifier === IContextService
|
||||
? scopedContextService
|
||||
: { executeCommand: scopedExecuteCommand }),
|
||||
};
|
||||
const { service, executeCommand, getRuntimeScope } = createService();
|
||||
getRuntimeScope.mockReturnValue(targetRuntimeScope);
|
||||
service.registerShortcut({
|
||||
id: 'base.operation.edit-cell',
|
||||
binding: KeyCode.ENTER,
|
||||
preconditions: (contextService) => contextService.getContextValue(FOCUSING_BASE),
|
||||
});
|
||||
|
||||
const event = createKeyboardEvent(KeyCode.ENTER);
|
||||
Object.defineProperty(event, 'target', {
|
||||
configurable: true,
|
||||
get: () => canvas,
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
|
||||
expect(getRuntimeScope).toHaveBeenCalledWith('child-base', canvas);
|
||||
expect(scopedExecuteCommand).toHaveBeenCalledWith('base.operation.edit-cell', undefined);
|
||||
expect(executeCommand).not.toHaveBeenCalled();
|
||||
expect(event.defaultPrevented).toBe(true);
|
||||
service.dispose();
|
||||
});
|
||||
|
||||
it('lets embed-owned native text editors handle select-all without dispatching Univer shortcuts', () => {
|
||||
const embedRoot = document.createElement('div');
|
||||
embedRoot.setAttribute('data-embed-interaction-boundary-owner', 'embed-1');
|
||||
@@ -422,7 +384,7 @@ describe('ShortcutService', () => {
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
|
||||
expect(getRuntimeScope).toHaveBeenCalledWith('child-doc', textEditor);
|
||||
expect(getRuntimeScope).toHaveBeenCalledWith('child-doc');
|
||||
expect(scopedExecuteCommand).toHaveBeenCalledWith('doc.command.select-all', undefined);
|
||||
expect(executeCommand).not.toHaveBeenCalled();
|
||||
expect(event.defaultPrevented).toBe(true);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { DependencyIdentifier, IDisposable } from '@univerjs/core';
|
||||
import type { IDisposable } from '@univerjs/core';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { createIdentifier, Disposable, ICommandService, IContextService, Optional, toDisposable } from '@univerjs/core';
|
||||
import { Subject } from 'rxjs';
|
||||
@@ -397,8 +397,9 @@ export class ShortcutService extends Disposable implements IShortcutService {
|
||||
return isNativeTextEditor && isEmbedBoundaryTarget(target);
|
||||
}
|
||||
|
||||
private _getRuntimeService<T>(event: KeyboardEvent, identifier: DependencyIdentifier<T>): T | undefined {
|
||||
const runtimeScope = this._runtimeScopeService.get(getEmbedChildUnitId(event.target), event.target);
|
||||
private _getRuntimeService<T>(event: KeyboardEvent, identifier: unknown): T | undefined {
|
||||
const childUnitId = getEmbedChildUnitId(event.target);
|
||||
const runtimeScope = this._runtimeScopeService.get(childUnitId);
|
||||
return runtimeScope?.has(identifier) ? runtimeScope.get<T>(identifier) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user