@tmagic/stage 1.0.0-beta.7 → 1.0.0-rc.1
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/LICENSE +5432 -0
- package/README.md +1 -0
- package/dist/tmagic-stage.es.js +763 -17642
- package/dist/tmagic-stage.es.js.map +1 -1
- package/dist/tmagic-stage.umd.js +769 -17651
- package/dist/tmagic-stage.umd.js.map +1 -1
- package/dist/types/src/Rule.d.ts +32 -0
- package/dist/types/src/StageCore.d.ts +21 -10
- package/dist/types/src/StageDragResize.d.ts +17 -11
- package/dist/types/src/StageHighlight.d.ts +27 -0
- package/dist/types/src/StageMask.d.ts +38 -36
- package/dist/types/src/StageRender.d.ts +1 -0
- package/dist/types/src/TargetCalibrate.d.ts +18 -0
- package/dist/types/src/const.d.ts +21 -0
- package/dist/types/src/types.d.ts +16 -9
- package/dist/types/src/util.d.ts +9 -5
- package/package.json +13 -7
- package/src/Rule.ts +150 -0
- package/src/StageCore.ts +152 -103
- package/src/StageDragResize.ts +214 -178
- package/src/StageHighlight.ts +81 -0
- package/src/StageMask.ts +170 -183
- package/src/StageRender.ts +25 -9
- package/src/TargetCalibrate.ts +135 -0
- package/src/const.ts +28 -0
- package/src/types.ts +34 -27
- package/src/util.ts +57 -7
- package/tsconfig.json +0 -9
- package/vite.config.ts +0 -27
package/src/StageMask.ts
CHANGED
|
@@ -16,17 +16,16 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { throttle } from 'lodash-es';
|
|
20
20
|
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
import { GHOST_EL_ID_PREFIX } from './const';
|
|
21
|
+
import { Mode, MouseButton, ZIndex } from './const';
|
|
22
|
+
import Rule from './Rule';
|
|
25
23
|
import type StageCore from './StageCore';
|
|
26
|
-
import type {
|
|
27
|
-
import {
|
|
24
|
+
import type { StageMaskConfig } from './types';
|
|
25
|
+
import { createDiv, getScrollParent, isFixedParent } from './util';
|
|
28
26
|
|
|
29
27
|
const wrapperClassName = 'editor-mask-wrapper';
|
|
28
|
+
const throttleTime = 100;
|
|
30
29
|
|
|
31
30
|
const hideScrollbar = () => {
|
|
32
31
|
const style = globalThis.document.createElement('style');
|
|
@@ -36,30 +35,30 @@ const hideScrollbar = () => {
|
|
|
36
35
|
globalThis.document.head.appendChild(style);
|
|
37
36
|
};
|
|
38
37
|
|
|
39
|
-
const createContent = (): HTMLDivElement =>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
const createContent = (): HTMLDivElement =>
|
|
39
|
+
createDiv({
|
|
40
|
+
className: 'editor-mask',
|
|
41
|
+
cssText: `
|
|
43
42
|
position: absolute;
|
|
44
43
|
top: 0;
|
|
45
44
|
left: 0;
|
|
46
45
|
transform: translate3d(0, 0, 0);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
46
|
+
`,
|
|
47
|
+
});
|
|
50
48
|
|
|
51
49
|
const createWrapper = (): HTMLDivElement => {
|
|
52
|
-
const el =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
const el = createDiv({
|
|
51
|
+
className: wrapperClassName,
|
|
52
|
+
cssText: `
|
|
53
|
+
position: absolute;
|
|
54
|
+
top: 0;
|
|
55
|
+
left: 0;
|
|
56
|
+
height: 100%;
|
|
57
|
+
width: 100%;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
z-index: ${ZIndex.MASK};
|
|
60
|
+
`,
|
|
61
|
+
});
|
|
63
62
|
|
|
64
63
|
hideScrollbar();
|
|
65
64
|
|
|
@@ -70,54 +69,56 @@ const createWrapper = (): HTMLDivElement => {
|
|
|
70
69
|
* 蒙层
|
|
71
70
|
* @description 用于拦截页面的点击动作,避免点击时触发组件自身动作;在编辑器中点击组件应当是选中组件;
|
|
72
71
|
*/
|
|
73
|
-
export default class StageMask extends
|
|
72
|
+
export default class StageMask extends Rule {
|
|
74
73
|
public content: HTMLDivElement = createContent();
|
|
75
|
-
public wrapper: HTMLDivElement
|
|
74
|
+
public wrapper: HTMLDivElement;
|
|
76
75
|
public core: StageCore;
|
|
77
76
|
public page: HTMLElement | null = null;
|
|
78
|
-
|
|
79
|
-
public
|
|
80
|
-
public
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
public pageScrollParent: HTMLElement | null = null;
|
|
78
|
+
public scrollTop = 0;
|
|
79
|
+
public scrollLeft = 0;
|
|
80
|
+
public width = 0;
|
|
81
|
+
public height = 0;
|
|
82
|
+
public wrapperHeight = 0;
|
|
83
|
+
public wrapperWidth = 0;
|
|
84
|
+
public maxScrollTop = 0;
|
|
85
|
+
public maxScrollLeft = 0;
|
|
86
|
+
|
|
87
|
+
private mode: Mode = Mode.ABSOLUTE;
|
|
88
|
+
private pageResizeObserver: ResizeObserver | null = null;
|
|
89
|
+
private wrapperResizeObserver: ResizeObserver | null = null;
|
|
90
|
+
/**
|
|
91
|
+
* 高亮事件处理函数
|
|
92
|
+
* @param event 事件对象
|
|
93
|
+
*/
|
|
94
|
+
private highlightHandler = throttle((event: MouseEvent): void => {
|
|
95
|
+
this.emit('highlight', event);
|
|
96
|
+
}, throttleTime);
|
|
86
97
|
|
|
87
98
|
constructor(config: StageMaskConfig) {
|
|
88
|
-
|
|
99
|
+
const wrapper = createWrapper();
|
|
100
|
+
super(wrapper);
|
|
89
101
|
|
|
102
|
+
this.wrapper = wrapper;
|
|
90
103
|
this.core = config.core;
|
|
91
|
-
const { config: coreConfig } = config.core;
|
|
92
104
|
|
|
93
|
-
this.canSelect = coreConfig.canSelect || ((el: HTMLElement) => !!el.id);
|
|
94
105
|
this.content.addEventListener('mousedown', this.mouseDownHandler);
|
|
95
|
-
this.content.addEventListener('contextmenu', this.contextmenuHandler);
|
|
96
106
|
this.wrapper.appendChild(this.content);
|
|
97
|
-
|
|
98
|
-
this.
|
|
99
|
-
this.
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* 设置成固定定位模式
|
|
104
|
-
*/
|
|
105
|
-
public setFixed(): void {
|
|
106
|
-
this.wrapper.scrollTo({
|
|
107
|
-
top: 0,
|
|
108
|
-
});
|
|
109
|
-
this.wrapper.style.overflow = 'hidden';
|
|
110
|
-
// 要等滚动条滚上去,才刷新选中框
|
|
111
|
-
setTimeout(() => {
|
|
112
|
-
this.core.dr.refresh();
|
|
113
|
-
});
|
|
107
|
+
this.content.addEventListener('wheel', this.mouseWheelHandler);
|
|
108
|
+
this.content.addEventListener('mousemove', this.highlightHandler);
|
|
109
|
+
this.content.addEventListener('mouseleave', this.mouseLeaveHandler);
|
|
114
110
|
}
|
|
115
111
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
public setMode(mode: Mode) {
|
|
113
|
+
this.mode = mode;
|
|
114
|
+
this.scroll();
|
|
115
|
+
if (mode === Mode.FIXED) {
|
|
116
|
+
this.content.style.width = `${this.wrapperWidth}px`;
|
|
117
|
+
this.content.style.height = `${this.wrapperHeight}px`;
|
|
118
|
+
} else {
|
|
119
|
+
this.content.style.width = `${this.width}px`;
|
|
120
|
+
this.content.style.height = `${this.height}px`;
|
|
121
|
+
}
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
/**
|
|
@@ -129,17 +130,32 @@ export default class StageMask extends EventEmitter {
|
|
|
129
130
|
if (!page) return;
|
|
130
131
|
|
|
131
132
|
this.page = page;
|
|
132
|
-
this.
|
|
133
|
+
this.pageScrollParent = getScrollParent(page) || this.core.renderer.contentWindow?.document.documentElement || null;
|
|
134
|
+
this.pageResizeObserver?.disconnect();
|
|
133
135
|
|
|
134
136
|
if (typeof ResizeObserver !== 'undefined') {
|
|
135
|
-
this.
|
|
137
|
+
this.pageResizeObserver = new ResizeObserver((entries) => {
|
|
136
138
|
const [entry] = entries;
|
|
137
139
|
const { clientHeight, clientWidth } = entry.target;
|
|
138
140
|
this.setHeight(clientHeight);
|
|
139
141
|
this.setWidth(clientWidth);
|
|
142
|
+
|
|
143
|
+
this.fixScrollValue();
|
|
144
|
+
this.scroll();
|
|
145
|
+
this.core.dr.updateMoveable();
|
|
140
146
|
});
|
|
141
147
|
|
|
142
|
-
this.
|
|
148
|
+
this.pageResizeObserver.observe(page);
|
|
149
|
+
|
|
150
|
+
this.wrapperResizeObserver = new ResizeObserver((entries) => {
|
|
151
|
+
const [entry] = entries;
|
|
152
|
+
const { clientHeight, clientWidth } = entry.target;
|
|
153
|
+
this.wrapperHeight = clientHeight;
|
|
154
|
+
this.wrapperWidth = clientWidth;
|
|
155
|
+
this.setMaxScrollLeft();
|
|
156
|
+
this.setMaxScrollTop();
|
|
157
|
+
});
|
|
158
|
+
this.wrapperResizeObserver.observe(this.wrapper);
|
|
143
159
|
}
|
|
144
160
|
}
|
|
145
161
|
|
|
@@ -151,12 +167,10 @@ export default class StageMask extends EventEmitter {
|
|
|
151
167
|
if (!this.content) throw new Error('content 不存在');
|
|
152
168
|
|
|
153
169
|
el.appendChild(this.wrapper);
|
|
170
|
+
}
|
|
154
171
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this.hGuides.resize();
|
|
158
|
-
});
|
|
159
|
-
this.parentResizeObserver.observe(el);
|
|
172
|
+
public setLayout(el: HTMLElement): void {
|
|
173
|
+
this.setMode(isFixedParent(el) ? Mode.FIXED : Mode.ABSOLUTE);
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
/**
|
|
@@ -165,165 +179,138 @@ export default class StageMask extends EventEmitter {
|
|
|
165
179
|
public destroy(): void {
|
|
166
180
|
this.content?.remove();
|
|
167
181
|
this.page = null;
|
|
168
|
-
this.
|
|
169
|
-
this.
|
|
170
|
-
this.
|
|
171
|
-
}
|
|
182
|
+
this.pageScrollParent = null;
|
|
183
|
+
this.pageResizeObserver?.disconnect();
|
|
184
|
+
this.wrapperResizeObserver?.disconnect();
|
|
172
185
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
* @param show 是否显示
|
|
176
|
-
*/
|
|
177
|
-
public showGuides(show = true) {
|
|
178
|
-
this.hGuides.setState({
|
|
179
|
-
showGuides: show,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
this.vGuides.setState({
|
|
183
|
-
showGuides: show,
|
|
184
|
-
});
|
|
186
|
+
this.content.removeEventListener('mouseleave', this.mouseLeaveHandler);
|
|
187
|
+
super.destroy();
|
|
185
188
|
}
|
|
186
189
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
* @param show 是否显示
|
|
190
|
-
*/
|
|
191
|
-
public showRule(show = true) {
|
|
192
|
-
// 当尺子隐藏时发现大小变化,显示后会变形,所以这里做重新初始化处理
|
|
193
|
-
if (show) {
|
|
194
|
-
this.hGuides.destroy();
|
|
195
|
-
this.hGuides = this.createGuides('horizontal', this.core.dr.horizontalGuidelines);
|
|
196
|
-
|
|
197
|
-
this.vGuides.destroy();
|
|
198
|
-
this.vGuides = this.createGuides('vertical', this.core.dr.verticalGuidelines);
|
|
199
|
-
} else {
|
|
200
|
-
this.hGuides.setState({
|
|
201
|
-
rulerStyle: {
|
|
202
|
-
visibility: 'hidden',
|
|
203
|
-
},
|
|
204
|
-
});
|
|
190
|
+
private scroll() {
|
|
191
|
+
let { scrollLeft, scrollTop } = this;
|
|
205
192
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
193
|
+
if (this.pageScrollParent) {
|
|
194
|
+
this.pageScrollParent.scrollTo({
|
|
195
|
+
top: scrollTop,
|
|
196
|
+
left: scrollLeft,
|
|
210
197
|
});
|
|
211
198
|
}
|
|
199
|
+
|
|
200
|
+
if (this.mode === Mode.FIXED) {
|
|
201
|
+
scrollLeft = 0;
|
|
202
|
+
scrollTop = 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.scrollRule(scrollTop);
|
|
206
|
+
this.scrollTo(scrollLeft, scrollTop);
|
|
212
207
|
}
|
|
213
208
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
*/
|
|
217
|
-
public clearGuides() {
|
|
218
|
-
this.vGuides.setState({
|
|
219
|
-
defaultGuides: [],
|
|
220
|
-
});
|
|
221
|
-
this.hGuides.setState({
|
|
222
|
-
defaultGuides: [],
|
|
223
|
-
});
|
|
209
|
+
private scrollTo(scrollLeft: number, scrollTop: number): void {
|
|
210
|
+
this.content.style.transform = `translate3d(${-scrollLeft}px, ${-scrollTop}px, 0)`;
|
|
224
211
|
}
|
|
225
212
|
|
|
226
213
|
/**
|
|
227
214
|
* 设置蒙层高度
|
|
228
215
|
* @param height 高度
|
|
229
216
|
*/
|
|
230
|
-
private setHeight(height: number
|
|
231
|
-
this.
|
|
217
|
+
private setHeight(height: number): void {
|
|
218
|
+
this.height = height;
|
|
219
|
+
this.setMaxScrollTop();
|
|
220
|
+
this.content.style.height = `${height}px`;
|
|
232
221
|
}
|
|
233
222
|
|
|
234
223
|
/**
|
|
235
224
|
* 设置蒙层宽度
|
|
236
225
|
* @param width 宽度
|
|
237
226
|
*/
|
|
238
|
-
private setWidth(width: number
|
|
239
|
-
this.
|
|
227
|
+
private setWidth(width: number): void {
|
|
228
|
+
this.width = width;
|
|
229
|
+
this.setMaxScrollLeft();
|
|
230
|
+
this.content.style.width = `${width}px`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 计算并设置最大滚动宽度
|
|
235
|
+
*/
|
|
236
|
+
private setMaxScrollLeft(): void {
|
|
237
|
+
this.maxScrollLeft = this.width - this.wrapperWidth;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 计算并设置最大滚动高度
|
|
242
|
+
*/
|
|
243
|
+
private setMaxScrollTop(): void {
|
|
244
|
+
this.maxScrollTop = this.height - this.wrapperHeight;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 修复滚动距离
|
|
249
|
+
* 由于滚动容器变化等因素,会导致当前滚动的距离不正确
|
|
250
|
+
*/
|
|
251
|
+
private fixScrollValue(): void {
|
|
252
|
+
if (this.scrollTop < 0) this.scrollTop = 0;
|
|
253
|
+
if (this.scrollLeft < 0) this.scrollLeft = 0;
|
|
254
|
+
if (this.maxScrollTop < this.scrollTop) this.scrollTop = this.maxScrollTop;
|
|
255
|
+
if (this.maxScrollLeft < this.scrollLeft) this.scrollLeft = this.maxScrollLeft;
|
|
240
256
|
}
|
|
241
257
|
|
|
242
258
|
/**
|
|
243
259
|
* 点击事件处理函数
|
|
244
260
|
* @param event 事件对象
|
|
245
261
|
*/
|
|
246
|
-
private mouseDownHandler =
|
|
247
|
-
|
|
262
|
+
private mouseDownHandler = (event: MouseEvent): void => {
|
|
263
|
+
this.emit('clearHighlight');
|
|
264
|
+
|
|
265
|
+
event.stopImmediatePropagation();
|
|
266
|
+
event.stopPropagation();
|
|
267
|
+
|
|
268
|
+
if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT) return;
|
|
248
269
|
|
|
249
270
|
// 点击的对象如果是选中框,则不需要再触发选中了,而可能是拖动行为
|
|
250
271
|
if ((event.target as HTMLDivElement).className.indexOf('moveable-control') !== -1) {
|
|
251
272
|
return;
|
|
252
273
|
}
|
|
253
274
|
|
|
254
|
-
this.content.
|
|
275
|
+
this.content.removeEventListener('mousemove', this.highlightHandler);
|
|
255
276
|
|
|
256
|
-
this.
|
|
257
|
-
};
|
|
277
|
+
this.emit('beforeSelect', event);
|
|
258
278
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
this.content.removeEventListener('mouseup', this.mouseUpHandler);
|
|
262
|
-
this.emit('selected', this.target);
|
|
263
|
-
this.target = null;
|
|
279
|
+
// 如果是右键点击,这里的mouseup事件监听没有效果
|
|
280
|
+
globalThis.document.addEventListener('mouseup', this.mouseUpHandler);
|
|
264
281
|
};
|
|
265
282
|
|
|
266
|
-
private
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
this.content.removeEventListener('mousemove', this.mouseMoveHandler);
|
|
283
|
+
private mouseUpHandler = (): void => {
|
|
284
|
+
globalThis.document.removeEventListener('mouseup', this.mouseUpHandler);
|
|
285
|
+
this.content.addEventListener('mousemove', this.highlightHandler);
|
|
286
|
+
this.emit('select');
|
|
272
287
|
};
|
|
273
288
|
|
|
274
|
-
private
|
|
275
|
-
|
|
276
|
-
this.
|
|
277
|
-
};
|
|
289
|
+
private mouseWheelHandler = (event: WheelEvent) => {
|
|
290
|
+
this.emit('clearHighlight');
|
|
291
|
+
if (!this.page) throw new Error('page 未初始化');
|
|
278
292
|
|
|
279
|
-
|
|
280
|
-
const { renderer, zoom } = this.core;
|
|
293
|
+
const { deltaY, deltaX } = event;
|
|
281
294
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
let y = event.clientY;
|
|
295
|
+
if (this.page.clientHeight < this.wrapperHeight && deltaY) return;
|
|
296
|
+
if (this.page.clientWidth < this.wrapperWidth && deltaX) return;
|
|
285
297
|
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
if (rect) {
|
|
289
|
-
x = x - rect.left;
|
|
290
|
-
y = y - rect.top;
|
|
291
|
-
}
|
|
298
|
+
if (this.maxScrollTop > 0) {
|
|
299
|
+
this.scrollTop = this.scrollTop + deltaY;
|
|
292
300
|
}
|
|
293
301
|
|
|
294
|
-
|
|
302
|
+
if (this.maxScrollLeft > 0) {
|
|
303
|
+
this.scrollLeft = this.scrollLeft + deltaX;
|
|
304
|
+
}
|
|
295
305
|
|
|
296
|
-
|
|
297
|
-
const stop = () => (stopped = true);
|
|
298
|
-
for (const el of els) {
|
|
299
|
-
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, stop))) {
|
|
300
|
-
if (stopped) break;
|
|
306
|
+
this.fixScrollValue();
|
|
301
307
|
|
|
302
|
-
|
|
303
|
-
this.target = el;
|
|
304
|
-
// 如果是右键点击,这里的mouseup事件监听没有效果
|
|
305
|
-
this.content.addEventListener('mouseup', this.mouseUpHandler);
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
308
|
+
this.scroll();
|
|
310
309
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
left: type === 'horizontal' ? 0 : '-30px',
|
|
314
|
-
top: type === 'horizontal' ? '-30px' : 0,
|
|
315
|
-
width: type === 'horizontal' ? '100%' : '30px',
|
|
316
|
-
height: type === 'horizontal' ? '30px' : '100%',
|
|
317
|
-
});
|
|
310
|
+
this.emit('scroll', event);
|
|
311
|
+
};
|
|
318
312
|
|
|
319
|
-
private
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
defaultGuides,
|
|
323
|
-
displayDragPos: true,
|
|
324
|
-
backgroundColor: '#fff',
|
|
325
|
-
lineColor: '#000',
|
|
326
|
-
textColor: '#000',
|
|
327
|
-
style: this.getGuidesStyle(type),
|
|
328
|
-
});
|
|
313
|
+
private mouseLeaveHandler = () => {
|
|
314
|
+
setTimeout(() => this.emit('clearHighlight'), throttleTime);
|
|
315
|
+
};
|
|
329
316
|
}
|
package/src/StageRender.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { EventEmitter } from 'events';
|
|
20
20
|
|
|
21
|
+
import { SELECTED_CLASS, ZIndex } from './const';
|
|
21
22
|
import StageCore from './StageCore';
|
|
22
23
|
import type { Runtime, RuntimeWindow, StageRenderConfig } from './types';
|
|
23
24
|
import { getHost, isSameDomain } from './util';
|
|
@@ -52,15 +53,7 @@ export default class StageRender extends EventEmitter {
|
|
|
52
53
|
height: 100%;
|
|
53
54
|
`;
|
|
54
55
|
|
|
55
|
-
this.iframe.
|
|
56
|
-
this.emit('onload');
|
|
57
|
-
if (this.render) {
|
|
58
|
-
const el = await this.render(this.core);
|
|
59
|
-
if (el) {
|
|
60
|
-
this.iframe?.contentDocument?.body?.appendChild(el);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
};
|
|
56
|
+
this.iframe.addEventListener('load', this.loadHandler);
|
|
64
57
|
}
|
|
65
58
|
|
|
66
59
|
public getMagicApi = () => ({
|
|
@@ -113,9 +106,32 @@ export default class StageRender extends EventEmitter {
|
|
|
113
106
|
* 销毁实例
|
|
114
107
|
*/
|
|
115
108
|
public destroy(): void {
|
|
109
|
+
this.iframe?.removeEventListener('load', this.loadHandler);
|
|
116
110
|
this.contentWindow = null;
|
|
117
111
|
this.iframe?.remove();
|
|
118
112
|
this.iframe = undefined;
|
|
119
113
|
this.removeAllListeners();
|
|
120
114
|
}
|
|
115
|
+
|
|
116
|
+
private loadHandler = async () => {
|
|
117
|
+
this.emit('onload');
|
|
118
|
+
|
|
119
|
+
if (this.render) {
|
|
120
|
+
const el = await this.render(this.core);
|
|
121
|
+
if (el) {
|
|
122
|
+
this.iframe?.contentDocument?.body?.appendChild(el);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (this.contentWindow) {
|
|
127
|
+
const style = this.contentWindow.document.createElement('style');
|
|
128
|
+
style.id = 'tmagic-stage-render';
|
|
129
|
+
style.innerHTML = `
|
|
130
|
+
.${SELECTED_CLASS}, .${SELECTED_CLASS}-parent {
|
|
131
|
+
z-index: ${ZIndex.SELECTED_EL};
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
this.contentWindow.document.head.appendChild(style);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
121
137
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
/* eslint-disable no-param-reassign */
|
|
20
|
+
import { EventEmitter } from 'events';
|
|
21
|
+
|
|
22
|
+
import { Mode } from './const';
|
|
23
|
+
import StageDragResize from './StageDragResize';
|
|
24
|
+
import StageMask from './StageMask';
|
|
25
|
+
import type { Offset, TargetCalibrateConfig } from './types';
|
|
26
|
+
import { getMode } from './util';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 将选中的节点修正定位后,添加一个操作节点到蒙层上
|
|
30
|
+
*/
|
|
31
|
+
export default class TargetCalibrate extends EventEmitter {
|
|
32
|
+
public parent: HTMLElement;
|
|
33
|
+
public mask: StageMask;
|
|
34
|
+
public dr: StageDragResize;
|
|
35
|
+
public operationEl: HTMLElement;
|
|
36
|
+
|
|
37
|
+
constructor(config: TargetCalibrateConfig) {
|
|
38
|
+
super();
|
|
39
|
+
|
|
40
|
+
this.parent = config.parent;
|
|
41
|
+
this.mask = config.mask;
|
|
42
|
+
this.dr = config.dr;
|
|
43
|
+
|
|
44
|
+
this.operationEl = globalThis.document.createElement('div');
|
|
45
|
+
this.parent.append(this.operationEl);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public update(el: HTMLElement, prefix: String): HTMLElement {
|
|
49
|
+
const { width, height } = el.getBoundingClientRect();
|
|
50
|
+
const { left, top } = this.getOffset(el);
|
|
51
|
+
this.operationEl.style.cssText = `
|
|
52
|
+
position: absolute;
|
|
53
|
+
left: ${left}px;
|
|
54
|
+
top: ${top}px;
|
|
55
|
+
width: ${width}px;
|
|
56
|
+
height: ${height}px;
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
this.operationEl.id = `${prefix}${el.id}`;
|
|
60
|
+
return this.operationEl;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public destroy(): void {
|
|
64
|
+
this.operationEl?.remove();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private getOffset(el: HTMLElement): Offset {
|
|
68
|
+
const { transform } = getComputedStyle(el);
|
|
69
|
+
const { offsetParent } = el;
|
|
70
|
+
|
|
71
|
+
let left = el.offsetLeft;
|
|
72
|
+
let top = el.offsetTop;
|
|
73
|
+
|
|
74
|
+
if (transform.indexOf('matrix') > -1) {
|
|
75
|
+
let a = 1;
|
|
76
|
+
let b = 1;
|
|
77
|
+
let c = 1;
|
|
78
|
+
let d = 1;
|
|
79
|
+
let e = 0;
|
|
80
|
+
let f = 0;
|
|
81
|
+
transform.replace(
|
|
82
|
+
/matrix\((.+), (.+), (.+), (.+), (.+), (.+)\)/,
|
|
83
|
+
($0: string, $1: string, $2: string, $3: string, $4: string, $5: string, $6: string): string => {
|
|
84
|
+
a = +$1;
|
|
85
|
+
b = +$2;
|
|
86
|
+
c = +$3;
|
|
87
|
+
d = +$4;
|
|
88
|
+
e = +$5;
|
|
89
|
+
f = +$6;
|
|
90
|
+
return transform;
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
left = a * left + c * top + e;
|
|
95
|
+
top = b * left + d * top + f;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (offsetParent) {
|
|
99
|
+
const parentOffset = this.getOffset(offsetParent as HTMLElement);
|
|
100
|
+
return {
|
|
101
|
+
left: left + parentOffset.left,
|
|
102
|
+
top: top + parentOffset.top,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 选中固定定位元素后editor-mask高度被置为视窗大小
|
|
107
|
+
if (this.dr.mode === Mode.FIXED) {
|
|
108
|
+
// 弹窗的情况
|
|
109
|
+
if (getMode(el) === Mode.FIXED) {
|
|
110
|
+
return {
|
|
111
|
+
left,
|
|
112
|
+
top,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
left: left - this.mask.scrollLeft,
|
|
118
|
+
top: top - this.mask.scrollTop,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 无父元素的固定定位需按滚动值计算
|
|
123
|
+
if (getMode(el) === Mode.FIXED) {
|
|
124
|
+
return {
|
|
125
|
+
left: left + this.mask.scrollLeft,
|
|
126
|
+
top: top + this.mask.scrollTop,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
left,
|
|
132
|
+
top,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
package/src/const.ts
CHANGED
|
@@ -19,5 +19,33 @@
|
|
|
19
19
|
// 流式布局下拖动时需要clone一个镜像节点,镜像节点的id前缀
|
|
20
20
|
export const GHOST_EL_ID_PREFIX = 'ghost_el_';
|
|
21
21
|
|
|
22
|
+
export const DRAG_EL_ID_PREFIX = 'drag_el_';
|
|
23
|
+
|
|
24
|
+
export const HIGHLIGHT_EL_ID_PREFIX = 'highlight_el_';
|
|
25
|
+
|
|
22
26
|
// 默认放到缩小倍数
|
|
23
27
|
export const DEFAULT_ZOOM = 1;
|
|
28
|
+
|
|
29
|
+
export enum GuidesType {
|
|
30
|
+
HORIZONTAL = 'horizontal',
|
|
31
|
+
VERTICAL = 'vertical',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export enum ZIndex {
|
|
35
|
+
MASK = '99999',
|
|
36
|
+
SELECTED_EL = '666',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export enum MouseButton {
|
|
40
|
+
LEFT = 0,
|
|
41
|
+
MIDDLE = 1,
|
|
42
|
+
RIGHT = 2,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export enum Mode {
|
|
46
|
+
ABSOLUTE = 'absolute',
|
|
47
|
+
FIXED = 'fixed',
|
|
48
|
+
SORTABLE = 'sortable',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const SELECTED_CLASS = 'tmagic-stage-selected-area';
|