@tmagic/stage 1.1.6 → 1.2.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.mjs +219 -95
- package/dist/tmagic-stage.mjs.map +1 -1
- package/dist/tmagic-stage.umd.js +219 -94
- package/dist/tmagic-stage.umd.js.map +1 -1
- package/package.json +4 -4
- package/src/MoveableSelectParentAble.ts +78 -0
- package/src/Rule.ts +9 -5
- package/src/StageCore.ts +48 -15
- package/src/StageDragResize.ts +25 -9
- package/src/StageMask.ts +112 -77
- package/src/StageRender.ts +9 -9
- package/src/types.ts +3 -2
- package/src/util.ts +6 -0
- package/types/MoveableSelectParentAble.d.ts +9 -0
- package/types/Rule.d.ts +4 -3
- package/types/StageCore.d.ts +9 -0
- package/types/StageMask.d.ts +31 -9
- package/types/StageRender.d.ts +2 -3
- package/types/types.d.ts +3 -2
- package/types/util.d.ts +1 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.2.0-beta.10",
|
|
3
3
|
"name": "@tmagic/stage",
|
|
4
4
|
"sideEffects": [
|
|
5
5
|
"dist/*"
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@scena/guides": "^0.17.0",
|
|
32
|
-
"@tmagic/core": "1.
|
|
33
|
-
"@tmagic/schema": "1.
|
|
34
|
-
"@tmagic/utils": "1.
|
|
32
|
+
"@tmagic/core": "1.2.0-beta.10",
|
|
33
|
+
"@tmagic/schema": "1.2.0-beta.10",
|
|
34
|
+
"@tmagic/utils": "1.2.0-beta.10",
|
|
35
35
|
"events": "^3.3.0",
|
|
36
36
|
"keycon": "^1.1.2",
|
|
37
37
|
"lodash-es": "^4.17.21",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { MoveableManagerInterface, Renderer } from 'moveable';
|
|
2
|
+
|
|
3
|
+
import type StageDragResize from './StageDragResize';
|
|
4
|
+
|
|
5
|
+
export default (dr: StageDragResize) => ({
|
|
6
|
+
name: 'selectParent',
|
|
7
|
+
props: {},
|
|
8
|
+
events: {},
|
|
9
|
+
render(moveable: MoveableManagerInterface<any, any>, React: Renderer) {
|
|
10
|
+
const rect = moveable.getRect();
|
|
11
|
+
const { pos2 } = moveable.state;
|
|
12
|
+
|
|
13
|
+
// use css for able
|
|
14
|
+
const editableViewer = moveable.useCSS(
|
|
15
|
+
'div',
|
|
16
|
+
`
|
|
17
|
+
{
|
|
18
|
+
position: absolute;
|
|
19
|
+
left: 0px;
|
|
20
|
+
top: 0px;
|
|
21
|
+
will-change: transform;
|
|
22
|
+
transform-origin: 0px 0px;
|
|
23
|
+
display: flex;
|
|
24
|
+
}
|
|
25
|
+
.moveable-button {
|
|
26
|
+
width: 20px;
|
|
27
|
+
height: 20px;
|
|
28
|
+
background: #4af;
|
|
29
|
+
border-radius: 4px;
|
|
30
|
+
appearance: none;
|
|
31
|
+
border: 0;
|
|
32
|
+
color: white;
|
|
33
|
+
font-size: 12px;
|
|
34
|
+
font-weight: bold;
|
|
35
|
+
}
|
|
36
|
+
`,
|
|
37
|
+
);
|
|
38
|
+
// Add key (required)
|
|
39
|
+
// Add class prefix moveable-(required)
|
|
40
|
+
return React.createElement(
|
|
41
|
+
editableViewer,
|
|
42
|
+
{
|
|
43
|
+
className: 'moveable-editable',
|
|
44
|
+
style: {
|
|
45
|
+
transform: `translate(${pos2[0] - 25}px, ${pos2[1] - 30}px) rotate(${rect.rotation}deg) translate(10px)`,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
React.createElement(
|
|
49
|
+
'button',
|
|
50
|
+
{
|
|
51
|
+
className: 'moveable-button',
|
|
52
|
+
title: '选中父组件',
|
|
53
|
+
onClick: () => {
|
|
54
|
+
dr.emit('select-parent');
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
React.createElement(
|
|
58
|
+
'svg',
|
|
59
|
+
{
|
|
60
|
+
width: '1em',
|
|
61
|
+
height: '1em',
|
|
62
|
+
viewBox: '0 0 16 16',
|
|
63
|
+
fill: 'none',
|
|
64
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
65
|
+
style: {
|
|
66
|
+
transform: 'rotate(90deg)',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
React.createElement('path', {
|
|
70
|
+
d: 'M13.0001 4V10H4.20718L5.85363 8.35355L5.14652 7.64645L2.64652 10.1464C2.45126 10.3417 2.45126 10.6583 2.64652 10.8536L5.14652 13.3536L5.85363 12.6464L4.20718 11H13.0001C13.5524 11 14.0001 10.5523 14.0001 10V4H13.0001Z',
|
|
71
|
+
fill: 'currentColor',
|
|
72
|
+
fillOpacity: '0.9',
|
|
73
|
+
}),
|
|
74
|
+
),
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
});
|
package/src/Rule.ts
CHANGED
|
@@ -12,6 +12,7 @@ export default class Rule extends EventEmitter {
|
|
|
12
12
|
|
|
13
13
|
private container: HTMLDivElement;
|
|
14
14
|
private containerResizeObserver: ResizeObserver;
|
|
15
|
+
private isShowGuides = true;
|
|
15
16
|
|
|
16
17
|
constructor(container: HTMLDivElement) {
|
|
17
18
|
super();
|
|
@@ -32,16 +33,18 @@ export default class Rule extends EventEmitter {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
*
|
|
36
|
-
* @param
|
|
36
|
+
* 是否显示辅助线
|
|
37
|
+
* @param isShowGuides 是否显示
|
|
37
38
|
*/
|
|
38
|
-
public showGuides(
|
|
39
|
+
public showGuides(isShowGuides = true) {
|
|
40
|
+
this.isShowGuides = isShowGuides;
|
|
41
|
+
|
|
39
42
|
this.hGuides.setState({
|
|
40
|
-
showGuides:
|
|
43
|
+
showGuides: isShowGuides,
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
this.vGuides.setState({
|
|
44
|
-
showGuides:
|
|
47
|
+
showGuides: isShowGuides,
|
|
45
48
|
});
|
|
46
49
|
}
|
|
47
50
|
|
|
@@ -135,6 +138,7 @@ export default class Rule extends EventEmitter {
|
|
|
135
138
|
lineColor: '#000',
|
|
136
139
|
textColor: '#000',
|
|
137
140
|
style: this.getGuidesStyle(type),
|
|
141
|
+
showGuides: this.isShowGuides,
|
|
138
142
|
});
|
|
139
143
|
|
|
140
144
|
private hGuidesChangeGuidesHandler = (e: GuidesEvents['changeGuides']) => {
|
package/src/StageCore.ts
CHANGED
|
@@ -62,6 +62,7 @@ export default class StageCore extends EventEmitter {
|
|
|
62
62
|
public isContainer: IsContainer;
|
|
63
63
|
|
|
64
64
|
private canSelect: CanSelect;
|
|
65
|
+
private pageResizeObserver: ResizeObserver | null = null;
|
|
65
66
|
|
|
66
67
|
constructor(config: StageCoreConfig) {
|
|
67
68
|
super();
|
|
@@ -75,8 +76,8 @@ export default class StageCore extends EventEmitter {
|
|
|
75
76
|
this.containerHighlightDuration = config.containerHighlightDuration || 800;
|
|
76
77
|
this.containerHighlightType = config.containerHighlightType;
|
|
77
78
|
|
|
78
|
-
this.renderer = new StageRender({
|
|
79
|
-
this.mask = new StageMask(
|
|
79
|
+
this.renderer = new StageRender({ runtimeUrl: config.runtimeUrl, render: this.render.bind(this) });
|
|
80
|
+
this.mask = new StageMask();
|
|
80
81
|
this.dr = new StageDragResize({ core: this, container: this.mask.content, mask: this.mask });
|
|
81
82
|
this.multiDr = new StageMultiDragResize({ core: this, container: this.mask.content, mask: this.mask });
|
|
82
83
|
this.highlightLayer = new StageHighlight({ core: this, container: this.mask.wrapper });
|
|
@@ -86,6 +87,7 @@ export default class StageCore extends EventEmitter {
|
|
|
86
87
|
});
|
|
87
88
|
this.renderer.on('page-el-update', (el: HTMLElement) => {
|
|
88
89
|
this.mask?.observe(el);
|
|
90
|
+
this.observePageResize(el);
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
this.mask
|
|
@@ -134,6 +136,7 @@ export default class StageCore extends EventEmitter {
|
|
|
134
136
|
this.selectedDomList.push(el);
|
|
135
137
|
}
|
|
136
138
|
this.multiSelect(this.selectedDomList);
|
|
139
|
+
this.emit('multiSelect', this.selectedDomList);
|
|
137
140
|
});
|
|
138
141
|
|
|
139
142
|
// 要先触发select,在触发update
|
|
@@ -143,6 +146,9 @@ export default class StageCore extends EventEmitter {
|
|
|
143
146
|
})
|
|
144
147
|
.on('sort', (data: UpdateEventData) => {
|
|
145
148
|
setTimeout(() => this.emit('sort', data));
|
|
149
|
+
})
|
|
150
|
+
.on('select-parent', () => {
|
|
151
|
+
this.emit('select-parent');
|
|
146
152
|
});
|
|
147
153
|
|
|
148
154
|
this.multiDr
|
|
@@ -159,7 +165,6 @@ export default class StageCore extends EventEmitter {
|
|
|
159
165
|
public getElementsFromPoint(event: MouseEvent) {
|
|
160
166
|
const { renderer, zoom } = this;
|
|
161
167
|
|
|
162
|
-
const doc = renderer.contentWindow?.document;
|
|
163
168
|
let x = event.clientX;
|
|
164
169
|
let y = event.clientY;
|
|
165
170
|
|
|
@@ -171,7 +176,7 @@ export default class StageCore extends EventEmitter {
|
|
|
171
176
|
}
|
|
172
177
|
}
|
|
173
178
|
|
|
174
|
-
return
|
|
179
|
+
return renderer.getDocument()?.elementsFromPoint(x / zoom, y / zoom) as HTMLElement[];
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
public async getElementFromPoint(event: MouseEvent) {
|
|
@@ -219,15 +224,16 @@ export default class StageCore extends EventEmitter {
|
|
|
219
224
|
this.dr.select(el, event);
|
|
220
225
|
|
|
221
226
|
if (this.config.autoScrollIntoView || el.dataset.autoScrollIntoView) {
|
|
222
|
-
this.mask.
|
|
227
|
+
this.mask.observerIntersection(el);
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
this.selectedDom = el;
|
|
226
231
|
|
|
227
|
-
|
|
228
|
-
|
|
232
|
+
const doc = this.renderer.getDocument();
|
|
233
|
+
if (doc) {
|
|
234
|
+
removeSelectedClassName(doc);
|
|
229
235
|
if (this.selectedDom) {
|
|
230
|
-
addSelectedClassName(this.selectedDom,
|
|
236
|
+
addSelectedClassName(this.selectedDom, doc);
|
|
231
237
|
}
|
|
232
238
|
}
|
|
233
239
|
}
|
|
@@ -238,9 +244,8 @@ export default class StageCore extends EventEmitter {
|
|
|
238
244
|
*/
|
|
239
245
|
public async multiSelect(idOrElList: HTMLElement[] | Id[]): Promise<void> {
|
|
240
246
|
this.clearSelectStatus('select');
|
|
241
|
-
|
|
242
|
-
this.multiDr.multiSelect(
|
|
243
|
-
this.emit('multiSelect', elList);
|
|
247
|
+
this.selectedDomList = await Promise.all(idOrElList.map(async (idOrEl) => await this.getTargetElement(idOrEl)));
|
|
248
|
+
this.multiDr.multiSelect(this.selectedDomList);
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
/**
|
|
@@ -254,7 +259,7 @@ export default class StageCore extends EventEmitter {
|
|
|
254
259
|
runtime?.update?.(data);
|
|
255
260
|
// 更新配置后,需要等组件渲染更新
|
|
256
261
|
setTimeout(() => {
|
|
257
|
-
const el = this.renderer.
|
|
262
|
+
const el = this.renderer.getDocument()?.getElementById(`${config.id}`);
|
|
258
263
|
// 有可能dom已经重新渲染,不再是原来的dom了,所以这里判断id,而不是判断el === this.selectedDom
|
|
259
264
|
if (el && el.id === this.selectedDom?.id) {
|
|
260
265
|
this.selectedDom = el;
|
|
@@ -337,7 +342,7 @@ export default class StageCore extends EventEmitter {
|
|
|
337
342
|
public async addContainerHighlightClassName(event: MouseEvent, exclude: Element[]) {
|
|
338
343
|
const els = this.getElementsFromPoint(event);
|
|
339
344
|
const { renderer } = this;
|
|
340
|
-
const doc = renderer.
|
|
345
|
+
const doc = renderer.getDocument();
|
|
341
346
|
|
|
342
347
|
if (!doc) return;
|
|
343
348
|
|
|
@@ -359,21 +364,49 @@ export default class StageCore extends EventEmitter {
|
|
|
359
364
|
* 销毁实例
|
|
360
365
|
*/
|
|
361
366
|
public destroy(): void {
|
|
362
|
-
const { mask, renderer, dr, highlightLayer } = this;
|
|
367
|
+
const { mask, renderer, dr, highlightLayer, pageResizeObserver } = this;
|
|
363
368
|
|
|
364
369
|
renderer.destroy();
|
|
365
370
|
mask.destroy();
|
|
366
371
|
dr.destroy();
|
|
367
372
|
highlightLayer.destroy();
|
|
373
|
+
pageResizeObserver?.disconnect();
|
|
368
374
|
|
|
369
375
|
this.removeAllListeners();
|
|
370
376
|
|
|
371
377
|
this.container = undefined;
|
|
372
378
|
}
|
|
373
379
|
|
|
380
|
+
/**
|
|
381
|
+
* 监听页面大小变化
|
|
382
|
+
*/
|
|
383
|
+
private observePageResize(page: HTMLElement): void {
|
|
384
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
385
|
+
this.pageResizeObserver = new ResizeObserver((entries) => {
|
|
386
|
+
this.mask.pageResize(entries);
|
|
387
|
+
|
|
388
|
+
if (this.dr.moveable) {
|
|
389
|
+
this.dr.updateMoveable();
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
this.pageResizeObserver.observe(page);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* 传入stageRender供其回调,获取业务方自定义渲染画布页面渲染结果
|
|
399
|
+
*/
|
|
400
|
+
private async render(): Promise<HTMLElement | null> {
|
|
401
|
+
if (this.config?.render) {
|
|
402
|
+
return await this.config.render(this);
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
|
|
374
407
|
private async getTargetElement(idOrEl: Id | HTMLElement): Promise<HTMLElement> {
|
|
375
408
|
if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
|
|
376
|
-
const el = this.renderer.
|
|
409
|
+
const el = this.renderer.getDocument()?.getElementById(`${idOrEl}`);
|
|
377
410
|
if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
|
|
378
411
|
return el;
|
|
379
412
|
}
|
package/src/StageDragResize.ts
CHANGED
|
@@ -27,6 +27,7 @@ import MoveableHelper from 'moveable-helper';
|
|
|
27
27
|
import { removeClassNameByClassName } from '@tmagic/utils';
|
|
28
28
|
|
|
29
29
|
import { DRAG_EL_ID_PREFIX, GHOST_EL_ID_PREFIX, GuidesType, Mode, ZIndex } from './const';
|
|
30
|
+
import selectParentAbles from './MoveableSelectParentAble';
|
|
30
31
|
import StageCore from './StageCore';
|
|
31
32
|
import StageMask from './StageMask';
|
|
32
33
|
import type { StageDragResizeConfig } from './types';
|
|
@@ -395,10 +396,14 @@ export default class StageDragResize extends EventEmitter {
|
|
|
395
396
|
this.dragStatus = StageDragStatus.END;
|
|
396
397
|
const frame = this.moveableHelper?.getFrame(e.target);
|
|
397
398
|
this.emit('update', {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
399
|
+
data: [
|
|
400
|
+
{
|
|
401
|
+
el: this.target,
|
|
402
|
+
style: {
|
|
403
|
+
transform: frame?.get('transform'),
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
],
|
|
402
407
|
});
|
|
403
408
|
});
|
|
404
409
|
}
|
|
@@ -422,10 +427,14 @@ export default class StageDragResize extends EventEmitter {
|
|
|
422
427
|
this.dragStatus = StageDragStatus.END;
|
|
423
428
|
const frame = this.moveableHelper?.getFrame(e.target);
|
|
424
429
|
this.emit('update', {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
430
|
+
data: [
|
|
431
|
+
{
|
|
432
|
+
el: this.target,
|
|
433
|
+
style: {
|
|
434
|
+
transform: frame?.get('transform'),
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
],
|
|
429
438
|
});
|
|
430
439
|
});
|
|
431
440
|
}
|
|
@@ -552,7 +561,7 @@ export default class StageDragResize extends EventEmitter {
|
|
|
552
561
|
resizable: true,
|
|
553
562
|
scalable: false,
|
|
554
563
|
rotatable: false,
|
|
555
|
-
snappable:
|
|
564
|
+
snappable: true,
|
|
556
565
|
snapGap: isAbsolute || isFixed,
|
|
557
566
|
snapThreshold: 5,
|
|
558
567
|
snapDigit: 0,
|
|
@@ -585,6 +594,13 @@ export default class StageDragResize extends EventEmitter {
|
|
|
585
594
|
bottom: isSortable ? undefined : this.container.clientHeight,
|
|
586
595
|
...(moveableOptions.bounds || {}),
|
|
587
596
|
},
|
|
597
|
+
|
|
598
|
+
props: {
|
|
599
|
+
selectParent: true,
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
ables: [selectParentAbles(this)],
|
|
603
|
+
|
|
588
604
|
...options,
|
|
589
605
|
...moveableOptions,
|
|
590
606
|
};
|
package/src/StageMask.ts
CHANGED
|
@@ -23,9 +23,7 @@ import { createDiv, injectStyle } from '@tmagic/utils';
|
|
|
23
23
|
|
|
24
24
|
import { Mode, MouseButton, ZIndex } from './const';
|
|
25
25
|
import Rule from './Rule';
|
|
26
|
-
import
|
|
27
|
-
import type { StageMaskConfig } from './types';
|
|
28
|
-
import { getScrollParent, isFixedParent } from './util';
|
|
26
|
+
import { getScrollParent, isFixedParent, isMoveableButton } from './util';
|
|
29
27
|
|
|
30
28
|
const wrapperClassName = 'editor-mask-wrapper';
|
|
31
29
|
const throttleTime = 100;
|
|
@@ -71,9 +69,7 @@ const createWrapper = (): HTMLDivElement => {
|
|
|
71
69
|
export default class StageMask extends Rule {
|
|
72
70
|
public content: HTMLDivElement = createContent();
|
|
73
71
|
public wrapper: HTMLDivElement;
|
|
74
|
-
public core: StageCore;
|
|
75
72
|
public page: HTMLElement | null = null;
|
|
76
|
-
public pageScrollParent: HTMLElement | null = null;
|
|
77
73
|
public scrollTop = 0;
|
|
78
74
|
public scrollLeft = 0;
|
|
79
75
|
public width = 0;
|
|
@@ -82,12 +78,13 @@ export default class StageMask extends Rule {
|
|
|
82
78
|
public wrapperWidth = 0;
|
|
83
79
|
public maxScrollTop = 0;
|
|
84
80
|
public maxScrollLeft = 0;
|
|
85
|
-
public intersectionObserver: IntersectionObserver | null = null;
|
|
86
81
|
public isMultiSelectStatus: Boolean = false;
|
|
87
82
|
|
|
88
83
|
private mode: Mode = Mode.ABSOLUTE;
|
|
89
|
-
private
|
|
84
|
+
private pageScrollParent: HTMLElement | null = null;
|
|
85
|
+
private intersectionObserver: IntersectionObserver | null = null;
|
|
90
86
|
private wrapperResizeObserver: ResizeObserver | null = null;
|
|
87
|
+
|
|
91
88
|
/**
|
|
92
89
|
* 高亮事件处理函数
|
|
93
90
|
* @param event 事件对象
|
|
@@ -96,31 +93,16 @@ export default class StageMask extends Rule {
|
|
|
96
93
|
this.emit('highlight', event);
|
|
97
94
|
}, throttleTime);
|
|
98
95
|
|
|
99
|
-
constructor(
|
|
96
|
+
constructor() {
|
|
100
97
|
const wrapper = createWrapper();
|
|
101
98
|
super(wrapper);
|
|
102
99
|
|
|
103
100
|
this.wrapper = wrapper;
|
|
104
|
-
this.core = config.core;
|
|
105
101
|
|
|
106
|
-
this.
|
|
102
|
+
this.initContentEventListener();
|
|
107
103
|
this.wrapper.appendChild(this.content);
|
|
108
|
-
this.content.addEventListener('wheel', this.mouseWheelHandler);
|
|
109
|
-
this.content.addEventListener('mousemove', this.highlightHandler);
|
|
110
|
-
this.content.addEventListener('mouseleave', this.mouseLeaveHandler);
|
|
111
|
-
|
|
112
|
-
const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
|
|
113
|
-
|
|
114
|
-
const ctrl = isMac ? 'meta' : 'ctrl';
|
|
115
104
|
|
|
116
|
-
|
|
117
|
-
e.inputEvent.preventDefault();
|
|
118
|
-
this.isMultiSelectStatus = true;
|
|
119
|
-
});
|
|
120
|
-
KeyController.global.keyup(ctrl, (e) => {
|
|
121
|
-
e.inputEvent.preventDefault();
|
|
122
|
-
this.isMultiSelectStatus = false;
|
|
123
|
-
});
|
|
105
|
+
this.initMultiSelectEvent();
|
|
124
106
|
}
|
|
125
107
|
|
|
126
108
|
public setMode(mode: Mode) {
|
|
@@ -136,63 +118,37 @@ export default class StageMask extends Rule {
|
|
|
136
118
|
}
|
|
137
119
|
|
|
138
120
|
/**
|
|
139
|
-
*
|
|
140
|
-
* @description
|
|
121
|
+
* 初始化视窗和蒙层监听,监听元素是否在视窗区域、监听mask蒙层所在的wrapper大小变化
|
|
122
|
+
* @description 初始化视窗和蒙层监听
|
|
141
123
|
* @param page 页面Dom节点
|
|
142
124
|
*/
|
|
143
125
|
public observe(page: HTMLElement): void {
|
|
144
126
|
if (!page) return;
|
|
145
127
|
|
|
146
128
|
this.page = page;
|
|
147
|
-
this.
|
|
148
|
-
this.
|
|
149
|
-
|
|
150
|
-
this.intersectionObserver?.disconnect();
|
|
151
|
-
|
|
152
|
-
if (typeof IntersectionObserver !== 'undefined') {
|
|
153
|
-
this.intersectionObserver = new IntersectionObserver(
|
|
154
|
-
(entries) => {
|
|
155
|
-
entries.forEach((entry) => {
|
|
156
|
-
const { target, intersectionRatio } = entry;
|
|
157
|
-
if (intersectionRatio <= 0) {
|
|
158
|
-
this.scrollIntoView(target);
|
|
159
|
-
}
|
|
160
|
-
this.intersectionObserver?.unobserve(target);
|
|
161
|
-
});
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
root: this.pageScrollParent,
|
|
165
|
-
rootMargin: '0px',
|
|
166
|
-
threshold: 1.0,
|
|
167
|
-
},
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (typeof ResizeObserver !== 'undefined') {
|
|
172
|
-
this.pageResizeObserver = new ResizeObserver((entries) => {
|
|
173
|
-
const [entry] = entries;
|
|
174
|
-
const { clientHeight, clientWidth } = entry.target;
|
|
175
|
-
this.setHeight(clientHeight);
|
|
176
|
-
this.setWidth(clientWidth);
|
|
129
|
+
this.initObserverIntersection();
|
|
130
|
+
this.initObserverWrapper();
|
|
131
|
+
}
|
|
177
132
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
133
|
+
/**
|
|
134
|
+
* 处理页面大小变更,同步页面和mask大小
|
|
135
|
+
* @param entries ResizeObserverEntry,获取页面最新大小
|
|
136
|
+
*/
|
|
137
|
+
public pageResize(entries: ResizeObserverEntry[]): void {
|
|
138
|
+
const [entry] = entries;
|
|
139
|
+
const { clientHeight, clientWidth } = entry.target;
|
|
140
|
+
this.setHeight(clientHeight);
|
|
141
|
+
this.setWidth(clientWidth);
|
|
183
142
|
|
|
184
|
-
|
|
143
|
+
this.scroll();
|
|
144
|
+
}
|
|
185
145
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.setMaxScrollTop();
|
|
193
|
-
});
|
|
194
|
-
this.wrapperResizeObserver.observe(this.wrapper);
|
|
195
|
-
}
|
|
146
|
+
/**
|
|
147
|
+
* 监听一个组件是否在画布可视区域内
|
|
148
|
+
* @param el 被选中的组件,可能是左侧目录树中选中的
|
|
149
|
+
*/
|
|
150
|
+
public observerIntersection(el: HTMLElement): void {
|
|
151
|
+
this.intersectionObserver?.observe(el);
|
|
196
152
|
}
|
|
197
153
|
|
|
198
154
|
/**
|
|
@@ -224,13 +180,89 @@ export default class StageMask extends Rule {
|
|
|
224
180
|
this.content?.remove();
|
|
225
181
|
this.page = null;
|
|
226
182
|
this.pageScrollParent = null;
|
|
227
|
-
this.pageResizeObserver?.disconnect();
|
|
228
183
|
this.wrapperResizeObserver?.disconnect();
|
|
229
184
|
|
|
230
185
|
this.content.removeEventListener('mouseleave', this.mouseLeaveHandler);
|
|
231
186
|
super.destroy();
|
|
232
187
|
}
|
|
233
188
|
|
|
189
|
+
/**
|
|
190
|
+
* 初始化content的事件监听
|
|
191
|
+
*/
|
|
192
|
+
private initContentEventListener(): void {
|
|
193
|
+
this.content.addEventListener('mousedown', this.mouseDownHandler);
|
|
194
|
+
this.content.addEventListener('wheel', this.mouseWheelHandler);
|
|
195
|
+
this.content.addEventListener('mousemove', this.highlightHandler);
|
|
196
|
+
this.content.addEventListener('mouseleave', this.mouseLeaveHandler);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 初始化多选事件监听
|
|
201
|
+
*/
|
|
202
|
+
private initMultiSelectEvent(): void {
|
|
203
|
+
const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
|
|
204
|
+
|
|
205
|
+
const ctrl = isMac ? 'meta' : 'ctrl';
|
|
206
|
+
|
|
207
|
+
KeyController.global.keydown(ctrl, (e) => {
|
|
208
|
+
e.inputEvent.preventDefault();
|
|
209
|
+
this.isMultiSelectStatus = true;
|
|
210
|
+
});
|
|
211
|
+
// ctrl+tab切到其他窗口,需要将多选状态置为false
|
|
212
|
+
KeyController.global.on('blur', () => {
|
|
213
|
+
this.isMultiSelectStatus = false;
|
|
214
|
+
});
|
|
215
|
+
KeyController.global.keyup(ctrl, (e) => {
|
|
216
|
+
e.inputEvent.preventDefault();
|
|
217
|
+
this.isMultiSelectStatus = false;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 监听选中元素是否在画布可视区域内,如果目标元素不在可视区域内,通过滚动使该元素出现在可视区域
|
|
223
|
+
*/
|
|
224
|
+
private initObserverIntersection(): void {
|
|
225
|
+
this.pageScrollParent = getScrollParent(this.page as HTMLElement) || null;
|
|
226
|
+
this.intersectionObserver?.disconnect();
|
|
227
|
+
|
|
228
|
+
if (typeof IntersectionObserver !== 'undefined') {
|
|
229
|
+
this.intersectionObserver = new IntersectionObserver(
|
|
230
|
+
(entries) => {
|
|
231
|
+
entries.forEach((entry) => {
|
|
232
|
+
const { target, intersectionRatio } = entry;
|
|
233
|
+
if (intersectionRatio <= 0) {
|
|
234
|
+
this.scrollIntoView(target);
|
|
235
|
+
}
|
|
236
|
+
this.intersectionObserver?.unobserve(target);
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
root: this.pageScrollParent,
|
|
241
|
+
rootMargin: '0px',
|
|
242
|
+
threshold: 1.0,
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 监听mask的容器大小变化
|
|
250
|
+
*/
|
|
251
|
+
private initObserverWrapper(): void {
|
|
252
|
+
this.wrapperResizeObserver?.disconnect();
|
|
253
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
254
|
+
this.wrapperResizeObserver = new ResizeObserver((entries) => {
|
|
255
|
+
const [entry] = entries;
|
|
256
|
+
const { clientHeight, clientWidth } = entry.target;
|
|
257
|
+
this.wrapperHeight = clientHeight;
|
|
258
|
+
this.wrapperWidth = clientWidth;
|
|
259
|
+
this.setMaxScrollLeft();
|
|
260
|
+
this.setMaxScrollTop();
|
|
261
|
+
});
|
|
262
|
+
this.wrapperResizeObserver.observe(this.wrapper);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
234
266
|
private scroll() {
|
|
235
267
|
this.fixScrollValue();
|
|
236
268
|
|
|
@@ -311,13 +343,16 @@ export default class StageMask extends Rule {
|
|
|
311
343
|
event.stopPropagation();
|
|
312
344
|
|
|
313
345
|
if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT) return;
|
|
346
|
+
if (!event.target) return;
|
|
347
|
+
|
|
348
|
+
const targetClassList = (event.target as HTMLDivElement).classList;
|
|
314
349
|
|
|
315
350
|
// 如果单击多选选中区域,则不需要再触发选中了,而可能是拖动行为
|
|
316
|
-
if (!this.isMultiSelectStatus &&
|
|
351
|
+
if (!this.isMultiSelectStatus && targetClassList.contains('moveable-area')) {
|
|
317
352
|
return;
|
|
318
353
|
}
|
|
319
|
-
// 点击对象如果是边框锚点,则可能是resize
|
|
320
|
-
if (
|
|
354
|
+
// 点击对象如果是边框锚点,则可能是resize; 点击对象是功能按钮
|
|
355
|
+
if (targetClassList.contains('moveable-control') || isMoveableButton(event.target as Element)) {
|
|
321
356
|
return;
|
|
322
357
|
}
|
|
323
358
|
|
package/src/StageRender.ts
CHANGED
|
@@ -20,7 +20,6 @@ import { EventEmitter } from 'events';
|
|
|
20
20
|
|
|
21
21
|
import { getHost, injectStyle, isSameDomain } from '@tmagic/utils';
|
|
22
22
|
|
|
23
|
-
import StageCore from './StageCore';
|
|
24
23
|
import style from './style.css?raw';
|
|
25
24
|
import type { Runtime, RuntimeWindow, StageRenderConfig } from './types';
|
|
26
25
|
|
|
@@ -34,16 +33,13 @@ export default class StageRender extends EventEmitter {
|
|
|
34
33
|
|
|
35
34
|
public runtimeUrl?: string;
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
private render?: () => Promise<HTMLElement | null>;
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
constructor({ core }: StageRenderConfig) {
|
|
38
|
+
constructor({ runtimeUrl, render }: StageRenderConfig) {
|
|
42
39
|
super();
|
|
43
40
|
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
46
|
-
this.render = core.config.render;
|
|
41
|
+
this.runtimeUrl = runtimeUrl || '';
|
|
42
|
+
this.render = render;
|
|
47
43
|
|
|
48
44
|
this.iframe = globalThis.document.createElement('iframe');
|
|
49
45
|
// 同源,直接加载
|
|
@@ -101,6 +97,10 @@ export default class StageRender extends EventEmitter {
|
|
|
101
97
|
});
|
|
102
98
|
};
|
|
103
99
|
|
|
100
|
+
public getDocument(): Document | undefined {
|
|
101
|
+
return this.contentWindow?.document;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
104
|
/**
|
|
105
105
|
* 销毁实例
|
|
106
106
|
*/
|
|
@@ -120,7 +120,7 @@ export default class StageRender extends EventEmitter {
|
|
|
120
120
|
if (!this.contentWindow) return;
|
|
121
121
|
|
|
122
122
|
if (this.render) {
|
|
123
|
-
const el = await this.render(
|
|
123
|
+
const el = await this.render();
|
|
124
124
|
if (el) {
|
|
125
125
|
this.contentWindow.document?.body?.appendChild(el);
|
|
126
126
|
}
|