@tmagic/stage 1.2.0-beta.7 → 1.2.0-beta.8
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 +199 -86
- package/dist/tmagic-stage.mjs.map +1 -1
- package/dist/tmagic-stage.umd.js +199 -85
- 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 +45 -12
- package/src/StageDragResize.ts +8 -0
- package/src/StageMask.ts +112 -81
- package/src/StageRender.ts +9 -9
- package/src/types.ts +2 -1
- 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 +2 -1
- package/types/util.d.ts +1 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.2.0-beta.
|
|
2
|
+
"version": "1.2.0-beta.8",
|
|
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.2.0-beta.
|
|
33
|
-
"@tmagic/schema": "1.2.0-beta.
|
|
34
|
-
"@tmagic/utils": "1.2.0-beta.
|
|
32
|
+
"@tmagic/core": "1.2.0-beta.8",
|
|
33
|
+
"@tmagic/schema": "1.2.0-beta.8",
|
|
34
|
+
"@tmagic/utils": "1.2.0-beta.8",
|
|
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
|
|
@@ -144,6 +146,9 @@ export default class StageCore extends EventEmitter {
|
|
|
144
146
|
})
|
|
145
147
|
.on('sort', (data: UpdateEventData) => {
|
|
146
148
|
setTimeout(() => this.emit('sort', data));
|
|
149
|
+
})
|
|
150
|
+
.on('select-parent', () => {
|
|
151
|
+
this.emit('select-parent');
|
|
147
152
|
});
|
|
148
153
|
|
|
149
154
|
this.multiDr
|
|
@@ -160,7 +165,6 @@ export default class StageCore extends EventEmitter {
|
|
|
160
165
|
public getElementsFromPoint(event: MouseEvent) {
|
|
161
166
|
const { renderer, zoom } = this;
|
|
162
167
|
|
|
163
|
-
const doc = renderer.contentWindow?.document;
|
|
164
168
|
let x = event.clientX;
|
|
165
169
|
let y = event.clientY;
|
|
166
170
|
|
|
@@ -172,7 +176,7 @@ export default class StageCore extends EventEmitter {
|
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
|
|
175
|
-
return
|
|
179
|
+
return renderer.getDocument()?.elementsFromPoint(x / zoom, y / zoom) as HTMLElement[];
|
|
176
180
|
}
|
|
177
181
|
|
|
178
182
|
public async getElementFromPoint(event: MouseEvent) {
|
|
@@ -220,15 +224,16 @@ export default class StageCore extends EventEmitter {
|
|
|
220
224
|
this.dr.select(el, event);
|
|
221
225
|
|
|
222
226
|
if (this.config.autoScrollIntoView || el.dataset.autoScrollIntoView) {
|
|
223
|
-
this.mask.
|
|
227
|
+
this.mask.observerIntersection(el);
|
|
224
228
|
}
|
|
225
229
|
|
|
226
230
|
this.selectedDom = el;
|
|
227
231
|
|
|
228
|
-
|
|
229
|
-
|
|
232
|
+
const doc = this.renderer.getDocument();
|
|
233
|
+
if (doc) {
|
|
234
|
+
removeSelectedClassName(doc);
|
|
230
235
|
if (this.selectedDom) {
|
|
231
|
-
addSelectedClassName(this.selectedDom,
|
|
236
|
+
addSelectedClassName(this.selectedDom, doc);
|
|
232
237
|
}
|
|
233
238
|
}
|
|
234
239
|
}
|
|
@@ -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';
|
|
@@ -593,6 +594,13 @@ export default class StageDragResize extends EventEmitter {
|
|
|
593
594
|
bottom: isSortable ? undefined : this.container.clientHeight,
|
|
594
595
|
...(moveableOptions.bounds || {}),
|
|
595
596
|
},
|
|
597
|
+
|
|
598
|
+
props: {
|
|
599
|
+
selectParent: true,
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
ables: [selectParentAbles(this)],
|
|
603
|
+
|
|
596
604
|
...options,
|
|
597
605
|
...moveableOptions,
|
|
598
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,35 +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
|
-
// ctrl+tab切到其他窗口,需要将多选状态置为false
|
|
121
|
-
KeyController.global.on('blur', () => {
|
|
122
|
-
this.isMultiSelectStatus = false;
|
|
123
|
-
});
|
|
124
|
-
KeyController.global.keyup(ctrl, (e) => {
|
|
125
|
-
e.inputEvent.preventDefault();
|
|
126
|
-
this.isMultiSelectStatus = false;
|
|
127
|
-
});
|
|
105
|
+
this.initMultiSelectEvent();
|
|
128
106
|
}
|
|
129
107
|
|
|
130
108
|
public setMode(mode: Mode) {
|
|
@@ -140,63 +118,37 @@ export default class StageMask extends Rule {
|
|
|
140
118
|
}
|
|
141
119
|
|
|
142
120
|
/**
|
|
143
|
-
*
|
|
144
|
-
* @description
|
|
121
|
+
* 初始化视窗和蒙层监听,监听元素是否在视窗区域、监听mask蒙层所在的wrapper大小变化
|
|
122
|
+
* @description 初始化视窗和蒙层监听
|
|
145
123
|
* @param page 页面Dom节点
|
|
146
124
|
*/
|
|
147
125
|
public observe(page: HTMLElement): void {
|
|
148
126
|
if (!page) return;
|
|
149
127
|
|
|
150
128
|
this.page = page;
|
|
151
|
-
this.
|
|
152
|
-
this.
|
|
153
|
-
|
|
154
|
-
this.intersectionObserver?.disconnect();
|
|
155
|
-
|
|
156
|
-
if (typeof IntersectionObserver !== 'undefined') {
|
|
157
|
-
this.intersectionObserver = new IntersectionObserver(
|
|
158
|
-
(entries) => {
|
|
159
|
-
entries.forEach((entry) => {
|
|
160
|
-
const { target, intersectionRatio } = entry;
|
|
161
|
-
if (intersectionRatio <= 0) {
|
|
162
|
-
this.scrollIntoView(target);
|
|
163
|
-
}
|
|
164
|
-
this.intersectionObserver?.unobserve(target);
|
|
165
|
-
});
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
root: this.pageScrollParent,
|
|
169
|
-
rootMargin: '0px',
|
|
170
|
-
threshold: 1.0,
|
|
171
|
-
},
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (typeof ResizeObserver !== 'undefined') {
|
|
176
|
-
this.pageResizeObserver = new ResizeObserver((entries) => {
|
|
177
|
-
const [entry] = entries;
|
|
178
|
-
const { clientHeight, clientWidth } = entry.target;
|
|
179
|
-
this.setHeight(clientHeight);
|
|
180
|
-
this.setWidth(clientWidth);
|
|
129
|
+
this.initObserverIntersection();
|
|
130
|
+
this.initObserverWrapper();
|
|
131
|
+
}
|
|
181
132
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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);
|
|
187
142
|
|
|
188
|
-
|
|
143
|
+
this.scroll();
|
|
144
|
+
}
|
|
189
145
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.setMaxScrollTop();
|
|
197
|
-
});
|
|
198
|
-
this.wrapperResizeObserver.observe(this.wrapper);
|
|
199
|
-
}
|
|
146
|
+
/**
|
|
147
|
+
* 监听一个组件是否在画布可视区域内
|
|
148
|
+
* @param el 被选中的组件,可能是左侧目录树中选中的
|
|
149
|
+
*/
|
|
150
|
+
public observerIntersection(el: HTMLElement): void {
|
|
151
|
+
this.intersectionObserver?.observe(el);
|
|
200
152
|
}
|
|
201
153
|
|
|
202
154
|
/**
|
|
@@ -228,13 +180,89 @@ export default class StageMask extends Rule {
|
|
|
228
180
|
this.content?.remove();
|
|
229
181
|
this.page = null;
|
|
230
182
|
this.pageScrollParent = null;
|
|
231
|
-
this.pageResizeObserver?.disconnect();
|
|
232
183
|
this.wrapperResizeObserver?.disconnect();
|
|
233
184
|
|
|
234
185
|
this.content.removeEventListener('mouseleave', this.mouseLeaveHandler);
|
|
235
186
|
super.destroy();
|
|
236
187
|
}
|
|
237
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
|
+
|
|
238
266
|
private scroll() {
|
|
239
267
|
this.fixScrollValue();
|
|
240
268
|
|
|
@@ -315,13 +343,16 @@ export default class StageMask extends Rule {
|
|
|
315
343
|
event.stopPropagation();
|
|
316
344
|
|
|
317
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;
|
|
318
349
|
|
|
319
350
|
// 如果单击多选选中区域,则不需要再触发选中了,而可能是拖动行为
|
|
320
|
-
if (!this.isMultiSelectStatus &&
|
|
351
|
+
if (!this.isMultiSelectStatus && targetClassList.contains('moveable-area')) {
|
|
321
352
|
return;
|
|
322
353
|
}
|
|
323
|
-
// 点击对象如果是边框锚点,则可能是resize
|
|
324
|
-
if (
|
|
354
|
+
// 点击对象如果是边框锚点,则可能是resize; 点击对象是功能按钮
|
|
355
|
+
if (targetClassList.contains('moveable-control') || isMoveableButton(event.target as Element)) {
|
|
325
356
|
return;
|
|
326
357
|
}
|
|
327
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
|
}
|
package/src/types.ts
CHANGED
package/src/util.ts
CHANGED
|
@@ -119,6 +119,9 @@ export const getScrollParent = (element: HTMLElement, includeHidden = false): HT
|
|
|
119
119
|
|
|
120
120
|
for (let parent = element; parent.parentElement; ) {
|
|
121
121
|
parent = parent.parentElement;
|
|
122
|
+
|
|
123
|
+
if (parent.tagName === 'HTML') return parent;
|
|
124
|
+
|
|
122
125
|
style = getComputedStyle(parent);
|
|
123
126
|
|
|
124
127
|
if (isAbsolute(style) && isStatic(style)) continue;
|
|
@@ -229,3 +232,6 @@ export const up = (deltaTop: number, target: HTMLElement | SVGElement): SortEven
|
|
|
229
232
|
dist: upEls.length && swapIndex > -1 ? upEls[swapIndex].id : target.id,
|
|
230
233
|
};
|
|
231
234
|
};
|
|
235
|
+
|
|
236
|
+
export const isMoveableButton = (target: Element) =>
|
|
237
|
+
target.classList.contains('moveable-button') || target.parentElement?.classList.contains('moveable-button');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MoveableManagerInterface, Renderer } from 'moveable';
|
|
2
|
+
import type StageDragResize from './StageDragResize';
|
|
3
|
+
declare const _default: (dr: StageDragResize) => {
|
|
4
|
+
name: string;
|
|
5
|
+
props: {};
|
|
6
|
+
events: {};
|
|
7
|
+
render(moveable: MoveableManagerInterface<any, any>, React: Renderer): any;
|
|
8
|
+
};
|
|
9
|
+
export default _default;
|
package/types/Rule.d.ts
CHANGED
|
@@ -8,12 +8,13 @@ export default class Rule extends EventEmitter {
|
|
|
8
8
|
verticalGuidelines: number[];
|
|
9
9
|
private container;
|
|
10
10
|
private containerResizeObserver;
|
|
11
|
+
private isShowGuides;
|
|
11
12
|
constructor(container: HTMLDivElement);
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param
|
|
14
|
+
* 是否显示辅助线
|
|
15
|
+
* @param isShowGuides 是否显示
|
|
15
16
|
*/
|
|
16
|
-
showGuides(
|
|
17
|
+
showGuides(isShowGuides?: boolean): void;
|
|
17
18
|
setGuides([hLines, vLines]: [number[], number[]]): void;
|
|
18
19
|
/**
|
|
19
20
|
* 清空所有参考线
|