@tmagic/stage 1.0.0-beta.6 → 1.0.0-beta.9
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/dist/tmagic-stage.es.js +582 -17579
- package/dist/tmagic-stage.es.js.map +1 -1
- package/dist/tmagic-stage.umd.js +587 -17586
- 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 +17 -6
- package/dist/types/src/StageDragResize.d.ts +13 -9
- package/dist/types/src/StageHighlight.d.ts +25 -0
- package/dist/types/src/StageMask.d.ts +22 -36
- package/dist/types/src/StageRender.d.ts +1 -0
- package/dist/types/src/const.d.ts +19 -0
- package/dist/types/src/types.d.ts +8 -8
- package/dist/types/src/util.d.ts +9 -5
- package/package.json +7 -5
- package/src/Rule.ts +150 -0
- package/src/StageCore.ts +127 -93
- package/src/StageDragResize.ts +174 -163
- package/src/StageHighlight.ts +67 -0
- package/src/StageMask.ts +136 -184
- package/src/StageRender.ts +25 -9
- package/src/const.ts +24 -0
- package/src/types.ts +26 -26
- package/src/util.ts +57 -7
package/src/StageCore.ts
CHANGED
|
@@ -18,25 +18,37 @@
|
|
|
18
18
|
|
|
19
19
|
import { EventEmitter } from 'events';
|
|
20
20
|
|
|
21
|
-
import { GuidesEvents } from '@scena/guides';
|
|
22
|
-
|
|
23
21
|
import { Id } from '@tmagic/schema';
|
|
24
22
|
|
|
25
|
-
import { DEFAULT_ZOOM } from './const';
|
|
23
|
+
import { DEFAULT_ZOOM, GHOST_EL_ID_PREFIX } from './const';
|
|
26
24
|
import StageDragResize from './StageDragResize';
|
|
25
|
+
import StageHighlight from './StageHighlight';
|
|
27
26
|
import StageMask from './StageMask';
|
|
28
27
|
import StageRender from './StageRender';
|
|
29
|
-
import {
|
|
30
|
-
|
|
28
|
+
import {
|
|
29
|
+
CanSelect,
|
|
30
|
+
GuidesEventData,
|
|
31
|
+
RemoveData,
|
|
32
|
+
Runtime,
|
|
33
|
+
SortEventData,
|
|
34
|
+
StageCoreConfig,
|
|
35
|
+
UpdateData,
|
|
36
|
+
UpdateEventData,
|
|
37
|
+
} from './types';
|
|
38
|
+
import { addSelectedClassName, removeSelectedClassName } from './util';
|
|
31
39
|
|
|
32
40
|
export default class StageCore extends EventEmitter {
|
|
33
41
|
public selectedDom: Element | undefined;
|
|
42
|
+
public highlightedDom: Element | undefined;
|
|
34
43
|
|
|
35
44
|
public renderer: StageRender;
|
|
36
45
|
public mask: StageMask;
|
|
37
46
|
public dr: StageDragResize;
|
|
47
|
+
public highlightLayer: StageHighlight;
|
|
38
48
|
public config: StageCoreConfig;
|
|
39
49
|
public zoom = DEFAULT_ZOOM;
|
|
50
|
+
public container?: HTMLDivElement;
|
|
51
|
+
private canSelect: CanSelect;
|
|
40
52
|
|
|
41
53
|
constructor(config: StageCoreConfig) {
|
|
42
54
|
super();
|
|
@@ -44,49 +56,109 @@ export default class StageCore extends EventEmitter {
|
|
|
44
56
|
this.config = config;
|
|
45
57
|
|
|
46
58
|
this.setZoom(config.zoom);
|
|
59
|
+
this.canSelect = config.canSelect || ((el: HTMLElement) => !!el.id);
|
|
47
60
|
|
|
48
61
|
this.renderer = new StageRender({ core: this });
|
|
49
62
|
this.mask = new StageMask({ core: this });
|
|
50
63
|
this.dr = new StageDragResize({ core: this, container: this.mask.content });
|
|
64
|
+
this.highlightLayer = new StageHighlight({ core: this, container: this.mask.content });
|
|
51
65
|
|
|
52
66
|
this.renderer.on('runtime-ready', (runtime: Runtime) => this.emit('runtime-ready', runtime));
|
|
53
67
|
this.renderer.on('page-el-update', (el: HTMLElement) => this.mask?.observe(el));
|
|
54
68
|
|
|
55
|
-
this.mask
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
this.mask
|
|
70
|
+
.on('beforeSelect', (event: MouseEvent) => {
|
|
71
|
+
this.setElementFromPoint(event);
|
|
72
|
+
})
|
|
73
|
+
.on('select', () => {
|
|
74
|
+
this.emit('select', this.selectedDom);
|
|
75
|
+
})
|
|
76
|
+
.on('changeGuides', (data: GuidesEventData) => {
|
|
77
|
+
this.dr.setGuidelines(data.type, data.guides);
|
|
78
|
+
this.emit('changeGuides', data);
|
|
79
|
+
})
|
|
80
|
+
.on('highlight', async (event: MouseEvent) => {
|
|
81
|
+
await this.setElementFromPoint(event);
|
|
82
|
+
this.highlightLayer.highlight(this.highlightedDom as HTMLElement);
|
|
83
|
+
this.emit('highlight', this.highlightedDom);
|
|
84
|
+
})
|
|
85
|
+
.on('clearHighlight', async () => {
|
|
86
|
+
this.highlightLayer.clearHighlight();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// 要先触发select,在触发update
|
|
90
|
+
this.dr
|
|
91
|
+
.on('update', (data: UpdateEventData) => {
|
|
92
|
+
setTimeout(() => this.emit('update', data));
|
|
93
|
+
})
|
|
94
|
+
.on('sort', (data: UpdateEventData) => {
|
|
95
|
+
setTimeout(() => this.emit('sort', data));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public async setElementFromPoint(event: MouseEvent) {
|
|
100
|
+
const { renderer, zoom } = this;
|
|
101
|
+
|
|
102
|
+
const doc = renderer.contentWindow?.document;
|
|
103
|
+
let x = event.clientX;
|
|
104
|
+
let y = event.clientY;
|
|
105
|
+
|
|
106
|
+
if (renderer.iframe) {
|
|
107
|
+
const rect = renderer.iframe.getClientRects()[0];
|
|
108
|
+
if (rect) {
|
|
109
|
+
x = x - rect.left;
|
|
110
|
+
y = y - rect.top;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const els = doc?.elementsFromPoint(x / zoom, y / zoom) as HTMLElement[];
|
|
62
115
|
|
|
63
|
-
|
|
64
|
-
|
|
116
|
+
let stopped = false;
|
|
117
|
+
const stop = () => (stopped = true);
|
|
118
|
+
for (const el of els) {
|
|
119
|
+
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, stop))) {
|
|
120
|
+
if (stopped) break;
|
|
121
|
+
if (event.type === 'mousemove') {
|
|
122
|
+
this.highlight(el);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
this.select(el, event);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
65
129
|
}
|
|
66
130
|
|
|
67
131
|
/**
|
|
68
132
|
* 选中组件
|
|
69
133
|
* @param idOrEl 组件Dom节点的id属性,或者Dom节点
|
|
70
134
|
*/
|
|
71
|
-
public async select(idOrEl: Id | HTMLElement): Promise<void> {
|
|
72
|
-
|
|
73
|
-
if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
|
|
74
|
-
const runtime = await this.renderer?.getRuntime();
|
|
75
|
-
el = await runtime?.select?.(`${idOrEl}`);
|
|
76
|
-
if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
|
|
77
|
-
} else {
|
|
78
|
-
el = idOrEl;
|
|
79
|
-
}
|
|
135
|
+
public async select(idOrEl: Id | HTMLElement, event?: MouseEvent): Promise<void> {
|
|
136
|
+
const el = await this.getTargetElement(idOrEl);
|
|
80
137
|
|
|
81
|
-
if (this.selectedDom
|
|
138
|
+
if (el === this.selectedDom) return;
|
|
82
139
|
|
|
83
|
-
await this.
|
|
140
|
+
const runtime = await this.renderer.getRuntime();
|
|
141
|
+
|
|
142
|
+
if (runtime?.beforeSelect) {
|
|
143
|
+
await runtime.beforeSelect(el);
|
|
144
|
+
}
|
|
84
145
|
|
|
146
|
+
this.mask.setLayout(el);
|
|
147
|
+
this.dr?.select(el, event);
|
|
85
148
|
this.selectedDom = el;
|
|
86
|
-
|
|
87
|
-
this.
|
|
149
|
+
|
|
150
|
+
if (this.renderer.contentWindow) {
|
|
151
|
+
removeSelectedClassName(this.renderer.contentWindow.document);
|
|
152
|
+
if (this.selectedDom) {
|
|
153
|
+
addSelectedClassName(this.selectedDom);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
88
156
|
}
|
|
89
157
|
|
|
158
|
+
/**
|
|
159
|
+
* 更新选中的节点
|
|
160
|
+
* @param data 更新的数据
|
|
161
|
+
*/
|
|
90
162
|
public update(data: UpdateData): void {
|
|
91
163
|
const { config } = data;
|
|
92
164
|
|
|
@@ -97,13 +169,24 @@ export default class StageCore extends EventEmitter {
|
|
|
97
169
|
const el = this.renderer.contentWindow?.document.getElementById(`${config.id}`);
|
|
98
170
|
if (el) {
|
|
99
171
|
// 更新了组件的布局,需要重新设置mask是否可以滚动
|
|
100
|
-
this.
|
|
172
|
+
this.mask.setLayout(el);
|
|
173
|
+
this.dr?.select(el);
|
|
101
174
|
}
|
|
102
|
-
this.dr?.refresh();
|
|
103
175
|
}, 0);
|
|
104
176
|
});
|
|
105
177
|
}
|
|
106
178
|
|
|
179
|
+
/**
|
|
180
|
+
* 高亮选中组件
|
|
181
|
+
* @param el 页面Dom节点
|
|
182
|
+
*/
|
|
183
|
+
public async highlight(idOrEl: HTMLElement | Id): Promise<void> {
|
|
184
|
+
const el = await this.getTargetElement(idOrEl);
|
|
185
|
+
if (el === this.highlightedDom) return;
|
|
186
|
+
this.highlightLayer.highlight(el);
|
|
187
|
+
this.highlightedDom = el;
|
|
188
|
+
}
|
|
189
|
+
|
|
107
190
|
public sortNode(data: SortEventData): void {
|
|
108
191
|
this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
|
|
109
192
|
}
|
|
@@ -125,17 +208,12 @@ export default class StageCore extends EventEmitter {
|
|
|
125
208
|
* @param el 将stage挂载到该Dom节点上
|
|
126
209
|
*/
|
|
127
210
|
public mount(el: HTMLDivElement): void {
|
|
211
|
+
this.container = el;
|
|
128
212
|
const { mask, renderer } = this;
|
|
129
213
|
|
|
130
214
|
renderer.mount(el);
|
|
131
215
|
mask.mount(el);
|
|
132
216
|
|
|
133
|
-
const { wrapper: maskWrapper, hGuides, vGuides } = mask;
|
|
134
|
-
|
|
135
|
-
maskWrapper.addEventListener('scroll', this.maskScrollHandler);
|
|
136
|
-
hGuides.on('changeGuides', this.hGuidesChangeGuidesHandler);
|
|
137
|
-
vGuides.on('changeGuides', this.vGuidesChangeGuidesHandler);
|
|
138
|
-
|
|
139
217
|
this.emit('mounted');
|
|
140
218
|
}
|
|
141
219
|
|
|
@@ -144,78 +222,34 @@ export default class StageCore extends EventEmitter {
|
|
|
144
222
|
*/
|
|
145
223
|
public clearGuides() {
|
|
146
224
|
this.mask.clearGuides();
|
|
147
|
-
this.dr.
|
|
148
|
-
this.dr.setVGuidelines([]);
|
|
225
|
+
this.dr.clearGuides();
|
|
149
226
|
}
|
|
150
227
|
|
|
151
228
|
/**
|
|
152
229
|
* 销毁实例
|
|
153
230
|
*/
|
|
154
231
|
public destroy(): void {
|
|
155
|
-
const { mask, renderer, dr } = this;
|
|
156
|
-
const { wrapper: maskWrapper, hGuides, vGuides } = mask;
|
|
232
|
+
const { mask, renderer, dr, highlightLayer } = this;
|
|
157
233
|
|
|
158
234
|
renderer.destroy();
|
|
159
235
|
mask.destroy();
|
|
160
236
|
dr.destroy();
|
|
161
|
-
|
|
162
|
-
hGuides.off('changeGuides', this.hGuidesChangeGuidesHandler);
|
|
163
|
-
vGuides.off('changeGuides', this.vGuidesChangeGuidesHandler);
|
|
164
|
-
this.removeAllListeners();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private maskScrollHandler = (event: Event) => {
|
|
168
|
-
const { mask, renderer } = this;
|
|
169
|
-
const { wrapper: maskWrapper, hGuides, vGuides } = mask;
|
|
170
|
-
const { scrollTop } = maskWrapper;
|
|
171
|
-
|
|
172
|
-
renderer?.contentWindow?.document.documentElement.scrollTo({ top: scrollTop });
|
|
173
|
-
|
|
174
|
-
hGuides.scrollGuides(scrollTop);
|
|
175
|
-
hGuides.scroll(0);
|
|
176
|
-
|
|
177
|
-
vGuides.scrollGuides(0);
|
|
178
|
-
vGuides.scroll(scrollTop);
|
|
237
|
+
highlightLayer.destroy();
|
|
179
238
|
|
|
180
|
-
this.
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
private hGuidesChangeGuidesHandler = (e: GuidesEvents['changeGuides']) => {
|
|
184
|
-
this.dr.setHGuidelines(e.guides);
|
|
185
|
-
this.emit('changeGuides');
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
private vGuidesChangeGuidesHandler = (e: GuidesEvents['changeGuides']) => {
|
|
189
|
-
this.dr.setVGuidelines(e.guides);
|
|
190
|
-
this.emit('changeGuides');
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
private setMaskLayout(el: HTMLElement): void {
|
|
194
|
-
let fixed = false;
|
|
195
|
-
let dom = el;
|
|
196
|
-
while (dom) {
|
|
197
|
-
fixed = isFixed(dom);
|
|
198
|
-
if (fixed) {
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
const { parentElement } = dom;
|
|
202
|
-
if (!parentElement || parentElement.tagName === 'BODY') {
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
dom = parentElement;
|
|
206
|
-
}
|
|
239
|
+
this.removeAllListeners();
|
|
207
240
|
|
|
208
|
-
|
|
209
|
-
this.mask?.setFixed();
|
|
210
|
-
} else {
|
|
211
|
-
this.mask?.setAbsolute();
|
|
212
|
-
}
|
|
241
|
+
this.container = undefined;
|
|
213
242
|
}
|
|
214
243
|
|
|
215
|
-
private async
|
|
216
|
-
|
|
217
|
-
if (
|
|
218
|
-
await
|
|
244
|
+
private async getTargetElement(idOrEl: Id | HTMLElement): Promise<HTMLElement> {
|
|
245
|
+
let el;
|
|
246
|
+
if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
|
|
247
|
+
const runtime = await this.renderer?.getRuntime();
|
|
248
|
+
el = await runtime?.select?.(`${idOrEl}`);
|
|
249
|
+
if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
|
|
250
|
+
} else {
|
|
251
|
+
el = idOrEl;
|
|
219
252
|
}
|
|
253
|
+
return el;
|
|
220
254
|
}
|
|
221
255
|
}
|