@tmagic/stage 1.2.0-beta.2 → 1.2.0-beta.21
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/README.md +62 -1
- package/dist/tmagic-stage.js +2157 -0
- package/dist/tmagic-stage.js.map +1 -0
- package/dist/{tmagic-stage.umd.js → tmagic-stage.umd.cjs} +1436 -993
- package/dist/tmagic-stage.umd.cjs.map +1 -0
- package/package.json +9 -8
- package/src/ActionManager.ts +534 -0
- package/src/DragResizeHelper.ts +338 -0
- package/src/MoveableOptionsManager.ts +249 -0
- package/src/MoveableSelectParentAble.ts +76 -0
- package/src/Rule.ts +13 -9
- package/src/StageCore.ts +220 -260
- package/src/StageDragResize.ts +83 -339
- package/src/StageHighlight.ts +17 -16
- package/src/StageMask.ts +88 -140
- package/src/StageMultiDragResize.ts +97 -198
- package/src/StageRender.ts +90 -15
- package/src/TargetShadow.ts +120 -0
- package/src/const.ts +1 -1
- package/src/types.ts +97 -19
- package/src/util.ts +24 -11
- package/types/ActionManager.d.ts +141 -0
- package/types/DragResizeHelper.d.ts +70 -0
- package/types/MoveableOptionsManager.d.ts +86 -0
- package/types/MoveableSelectParentAble.d.ts +8 -0
- package/types/Rule.d.ts +4 -3
- package/types/StageCore.d.ts +65 -39
- package/types/StageDragResize.d.ts +11 -40
- package/types/StageHighlight.d.ts +3 -4
- package/types/StageMask.d.ts +23 -22
- package/types/StageMultiDragResize.d.ts +8 -24
- package/types/StageRender.d.ts +24 -6
- package/types/TargetShadow.d.ts +22 -0
- package/types/const.d.ts +1 -1
- package/types/types.d.ts +85 -18
- package/types/util.d.ts +9 -8
- package/dist/tmagic-stage.mjs +0 -1715
- package/dist/tmagic-stage.mjs.map +0 -1
- package/dist/tmagic-stage.umd.js.map +0 -1
- package/src/TargetCalibrate.ts +0 -119
- package/types/TargetCalibrate.d.ts +0 -20
|
@@ -16,39 +16,46 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { EventEmitter } from 'events';
|
|
20
|
-
|
|
21
|
-
import type { MoveableOptions, OnDragStart, OnResizeStart } from 'moveable';
|
|
22
19
|
import Moveable from 'moveable';
|
|
23
|
-
import MoveableHelper from 'moveable-helper';
|
|
24
20
|
|
|
25
|
-
import { DRAG_EL_ID_PREFIX,
|
|
26
|
-
import
|
|
27
|
-
import
|
|
28
|
-
import {
|
|
29
|
-
import { calcValueByFontsize, getMode
|
|
21
|
+
import { DRAG_EL_ID_PREFIX, Mode } from './const';
|
|
22
|
+
import DragResizeHelper from './DragResizeHelper';
|
|
23
|
+
import MoveableOptionsManager from './MoveableOptionsManager';
|
|
24
|
+
import { GetRenderDocument, MoveableOptionsManagerConfig, StageDragStatus, StageMultiDragResizeConfig } from './types';
|
|
25
|
+
import { calcValueByFontsize, getMode } from './util';
|
|
30
26
|
|
|
31
|
-
export default class StageMultiDragResize extends
|
|
32
|
-
public core: StageCore;
|
|
33
|
-
public mask: StageMask;
|
|
27
|
+
export default class StageMultiDragResize extends MoveableOptionsManager {
|
|
34
28
|
/** 画布容器 */
|
|
35
29
|
public container: HTMLElement;
|
|
36
30
|
/** 多选:目标节点组 */
|
|
37
31
|
public targetList: HTMLElement[] = [];
|
|
38
|
-
/** 多选:目标节点在蒙层中的占位节点组 */
|
|
39
|
-
public dragElList: HTMLDivElement[] = [];
|
|
40
32
|
/** Moveable多选拖拽类实例 */
|
|
41
33
|
public moveableForMulti?: Moveable;
|
|
42
|
-
/** 拖动状态 */
|
|
43
34
|
public dragStatus: StageDragStatus = StageDragStatus.END;
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
35
|
+
private dragResizeHelper: DragResizeHelper;
|
|
36
|
+
private getRenderDocument: GetRenderDocument;
|
|
37
|
+
|
|
38
|
+
constructor(config: StageMultiDragResizeConfig) {
|
|
39
|
+
const moveableOptionsManagerConfig: MoveableOptionsManagerConfig = {
|
|
40
|
+
container: config.container,
|
|
41
|
+
moveableOptions: config.multiMoveableOptions,
|
|
42
|
+
getRootContainer: config.getRootContainer,
|
|
43
|
+
};
|
|
44
|
+
super(moveableOptionsManagerConfig);
|
|
48
45
|
|
|
49
|
-
this.core = config.core;
|
|
50
46
|
this.container = config.container;
|
|
51
|
-
this.
|
|
47
|
+
this.getRenderDocument = config.getRenderDocument;
|
|
48
|
+
|
|
49
|
+
this.dragResizeHelper = new DragResizeHelper({
|
|
50
|
+
container: config.container,
|
|
51
|
+
updateDragEl: config.updateDragEl,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this.on('update-moveable', () => {
|
|
55
|
+
if (this.moveableForMulti) {
|
|
56
|
+
this.updateMoveable();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
/**
|
|
@@ -56,168 +63,108 @@ export default class StageMultiDragResize extends EventEmitter {
|
|
|
56
63
|
* @param els
|
|
57
64
|
*/
|
|
58
65
|
public multiSelect(els: HTMLElement[]): void {
|
|
66
|
+
if (els.length === 0) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.mode = getMode(els[0]);
|
|
59
70
|
this.targetList = els;
|
|
60
|
-
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// 业务方校准
|
|
69
|
-
if (typeof this.core.config.updateDragEl === 'function') {
|
|
70
|
-
this.core.config.updateDragEl(dragElDiv, elItem);
|
|
71
|
-
}
|
|
72
|
-
return dragElDiv;
|
|
73
|
-
});
|
|
71
|
+
|
|
72
|
+
this.dragResizeHelper.updateGroup(els);
|
|
73
|
+
|
|
74
|
+
// 设置周围元素,用于选中元素跟周围元素的对齐辅助
|
|
75
|
+
const elementGuidelines: HTMLElement[] =
|
|
76
|
+
Array.prototype.slice.call(this.targetList[0].parentElement?.children) || [];
|
|
77
|
+
this.setElementGuidelines(this.targetList, elementGuidelines);
|
|
78
|
+
|
|
74
79
|
this.moveableForMulti?.destroy();
|
|
75
|
-
this.
|
|
80
|
+
this.dragResizeHelper.clear();
|
|
76
81
|
this.moveableForMulti = new Moveable(
|
|
77
82
|
this.container,
|
|
78
|
-
this.getOptions({
|
|
79
|
-
target: this.
|
|
83
|
+
this.getOptions(true, {
|
|
84
|
+
target: this.dragResizeHelper.getShadowEls(),
|
|
80
85
|
}),
|
|
81
86
|
);
|
|
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
|
-
|
|
89
|
-
const setFrames = (events: OnDragStart[] | OnResizeStart[]) => {
|
|
90
|
-
// 记录拖动前快照
|
|
91
|
-
events.forEach((ev) => {
|
|
92
|
-
// 实际目标元素
|
|
93
|
-
const matchEventTarget = this.targetList.find(
|
|
94
|
-
(targetItem) => targetItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ''),
|
|
95
|
-
);
|
|
96
|
-
if (!matchEventTarget) return;
|
|
97
|
-
frames.push({
|
|
98
|
-
left: matchEventTarget.offsetLeft,
|
|
99
|
-
top: matchEventTarget.offsetTop,
|
|
100
|
-
id: matchEventTarget.id,
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
};
|
|
104
87
|
|
|
105
88
|
this.moveableForMulti
|
|
106
|
-
.on('resizeGroupStart', (
|
|
107
|
-
|
|
108
|
-
this.multiMoveableHelper?.onResizeGroupStart(params);
|
|
109
|
-
setFrames(events);
|
|
89
|
+
.on('resizeGroupStart', (e) => {
|
|
90
|
+
this.dragResizeHelper.onResizeGroupStart(e);
|
|
110
91
|
this.dragStatus = StageDragStatus.START;
|
|
111
92
|
})
|
|
112
|
-
.on('resizeGroup', (
|
|
113
|
-
|
|
114
|
-
// 拖动过程更新
|
|
115
|
-
events.forEach((ev) => {
|
|
116
|
-
const { width, height, beforeTranslate } = ev.drag;
|
|
117
|
-
const frameSnapShot = frames.find(
|
|
118
|
-
(frameItem) => frameItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ''),
|
|
119
|
-
);
|
|
120
|
-
if (!frameSnapShot) return;
|
|
121
|
-
const targeEl = this.targetList.find(
|
|
122
|
-
(targetItem) => targetItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ''),
|
|
123
|
-
);
|
|
124
|
-
if (!targeEl) return;
|
|
125
|
-
// 元素与其所属组同时加入多选列表时,只更新父元素
|
|
126
|
-
const isParentIncluded = this.targetList.find((targetItem) => targetItem.id === targeEl.parentElement?.id);
|
|
127
|
-
if (!isParentIncluded) {
|
|
128
|
-
// 更新页面元素位置
|
|
129
|
-
targeEl.style.left = `${frameSnapShot.left + beforeTranslate[0]}px`;
|
|
130
|
-
targeEl.style.top = `${frameSnapShot.top + beforeTranslate[1]}px`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 更新页面元素位置
|
|
134
|
-
targeEl.style.width = `${width}px`;
|
|
135
|
-
targeEl.style.height = `${height}px`;
|
|
136
|
-
});
|
|
137
|
-
this.multiMoveableHelper?.onResizeGroup(params);
|
|
93
|
+
.on('resizeGroup', (e) => {
|
|
94
|
+
this.dragResizeHelper.onResizeGroup(e);
|
|
138
95
|
this.dragStatus = StageDragStatus.ING;
|
|
139
96
|
})
|
|
140
97
|
.on('resizeGroupEnd', () => {
|
|
141
98
|
this.update(true);
|
|
142
99
|
this.dragStatus = StageDragStatus.END;
|
|
143
100
|
})
|
|
144
|
-
.on('dragGroupStart', (
|
|
145
|
-
|
|
146
|
-
this.multiMoveableHelper?.onDragGroupStart(params);
|
|
147
|
-
// 记录拖动前快照
|
|
148
|
-
setFrames(events);
|
|
101
|
+
.on('dragGroupStart', (e) => {
|
|
102
|
+
this.dragResizeHelper.onDragGroupStart(e);
|
|
149
103
|
this.dragStatus = StageDragStatus.START;
|
|
150
104
|
})
|
|
151
|
-
.on('dragGroup', (
|
|
152
|
-
|
|
153
|
-
// 拖动过程更新
|
|
154
|
-
events.forEach((ev) => {
|
|
155
|
-
const frameSnapShot = frames.find(
|
|
156
|
-
(frameItem) => frameItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ''),
|
|
157
|
-
);
|
|
158
|
-
if (!frameSnapShot) return;
|
|
159
|
-
const targeEl = this.targetList.find(
|
|
160
|
-
(targetItem) => targetItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ''),
|
|
161
|
-
);
|
|
162
|
-
if (!targeEl) return;
|
|
163
|
-
// 元素与其所属组同时加入多选列表时,只更新父元素
|
|
164
|
-
const isParentIncluded = this.targetList.find((targetItem) => targetItem.id === targeEl.parentElement?.id);
|
|
165
|
-
if (!isParentIncluded) {
|
|
166
|
-
// 更新页面元素位置
|
|
167
|
-
targeEl.style.left = `${frameSnapShot.left + ev.beforeTranslate[0]}px`;
|
|
168
|
-
targeEl.style.top = `${frameSnapShot.top + ev.beforeTranslate[1]}px`;
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
this.multiMoveableHelper?.onDragGroup(params);
|
|
105
|
+
.on('dragGroup', (e) => {
|
|
106
|
+
this.dragResizeHelper.onDragGroup(e);
|
|
172
107
|
this.dragStatus = StageDragStatus.ING;
|
|
173
108
|
})
|
|
174
109
|
.on('dragGroupEnd', () => {
|
|
175
110
|
this.update();
|
|
176
111
|
this.dragStatus = StageDragStatus.END;
|
|
177
112
|
})
|
|
178
|
-
.on('clickGroup', (
|
|
179
|
-
const { inputTarget, targets } =
|
|
180
|
-
//
|
|
181
|
-
if (
|
|
182
|
-
this.emit('select', inputTarget.id.replace(DRAG_EL_ID_PREFIX, ''));
|
|
113
|
+
.on('clickGroup', (e) => {
|
|
114
|
+
const { inputTarget, targets } = e;
|
|
115
|
+
// 如果有多个元素被选中,同时点击的元素在选中元素中的其中一项,可能是多选态切换为该元素的单选态,抛事件给上一层继续判断是否切换
|
|
116
|
+
if (targets.length > 1 && targets.includes(inputTarget)) {
|
|
117
|
+
this.emit('change-to-select', inputTarget.id.replace(DRAG_EL_ID_PREFIX, ''));
|
|
183
118
|
}
|
|
184
119
|
});
|
|
185
120
|
}
|
|
186
121
|
|
|
187
|
-
public canSelect(el: HTMLElement,
|
|
188
|
-
// 多选状态下不可以选中magic-ui-page,并停止继续向上层选中
|
|
189
|
-
if (el.className.includes(PAGE_CLASS)) {
|
|
190
|
-
this.core.highlightedDom = undefined;
|
|
191
|
-
this.core.highlightLayer.clearHighlight();
|
|
192
|
-
stop();
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
122
|
+
public canSelect(el: HTMLElement, selectedEl: HTMLElement | undefined): boolean {
|
|
195
123
|
const currentTargetMode = getMode(el);
|
|
196
|
-
let
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
124
|
+
let selectedElMode = '';
|
|
125
|
+
|
|
126
|
+
// 流式布局不支持多选
|
|
127
|
+
if (currentTargetMode === Mode.SORTABLE) {
|
|
128
|
+
return false;
|
|
200
129
|
}
|
|
201
|
-
|
|
130
|
+
|
|
131
|
+
if (this.targetList.length === 0 && selectedEl) {
|
|
202
132
|
// 单选后添加到多选的情况
|
|
203
|
-
|
|
133
|
+
selectedElMode = getMode(selectedEl);
|
|
204
134
|
} else if (this.targetList.length > 0) {
|
|
205
135
|
// 已加入多选列表的布局模式是一样的,取第一个判断
|
|
206
|
-
|
|
136
|
+
selectedElMode = getMode(this.targetList[0]);
|
|
207
137
|
}
|
|
208
138
|
// 定位模式不同,不可混选
|
|
209
|
-
if (currentTargetMode !==
|
|
139
|
+
if (currentTargetMode !== selectedElMode) {
|
|
210
140
|
return false;
|
|
211
141
|
}
|
|
212
142
|
return true;
|
|
213
143
|
}
|
|
214
144
|
|
|
145
|
+
public updateMoveable(eleList = this.targetList) {
|
|
146
|
+
if (!this.moveableForMulti) return;
|
|
147
|
+
if (!eleList) throw new Error('未选中任何节点');
|
|
148
|
+
|
|
149
|
+
this.targetList = eleList;
|
|
150
|
+
this.dragResizeHelper.setTargetList(eleList);
|
|
151
|
+
|
|
152
|
+
const options = this.getOptions(true, {
|
|
153
|
+
target: this.dragResizeHelper.getShadowEls(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
157
|
+
(this.moveableForMulti as any)[key] = value;
|
|
158
|
+
});
|
|
159
|
+
this.moveableForMulti.updateTarget();
|
|
160
|
+
}
|
|
161
|
+
|
|
215
162
|
/**
|
|
216
163
|
* 清除多选状态
|
|
217
164
|
*/
|
|
218
165
|
public clearSelectStatus(): void {
|
|
219
166
|
if (!this.moveableForMulti) return;
|
|
220
|
-
this.
|
|
167
|
+
this.dragResizeHelper.clearMultiSelectStatus();
|
|
221
168
|
this.moveableForMulti.target = null;
|
|
222
169
|
this.moveableForMulti.updateTarget();
|
|
223
170
|
this.targetList = [];
|
|
@@ -228,14 +175,7 @@ export default class StageMultiDragResize extends EventEmitter {
|
|
|
228
175
|
*/
|
|
229
176
|
public destroy(): void {
|
|
230
177
|
this.moveableForMulti?.destroy();
|
|
231
|
-
this.
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* 清除蒙层占位节点
|
|
236
|
-
*/
|
|
237
|
-
public destroyDragElList(): void {
|
|
238
|
-
this.dragElList.forEach((dragElItem) => dragElItem?.remove());
|
|
178
|
+
this.dragResizeHelper.destroy();
|
|
239
179
|
}
|
|
240
180
|
|
|
241
181
|
/**
|
|
@@ -245,60 +185,19 @@ export default class StageMultiDragResize extends EventEmitter {
|
|
|
245
185
|
private update(isResize = false): void {
|
|
246
186
|
if (this.targetList.length === 0) return;
|
|
247
187
|
|
|
248
|
-
const
|
|
249
|
-
const doc = contentWindow?.document;
|
|
188
|
+
const doc = this.getRenderDocument();
|
|
250
189
|
if (!doc) return;
|
|
251
190
|
|
|
252
|
-
this.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
style: isResize ? { left, top, width, height } : { left, top },
|
|
262
|
-
};
|
|
263
|
-
}),
|
|
264
|
-
parentEl: null,
|
|
191
|
+
const data = this.targetList.map((targetItem) => {
|
|
192
|
+
const left = calcValueByFontsize(doc, targetItem.offsetLeft);
|
|
193
|
+
const top = calcValueByFontsize(doc, targetItem.offsetTop);
|
|
194
|
+
const width = calcValueByFontsize(doc, targetItem.clientWidth);
|
|
195
|
+
const height = calcValueByFontsize(doc, targetItem.clientHeight);
|
|
196
|
+
return {
|
|
197
|
+
el: targetItem,
|
|
198
|
+
style: isResize ? { left, top, width, height } : { left, top },
|
|
199
|
+
};
|
|
265
200
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* 获取moveable options参数
|
|
270
|
-
* @param {MoveableOptions} options
|
|
271
|
-
* @return {MoveableOptions} moveable options参数
|
|
272
|
-
*/
|
|
273
|
-
private getOptions(options: MoveableOptions = {}): MoveableOptions {
|
|
274
|
-
let { multiMoveableOptions = {} } = this.core.config;
|
|
275
|
-
|
|
276
|
-
if (typeof multiMoveableOptions === 'function') {
|
|
277
|
-
multiMoveableOptions = multiMoveableOptions(this.core);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return {
|
|
281
|
-
defaultGroupRotate: 0,
|
|
282
|
-
defaultGroupOrigin: '50% 50%',
|
|
283
|
-
draggable: true,
|
|
284
|
-
resizable: true,
|
|
285
|
-
throttleDrag: 0,
|
|
286
|
-
startDragRotate: 0,
|
|
287
|
-
throttleDragRotate: 0,
|
|
288
|
-
zoom: 1,
|
|
289
|
-
origin: true,
|
|
290
|
-
padding: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
291
|
-
snappable: true,
|
|
292
|
-
bounds: {
|
|
293
|
-
top: 0,
|
|
294
|
-
// 设置0的话无法移动到left为0,所以只能设置为-1
|
|
295
|
-
left: -1,
|
|
296
|
-
right: this.container.clientWidth - 1,
|
|
297
|
-
bottom: this.container.clientHeight,
|
|
298
|
-
...(multiMoveableOptions.bounds || {}),
|
|
299
|
-
},
|
|
300
|
-
...options,
|
|
301
|
-
...multiMoveableOptions,
|
|
302
|
-
};
|
|
201
|
+
this.emit('update', data, null);
|
|
303
202
|
}
|
|
304
203
|
}
|
package/src/StageRender.ts
CHANGED
|
@@ -18,32 +18,30 @@
|
|
|
18
18
|
|
|
19
19
|
import { EventEmitter } from 'events';
|
|
20
20
|
|
|
21
|
+
import { Id } from '@tmagic/schema';
|
|
21
22
|
import { getHost, injectStyle, isSameDomain } from '@tmagic/utils';
|
|
22
23
|
|
|
23
|
-
import
|
|
24
|
+
import { DEFAULT_ZOOM } from './const';
|
|
24
25
|
import style from './style.css?raw';
|
|
25
|
-
import type { Runtime, RuntimeWindow, StageRenderConfig } from './types';
|
|
26
|
+
import type { Point, RemoveData, Runtime, RuntimeWindow, StageRenderConfig, UpdateData } from './types';
|
|
27
|
+
import { addSelectedClassName, removeSelectedClassName } from './util';
|
|
26
28
|
|
|
27
29
|
export default class StageRender extends EventEmitter {
|
|
28
30
|
/** 组件的js、css执行的环境,直接渲染为当前window,iframe渲染则为iframe.contentWindow */
|
|
29
31
|
public contentWindow: RuntimeWindow | null = null;
|
|
30
|
-
|
|
31
32
|
public runtime: Runtime | null = null;
|
|
32
|
-
|
|
33
33
|
public iframe?: HTMLIFrameElement;
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
private runtimeUrl?: string;
|
|
36
|
+
private zoom = DEFAULT_ZOOM;
|
|
37
|
+
private customizedRender?: () => Promise<HTMLElement | null>;
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
constructor({ core }: StageRenderConfig) {
|
|
39
|
+
constructor({ runtimeUrl, zoom, customizedRender }: StageRenderConfig) {
|
|
42
40
|
super();
|
|
43
41
|
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
42
|
+
this.runtimeUrl = runtimeUrl || '';
|
|
43
|
+
this.customizedRender = customizedRender;
|
|
44
|
+
this.setZoom(zoom);
|
|
47
45
|
|
|
48
46
|
this.iframe = globalThis.document.createElement('iframe');
|
|
49
47
|
// 同源,直接加载
|
|
@@ -67,6 +65,38 @@ export default class StageRender extends EventEmitter {
|
|
|
67
65
|
},
|
|
68
66
|
});
|
|
69
67
|
|
|
68
|
+
public async add(data: UpdateData): Promise<void> {
|
|
69
|
+
const runtime = await this.getRuntime();
|
|
70
|
+
return runtime?.add?.(data);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public async remove(data: RemoveData): Promise<void> {
|
|
74
|
+
const runtime = await this.getRuntime();
|
|
75
|
+
return runtime?.remove?.(data);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public async update(data: UpdateData): Promise<void> {
|
|
79
|
+
const runtime = await this.getRuntime();
|
|
80
|
+
// 更新画布中的组件
|
|
81
|
+
runtime?.update?.(data);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public async select(els: HTMLElement[]): Promise<void> {
|
|
85
|
+
const runtime = await this.getRuntime();
|
|
86
|
+
|
|
87
|
+
for (const el of els) {
|
|
88
|
+
await runtime?.select?.(el.id);
|
|
89
|
+
if (runtime?.beforeSelect) {
|
|
90
|
+
await runtime.beforeSelect(el);
|
|
91
|
+
}
|
|
92
|
+
this.flagSelectedEl(el);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public setZoom(zoom: number = DEFAULT_ZOOM): void {
|
|
97
|
+
this.zoom = zoom;
|
|
98
|
+
}
|
|
99
|
+
|
|
70
100
|
/**
|
|
71
101
|
* 挂载Dom节点
|
|
72
102
|
* @param el 将页面挂载到该Dom节点上
|
|
@@ -101,6 +131,39 @@ export default class StageRender extends EventEmitter {
|
|
|
101
131
|
});
|
|
102
132
|
};
|
|
103
133
|
|
|
134
|
+
public getDocument(): Document | undefined {
|
|
135
|
+
return this.contentWindow?.document;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 通过坐标获得坐标下所有HTML元素数组
|
|
140
|
+
* @param point 坐标
|
|
141
|
+
* @returns 坐标下方所有HTML元素数组,会包含父元素直至html,元素层叠时返回顺序是从上到下
|
|
142
|
+
*/
|
|
143
|
+
public getElementsFromPoint(point: Point): HTMLElement[] {
|
|
144
|
+
let x = point.clientX;
|
|
145
|
+
let y = point.clientY;
|
|
146
|
+
|
|
147
|
+
if (this.iframe) {
|
|
148
|
+
const rect = this.iframe.getClientRects()[0];
|
|
149
|
+
if (rect) {
|
|
150
|
+
x = x - rect.left;
|
|
151
|
+
y = y - rect.top;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return this.getDocument()?.elementsFromPoint(x / this.zoom, y / this.zoom) as HTMLElement[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public getTargetElement(idOrEl: Id | HTMLElement): HTMLElement {
|
|
159
|
+
if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
|
|
160
|
+
const el = this.getDocument()?.getElementById(`${idOrEl}`);
|
|
161
|
+
if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
|
|
162
|
+
return el;
|
|
163
|
+
}
|
|
164
|
+
return idOrEl;
|
|
165
|
+
}
|
|
166
|
+
|
|
104
167
|
/**
|
|
105
168
|
* 销毁实例
|
|
106
169
|
*/
|
|
@@ -112,6 +175,18 @@ export default class StageRender extends EventEmitter {
|
|
|
112
175
|
this.removeAllListeners();
|
|
113
176
|
}
|
|
114
177
|
|
|
178
|
+
/**
|
|
179
|
+
* 在runtime中对被选中的元素进行标记,部分组件有对选中态进行特殊显示的需求
|
|
180
|
+
* @param el 被选中的元素
|
|
181
|
+
*/
|
|
182
|
+
private flagSelectedEl(el: HTMLElement): void {
|
|
183
|
+
const doc = this.getDocument();
|
|
184
|
+
if (doc) {
|
|
185
|
+
removeSelectedClassName(doc);
|
|
186
|
+
addSelectedClassName(el, doc);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
115
190
|
private loadHandler = async () => {
|
|
116
191
|
if (!this.contentWindow?.magic) {
|
|
117
192
|
this.postTmagicRuntimeReady();
|
|
@@ -119,8 +194,8 @@ export default class StageRender extends EventEmitter {
|
|
|
119
194
|
|
|
120
195
|
if (!this.contentWindow) return;
|
|
121
196
|
|
|
122
|
-
if (this.
|
|
123
|
-
const el = await this.
|
|
197
|
+
if (this.customizedRender) {
|
|
198
|
+
const el = await this.customizedRender();
|
|
124
199
|
if (el) {
|
|
125
200
|
this.contentWindow.document?.body?.appendChild(el);
|
|
126
201
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
import { Mode, ZIndex } from './const';
|
|
19
|
+
import type { TargetElement as ShadowElement, TargetShadowConfig, UpdateDragEl } from './types';
|
|
20
|
+
import { getTargetElStyle, isFixedParent } from './util';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 将选中的节点修正定位后,添加一个操作节点到蒙层上
|
|
24
|
+
*/
|
|
25
|
+
export default class TargetShadow {
|
|
26
|
+
public el?: ShadowElement;
|
|
27
|
+
public els: ShadowElement[] = [];
|
|
28
|
+
|
|
29
|
+
private idPrefix = 'target_calibrate_';
|
|
30
|
+
private container: HTMLElement;
|
|
31
|
+
private scrollLeft = 0;
|
|
32
|
+
private scrollTop = 0;
|
|
33
|
+
private zIndex?: ZIndex;
|
|
34
|
+
|
|
35
|
+
private updateDragEl?: UpdateDragEl;
|
|
36
|
+
|
|
37
|
+
constructor(config: TargetShadowConfig) {
|
|
38
|
+
this.container = config.container;
|
|
39
|
+
|
|
40
|
+
if (config.updateDragEl) {
|
|
41
|
+
this.updateDragEl = config.updateDragEl;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof config.zIndex !== 'undefined') {
|
|
45
|
+
this.zIndex = config.zIndex;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (config.idPrefix) {
|
|
49
|
+
this.idPrefix = config.idPrefix;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.container.addEventListener('customScroll', this.scrollHandler);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public update(target: ShadowElement): ShadowElement {
|
|
56
|
+
this.el = this.updateEl(target, this.el);
|
|
57
|
+
|
|
58
|
+
return this.el;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public updateGroup(targetGroup: ShadowElement[]): ShadowElement[] {
|
|
62
|
+
if (this.els.length > targetGroup.length) {
|
|
63
|
+
this.els.slice(targetGroup.length - 1).forEach((el) => {
|
|
64
|
+
el.remove();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.els = targetGroup.map((target, index) => this.updateEl(target, this.els[index]));
|
|
69
|
+
|
|
70
|
+
return this.els;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public destroyEl(): void {
|
|
74
|
+
this.el?.remove();
|
|
75
|
+
this.el = undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public destroyEls(): void {
|
|
79
|
+
this.els.forEach((el) => {
|
|
80
|
+
el.remove();
|
|
81
|
+
});
|
|
82
|
+
this.els = [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public destroy(): void {
|
|
86
|
+
this.container.removeEventListener('customScroll', this.scrollHandler);
|
|
87
|
+
this.destroyEl();
|
|
88
|
+
this.destroyEls();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private updateEl(target: ShadowElement, src?: ShadowElement): ShadowElement {
|
|
92
|
+
const el = src || globalThis.document.createElement('div');
|
|
93
|
+
|
|
94
|
+
el.id = `${this.idPrefix}${target.id}`;
|
|
95
|
+
|
|
96
|
+
el.style.cssText = getTargetElStyle(target, this.zIndex);
|
|
97
|
+
|
|
98
|
+
if (typeof this.updateDragEl === 'function') {
|
|
99
|
+
this.updateDragEl(el, target);
|
|
100
|
+
}
|
|
101
|
+
const isFixed = isFixedParent(target);
|
|
102
|
+
const mode = this.container.dataset.mode || Mode.ABSOLUTE;
|
|
103
|
+
if (isFixed && mode !== Mode.FIXED) {
|
|
104
|
+
el.style.transform = `translate3d(${this.scrollLeft}px, ${this.scrollTop}px, 0)`;
|
|
105
|
+
} else if (!isFixed && mode === Mode.FIXED) {
|
|
106
|
+
el.style.transform = `translate3d(${-this.scrollLeft}px, ${-this.scrollTop}px, 0)`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!globalThis.document.getElementById(el.id)) {
|
|
110
|
+
this.container.append(el);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return el;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private scrollHandler = (e: any) => {
|
|
117
|
+
this.scrollLeft = e.detail.scrollLeft;
|
|
118
|
+
this.scrollTop = e.detail.scrollTop;
|
|
119
|
+
};
|
|
120
|
+
}
|
package/src/const.ts
CHANGED
|
@@ -25,7 +25,7 @@ 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
|
|
28
|
+
export const CONTAINER_HIGHLIGHT_CLASS_NAME = 'tmagic-stage-container-highlight';
|
|
29
29
|
|
|
30
30
|
export const PAGE_CLASS = 'magic-ui-page';
|
|
31
31
|
|