@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/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,76 +56,165 @@ 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.wrapper });
|
|
51
65
|
|
|
52
|
-
this.renderer.on('runtime-ready', (runtime: Runtime) =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this.mask.on('select', async (el: Element) => {
|
|
56
|
-
await this.dr?.select(el as HTMLElement);
|
|
66
|
+
this.renderer.on('runtime-ready', (runtime: Runtime) => {
|
|
67
|
+
this.emit('runtime-ready', runtime);
|
|
57
68
|
});
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
60
|
-
this.emit('select', el);
|
|
69
|
+
this.renderer.on('page-el-update', (el: HTMLElement) => {
|
|
70
|
+
this.mask?.observe(el);
|
|
61
71
|
});
|
|
62
72
|
|
|
63
|
-
this.
|
|
64
|
-
|
|
73
|
+
this.mask
|
|
74
|
+
.on('beforeSelect', (event: MouseEvent) => {
|
|
75
|
+
this.setElementFromPoint(event);
|
|
76
|
+
})
|
|
77
|
+
.on('select', () => {
|
|
78
|
+
this.emit('select', this.selectedDom);
|
|
79
|
+
})
|
|
80
|
+
.on('changeGuides', (data: GuidesEventData) => {
|
|
81
|
+
this.dr.setGuidelines(data.type, data.guides);
|
|
82
|
+
this.emit('changeGuides', data);
|
|
83
|
+
})
|
|
84
|
+
.on('highlight', async (event: MouseEvent) => {
|
|
85
|
+
await this.setElementFromPoint(event);
|
|
86
|
+
if (this.highlightedDom === this.selectedDom) {
|
|
87
|
+
this.highlightLayer.clearHighlight();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.highlightLayer.highlight(this.highlightedDom as HTMLElement);
|
|
91
|
+
this.emit('highlight', this.highlightedDom);
|
|
92
|
+
})
|
|
93
|
+
.on('clearHighlight', async () => {
|
|
94
|
+
this.highlightLayer.clearHighlight();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// 要先触发select,在触发update
|
|
98
|
+
this.dr
|
|
99
|
+
.on('update', (data: UpdateEventData) => {
|
|
100
|
+
setTimeout(() => this.emit('update', data));
|
|
101
|
+
})
|
|
102
|
+
.on('sort', (data: UpdateEventData) => {
|
|
103
|
+
setTimeout(() => this.emit('sort', data));
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public async setElementFromPoint(event: MouseEvent) {
|
|
108
|
+
const { renderer, zoom } = this;
|
|
109
|
+
|
|
110
|
+
const doc = renderer.contentWindow?.document;
|
|
111
|
+
let x = event.clientX;
|
|
112
|
+
let y = event.clientY;
|
|
113
|
+
|
|
114
|
+
if (renderer.iframe) {
|
|
115
|
+
const rect = renderer.iframe.getClientRects()[0];
|
|
116
|
+
if (rect) {
|
|
117
|
+
x = x - rect.left;
|
|
118
|
+
y = y - rect.top;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const els = doc?.elementsFromPoint(x / zoom, y / zoom) as HTMLElement[];
|
|
123
|
+
|
|
124
|
+
let stopped = false;
|
|
125
|
+
const stop = () => (stopped = true);
|
|
126
|
+
for (const el of els) {
|
|
127
|
+
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, event, stop))) {
|
|
128
|
+
if (stopped) break;
|
|
129
|
+
if (event.type === 'mousemove') {
|
|
130
|
+
this.highlight(el);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
this.select(el, event);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
65
137
|
}
|
|
66
138
|
|
|
67
139
|
/**
|
|
68
140
|
* 选中组件
|
|
69
141
|
* @param idOrEl 组件Dom节点的id属性,或者Dom节点
|
|
70
142
|
*/
|
|
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
|
-
}
|
|
143
|
+
public async select(idOrEl: Id | HTMLElement, event?: MouseEvent): Promise<void> {
|
|
144
|
+
const el = await this.getTargetElement(idOrEl);
|
|
80
145
|
|
|
81
|
-
if (this.selectedDom
|
|
146
|
+
if (el === this.selectedDom) return;
|
|
82
147
|
|
|
83
|
-
await this.
|
|
148
|
+
const runtime = await this.renderer.getRuntime();
|
|
149
|
+
|
|
150
|
+
await runtime?.select?.(el.id);
|
|
151
|
+
|
|
152
|
+
if (runtime?.beforeSelect) {
|
|
153
|
+
await runtime.beforeSelect(el);
|
|
154
|
+
}
|
|
84
155
|
|
|
156
|
+
this.mask.setLayout(el);
|
|
157
|
+
this.dr.select(el, event);
|
|
85
158
|
this.selectedDom = el;
|
|
86
|
-
|
|
87
|
-
this.
|
|
159
|
+
|
|
160
|
+
if (this.renderer.contentWindow) {
|
|
161
|
+
removeSelectedClassName(this.renderer.contentWindow.document);
|
|
162
|
+
if (this.selectedDom) {
|
|
163
|
+
addSelectedClassName(this.selectedDom);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
88
166
|
}
|
|
89
167
|
|
|
90
|
-
|
|
168
|
+
/**
|
|
169
|
+
* 更新选中的节点
|
|
170
|
+
* @param data 更新的数据
|
|
171
|
+
*/
|
|
172
|
+
public update(data: UpdateData): Promise<void> {
|
|
91
173
|
const { config } = data;
|
|
92
174
|
|
|
93
|
-
this.renderer?.getRuntime().then((runtime) => {
|
|
175
|
+
return this.renderer?.getRuntime().then((runtime) => {
|
|
94
176
|
runtime?.update?.(data);
|
|
95
177
|
// 更新配置后,需要等组件渲染更新
|
|
96
178
|
setTimeout(() => {
|
|
97
179
|
const el = this.renderer.contentWindow?.document.getElementById(`${config.id}`);
|
|
98
|
-
|
|
180
|
+
// 有可能dom已经重新渲染,不再是原来的dom了,所以这里判断id,而不是判断el === this.selectedDom
|
|
181
|
+
if (el && el.id === this.selectedDom?.id) {
|
|
182
|
+
this.selectedDom = el;
|
|
99
183
|
// 更新了组件的布局,需要重新设置mask是否可以滚动
|
|
100
|
-
this.
|
|
184
|
+
this.mask.setLayout(el);
|
|
185
|
+
this.dr.updateMoveable(el);
|
|
101
186
|
}
|
|
102
|
-
this.dr?.refresh();
|
|
103
187
|
}, 0);
|
|
104
188
|
});
|
|
105
189
|
}
|
|
106
190
|
|
|
107
|
-
|
|
108
|
-
|
|
191
|
+
/**
|
|
192
|
+
* 高亮选中组件
|
|
193
|
+
* @param el 页面Dom节点
|
|
194
|
+
*/
|
|
195
|
+
public async highlight(idOrEl: HTMLElement | Id): Promise<void> {
|
|
196
|
+
let el;
|
|
197
|
+
try {
|
|
198
|
+
el = await this.getTargetElement(idOrEl);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
this.highlightLayer.clearHighlight();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (el === this.highlightedDom) return;
|
|
204
|
+
this.highlightLayer.highlight(el);
|
|
205
|
+
this.highlightedDom = el;
|
|
109
206
|
}
|
|
110
207
|
|
|
111
|
-
public
|
|
112
|
-
this.renderer?.getRuntime().then((runtime) => runtime?.
|
|
208
|
+
public sortNode(data: SortEventData): Promise<void> {
|
|
209
|
+
return this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
|
|
113
210
|
}
|
|
114
211
|
|
|
115
|
-
public
|
|
116
|
-
this.renderer?.getRuntime().then((runtime) => runtime?.
|
|
212
|
+
public add(data: UpdateData): Promise<void> {
|
|
213
|
+
return this.renderer?.getRuntime().then((runtime) => runtime?.add?.(data));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public remove(data: RemoveData): Promise<void> {
|
|
217
|
+
return this.renderer?.getRuntime().then((runtime) => runtime?.remove?.(data));
|
|
117
218
|
}
|
|
118
219
|
|
|
119
220
|
public setZoom(zoom: number = DEFAULT_ZOOM): void {
|
|
@@ -125,17 +226,12 @@ export default class StageCore extends EventEmitter {
|
|
|
125
226
|
* @param el 将stage挂载到该Dom节点上
|
|
126
227
|
*/
|
|
127
228
|
public mount(el: HTMLDivElement): void {
|
|
229
|
+
this.container = el;
|
|
128
230
|
const { mask, renderer } = this;
|
|
129
231
|
|
|
130
232
|
renderer.mount(el);
|
|
131
233
|
mask.mount(el);
|
|
132
234
|
|
|
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
235
|
this.emit('mounted');
|
|
140
236
|
}
|
|
141
237
|
|
|
@@ -144,78 +240,31 @@ export default class StageCore extends EventEmitter {
|
|
|
144
240
|
*/
|
|
145
241
|
public clearGuides() {
|
|
146
242
|
this.mask.clearGuides();
|
|
147
|
-
this.dr.
|
|
148
|
-
this.dr.setVGuidelines([]);
|
|
243
|
+
this.dr.clearGuides();
|
|
149
244
|
}
|
|
150
245
|
|
|
151
246
|
/**
|
|
152
247
|
* 销毁实例
|
|
153
248
|
*/
|
|
154
249
|
public destroy(): void {
|
|
155
|
-
const { mask, renderer, dr } = this;
|
|
156
|
-
const { wrapper: maskWrapper, hGuides, vGuides } = mask;
|
|
250
|
+
const { mask, renderer, dr, highlightLayer } = this;
|
|
157
251
|
|
|
158
252
|
renderer.destroy();
|
|
159
253
|
mask.destroy();
|
|
160
254
|
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);
|
|
255
|
+
highlightLayer.destroy();
|
|
179
256
|
|
|
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
|
-
}
|
|
257
|
+
this.removeAllListeners();
|
|
207
258
|
|
|
208
|
-
|
|
209
|
-
this.mask?.setFixed();
|
|
210
|
-
} else {
|
|
211
|
-
this.mask?.setAbsolute();
|
|
212
|
-
}
|
|
259
|
+
this.container = undefined;
|
|
213
260
|
}
|
|
214
261
|
|
|
215
|
-
private async
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
262
|
+
private async getTargetElement(idOrEl: Id | HTMLElement): Promise<HTMLElement> {
|
|
263
|
+
if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
|
|
264
|
+
const el = this.renderer.contentWindow?.document.getElementById(`${idOrEl}`);
|
|
265
|
+
if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
|
|
266
|
+
return el;
|
|
219
267
|
}
|
|
268
|
+
return idOrEl;
|
|
220
269
|
}
|
|
221
270
|
}
|