diff --git a/packages/stage/src/ActionManager.ts b/packages/stage/src/ActionManager.ts index 751e9f48..5420d8d6 100644 --- a/packages/stage/src/ActionManager.ts +++ b/packages/stage/src/ActionManager.ts @@ -25,6 +25,7 @@ import { Id } from '@tmagic/schema'; import { addClassName, getDocument, removeClassNameByClassName } from '@tmagic/utils'; import { CONTAINER_HIGHLIGHT_CLASS_NAME, GHOST_EL_ID_PREFIX, GuidesType, MouseButton, PAGE_CLASS } from './const'; +import DragResizeHelper from './DragResizeHelper'; import StageDragResize from './StageDragResize'; import StageHighlight from './StageHighlight'; import StageMultiDragResize from './StageMultiDragResize'; @@ -101,27 +102,30 @@ export default class ActionManager extends EventEmitter { this.getRenderDocument = config.getRenderDocument; this.isContainer = config.isContainer; + const createDrHelper = () => + new DragResizeHelper({ + container: config.container, + updateDragEl: config.updateDragEl, + }); + this.dr = new StageDragResize({ container: config.container, disabledDragStart: config.disabledDragStart, + moveableOptions: this.changeCallback(config.moveableOptions), + dragResizeHelper: createDrHelper(), getRootContainer: config.getRootContainer, getRenderDocument: config.getRenderDocument, - updateDragEl: config.updateDragEl, - markContainerEnd: () => this.markContainerEnd(), - delayedMarkContainer: (event: MouseEvent, exclude: Element[]) => { - if (this.canAddToContainer()) { - return this.delayedMarkContainer(event, exclude); - } - return undefined; - }, - moveableOptions: this.changeCallback(config.moveableOptions), + markContainerEnd: this.markContainerEnd.bind(this), + delayedMarkContainer: this.delayedMarkContainer.bind(this), }); this.multiDr = new StageMultiDragResize({ container: config.container, multiMoveableOptions: config.multiMoveableOptions, + dragResizeHelper: createDrHelper(), getRootContainer: config.getRootContainer, getRenderDocument: config.getRenderDocument, - updateDragEl: config.updateDragEl, + markContainerEnd: this.markContainerEnd.bind(this), + delayedMarkContainer: this.delayedMarkContainer.bind(this), }); this.highlightLayer = new StageHighlight({ container: config.container, @@ -320,10 +324,13 @@ export default class ActionManager extends EventEmitter { * @param excludeElList 计算鼠标所在容器时要排除的元素列表 * @returns timeoutId,调用方在鼠标移走时要取消该timeout,阻止标记 */ - public delayedMarkContainer(event: MouseEvent, excludeElList: Element[] = []): NodeJS.Timeout { - return globalThis.setTimeout(() => { - this.addContainerHighlightClassName(event, excludeElList); - }, this.containerHighlightDuration); + public delayedMarkContainer(event: MouseEvent, excludeElList: Element[] = []): NodeJS.Timeout | undefined { + if (this.canAddToContainer()) { + return globalThis.setTimeout(() => { + this.addContainerHighlightClassName(event, excludeElList); + }, this.containerHighlightDuration); + } + return undefined; } public destroy(): void { @@ -466,8 +473,8 @@ export default class ActionManager extends EventEmitter { }); this.multiDr - .on('update', (data: UpdateEventData, parentEl: HTMLElement | null) => { - this.emit('multi-update', data, parentEl); + .on('update', (data: UpdateEventData) => { + this.emit('multi-update', data); }) .on('change-to-select', async (id: Id) => { // 如果还在多选状态,不触发切换到单选 diff --git a/packages/stage/src/DragResizeHelper.ts b/packages/stage/src/DragResizeHelper.ts index 419ffa35..0053dd74 100644 --- a/packages/stage/src/DragResizeHelper.ts +++ b/packages/stage/src/DragResizeHelper.ts @@ -34,8 +34,8 @@ import MoveableHelper from 'moveable-helper'; import { DRAG_EL_ID_PREFIX, GHOST_EL_ID_PREFIX, Mode, ZIndex } from './const'; import TargetShadow from './TargetShadow'; -import { DragResizeHelperConfig, TargetElement } from './types'; -import { getAbsolutePosition, getOffset } from './util'; +import { DragResizeHelperConfig, Rect, TargetElement } from './types'; +import { calcValueByFontsize, getAbsolutePosition, getOffset } from './util'; /** * 拖拽/改变大小等操作发生时,moveable会抛出各种状态事件,DragResizeHelper负责响应这些事件,对目标节点target和拖拽节点targetShadow进行修改; @@ -282,6 +282,44 @@ export default class DragResizeHelper { this.moveableHelper.onDragGroup(e); } + public getUpdatedElRect(el: HTMLElement, parentEl: HTMLElement | null, doc: Document): Rect { + const offset = this.mode === Mode.SORTABLE ? { left: 0, top: 0 } : { left: el.offsetLeft, top: el.offsetTop }; + + let left = calcValueByFontsize(doc, offset.left); + let top = calcValueByFontsize(doc, offset.top); + const width = calcValueByFontsize(doc, el.clientWidth); + const height = calcValueByFontsize(doc, el.clientHeight); + + let shadowEl = this.getShadowEl(); + const shadowEls = this.getShadowEls(); + + if (shadowEls.length) { + shadowEl = shadowEls.find((item) => item.id.endsWith(el.id)); + } + + if (parentEl && this.mode === Mode.ABSOLUTE && shadowEl) { + const targetShadowHtmlEl = shadowEl as HTMLElement; + const targetShadowElOffsetLeft = targetShadowHtmlEl.offsetLeft || 0; + const targetShadowElOffsetTop = targetShadowHtmlEl.offsetTop || 0; + + const frame = this.getFrame(shadowEl); + + const [translateX, translateY] = frame?.properties.transform.translate.value; + const { left: parentLeft, top: parentTop } = getOffset(parentEl); + + left = + calcValueByFontsize(doc, targetShadowElOffsetLeft) + + parseFloat(translateX) - + calcValueByFontsize(doc, parentLeft); + top = + calcValueByFontsize(doc, targetShadowElOffsetTop) + + parseFloat(translateY) - + calcValueByFontsize(doc, parentTop); + } + + return { width, height, left, top }; + } + /** * 多选状态设置多个节点的快照 */ diff --git a/packages/stage/src/StageCore.ts b/packages/stage/src/StageCore.ts index a24359e6..c37cd5a1 100644 --- a/packages/stage/src/StageCore.ts +++ b/packages/stage/src/StageCore.ts @@ -189,7 +189,10 @@ export default class StageCore extends EventEmitter { /** * @deprecated 废弃接口,建议用delayedMarkContainer代替 */ - public getAddContainerHighlightClassNameTimeout(event: MouseEvent, excludeElList: Element[] = []): NodeJS.Timeout { + public getAddContainerHighlightClassNameTimeout( + event: MouseEvent, + excludeElList: Element[] = [], + ): NodeJS.Timeout | undefined { return this.delayedMarkContainer(event, excludeElList); } @@ -200,7 +203,7 @@ export default class StageCore extends EventEmitter { * @param excludeElList 计算鼠标所在容器时要排除的元素列表 * @returns timeoutId,调用方在鼠标移走时要取消该timeout,阻止标记 */ - public delayedMarkContainer(event: MouseEvent, excludeElList: Element[] = []): NodeJS.Timeout { + public delayedMarkContainer(event: MouseEvent, excludeElList: Element[] = []): NodeJS.Timeout | undefined { return this.actionManager.delayedMarkContainer(event, excludeElList); } @@ -331,8 +334,8 @@ export default class StageCore extends EventEmitter { // 先保证画布内完成渲染,再通知外部更新 setTimeout(() => this.emit('select', el)); }) - .on('multi-update', (data: UpdateEventData, parentEl: HTMLElement | null) => { - this.emit('update', { data, parentEl }); + .on('multi-update', (data: UpdateEventData) => { + this.emit('update', data); }); } diff --git a/packages/stage/src/StageDragResize.ts b/packages/stage/src/StageDragResize.ts index 38ac1264..8a5041b4 100644 --- a/packages/stage/src/StageDragResize.ts +++ b/packages/stage/src/StageDragResize.ts @@ -24,7 +24,7 @@ import DragResizeHelper from './DragResizeHelper'; import MoveableOptionsManager from './MoveableOptionsManager'; import type { DelayedMarkContainer, GetRenderDocument, MarkContainerEnd, StageDragResizeConfig } from './types'; import { StageDragStatus } from './types'; -import { calcValueByFontsize, down, getMode, getOffset, up } from './util'; +import { down, getMode, up } from './util'; /** * 管理单选操作,响应选中操作,初始化moveableOption参数并初始化moveable,处理moveable回调事件对组件进行更新 @@ -51,10 +51,7 @@ export default class StageDragResize extends MoveableOptionsManager { this.delayedMarkContainer = config.delayedMarkContainer; this.disabledDragStart = config.disabledDragStart; - this.dragResizeHelper = new DragResizeHelper({ - container: config.container, - updateDragEl: config.updateDragEl, - }); + this.dragResizeHelper = config.dragResizeHelper; this.on('update-moveable', () => { if (this.moveable) { @@ -315,40 +312,13 @@ export default class StageDragResize extends MoveableOptionsManager { if (!doc) return; - const offset = - this.mode === Mode.SORTABLE ? { left: 0, top: 0 } : { left: this.target.offsetLeft, top: this.target.offsetTop }; - - let left = calcValueByFontsize(doc, offset.left); - let top = calcValueByFontsize(doc, offset.top); - const width = calcValueByFontsize(doc, this.target.clientWidth); - const height = calcValueByFontsize(doc, this.target.clientHeight); - - const shadowEl = this.dragResizeHelper.getShadowEl(); - if (parentEl && this.mode === Mode.ABSOLUTE && shadowEl) { - const targetShadowHtmlEl = shadowEl as HTMLElement; - const targetShadowElOffsetLeft = targetShadowHtmlEl.offsetLeft || 0; - const targetShadowElOffsetTop = targetShadowHtmlEl.offsetTop || 0; - - const frame = this.dragResizeHelper.getFrame(shadowEl); - - const [translateX, translateY] = frame?.properties.transform.translate.value; - const { left: parentLeft, top: parentTop } = getOffset(parentEl); - - left = - calcValueByFontsize(doc, targetShadowElOffsetLeft) + - parseFloat(translateX) - - calcValueByFontsize(doc, parentLeft); - top = - calcValueByFontsize(doc, targetShadowElOffsetTop) + - parseFloat(translateY) - - calcValueByFontsize(doc, parentTop); - } + const rect = this.dragResizeHelper.getUpdatedElRect(this.target, parentEl, doc); this.emit('update', { data: [ { el: this.target, - style: isResize ? { left, top, width, height } : { left, top }, + style: isResize ? rect : { left: rect.left, top: rect.top }, }, ], parentEl, diff --git a/packages/stage/src/StageMultiDragResize.ts b/packages/stage/src/StageMultiDragResize.ts index 6d4ae831..d62e4826 100644 --- a/packages/stage/src/StageMultiDragResize.ts +++ b/packages/stage/src/StageMultiDragResize.ts @@ -21,8 +21,15 @@ import Moveable from 'moveable'; import { DRAG_EL_ID_PREFIX, Mode } from './const'; import DragResizeHelper from './DragResizeHelper'; import MoveableOptionsManager from './MoveableOptionsManager'; -import { GetRenderDocument, MoveableOptionsManagerConfig, StageDragStatus, StageMultiDragResizeConfig } from './types'; -import { calcValueByFontsize, getMode } from './util'; +import { + DelayedMarkContainer, + GetRenderDocument, + MarkContainerEnd, + MoveableOptionsManagerConfig, + StageDragStatus, + StageMultiDragResizeConfig, +} from './types'; +import { getMode } from './util'; export default class StageMultiDragResize extends MoveableOptionsManager { /** 画布容器 */ @@ -34,6 +41,8 @@ export default class StageMultiDragResize extends MoveableOptionsManager { public dragStatus: StageDragStatus = StageDragStatus.END; private dragResizeHelper: DragResizeHelper; private getRenderDocument: GetRenderDocument; + private delayedMarkContainer: DelayedMarkContainer; + private markContainerEnd: MarkContainerEnd; constructor(config: StageMultiDragResizeConfig) { const moveableOptionsManagerConfig: MoveableOptionsManagerConfig = { @@ -43,13 +52,12 @@ export default class StageMultiDragResize extends MoveableOptionsManager { }; super(moveableOptionsManagerConfig); + this.delayedMarkContainer = config.delayedMarkContainer; + this.markContainerEnd = config.markContainerEnd; this.container = config.container; this.getRenderDocument = config.getRenderDocument; - this.dragResizeHelper = new DragResizeHelper({ - container: config.container, - updateDragEl: config.updateDragEl, - }); + this.dragResizeHelper = config.dragResizeHelper; this.on('update-moveable', () => { if (this.moveableForMulti) { @@ -85,6 +93,8 @@ export default class StageMultiDragResize extends MoveableOptionsManager { }), ); + let timeout: NodeJS.Timeout | undefined; + this.moveableForMulti .on('resizeGroupStart', (e) => { this.dragResizeHelper.onResizeGroupStart(e); @@ -103,11 +113,18 @@ export default class StageMultiDragResize extends MoveableOptionsManager { this.dragStatus = StageDragStatus.START; }) .on('dragGroup', (e) => { + if (timeout) { + globalThis.clearTimeout(timeout); + timeout = undefined; + } + timeout = this.delayedMarkContainer(e.inputEvent, this.targetList); + this.dragResizeHelper.onDragGroup(e); this.dragStatus = StageDragStatus.ING; }) .on('dragGroupEnd', () => { - this.update(); + const parentEl = this.markContainerEnd(); + this.update(false, parentEl); this.dragStatus = StageDragStatus.END; }) .on('clickGroup', (e) => { @@ -182,22 +199,19 @@ export default class StageMultiDragResize extends MoveableOptionsManager { * 拖拽完成后将更新的位置信息暴露给上层业务方,业务方可以接收事件进行保存 * @param isResize 是否进行大小缩放 */ - private update(isResize = false): void { + private update(isResize = false, parentEl: HTMLElement | null = null): void { if (this.targetList.length === 0) return; const doc = this.getRenderDocument(); if (!doc) return; const data = this.targetList.map((targetItem) => { - const left = calcValueByFontsize(doc, targetItem.offsetLeft); - const top = calcValueByFontsize(doc, targetItem.offsetTop); - const width = calcValueByFontsize(doc, targetItem.clientWidth); - const height = calcValueByFontsize(doc, targetItem.clientHeight); + const rect = this.dragResizeHelper.getUpdatedElRect(targetItem, parentEl, doc); return { el: targetItem, - style: isResize ? { left, top, width, height } : { left, top }, + style: isResize ? rect : { left: rect.left, top: rect.top }, }; }); - this.emit('update', data, null); + this.emit('update', { data, parentEl }); } } diff --git a/packages/stage/src/types.ts b/packages/stage/src/types.ts index a34927e5..7c3ad977 100644 --- a/packages/stage/src/types.ts +++ b/packages/stage/src/types.ts @@ -22,6 +22,7 @@ import Core from '@tmagic/core'; import type { Id, MApp, MContainer, MNode } from '@tmagic/schema'; import { GuidesType, ZIndex } from './const'; +import DragResizeHelper from './DragResizeHelper'; import StageCore from './StageCore'; export type TargetElement = HTMLElement | SVGElement; @@ -112,21 +113,23 @@ export interface StageMaskConfig { export interface StageDragResizeConfig { container: HTMLElement; + dragResizeHelper: DragResizeHelper; moveableOptions?: CustomizeMoveableOptions; disabledDragStart?: boolean; getRootContainer: GetRootContainer; getRenderDocument: GetRenderDocument; markContainerEnd: MarkContainerEnd; delayedMarkContainer: DelayedMarkContainer; - updateDragEl?: UpdateDragEl; } export interface StageMultiDragResizeConfig { container: HTMLElement; + dragResizeHelper: DragResizeHelper; multiMoveableOptions?: CustomizeMoveableOptions; getRootContainer: GetRootContainer; getRenderDocument: GetRenderDocument; - updateDragEl?: UpdateDragEl; + markContainerEnd: MarkContainerEnd; + delayedMarkContainer: DelayedMarkContainer; } export interface DragResizeHelperConfig {