focusTracker 100%

This commit is contained in:
kangwei 2020-05-07 16:14:12 +08:00
parent e21a74a15e
commit eb26a10b09
7 changed files with 147 additions and 107 deletions

View File

@ -1,6 +1,5 @@
{
"entry": {
"index": "src/vision/index.ts",
"vision-preset": "../vision-preset/src/index.ts",
"react-simulator-renderer": "../react-simulator-renderer/src/index.ts"
},

View File

@ -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 Viewport from './viewport';
import { createSimulator } from './create-simulator';
@ -214,6 +214,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
// bind hotkey & clipboard
hotkey.mount(this._contentWindow);
focusTracker.mount(this._contentWindow);
clipboard.injectCopyPaster(this._contentDocument);
// TODO: dispose the bindings
}

View 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);

View File

@ -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();
}
}
}

View File

@ -2,3 +2,4 @@ export * from './get-public-path';
export * from './goldlog';
export * from './obx';
export * from './request';
export * from './focus-tracker';

View File

@ -1,6 +1,6 @@
import { Component, Fragment } from 'react';
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 Area from '../area';
import Panel from '../widget/panel';
@ -12,7 +12,7 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
}
private dispose?: () => void;
// private focusing?: FocusingItem;
private focusing?: Focusable;
private shell: HTMLElement | null = null;
componentDidMount() {
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);
}
/*
this.focusing = focusingTrack.create(this.shell!, {
this.focusing = focusTracker.create({
range: this.shell!,
onEsc: () => {
this.props.area.setVisible(false);
},
onBlur: () => {
this.props.area.setVisible(false);
},
// modal: boolean
});
*/
this.onEffect();
}
onEffect() {
/*
const { area } = this.props;
if (area.visible) {
this.focusing?.active();
} else {
this.focusing?.suspense();
}
*/
}
componentDidUpdate() {
@ -53,7 +49,7 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
}
componentWillUnmount() {
// this.focusing?.purge();
this.focusing?.purge();
this.dispose?.();
}

View File

@ -1,6 +1,6 @@
import { isObject } from './is-object';
export function isPlainObject(value: any): value is object {
export function isPlainObject(value: any): value is any {
if (!isObject(value)) {
return false;
}