diff --git a/packages/demo/build.json b/packages/demo/build.json index 376c27c63..a6719240e 100644 --- a/packages/demo/build.json +++ b/packages/demo/build.json @@ -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" }, diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index e90056a67..82e779759 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.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 Viewport from './viewport'; import { createSimulator } from './create-simulator'; @@ -214,6 +214,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost { + 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); diff --git a/packages/editor-core/src/utils/focusing-track.ts b/packages/editor-core/src/utils/focusing-track.ts deleted file mode 100644 index 3e04d0000..000000000 --- a/packages/editor-core/src/utils/focusing-track.ts +++ /dev/null @@ -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(); - } - } -} diff --git a/packages/editor-core/src/utils/index.ts b/packages/editor-core/src/utils/index.ts index 8e2f1ccfa..6a4ef399a 100644 --- a/packages/editor-core/src/utils/index.ts +++ b/packages/editor-core/src/utils/index.ts @@ -2,3 +2,4 @@ export * from './get-public-path'; export * from './goldlog'; export * from './obx'; export * from './request'; +export * from './focus-tracker'; diff --git a/packages/editor-skeleton/src/layouts/left-float-pane.tsx b/packages/editor-skeleton/src/layouts/left-float-pane.tsx index 62c1bc3d5..c28d7af91 100644 --- a/packages/editor-skeleton/src/layouts/left-float-pane.tsx +++ b/packages/editor-skeleton/src/layouts/left-float-pane.tsx @@ -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 }> } 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 }> 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 }> } componentWillUnmount() { - // this.focusing?.purge(); + this.focusing?.purge(); this.dispose?.(); } diff --git a/packages/utils/src/is-plain-object.ts b/packages/utils/src/is-plain-object.ts index 59260d6cb..17bc6266f 100644 --- a/packages/utils/src/is-plain-object.ts +++ b/packages/utils/src/is-plain-object.ts @@ -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; }