@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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.6",
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.1.6",
33
- "@tmagic/schema": "1.1.6",
34
- "@tmagic/utils": "1.1.6",
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 show 是否显示
36
+ * 是否显示辅助线
37
+ * @param isShowGuides 是否显示
37
38
  */
38
- public showGuides(show = true) {
39
+ public showGuides(isShowGuides = true) {
40
+ this.isShowGuides = isShowGuides;
41
+
39
42
  this.hGuides.setState({
40
- showGuides: show,
43
+ showGuides: isShowGuides,
41
44
  });
42
45
 
43
46
  this.vGuides.setState({
44
- showGuides: show,
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({ core: this });
79
- this.mask = new StageMask({ core: this });
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 doc?.elementsFromPoint(x / zoom, y / zoom) as HTMLElement[];
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.intersectionObserver?.observe(el);
227
+ this.mask.observerIntersection(el);
223
228
  }
224
229
 
225
230
  this.selectedDom = el;
226
231
 
227
- if (this.renderer.contentWindow) {
228
- removeSelectedClassName(this.renderer.contentWindow.document);
232
+ const doc = this.renderer.getDocument();
233
+ if (doc) {
234
+ removeSelectedClassName(doc);
229
235
  if (this.selectedDom) {
230
- addSelectedClassName(this.selectedDom, this.renderer.contentWindow.document);
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
- const elList = await Promise.all(idOrElList.map(async (idOrEl) => await this.getTargetElement(idOrEl)));
242
- this.multiDr.multiSelect(elList);
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.contentWindow?.document.getElementById(`${config.id}`);
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.contentWindow?.document;
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.contentWindow?.document.getElementById(`${idOrEl}`);
409
+ const el = this.renderer.getDocument()?.getElementById(`${idOrEl}`);
377
410
  if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
378
411
  return el;
379
412
  }
@@ -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
- el: this.target,
399
- style: {
400
- transform: frame?.get('transform'),
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
- el: this.target,
426
- style: {
427
- transform: frame?.get('transform'),
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: isAbsolute || isFixed,
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 type StageCore from './StageCore';
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 pageResizeObserver: ResizeObserver | null = null;
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(config: StageMaskConfig) {
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.content.addEventListener('mousedown', this.mouseDownHandler);
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
- KeyController.global.keydown(ctrl, (e) => {
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 同步页面与mask的大小
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.pageScrollParent = getScrollParent(page) || this.core.renderer.contentWindow?.document.documentElement || null;
148
- this.pageResizeObserver?.disconnect();
149
- this.wrapperResizeObserver?.disconnect();
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
- this.scroll();
179
- if (this.core.dr.moveable) {
180
- this.core.dr.updateMoveable();
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
- this.pageResizeObserver.observe(page);
143
+ this.scroll();
144
+ }
185
145
 
186
- this.wrapperResizeObserver = new ResizeObserver((entries) => {
187
- const [entry] = entries;
188
- const { clientHeight, clientWidth } = entry.target;
189
- this.wrapperHeight = clientHeight;
190
- this.wrapperWidth = clientWidth;
191
- this.setMaxScrollLeft();
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 && (event.target as HTMLDivElement).className.indexOf('moveable-area') !== -1) {
351
+ if (!this.isMultiSelectStatus && targetClassList.contains('moveable-area')) {
317
352
  return;
318
353
  }
319
- // 点击对象如果是边框锚点,则可能是resize
320
- if ((event.target as HTMLDivElement).className.indexOf('moveable-control') !== -1) {
354
+ // 点击对象如果是边框锚点,则可能是resize; 点击对象是功能按钮
355
+ if (targetClassList.contains('moveable-control') || isMoveableButton(event.target as Element)) {
321
356
  return;
322
357
  }
323
358
 
@@ -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
- public core: StageCore;
36
+ private render?: () => Promise<HTMLElement | null>;
38
37
 
39
- private render?: (renderer: StageCore) => Promise<HTMLElement> | HTMLElement;
40
-
41
- constructor({ core }: StageRenderConfig) {
38
+ constructor({ runtimeUrl, render }: StageRenderConfig) {
42
39
  super();
43
40
 
44
- this.core = core;
45
- this.runtimeUrl = core.config.runtimeUrl || '';
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(this.core);
123
+ const el = await this.render();
124
124
  if (el) {
125
125
  this.contentWindow.document?.body?.appendChild(el);
126
126
  }