mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-24 18:58:11 +00:00
focusTracker 100%
This commit is contained in:
parent
e21a74a15e
commit
eb26a10b09
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"entry": {
|
"entry": {
|
||||||
"index": "src/vision/index.ts",
|
|
||||||
"vision-preset": "../vision-preset/src/index.ts",
|
"vision-preset": "../vision-preset/src/index.ts",
|
||||||
"react-simulator-renderer": "../react-simulator-renderer/src/index.ts"
|
"react-simulator-renderer": "../react-simulator-renderer/src/index.ts"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { obx, autorun, computed, getPublicPath, hotkey } from '@ali/lowcode-editor-core';
|
import { obx, autorun, computed, getPublicPath, hotkey, focusTracker } from '@ali/lowcode-editor-core';
|
||||||
import { ISimulatorHost, Component, NodeInstance, ComponentInstance } from '../simulator';
|
import { ISimulatorHost, Component, NodeInstance, ComponentInstance } from '../simulator';
|
||||||
import Viewport from './viewport';
|
import Viewport from './viewport';
|
||||||
import { createSimulator } from './create-simulator';
|
import { createSimulator } from './create-simulator';
|
||||||
@ -214,6 +214,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
|||||||
|
|
||||||
// bind hotkey & clipboard
|
// bind hotkey & clipboard
|
||||||
hotkey.mount(this._contentWindow);
|
hotkey.mount(this._contentWindow);
|
||||||
|
focusTracker.mount(this._contentWindow);
|
||||||
clipboard.injectCopyPaster(this._contentDocument);
|
clipboard.injectCopyPaster(this._contentDocument);
|
||||||
// TODO: dispose the bindings
|
// TODO: dispose the bindings
|
||||||
}
|
}
|
||||||
|
|||||||
138
packages/editor-core/src/utils/focus-tracker.ts
Normal file
138
packages/editor-core/src/utils/focus-tracker.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
export class FocusTracker {
|
||||||
|
mount(win: Window) {
|
||||||
|
const checkDown = (e: MouseEvent) => {
|
||||||
|
if (this.checkModalDown(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.first && !this.first.internalCheckInRange(e)) {
|
||||||
|
this.internalSuspenseItem(this.first);
|
||||||
|
this.first.internalTriggerBlur();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
win.document.addEventListener('mousedown', checkDown, true);
|
||||||
|
return () => {
|
||||||
|
win.document.removeEventListener('mousedown', checkDown, true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private actives: Focusable[] = [];
|
||||||
|
get first() {
|
||||||
|
return this.actives[0];
|
||||||
|
}
|
||||||
|
private modals: Array<{ checkDown: (e: MouseEvent) => boolean; checkOpen: () => boolean }> = [];
|
||||||
|
addModal(checkDown: (e: MouseEvent) => boolean, checkOpen: () => boolean) {
|
||||||
|
this.modals.push({
|
||||||
|
checkDown,
|
||||||
|
checkOpen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private checkModalOpen(): boolean {
|
||||||
|
return this.modals.some(item => item.checkOpen());
|
||||||
|
}
|
||||||
|
private checkModalDown(e: MouseEvent): boolean {
|
||||||
|
return this.modals.some(item => item.checkDown(e));
|
||||||
|
}
|
||||||
|
execSave() {
|
||||||
|
// has Modal return;
|
||||||
|
if (this.checkModalOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// catch
|
||||||
|
if (this.first) {
|
||||||
|
this.first.internalTriggerSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
execEsc() {
|
||||||
|
if (this.first) {
|
||||||
|
this.internalSuspenseItem(this.first);
|
||||||
|
this.first.internalTriggerEsc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
create(config: FocusableConfig) {
|
||||||
|
return new Focusable(this, config);
|
||||||
|
}
|
||||||
|
internalActiveItem(item: Focusable) {
|
||||||
|
const first = this.actives[0];
|
||||||
|
if (first === item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const i = this.actives.indexOf(item);
|
||||||
|
if (i > -1) {
|
||||||
|
this.actives.splice(i, 1);
|
||||||
|
}
|
||||||
|
this.actives.unshift(item);
|
||||||
|
if (!item.isModal && first) {
|
||||||
|
// trigger Blur
|
||||||
|
first.internalTriggerBlur();
|
||||||
|
}
|
||||||
|
// trigger onActive
|
||||||
|
item.internalTriggerActive();
|
||||||
|
}
|
||||||
|
internalSuspenseItem(item: Focusable) {
|
||||||
|
const i = this.actives.indexOf(item);
|
||||||
|
if (i > -1) {
|
||||||
|
this.actives.splice(i, 1);
|
||||||
|
this.first?.internalTriggerActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FocusableConfig {
|
||||||
|
range: HTMLElement | ((e: MouseEvent) => boolean);
|
||||||
|
modal?: boolean; // 模态窗口级别
|
||||||
|
onEsc?: () => void;
|
||||||
|
onBlur?: () => void;
|
||||||
|
onSave?: () => void;
|
||||||
|
onActive?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Focusable {
|
||||||
|
readonly isModal: boolean;
|
||||||
|
constructor(private tracker: FocusTracker, private config: FocusableConfig) {
|
||||||
|
this.isModal = config.modal == null ? false : config.modal;
|
||||||
|
}
|
||||||
|
active() {
|
||||||
|
this.tracker.internalActiveItem(this);
|
||||||
|
}
|
||||||
|
suspense() {
|
||||||
|
this.tracker.internalSuspenseItem(this);
|
||||||
|
}
|
||||||
|
purge() {
|
||||||
|
this.tracker.internalSuspenseItem(this);
|
||||||
|
}
|
||||||
|
internalCheckInRange(e: MouseEvent) {
|
||||||
|
const { range } = this.config;
|
||||||
|
if (!range) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof range === 'function') {
|
||||||
|
return range(e);
|
||||||
|
}
|
||||||
|
return range.contains(e.target as HTMLElement);
|
||||||
|
}
|
||||||
|
internalTriggerBlur() {
|
||||||
|
if (this.config.onBlur) {
|
||||||
|
this.config.onBlur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internalTriggerSave() {
|
||||||
|
if (this.config.onSave) {
|
||||||
|
this.config.onSave();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
internalTriggerEsc() {
|
||||||
|
if (this.config.onEsc) {
|
||||||
|
this.config.onEsc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internalTriggerActive() {
|
||||||
|
if (this.config.onActive) {
|
||||||
|
this.config.onActive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const focusTracker = new FocusTracker();
|
||||||
|
|
||||||
|
focusTracker.mount(window);
|
||||||
@ -1,95 +0,0 @@
|
|||||||
import { hotkey } from '../hotkey';
|
|
||||||
|
|
||||||
class FocusingManager {
|
|
||||||
deploy() {
|
|
||||||
// in
|
|
||||||
hotkey.bind('esc', () => {
|
|
||||||
// do esc
|
|
||||||
});
|
|
||||||
hotkey.bind(['command + s', 'ctrl + s'], () => {
|
|
||||||
// do save
|
|
||||||
// do esc
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private actives: Focusable[] = [];
|
|
||||||
send(e: MouseEvent) {
|
|
||||||
// if keyborad event check is esc or
|
|
||||||
}
|
|
||||||
addModalCheck(check) {
|
|
||||||
|
|
||||||
}
|
|
||||||
create(config: FocusableConfig) {
|
|
||||||
|
|
||||||
}
|
|
||||||
internalActiveItem(item: Focusable) {
|
|
||||||
const first = this.actives[0];
|
|
||||||
if (first === item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const i = this.actives.indexOf(item);
|
|
||||||
if (i > -1) {
|
|
||||||
this.actives.splice(i, 1);
|
|
||||||
}
|
|
||||||
this.actives.unshift(item);
|
|
||||||
if (!item.isModal && first) {
|
|
||||||
// trigger Blur
|
|
||||||
first.internalTriggerBlur();
|
|
||||||
}
|
|
||||||
// trigger onActive
|
|
||||||
}
|
|
||||||
internalSuspenceItem(item: Focusable) {
|
|
||||||
const i = this.actives.indexOf(item);
|
|
||||||
if (i > -1) {
|
|
||||||
this.actives.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FocusableConfig {
|
|
||||||
range: HTMLElement | ((e: MouseEvent) => boolean);
|
|
||||||
modal?: boolean;
|
|
||||||
onEsc?: () => void;
|
|
||||||
onBlur?: () => void;
|
|
||||||
onSave?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Focusable {
|
|
||||||
readonly isModal: boolean;
|
|
||||||
constructor(private manager: FocusingManager, private config: FocusableConfig) {
|
|
||||||
this.isModal = config.modal == null ? false : config.modal;
|
|
||||||
}
|
|
||||||
active() {
|
|
||||||
this.manager.internalActiveItem(this);
|
|
||||||
}
|
|
||||||
suspence() {
|
|
||||||
this.manager.internalSuspenceItem(this);
|
|
||||||
}
|
|
||||||
purge() {
|
|
||||||
this.manager.internalSuspenceItem(this);
|
|
||||||
}
|
|
||||||
internalCheckInRange(e: MouseEvent) {
|
|
||||||
const { range } = this.config;
|
|
||||||
if (!range) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (typeof range === 'function') {
|
|
||||||
return range(e);
|
|
||||||
}
|
|
||||||
return range.contains(e.target as HTMLElement);
|
|
||||||
}
|
|
||||||
internalTriggerBlur() {
|
|
||||||
if (this.config.onBlur) {
|
|
||||||
this.config.onBlur();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internalTriggerSave() {
|
|
||||||
if (this.config.onSave) {
|
|
||||||
this.config.onSave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internalTriggerEsc() {
|
|
||||||
if (this.config.onEsc) {
|
|
||||||
this.config.onEsc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,3 +2,4 @@ export * from './get-public-path';
|
|||||||
export * from './goldlog';
|
export * from './goldlog';
|
||||||
export * from './obx';
|
export * from './obx';
|
||||||
export * from './request';
|
export * from './request';
|
||||||
|
export * from './focus-tracker';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Component, Fragment } from 'react';
|
import { Component, Fragment } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { observer } from '@ali/lowcode-editor-core';
|
import { observer, Focusable, focusTracker } from '@ali/lowcode-editor-core';
|
||||||
import { Button, Icon } from '@alifd/next';
|
import { Button, Icon } from '@alifd/next';
|
||||||
import Area from '../area';
|
import Area from '../area';
|
||||||
import Panel from '../widget/panel';
|
import Panel from '../widget/panel';
|
||||||
@ -12,7 +12,7 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private dispose?: () => void;
|
private dispose?: () => void;
|
||||||
// private focusing?: FocusingItem;
|
private focusing?: Focusable;
|
||||||
private shell: HTMLElement | null = null;
|
private shell: HTMLElement | null = null;
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { area } = this.props;
|
const { area } = this.props;
|
||||||
@ -22,30 +22,26 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
|
|||||||
area.skeleton.editor.removeListener('designer.dragstart', triggerClose);
|
area.skeleton.editor.removeListener('designer.dragstart', triggerClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
this.focusing = focusTracker.create({
|
||||||
this.focusing = focusingTrack.create(this.shell!, {
|
range: this.shell!,
|
||||||
onEsc: () => {
|
onEsc: () => {
|
||||||
this.props.area.setVisible(false);
|
this.props.area.setVisible(false);
|
||||||
},
|
},
|
||||||
onBlur: () => {
|
onBlur: () => {
|
||||||
this.props.area.setVisible(false);
|
this.props.area.setVisible(false);
|
||||||
},
|
},
|
||||||
// modal: boolean
|
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
this.onEffect();
|
this.onEffect();
|
||||||
}
|
}
|
||||||
|
|
||||||
onEffect() {
|
onEffect() {
|
||||||
/*
|
|
||||||
const { area } = this.props;
|
const { area } = this.props;
|
||||||
if (area.visible) {
|
if (area.visible) {
|
||||||
this.focusing?.active();
|
this.focusing?.active();
|
||||||
} else {
|
} else {
|
||||||
this.focusing?.suspense();
|
this.focusing?.suspense();
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
@ -53,7 +49,7 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
// this.focusing?.purge();
|
this.focusing?.purge();
|
||||||
this.dispose?.();
|
this.dispose?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { isObject } from './is-object';
|
import { isObject } from './is-object';
|
||||||
|
|
||||||
export function isPlainObject(value: any): value is object {
|
export function isPlainObject(value: any): value is any {
|
||||||
if (!isObject(value)) {
|
if (!isObject(value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user