@tmagic/stage 1.0.6 → 1.1.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{tmagic-stage.es.js → tmagic-stage.mjs} +689 -295
- package/dist/tmagic-stage.mjs.map +1 -0
- package/dist/tmagic-stage.umd.js +722 -310
- package/dist/tmagic-stage.umd.js.map +1 -1
- package/package.json +14 -11
- package/src/StageCore.ts +126 -19
- package/src/StageDragResize.ts +155 -162
- package/src/StageHighlight.ts +1 -2
- package/src/StageMask.ts +33 -11
- package/src/StageMultiDragResize.ts +260 -0
- package/src/StageRender.ts +31 -17
- package/src/TargetCalibrate.ts +2 -2
- package/src/const.ts +4 -0
- package/src/index.ts +1 -0
- package/src/style.css +11 -0
- package/src/types.ts +40 -13
- package/src/util.ts +102 -22
- package/tsconfig.build.json +13 -0
- package/{dist/types/src → types}/Rule.d.ts +0 -0
- package/{dist/types/src → types}/StageCore.d.ts +26 -4
- package/{dist/types/src → types}/StageDragResize.d.ts +10 -19
- package/{dist/types/src → types}/StageHighlight.d.ts +0 -0
- package/{dist/types/src → types}/StageMask.d.ts +1 -0
- package/types/StageMultiDragResize.d.ts +51 -0
- package/{dist/types/src → types}/StageRender.d.ts +1 -0
- package/{dist/types/src → types}/TargetCalibrate.d.ts +0 -0
- package/{dist/types/src → types}/const.d.ts +2 -0
- package/{dist/types/src → types}/index.d.ts +1 -0
- package/types/logger.d.ts +5 -0
- package/{dist/types/src → types}/types.d.ts +38 -13
- package/{dist/types/src → types}/util.d.ts +17 -7
- package/dist/tmagic-stage.es.js.map +0 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { EventEmitter } from 'events';
|
|
20
|
+
|
|
21
|
+
import type { MoveableOptions } from 'moveable';
|
|
22
|
+
import Moveable from 'moveable';
|
|
23
|
+
import MoveableHelper from 'moveable-helper';
|
|
24
|
+
|
|
25
|
+
import { DRAG_EL_ID_PREFIX, PAGE_CLASS } from './const';
|
|
26
|
+
import StageCore from './StageCore';
|
|
27
|
+
import StageMask from './StageMask';
|
|
28
|
+
import { StageDragResizeConfig, StageDragStatus } from './types';
|
|
29
|
+
import { calcValueByFontsize, getMode, getTargetElStyle } from './util';
|
|
30
|
+
|
|
31
|
+
export default class StageMultiDragResize extends EventEmitter {
|
|
32
|
+
public core: StageCore;
|
|
33
|
+
public mask: StageMask;
|
|
34
|
+
/** 画布容器 */
|
|
35
|
+
public container: HTMLElement;
|
|
36
|
+
/** 多选:目标节点组 */
|
|
37
|
+
public targetList: HTMLElement[] = [];
|
|
38
|
+
/** 多选:目标节点在蒙层中的占位节点组 */
|
|
39
|
+
public dragElList: HTMLDivElement[] = [];
|
|
40
|
+
/** Moveable多选拖拽类实例 */
|
|
41
|
+
public moveableForMulti?: Moveable;
|
|
42
|
+
/** 拖动状态 */
|
|
43
|
+
public dragStatus: StageDragStatus = StageDragStatus.END;
|
|
44
|
+
private multiMoveableHelper?: MoveableHelper;
|
|
45
|
+
|
|
46
|
+
constructor(config: StageDragResizeConfig) {
|
|
47
|
+
super();
|
|
48
|
+
|
|
49
|
+
this.core = config.core;
|
|
50
|
+
this.container = config.container;
|
|
51
|
+
this.mask = config.mask;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 多选
|
|
56
|
+
* @param els
|
|
57
|
+
*/
|
|
58
|
+
public multiSelect(els: HTMLElement[]): void {
|
|
59
|
+
this.targetList = els;
|
|
60
|
+
this.core.dr.destroyDragEl();
|
|
61
|
+
this.destroyDragElList();
|
|
62
|
+
// 生成虚拟多选节点
|
|
63
|
+
this.dragElList = els.map((elItem) => {
|
|
64
|
+
const dragElDiv = globalThis.document.createElement('div');
|
|
65
|
+
this.container.append(dragElDiv);
|
|
66
|
+
dragElDiv.style.cssText = getTargetElStyle(elItem);
|
|
67
|
+
dragElDiv.id = `${DRAG_EL_ID_PREFIX}${elItem.id}`;
|
|
68
|
+
// 业务方校准
|
|
69
|
+
if (typeof this.core.config.updateDragEl === 'function') {
|
|
70
|
+
this.core.config.updateDragEl(dragElDiv, elItem);
|
|
71
|
+
}
|
|
72
|
+
return dragElDiv;
|
|
73
|
+
});
|
|
74
|
+
this.moveableForMulti?.destroy();
|
|
75
|
+
this.multiMoveableHelper?.clear();
|
|
76
|
+
this.moveableForMulti = new Moveable(
|
|
77
|
+
this.container,
|
|
78
|
+
this.getOptions({
|
|
79
|
+
target: this.dragElList,
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
this.multiMoveableHelper = MoveableHelper.create({
|
|
83
|
+
useBeforeRender: true,
|
|
84
|
+
useRender: false,
|
|
85
|
+
createAuto: true,
|
|
86
|
+
});
|
|
87
|
+
const frames: { left: number; top: number; id: string }[] = [];
|
|
88
|
+
this.moveableForMulti
|
|
89
|
+
.on('dragGroupStart', (params) => {
|
|
90
|
+
const { events } = params;
|
|
91
|
+
this.multiMoveableHelper?.onDragGroupStart(params);
|
|
92
|
+
// 记录拖动前快照
|
|
93
|
+
events.forEach((ev) => {
|
|
94
|
+
// 实际目标元素
|
|
95
|
+
const matchEventTarget = this.targetList.find(
|
|
96
|
+
(targetItem) => targetItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ''),
|
|
97
|
+
);
|
|
98
|
+
if (!matchEventTarget) return;
|
|
99
|
+
frames.push({
|
|
100
|
+
left: matchEventTarget.offsetLeft,
|
|
101
|
+
top: matchEventTarget.offsetTop,
|
|
102
|
+
id: matchEventTarget.id,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
this.dragStatus = StageDragStatus.START;
|
|
106
|
+
})
|
|
107
|
+
.on('dragGroup', (params) => {
|
|
108
|
+
const { events } = params;
|
|
109
|
+
// 拖动过程更新
|
|
110
|
+
events.forEach((ev) => {
|
|
111
|
+
const frameSnapShot = frames.find(
|
|
112
|
+
(frameItem) => frameItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ''),
|
|
113
|
+
);
|
|
114
|
+
if (!frameSnapShot) return;
|
|
115
|
+
const targeEl = this.targetList.find(
|
|
116
|
+
(targetItem) => targetItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ''),
|
|
117
|
+
);
|
|
118
|
+
if (!targeEl) return;
|
|
119
|
+
// 元素与其所属组同时加入多选列表时,只更新父元素
|
|
120
|
+
const isParentIncluded = this.targetList.find((targetItem) => targetItem.id === targeEl.parentElement?.id);
|
|
121
|
+
if (!isParentIncluded) {
|
|
122
|
+
// 更新页面元素位置
|
|
123
|
+
targeEl.style.left = `${frameSnapShot.left + ev.beforeTranslate[0]}px`;
|
|
124
|
+
targeEl.style.top = `${frameSnapShot.top + ev.beforeTranslate[1]}px`;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
this.multiMoveableHelper?.onDragGroup(params);
|
|
128
|
+
this.dragStatus = StageDragStatus.ING;
|
|
129
|
+
})
|
|
130
|
+
.on('dragGroupEnd', () => {
|
|
131
|
+
this.update();
|
|
132
|
+
this.dragStatus = StageDragStatus.END;
|
|
133
|
+
})
|
|
134
|
+
.on('clickGroup', (params) => {
|
|
135
|
+
const { inputTarget, targets } = params;
|
|
136
|
+
// 如果此时mask不处于多选状态下,且有多个元素被选中,同时点击的元素在选中元素中的其中一项,代表多选态切换为该元素的单选态
|
|
137
|
+
if (!this.mask.isMultiSelectStatus && targets.length > 1 && targets.includes(inputTarget)) {
|
|
138
|
+
this.emit('select', inputTarget.id.replace(DRAG_EL_ID_PREFIX, ''));
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public canSelect(el: HTMLElement, stop: () => boolean): Boolean {
|
|
144
|
+
// 多选状态下不可以选中magic-ui-page,并停止继续向上层选中
|
|
145
|
+
if (el.className.includes(PAGE_CLASS)) {
|
|
146
|
+
this.core.highlightedDom = undefined;
|
|
147
|
+
this.core.highlightLayer.clearHighlight();
|
|
148
|
+
stop();
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const currentTargetMode = getMode(el);
|
|
152
|
+
let selectedDomMode = '';
|
|
153
|
+
if (this.core.selectedDom?.className.includes(PAGE_CLASS)) {
|
|
154
|
+
// 先单击选中了页面(magic-ui-page),再按住多选键多选时,任一元素均可选中
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
if (this.targetList.length === 0 && this.core.selectedDom) {
|
|
158
|
+
// 单选后添加到多选的情况
|
|
159
|
+
selectedDomMode = getMode(this.core.selectedDom);
|
|
160
|
+
} else if (this.targetList.length > 0) {
|
|
161
|
+
// 已加入多选列表的布局模式是一样的,取第一个判断
|
|
162
|
+
selectedDomMode = getMode(this.targetList[0]);
|
|
163
|
+
}
|
|
164
|
+
// 定位模式不同,不可混选
|
|
165
|
+
if (currentTargetMode !== selectedDomMode) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 清除多选状态
|
|
173
|
+
*/
|
|
174
|
+
public clearSelectStatus(): void {
|
|
175
|
+
if (!this.moveableForMulti) return;
|
|
176
|
+
this.destroyDragElList();
|
|
177
|
+
this.moveableForMulti.target = null;
|
|
178
|
+
this.moveableForMulti.updateTarget();
|
|
179
|
+
this.targetList = [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 销毁实例
|
|
184
|
+
*/
|
|
185
|
+
public destroy(): void {
|
|
186
|
+
this.moveableForMulti?.destroy();
|
|
187
|
+
this.destroyDragElList();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 清除蒙层占位节点
|
|
192
|
+
*/
|
|
193
|
+
public destroyDragElList(): void {
|
|
194
|
+
this.dragElList.forEach((dragElItem) => dragElItem?.remove());
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 拖拽完成后将更新的位置信息暴露给上层业务方,业务方可以接收事件进行保存
|
|
199
|
+
* @param isResize 是否进行大小缩放
|
|
200
|
+
*/
|
|
201
|
+
private update(isResize = false): void {
|
|
202
|
+
if (this.targetList.length === 0) return;
|
|
203
|
+
|
|
204
|
+
const { contentWindow } = this.core.renderer;
|
|
205
|
+
const doc = contentWindow?.document;
|
|
206
|
+
if (!doc) return;
|
|
207
|
+
|
|
208
|
+
this.emit('update', {
|
|
209
|
+
data: this.targetList.map((targetItem) => {
|
|
210
|
+
const offset = { left: targetItem.offsetLeft, top: targetItem.offsetTop };
|
|
211
|
+
const left = calcValueByFontsize(doc, offset.left);
|
|
212
|
+
const top = calcValueByFontsize(doc, offset.top);
|
|
213
|
+
const width = calcValueByFontsize(doc, targetItem.clientWidth);
|
|
214
|
+
const height = calcValueByFontsize(doc, targetItem.clientHeight);
|
|
215
|
+
return {
|
|
216
|
+
el: targetItem,
|
|
217
|
+
style: isResize ? { left, top, width, height } : { left, top },
|
|
218
|
+
};
|
|
219
|
+
}),
|
|
220
|
+
parentEl: null,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 获取moveable options参数
|
|
226
|
+
* @param {MoveableOptions} options
|
|
227
|
+
* @return {MoveableOptions} moveable options参数
|
|
228
|
+
*/
|
|
229
|
+
private getOptions(options: MoveableOptions = {}): MoveableOptions {
|
|
230
|
+
let { multiMoveableOptions = {} } = this.core.config;
|
|
231
|
+
|
|
232
|
+
if (typeof multiMoveableOptions === 'function') {
|
|
233
|
+
multiMoveableOptions = multiMoveableOptions(this.core);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
defaultGroupRotate: 0,
|
|
238
|
+
defaultGroupOrigin: '50% 50%',
|
|
239
|
+
draggable: true,
|
|
240
|
+
resizable: false,
|
|
241
|
+
throttleDrag: 0,
|
|
242
|
+
startDragRotate: 0,
|
|
243
|
+
throttleDragRotate: 0,
|
|
244
|
+
zoom: 1,
|
|
245
|
+
origin: true,
|
|
246
|
+
padding: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
247
|
+
snappable: true,
|
|
248
|
+
bounds: {
|
|
249
|
+
top: 0,
|
|
250
|
+
// 设置0的话无法移动到left为0,所以只能设置为-1
|
|
251
|
+
left: -1,
|
|
252
|
+
right: this.container.clientWidth - 1,
|
|
253
|
+
bottom: this.container.clientHeight,
|
|
254
|
+
...(multiMoveableOptions.bounds || {}),
|
|
255
|
+
},
|
|
256
|
+
...options,
|
|
257
|
+
...multiMoveableOptions,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
package/src/StageRender.ts
CHANGED
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
import { EventEmitter } from 'events';
|
|
20
20
|
|
|
21
|
+
import { getHost, injectStyle, isSameDomain } from '@tmagic/utils';
|
|
22
|
+
|
|
21
23
|
import StageCore from './StageCore';
|
|
24
|
+
import style from './style.css?raw';
|
|
22
25
|
import type { Runtime, RuntimeWindow, StageRenderConfig } from './types';
|
|
23
|
-
import { getHost, isSameDomain } from './util';
|
|
24
26
|
|
|
25
27
|
export default class StageRender extends EventEmitter {
|
|
26
28
|
/** 组件的js、css执行的环境,直接渲染为当前window,iframe渲染则为iframe.contentWindow */
|
|
@@ -70,20 +72,22 @@ export default class StageRender extends EventEmitter {
|
|
|
70
72
|
* @param el 将页面挂载到该Dom节点上
|
|
71
73
|
*/
|
|
72
74
|
public async mount(el: HTMLDivElement) {
|
|
73
|
-
if (this.iframe) {
|
|
74
|
-
if (!isSameDomain(this.runtimeUrl) && this.runtimeUrl) {
|
|
75
|
-
// 不同域,使用srcdoc发起异步请求,需要目标地址支持跨域
|
|
76
|
-
let html = await fetch(this.runtimeUrl).then((res) => res.text());
|
|
77
|
-
// 使用base, 解决相对路径或绝对路径的问题
|
|
78
|
-
const base = `${location.protocol}//${getHost(this.runtimeUrl)}`;
|
|
79
|
-
html = html.replace('<head>', `<head>\n<base href="${base}">`);
|
|
80
|
-
this.iframe.srcdoc = html;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
el.appendChild<HTMLIFrameElement>(this.iframe);
|
|
84
|
-
} else {
|
|
75
|
+
if (!this.iframe) {
|
|
85
76
|
throw Error('mount 失败');
|
|
86
77
|
}
|
|
78
|
+
|
|
79
|
+
if (!isSameDomain(this.runtimeUrl) && this.runtimeUrl) {
|
|
80
|
+
// 不同域,使用srcdoc发起异步请求,需要目标地址支持跨域
|
|
81
|
+
let html = await fetch(this.runtimeUrl).then((res) => res.text());
|
|
82
|
+
// 使用base, 解决相对路径或绝对路径的问题
|
|
83
|
+
const base = `${location.protocol}//${getHost(this.runtimeUrl)}`;
|
|
84
|
+
html = html.replace('<head>', `<head>\n<base href="${base}">`);
|
|
85
|
+
this.iframe.srcdoc = html;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
el.appendChild<HTMLIFrameElement>(this.iframe);
|
|
89
|
+
|
|
90
|
+
this.postTmagicRuntimeReady();
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
public getRuntime = (): Promise<Runtime> => {
|
|
@@ -109,24 +113,34 @@ export default class StageRender extends EventEmitter {
|
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
private loadHandler = async () => {
|
|
112
|
-
this.contentWindow
|
|
116
|
+
if (!this.contentWindow?.magic) {
|
|
117
|
+
this.postTmagicRuntimeReady();
|
|
118
|
+
}
|
|
113
119
|
|
|
114
|
-
this.contentWindow
|
|
120
|
+
if (!this.contentWindow) return;
|
|
115
121
|
|
|
116
122
|
if (this.render) {
|
|
117
123
|
const el = await this.render(this.core);
|
|
118
124
|
if (el) {
|
|
119
|
-
this.
|
|
125
|
+
this.contentWindow.document?.body?.appendChild(el);
|
|
120
126
|
}
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
this.emit('onload');
|
|
124
130
|
|
|
131
|
+
injectStyle(this.contentWindow.document, style);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
private postTmagicRuntimeReady() {
|
|
135
|
+
this.contentWindow = this.iframe?.contentWindow as RuntimeWindow;
|
|
136
|
+
|
|
137
|
+
this.contentWindow.magic = this.getMagicApi();
|
|
138
|
+
|
|
125
139
|
this.contentWindow.postMessage(
|
|
126
140
|
{
|
|
127
141
|
tmagicRuntimeReady: true,
|
|
128
142
|
},
|
|
129
143
|
'*',
|
|
130
144
|
);
|
|
131
|
-
}
|
|
145
|
+
}
|
|
132
146
|
}
|
package/src/TargetCalibrate.ts
CHANGED
|
@@ -16,10 +16,9 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
/* eslint-disable no-param-reassign */
|
|
20
19
|
import { EventEmitter } from 'events';
|
|
21
20
|
|
|
22
|
-
import { Mode } from './const';
|
|
21
|
+
import { Mode, ZIndex } from './const';
|
|
23
22
|
import StageCore from './StageCore';
|
|
24
23
|
import StageDragResize from './StageDragResize';
|
|
25
24
|
import StageMask from './StageMask';
|
|
@@ -58,6 +57,7 @@ export default class TargetCalibrate extends EventEmitter {
|
|
|
58
57
|
top: ${top}px;
|
|
59
58
|
width: ${el.clientWidth}px;
|
|
60
59
|
height: ${el.clientHeight}px;
|
|
60
|
+
z-index: ${ZIndex.DRAG_EL};
|
|
61
61
|
`;
|
|
62
62
|
|
|
63
63
|
this.operationEl.id = `${prefix}${el.id}`;
|
package/src/const.ts
CHANGED
|
@@ -25,6 +25,10 @@ export const DRAG_EL_ID_PREFIX = 'drag_el_';
|
|
|
25
25
|
/** 高亮时需要在蒙层中创建一个占位节点,该节点的id前缀 */
|
|
26
26
|
export const HIGHLIGHT_EL_ID_PREFIX = 'highlight_el_';
|
|
27
27
|
|
|
28
|
+
export const CONTAINER_HIGHLIGHT_CLASS = 'tmagic-stage-container-highlight';
|
|
29
|
+
|
|
30
|
+
export const PAGE_CLASS = 'magic-ui-page';
|
|
31
|
+
|
|
28
32
|
/** 默认放到缩小倍数 */
|
|
29
33
|
export const DEFAULT_ZOOM = 1;
|
|
30
34
|
|
package/src/index.ts
CHANGED
package/src/style.css
ADDED
package/src/types.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import { MoveableOptions } from 'moveable';
|
|
20
20
|
|
|
21
21
|
import Core from '@tmagic/core';
|
|
22
|
-
import type { Id, MApp, MNode } from '@tmagic/schema';
|
|
22
|
+
import type { Id, MApp, MContainer, MNode } from '@tmagic/schema';
|
|
23
23
|
|
|
24
24
|
import { GuidesType } from './const';
|
|
25
25
|
import StageCore from './StageCore';
|
|
@@ -27,6 +27,12 @@ import StageDragResize from './StageDragResize';
|
|
|
27
27
|
import StageMask from './StageMask';
|
|
28
28
|
|
|
29
29
|
export type CanSelect = (el: HTMLElement, event: MouseEvent, stop: () => boolean) => boolean | Promise<boolean>;
|
|
30
|
+
export type IsContainer = (el: HTMLElement) => boolean | Promise<boolean>;
|
|
31
|
+
|
|
32
|
+
export enum ContainerHighlightType {
|
|
33
|
+
DEFAULT = 'default',
|
|
34
|
+
ALT = 'alt',
|
|
35
|
+
}
|
|
30
36
|
|
|
31
37
|
export type StageCoreConfig = {
|
|
32
38
|
/** 需要对齐的dom节点的CSS选择器字符串 */
|
|
@@ -34,7 +40,12 @@ export type StageCoreConfig = {
|
|
|
34
40
|
/** 放大倍数,默认1倍 */
|
|
35
41
|
zoom?: number;
|
|
36
42
|
canSelect?: CanSelect;
|
|
43
|
+
isContainer: IsContainer;
|
|
44
|
+
containerHighlightClassName?: string;
|
|
45
|
+
containerHighlightDuration?: number;
|
|
46
|
+
containerHighlightType?: ContainerHighlightType;
|
|
37
47
|
moveableOptions?: ((core?: StageCore) => MoveableOptions) | MoveableOptions;
|
|
48
|
+
multiMoveableOptions?: ((core?: StageCore) => MoveableOptions) | MoveableOptions;
|
|
38
49
|
/** runtime 的HTML地址,可以是一个HTTP地址,如果和编辑器不同域,需要设置跨域,也可以是一个相对或绝对路径 */
|
|
39
50
|
runtimeUrl?: string;
|
|
40
51
|
render?: (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
|
|
@@ -53,6 +64,17 @@ export interface StageMaskConfig {
|
|
|
53
64
|
export interface StageDragResizeConfig {
|
|
54
65
|
core: StageCore;
|
|
55
66
|
container: HTMLElement;
|
|
67
|
+
mask: StageMask;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 拖动状态 */
|
|
71
|
+
export enum StageDragStatus {
|
|
72
|
+
/** 开始拖动 */
|
|
73
|
+
START = 'start',
|
|
74
|
+
/** 拖动中 */
|
|
75
|
+
ING = 'ing',
|
|
76
|
+
/** 拖动结束 */
|
|
77
|
+
END = 'end',
|
|
56
78
|
}
|
|
57
79
|
|
|
58
80
|
export type Rect = {
|
|
@@ -71,18 +93,21 @@ export interface GuidesEventData {
|
|
|
71
93
|
}
|
|
72
94
|
|
|
73
95
|
export interface UpdateEventData {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
data: {
|
|
97
|
+
el: HTMLElement;
|
|
98
|
+
style: {
|
|
99
|
+
width?: number;
|
|
100
|
+
height?: number;
|
|
101
|
+
left?: number;
|
|
102
|
+
top?: number;
|
|
103
|
+
transform?: {
|
|
104
|
+
rotate?: string;
|
|
105
|
+
scale?: string;
|
|
106
|
+
};
|
|
84
107
|
};
|
|
85
|
-
|
|
108
|
+
ghostEl?: HTMLElement;
|
|
109
|
+
}[];
|
|
110
|
+
parentEl: HTMLElement | null;
|
|
86
111
|
}
|
|
87
112
|
|
|
88
113
|
export interface SortEventData {
|
|
@@ -93,18 +118,20 @@ export interface SortEventData {
|
|
|
93
118
|
|
|
94
119
|
export interface UpdateData {
|
|
95
120
|
config: MNode;
|
|
121
|
+
parent?: MContainer;
|
|
122
|
+
parentId: Id;
|
|
96
123
|
root: MApp;
|
|
97
124
|
}
|
|
98
125
|
|
|
99
126
|
export interface RemoveData {
|
|
100
127
|
id: Id;
|
|
128
|
+
parentId: Id;
|
|
101
129
|
root: MApp;
|
|
102
130
|
}
|
|
103
131
|
|
|
104
132
|
export interface Runtime {
|
|
105
133
|
getApp?: () => Core;
|
|
106
134
|
beforeSelect?: (el: HTMLElement) => Promise<boolean> | boolean;
|
|
107
|
-
getSnapElements?: (el?: HTMLElement) => HTMLElement[];
|
|
108
135
|
updateRootConfig?: (config: MApp) => void;
|
|
109
136
|
updatePageId?: (id: Id) => void;
|
|
110
137
|
select?: (id: Id) => Promise<HTMLElement> | HTMLElement;
|
package/src/util.ts
CHANGED
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
|
+
import { removeClassName } from '@tmagic/utils';
|
|
18
19
|
|
|
19
|
-
import { Mode, SELECTED_CLASS } from './const';
|
|
20
|
-
import type { Offset } from './types';
|
|
20
|
+
import { GHOST_EL_ID_PREFIX, Mode, SELECTED_CLASS, ZIndex } from './const';
|
|
21
|
+
import type { Offset, SortEventData } from './types';
|
|
21
22
|
|
|
22
23
|
const getParents = (el: Element, relative: Element) => {
|
|
23
24
|
let cur: Element | null = el.parentElement;
|
|
@@ -49,6 +50,21 @@ export const getOffset = (el: HTMLElement): Offset => {
|
|
|
49
50
|
};
|
|
50
51
|
};
|
|
51
52
|
|
|
53
|
+
// 将蒙层占位节点覆盖在原节点上方
|
|
54
|
+
export const getTargetElStyle = (el: HTMLElement) => {
|
|
55
|
+
const offset = getOffset(el);
|
|
56
|
+
const { transform } = getComputedStyle(el);
|
|
57
|
+
return `
|
|
58
|
+
position: absolute;
|
|
59
|
+
transform: ${transform};
|
|
60
|
+
left: ${offset.left}px;
|
|
61
|
+
top: ${offset.top}px;
|
|
62
|
+
width: ${el.clientWidth}px;
|
|
63
|
+
height: ${el.clientHeight}px;
|
|
64
|
+
z-index: ${ZIndex.DRAG_EL};
|
|
65
|
+
`;
|
|
66
|
+
};
|
|
67
|
+
|
|
52
68
|
export const getAbsolutePosition = (el: HTMLElement, { top, left }: Offset) => {
|
|
53
69
|
const { offsetParent } = el;
|
|
54
70
|
|
|
@@ -63,16 +79,6 @@ export const getAbsolutePosition = (el: HTMLElement, { top, left }: Offset) => {
|
|
|
63
79
|
return { left, top };
|
|
64
80
|
};
|
|
65
81
|
|
|
66
|
-
export const getHost = (targetUrl: string) => targetUrl.match(/\/\/([^/]+)/)?.[1];
|
|
67
|
-
|
|
68
|
-
export const isSameDomain = (targetUrl = '', source = globalThis.location.host) => {
|
|
69
|
-
const isHttpUrl = /^(http[s]?:)?\/\//.test(targetUrl);
|
|
70
|
-
|
|
71
|
-
if (!isHttpUrl) return true;
|
|
72
|
-
|
|
73
|
-
return getHost(targetUrl) === source;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
82
|
export const isAbsolute = (style: CSSStyleDeclaration): boolean => style.position === 'absolute';
|
|
77
83
|
|
|
78
84
|
export const isRelative = (style: CSSStyleDeclaration): boolean => style.position === 'relative';
|
|
@@ -123,21 +129,14 @@ export const getScrollParent = (element: HTMLElement, includeHidden = false): HT
|
|
|
123
129
|
return null;
|
|
124
130
|
};
|
|
125
131
|
|
|
126
|
-
export const createDiv = ({ className, cssText }: { className: string; cssText: string }) => {
|
|
127
|
-
const el = globalThis.document.createElement('div');
|
|
128
|
-
el.className = className;
|
|
129
|
-
el.style.cssText = cssText;
|
|
130
|
-
return el;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
132
|
export const removeSelectedClassName = (doc: Document) => {
|
|
134
133
|
const oldEl = doc.querySelector(`.${SELECTED_CLASS}`);
|
|
135
134
|
|
|
136
135
|
if (oldEl) {
|
|
137
|
-
oldEl
|
|
138
|
-
(oldEl.parentNode as
|
|
136
|
+
removeClassName(oldEl, SELECTED_CLASS);
|
|
137
|
+
if (oldEl.parentNode) removeClassName(oldEl.parentNode as Element, `${SELECTED_CLASS}-parent`);
|
|
139
138
|
doc.querySelectorAll(`.${SELECTED_CLASS}-parents`).forEach((item) => {
|
|
140
|
-
item
|
|
139
|
+
removeClassName(item, `${SELECTED_CLASS}-parents`);
|
|
141
140
|
});
|
|
142
141
|
}
|
|
143
142
|
};
|
|
@@ -149,3 +148,84 @@ export const addSelectedClassName = (el: Element, doc: Document) => {
|
|
|
149
148
|
item.classList.add(`${SELECTED_CLASS}-parents`);
|
|
150
149
|
});
|
|
151
150
|
};
|
|
151
|
+
|
|
152
|
+
export const calcValueByFontsize = (doc: Document, value: number) => {
|
|
153
|
+
const { fontSize } = doc.documentElement.style;
|
|
154
|
+
|
|
155
|
+
if (fontSize) {
|
|
156
|
+
const times = globalThis.parseFloat(fontSize) / 100;
|
|
157
|
+
return Number((value / times).toFixed(2));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return value;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 下移组件位置
|
|
165
|
+
* @param {number} deltaTop 偏移量
|
|
166
|
+
* @param {Object} detail 当前选中的组件配置
|
|
167
|
+
*/
|
|
168
|
+
export const down = (deltaTop: number, target: HTMLElement | SVGElement): SortEventData | void => {
|
|
169
|
+
let swapIndex = 0;
|
|
170
|
+
let addUpH = target.clientHeight;
|
|
171
|
+
const brothers = Array.from(target.parentNode?.children || []).filter(
|
|
172
|
+
(node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
|
|
173
|
+
);
|
|
174
|
+
const index = brothers.indexOf(target);
|
|
175
|
+
// 往下移动
|
|
176
|
+
const downEls = brothers.slice(index + 1) as HTMLElement[];
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < downEls.length; i++) {
|
|
179
|
+
const ele = downEls[i];
|
|
180
|
+
// 是 fixed 不做处理
|
|
181
|
+
if (ele.style?.position === 'fixed') {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
addUpH += ele.clientHeight / 2;
|
|
185
|
+
if (deltaTop <= addUpH) {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
addUpH += ele.clientHeight / 2;
|
|
189
|
+
swapIndex = i;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
src: target.id,
|
|
193
|
+
dist: downEls.length && swapIndex > -1 ? downEls[swapIndex].id : target.id,
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 上移组件位置
|
|
199
|
+
* @param {Array} brothers 处于同一容器下的所有子组件配置
|
|
200
|
+
* @param {number} index 当前组件所处的位置
|
|
201
|
+
* @param {number} deltaTop 偏移量
|
|
202
|
+
* @param {Object} detail 当前选中的组件配置
|
|
203
|
+
*/
|
|
204
|
+
export const up = (deltaTop: number, target: HTMLElement | SVGElement): SortEventData | void => {
|
|
205
|
+
const brothers = Array.from(target.parentNode?.children || []).filter(
|
|
206
|
+
(node) => !node.id.startsWith(GHOST_EL_ID_PREFIX),
|
|
207
|
+
);
|
|
208
|
+
const index = brothers.indexOf(target);
|
|
209
|
+
// 往上移动
|
|
210
|
+
const upEls = brothers.slice(0, index) as HTMLElement[];
|
|
211
|
+
|
|
212
|
+
let addUpH = target.clientHeight;
|
|
213
|
+
let swapIndex = upEls.length - 1;
|
|
214
|
+
|
|
215
|
+
for (let i = upEls.length - 1; i >= 0; i--) {
|
|
216
|
+
const ele = upEls[i];
|
|
217
|
+
if (!ele) continue;
|
|
218
|
+
// 是 fixed 不做处理
|
|
219
|
+
if (ele.style.position === 'fixed') continue;
|
|
220
|
+
|
|
221
|
+
addUpH += ele.clientHeight / 2;
|
|
222
|
+
if (-deltaTop <= addUpH) break;
|
|
223
|
+
addUpH += ele.clientHeight / 2;
|
|
224
|
+
|
|
225
|
+
swapIndex = i;
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
src: target.id,
|
|
229
|
+
dist: upEls.length && swapIndex > -1 ? upEls[swapIndex].id : target.id,
|
|
230
|
+
};
|
|
231
|
+
};
|
|
File without changes
|