diff --git a/packages/stage/src/StageDragResize.ts b/packages/stage/src/StageDragResize.ts index 7f3d09dc..83412e4a 100644 --- a/packages/stage/src/StageDragResize.ts +++ b/packages/stage/src/StageDragResize.ts @@ -46,11 +46,11 @@ export default class StageDragResize extends EventEmitter { public horizontalGuidelines: number[] = []; public verticalGuidelines: number[] = []; public elementGuidelines: HTMLElement[] = []; + public mode: Mode = Mode.ABSOLUTE; private moveableOptions: MoveableOptions = {}; private dragStatus: ActionStatus = ActionStatus.END; private ghostEl: HTMLElement | undefined; - private mode: Mode = Mode.ABSOLUTE; private moveableHelper?: MoveableHelper; constructor(config: StageDragResizeConfig) { @@ -354,6 +354,7 @@ export default class StageDragResize extends EventEmitter { top: ${offset.top}px; width: ${width}px; height: ${height}px; + z-index: 9; `; this.dragEl.id = `${DRAG_EL_ID_PREFIX}${el.id}`; diff --git a/packages/stage/src/StageHighlight.ts b/packages/stage/src/StageHighlight.ts index 4ec5a10a..59bbff9e 100644 --- a/packages/stage/src/StageHighlight.ts +++ b/packages/stage/src/StageHighlight.ts @@ -21,19 +21,27 @@ import { EventEmitter } from 'events'; import Moveable from 'moveable'; +import { HIGHLIGHT_EL_ID_PREFIX } from './const'; import StageCore from './StageCore'; +import TargetCalibrate from './TargetCalibrate'; import type { StageHighlightConfig } from './types'; export default class StageHighlight extends EventEmitter { public core: StageCore; public container: HTMLElement; public target?: HTMLElement; public moveable?: Moveable; + public calibrationTarget: TargetCalibrate; constructor(config: StageHighlightConfig) { super(); this.core = config.core; this.container = config.container; + this.calibrationTarget = new TargetCalibrate({ + parent: this.core.mask.content, + mask: this.core.mask, + dr: this.core.dr, + }); } /** @@ -44,10 +52,12 @@ export default class StageHighlight extends EventEmitter { if (!el || el === this.target) return; this.target = el; this.moveable?.destroy(); + this.moveable = new Moveable(this.container, { - target: this.target, - scrollable: true, + target: this.calibrationTarget.update(el, HIGHLIGHT_EL_ID_PREFIX), origin: false, + rootContainer: this.core.container, + zoom: 1, }); } @@ -66,5 +76,6 @@ export default class StageHighlight extends EventEmitter { */ public destroy(): void { this.moveable?.destroy(); + this.calibrationTarget.destroy(); } } diff --git a/packages/stage/src/TargetCalibrate.ts b/packages/stage/src/TargetCalibrate.ts new file mode 100644 index 00000000..6e01bce2 --- /dev/null +++ b/packages/stage/src/TargetCalibrate.ts @@ -0,0 +1,147 @@ +/* + * Tencent is pleased to support the open source community by making TMagicEditor available. + * + * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * + * 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. + */ + +/* eslint-disable no-param-reassign */ +import { EventEmitter } from 'events'; + +import { Mode } from './const'; +import StageDragResize from './StageDragResize'; +import StageMask from './StageMask'; +import type { Offset, Rect, TargetCalibrateConfig } from './types'; +import { getMode } from './util'; + +/** + * 将选中的节点修正定位后,添加一个操作节点到蒙层上 + */ +export default class TargetCalibrate extends EventEmitter { + public parent: HTMLElement; + public mask: StageMask; + public dr: StageDragResize; + public operationEl: HTMLElement; + + constructor(config: TargetCalibrateConfig) { + super(); + + this.parent = config.parent; + this.mask = config.mask; + this.dr = config.dr; + + this.operationEl = globalThis.document.createElement('div'); + this.parent.append(this.operationEl); + } + + public update(el: HTMLElement, prefix: String): HTMLElement { + const { width, height } = el.getBoundingClientRect(); + const { left, top } = this.getOffset(el); + this.operationEl.style.cssText = ` + position: absolute; + left: ${left}px; + top: ${top}px; + width: ${width}px; + height: ${height}px; + `; + + this.operationEl.id = `${prefix}${el.id}`; + return this.operationEl; + } + + /** + * 设置样式属性 + * @param rect 样式属性 + */ + public resetRect(rect: Rect): void { + this.operationEl.style.width = `${rect.width}px`; + this.operationEl.style.height = `${rect.height}px`; + Object.keys(rect).forEach((key: string) => { + this.operationEl.style[key] = `${rect[key]}px`; + }); + } + + public destroy(): void { + this.operationEl?.remove(); + } + + private getOffset(el: HTMLElement): Offset { + const { transform } = getComputedStyle(el); + const { offsetParent } = el; + + let left = el.offsetLeft; + let top = el.offsetTop; + + if (transform.indexOf('matrix') > -1) { + let a = 1; + let b = 1; + let c = 1; + let d = 1; + let e = 0; + let f = 0; + transform.replace( + /matrix\((.+), (.+), (.+), (.+), (.+), (.+)\)/, + ($0: string, $1: string, $2: string, $3: string, $4: string, $5: string, $6: string): string => { + a = +$1; + b = +$2; + c = +$3; + d = +$4; + e = +$5; + f = +$6; + return transform; + }, + ); + + left = a * left + c * top + e; + top = b * left + d * top + f; + } + + if (offsetParent) { + const parentOffset = this.getOffset(offsetParent as HTMLElement); + return { + left: left + parentOffset.left, + top: top + parentOffset.top, + }; + } + + // 选中固定定位元素后editor-mask高度被置为视窗大小 + if (this.dr.mode === Mode.FIXED) { + // 弹窗的情况 + if (getMode(el) === Mode.FIXED) { + return { + left, + top, + }; + } + + return { + left: left - this.mask.scrollLeft, + top: top - this.mask.scrollTop, + }; + } + + // 无父元素的固定定位需按滚动值计算 + if (getMode(el) === Mode.FIXED) { + return { + left: left + this.mask.scrollLeft, + top: top + this.mask.scrollTop, + }; + } + + return { + left, + top, + }; + } +} diff --git a/packages/stage/src/const.ts b/packages/stage/src/const.ts index 727580cb..4fd0f800 100644 --- a/packages/stage/src/const.ts +++ b/packages/stage/src/const.ts @@ -21,6 +21,8 @@ export const GHOST_EL_ID_PREFIX = 'ghost_el_'; export const DRAG_EL_ID_PREFIX = 'drag_el_'; +export const HIGHLIGHT_EL_ID_PREFIX = 'highlight_el_'; + // 默认放到缩小倍数 export const DEFAULT_ZOOM = 1; diff --git a/packages/stage/src/types.ts b/packages/stage/src/types.ts index 62419510..6c50ed24 100644 --- a/packages/stage/src/types.ts +++ b/packages/stage/src/types.ts @@ -22,6 +22,8 @@ import { Id, MApp, MNode } from '@tmagic/schema'; import { GuidesType } from './const'; import StageCore from './StageCore'; +import StageDragResize from './StageDragResize'; +import StageMask from './StageMask'; export type CanSelect = (el: HTMLElement, event: MouseEvent, stop: () => boolean) => boolean | Promise; @@ -54,7 +56,6 @@ export type Rect = { width: number; height: number; } & Offset; - export interface Offset { left: number; top: number; @@ -119,3 +120,9 @@ export interface StageHighlightConfig { core: StageCore; container: HTMLElement; } + +export interface TargetCalibrateConfig { + parent: HTMLElement; + mask: StageMask; + dr: StageDragResize; +}