@tmagic/stage 1.0.6 → 1.1.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.
@@ -1,4 +1,6 @@
1
1
  import EventEmitter$1, { EventEmitter } from 'events';
2
+ import { removeClassName, removeClassNameByClassName, createDiv, injectStyle, isSameDomain, getHost, addClassName } from '@tmagic/utils';
3
+ import KeyController from 'keycon';
2
4
  import Moveable from 'moveable';
3
5
  import MoveableHelper from 'moveable-helper';
4
6
  import { throttle } from 'lodash-es';
@@ -7,6 +9,8 @@ import Guides from '@scena/guides';
7
9
  const GHOST_EL_ID_PREFIX = "ghost_el_";
8
10
  const DRAG_EL_ID_PREFIX = "drag_el_";
9
11
  const HIGHLIGHT_EL_ID_PREFIX = "highlight_el_";
12
+ const CONTAINER_HIGHLIGHT_CLASS = "tmagic-stage-container-highlight";
13
+ const PAGE_CLASS = "magic-ui-page";
10
14
  const DEFAULT_ZOOM = 1;
11
15
  var GuidesType = /* @__PURE__ */ ((GuidesType2) => {
12
16
  GuidesType2["HORIZONTAL"] = "horizontal";
@@ -34,6 +38,18 @@ var Mode = /* @__PURE__ */ ((Mode2) => {
34
38
  })(Mode || {});
35
39
  const SELECTED_CLASS = "tmagic-stage-selected-area";
36
40
 
41
+ var ContainerHighlightType = /* @__PURE__ */ ((ContainerHighlightType2) => {
42
+ ContainerHighlightType2["DEFAULT"] = "default";
43
+ ContainerHighlightType2["ALT"] = "alt";
44
+ return ContainerHighlightType2;
45
+ })(ContainerHighlightType || {});
46
+ var StageDragStatus = /* @__PURE__ */ ((StageDragStatus2) => {
47
+ StageDragStatus2["START"] = "start";
48
+ StageDragStatus2["ING"] = "ing";
49
+ StageDragStatus2["END"] = "end";
50
+ return StageDragStatus2;
51
+ })(StageDragStatus || {});
52
+
37
53
  const getParents = (el, relative) => {
38
54
  let cur = el.parentElement;
39
55
  const parents = [];
@@ -59,6 +75,19 @@ const getOffset = (el) => {
59
75
  top
60
76
  };
61
77
  };
78
+ const getTargetElStyle = (el) => {
79
+ const offset = getOffset(el);
80
+ const { transform } = getComputedStyle(el);
81
+ return `
82
+ position: absolute;
83
+ transform: ${transform};
84
+ left: ${offset.left}px;
85
+ top: ${offset.top}px;
86
+ width: ${el.clientWidth}px;
87
+ height: ${el.clientHeight}px;
88
+ z-index: ${ZIndex.DRAG_EL};
89
+ `;
90
+ };
62
91
  const getAbsolutePosition = (el, { top, left }) => {
63
92
  const { offsetParent } = el;
64
93
  if (offsetParent) {
@@ -70,13 +99,6 @@ const getAbsolutePosition = (el, { top, left }) => {
70
99
  }
71
100
  return { left, top };
72
101
  };
73
- const getHost = (targetUrl) => targetUrl.match(/\/\/([^/]+)/)?.[1];
74
- const isSameDomain = (targetUrl = "", source = globalThis.location.host) => {
75
- const isHttpUrl = /^(http[s]?:)?\/\//.test(targetUrl);
76
- if (!isHttpUrl)
77
- return true;
78
- return getHost(targetUrl) === source;
79
- };
80
102
  const isAbsolute = (style) => style.position === "absolute";
81
103
  const isRelative = (style) => style.position === "relative";
82
104
  const isStatic = (style) => style.position === "static";
@@ -120,19 +142,14 @@ const getScrollParent = (element, includeHidden = false) => {
120
142
  }
121
143
  return null;
122
144
  };
123
- const createDiv = ({ className, cssText }) => {
124
- const el = globalThis.document.createElement("div");
125
- el.className = className;
126
- el.style.cssText = cssText;
127
- return el;
128
- };
129
145
  const removeSelectedClassName = (doc) => {
130
146
  const oldEl = doc.querySelector(`.${SELECTED_CLASS}`);
131
147
  if (oldEl) {
132
- oldEl.classList.remove(SELECTED_CLASS);
133
- oldEl.parentNode?.classList.remove(`${SELECTED_CLASS}-parent`);
148
+ removeClassName(oldEl, SELECTED_CLASS);
149
+ if (oldEl.parentNode)
150
+ removeClassName(oldEl.parentNode, `${SELECTED_CLASS}-parent`);
134
151
  doc.querySelectorAll(`.${SELECTED_CLASS}-parents`).forEach((item) => {
135
- item.classList.remove(`${SELECTED_CLASS}-parents`);
152
+ removeClassName(item, `${SELECTED_CLASS}-parents`);
136
153
  });
137
154
  }
138
155
  };
@@ -143,20 +160,94 @@ const addSelectedClassName = (el, doc) => {
143
160
  item.classList.add(`${SELECTED_CLASS}-parents`);
144
161
  });
145
162
  };
163
+ const calcValueByFontsize = (doc, value) => {
164
+ const { fontSize } = doc.documentElement.style;
165
+ if (fontSize) {
166
+ const times = globalThis.parseFloat(fontSize) / 100;
167
+ return Number((value / times).toFixed(2));
168
+ }
169
+ return value;
170
+ };
171
+ const down = (deltaTop, target) => {
172
+ let swapIndex = 0;
173
+ let addUpH = target.clientHeight;
174
+ const brothers = Array.from(target.parentNode?.children || []).filter((node) => !node.id.startsWith(GHOST_EL_ID_PREFIX));
175
+ const index = brothers.indexOf(target);
176
+ const downEls = brothers.slice(index + 1);
177
+ for (let i = 0; i < downEls.length; i++) {
178
+ const ele = downEls[i];
179
+ if (ele.style?.position === "fixed") {
180
+ continue;
181
+ }
182
+ addUpH += ele.clientHeight / 2;
183
+ if (deltaTop <= addUpH) {
184
+ break;
185
+ }
186
+ addUpH += ele.clientHeight / 2;
187
+ swapIndex = i;
188
+ }
189
+ return {
190
+ src: target.id,
191
+ dist: downEls.length && swapIndex > -1 ? downEls[swapIndex].id : target.id
192
+ };
193
+ };
194
+ const up = (deltaTop, target) => {
195
+ const brothers = Array.from(target.parentNode?.children || []).filter((node) => !node.id.startsWith(GHOST_EL_ID_PREFIX));
196
+ const index = brothers.indexOf(target);
197
+ const upEls = brothers.slice(0, index);
198
+ let addUpH = target.clientHeight;
199
+ let swapIndex = upEls.length - 1;
200
+ for (let i = upEls.length - 1; i >= 0; i--) {
201
+ const ele = upEls[i];
202
+ if (!ele)
203
+ continue;
204
+ if (ele.style.position === "fixed")
205
+ continue;
206
+ addUpH += ele.clientHeight / 2;
207
+ if (-deltaTop <= addUpH)
208
+ break;
209
+ addUpH += ele.clientHeight / 2;
210
+ swapIndex = i;
211
+ }
212
+ return {
213
+ src: target.id,
214
+ dist: upEls.length && swapIndex > -1 ? upEls[swapIndex].id : target.id
215
+ };
216
+ };
146
217
 
147
218
  class StageDragResize extends EventEmitter {
219
+ core;
220
+ mask;
221
+ container;
222
+ target;
223
+ dragEl;
224
+ moveable;
225
+ horizontalGuidelines = [];
226
+ verticalGuidelines = [];
227
+ elementGuidelines = [];
228
+ mode = Mode.ABSOLUTE;
229
+ moveableOptions = {};
230
+ dragStatus = StageDragStatus.END;
231
+ ghostEl;
232
+ moveableHelper;
233
+ isContainerHighlight = false;
148
234
  constructor(config) {
149
235
  super();
150
- this.horizontalGuidelines = [];
151
- this.verticalGuidelines = [];
152
- this.elementGuidelines = [];
153
- this.mode = Mode.ABSOLUTE;
154
- this.moveableOptions = {};
155
- this.dragStatus = "end" /* END */;
156
236
  this.core = config.core;
157
237
  this.container = config.container;
158
- this.dragEl = globalThis.document.createElement("div");
159
- this.container.append(this.dragEl);
238
+ this.mask = config.mask;
239
+ KeyController.global.keydown("alt", (e) => {
240
+ e.inputEvent.preventDefault();
241
+ this.isContainerHighlight = true;
242
+ });
243
+ KeyController.global.keyup("alt", (e) => {
244
+ e.inputEvent.preventDefault();
245
+ const doc = this.core.renderer.contentWindow?.document;
246
+ if (doc && this.canContainerHighlight()) {
247
+ removeClassNameByClassName(doc, this.core.containerHighlightClassName);
248
+ }
249
+ this.isContainerHighlight = false;
250
+ });
160
251
  }
161
252
  select(el, event) {
162
253
  const oldTarget = this.target;
@@ -207,11 +298,21 @@ class StageDragResize extends EventEmitter {
207
298
  this.moveableOptions.verticalGuidelines = [];
208
299
  this.updateMoveable();
209
300
  }
301
+ clearSelectStatus() {
302
+ if (!this.moveable)
303
+ return;
304
+ this.destroyDragEl();
305
+ this.moveable.target = null;
306
+ this.moveable.updateTarget();
307
+ }
308
+ destroyDragEl() {
309
+ this.dragEl?.remove();
310
+ }
210
311
  destroy() {
211
312
  this.moveable?.destroy();
212
313
  this.destroyGhostEl();
213
314
  this.destroyDragEl();
214
- this.dragStatus = "end" /* END */;
315
+ this.dragStatus = StageDragStatus.END;
215
316
  this.removeAllListeners();
216
317
  }
217
318
  init(el) {
@@ -220,7 +321,14 @@ class StageDragResize extends EventEmitter {
220
321
  }
221
322
  this.mode = getMode(el);
222
323
  this.destroyGhostEl();
223
- this.updateDragEl(el);
324
+ this.destroyDragEl();
325
+ this.dragEl = globalThis.document.createElement("div");
326
+ this.container.append(this.dragEl);
327
+ this.dragEl.style.cssText = getTargetElStyle(el);
328
+ this.dragEl.id = `${DRAG_EL_ID_PREFIX}${el.id}`;
329
+ if (typeof this.core.config.updateDragEl === "function") {
330
+ this.core.config.updateDragEl(this.dragEl, el);
331
+ }
224
332
  this.moveableOptions = this.getOptions({
225
333
  target: this.dragEl
226
334
  });
@@ -231,19 +339,22 @@ class StageDragResize extends EventEmitter {
231
339
  });
232
340
  this.elementGuidelines = [];
233
341
  if (this.mode === Mode.ABSOLUTE) {
234
- const frame = document.createDocumentFragment();
235
- for (const node of nodes) {
236
- const { width, height } = node.getBoundingClientRect();
237
- if (node === this.target)
238
- continue;
239
- const { left, top } = getOffset(node);
240
- const elementGuideline = document.createElement("div");
241
- elementGuideline.style.cssText = `position: absolute;width: ${width}px;height: ${height}px;top: ${top}px;left: ${left}px`;
242
- this.elementGuidelines.push(elementGuideline);
243
- frame.append(elementGuideline);
244
- }
245
- this.container.append(frame);
342
+ this.container.append(this.createGuidelineElements(nodes));
343
+ }
344
+ }
345
+ createGuidelineElements(nodes) {
346
+ const frame = globalThis.document.createDocumentFragment();
347
+ for (const node of nodes) {
348
+ const { width, height } = node.getBoundingClientRect();
349
+ if (node === this.target)
350
+ continue;
351
+ const { left, top } = getOffset(node);
352
+ const elementGuideline = globalThis.document.createElement("div");
353
+ elementGuideline.style.cssText = `position: absolute;width: ${width}px;height: ${height}px;top: ${top}px;left: ${left}px`;
354
+ this.elementGuidelines.push(elementGuideline);
355
+ frame.append(elementGuideline);
246
356
  }
357
+ return frame;
247
358
  }
248
359
  initMoveable() {
249
360
  this.moveable?.destroy();
@@ -265,7 +376,7 @@ class StageDragResize extends EventEmitter {
265
376
  this.moveable.on("resizeStart", (e) => {
266
377
  if (!this.target)
267
378
  return;
268
- this.dragStatus = "start" /* START */;
379
+ this.dragStatus = StageDragStatus.START;
269
380
  this.moveableHelper?.onResizeStart(e);
270
381
  frame.top = this.target.offsetTop;
271
382
  frame.left = this.target.offsetLeft;
@@ -274,7 +385,7 @@ class StageDragResize extends EventEmitter {
274
385
  if (!this.moveable || !this.target || !this.dragEl)
275
386
  return;
276
387
  const { beforeTranslate } = drag;
277
- this.dragStatus = "ing" /* ING */;
388
+ this.dragStatus = StageDragStatus.ING;
278
389
  this.moveableHelper?.onResize(e);
279
390
  if (this.mode === Mode.SORTABLE) {
280
391
  this.target.style.top = "0px";
@@ -285,7 +396,7 @@ class StageDragResize extends EventEmitter {
285
396
  this.target.style.width = `${width}px`;
286
397
  this.target.style.height = `${height}px`;
287
398
  }).on("resizeEnd", () => {
288
- this.dragStatus = "end" /* END */;
399
+ this.dragStatus = StageDragStatus.END;
289
400
  this.update(true);
290
401
  });
291
402
  }
@@ -296,10 +407,13 @@ class StageDragResize extends EventEmitter {
296
407
  left: 0,
297
408
  top: 0
298
409
  };
410
+ let timeout;
411
+ const { contentWindow } = this.core.renderer;
412
+ const doc = contentWindow?.document;
299
413
  this.moveable.on("dragStart", (e) => {
300
414
  if (!this.target)
301
415
  throw new Error("\u672A\u9009\u4E2D\u7EC4\u4EF6");
302
- this.dragStatus = "start" /* START */;
416
+ this.dragStatus = StageDragStatus.START;
303
417
  this.moveableHelper?.onDragStart(e);
304
418
  if (this.mode === Mode.SORTABLE) {
305
419
  this.ghostEl = this.generateGhostEl(this.target);
@@ -309,7 +423,14 @@ class StageDragResize extends EventEmitter {
309
423
  }).on("drag", (e) => {
310
424
  if (!this.target || !this.dragEl)
311
425
  return;
312
- this.dragStatus = "ing" /* ING */;
426
+ if (timeout) {
427
+ globalThis.clearTimeout(timeout);
428
+ timeout = void 0;
429
+ }
430
+ if (this.canContainerHighlight()) {
431
+ timeout = this.core.getAddContainerHighlightClassNameTimeout(e.inputEvent, [this.target]);
432
+ }
433
+ this.dragStatus = StageDragStatus.ING;
313
434
  if (this.ghostEl) {
314
435
  this.ghostEl.style.top = `${frame.top + e.beforeTranslate[1]}px`;
315
436
  return;
@@ -318,16 +439,28 @@ class StageDragResize extends EventEmitter {
318
439
  this.target.style.left = `${frame.left + e.beforeTranslate[0]}px`;
319
440
  this.target.style.top = `${frame.top + e.beforeTranslate[1]}px`;
320
441
  }).on("dragEnd", () => {
321
- if (this.dragStatus === "ing" /* ING */) {
322
- switch (this.mode) {
323
- case Mode.SORTABLE:
324
- this.sort();
325
- break;
326
- default:
327
- this.update();
442
+ if (timeout) {
443
+ globalThis.clearTimeout(timeout);
444
+ timeout = void 0;
445
+ }
446
+ let parentEl = null;
447
+ if (doc && this.canContainerHighlight()) {
448
+ parentEl = removeClassNameByClassName(doc, this.core.containerHighlightClassName);
449
+ }
450
+ if (this.dragStatus === StageDragStatus.ING) {
451
+ if (parentEl) {
452
+ this.update(false, parentEl);
453
+ } else {
454
+ switch (this.mode) {
455
+ case Mode.SORTABLE:
456
+ this.sort();
457
+ break;
458
+ default:
459
+ this.update();
460
+ }
328
461
  }
329
462
  }
330
- this.dragStatus = "end" /* END */;
463
+ this.dragStatus = StageDragStatus.END;
331
464
  this.destroyGhostEl();
332
465
  });
333
466
  }
@@ -335,17 +468,17 @@ class StageDragResize extends EventEmitter {
335
468
  if (!this.moveable)
336
469
  throw new Error("moveable \u672A\u521D\u59CB\u5316");
337
470
  this.moveable.on("rotateStart", (e) => {
338
- this.dragStatus = "start" /* START */;
471
+ this.dragStatus = StageDragStatus.START;
339
472
  this.moveableHelper?.onRotateStart(e);
340
473
  }).on("rotate", (e) => {
341
474
  if (!this.target || !this.dragEl)
342
475
  return;
343
- this.dragStatus = "ing" /* ING */;
476
+ this.dragStatus = StageDragStatus.ING;
344
477
  this.moveableHelper?.onRotate(e);
345
478
  const frame = this.moveableHelper?.getFrame(e.target);
346
479
  this.target.style.transform = frame?.toCSSObject().transform || "";
347
480
  }).on("rotateEnd", (e) => {
348
- this.dragStatus = "end" /* END */;
481
+ this.dragStatus = StageDragStatus.END;
349
482
  const frame = this.moveableHelper?.getFrame(e.target);
350
483
  this.emit("update", {
351
484
  el: this.target,
@@ -359,17 +492,17 @@ class StageDragResize extends EventEmitter {
359
492
  if (!this.moveable)
360
493
  throw new Error("moveable \u672A\u521D\u59CB\u5316");
361
494
  this.moveable.on("scaleStart", (e) => {
362
- this.dragStatus = "start" /* START */;
495
+ this.dragStatus = StageDragStatus.START;
363
496
  this.moveableHelper?.onScaleStart(e);
364
497
  }).on("scale", (e) => {
365
498
  if (!this.target || !this.dragEl)
366
499
  return;
367
- this.dragStatus = "ing" /* ING */;
500
+ this.dragStatus = StageDragStatus.ING;
368
501
  this.moveableHelper?.onScale(e);
369
502
  const frame = this.moveableHelper?.getFrame(e.target);
370
503
  this.target.style.transform = frame?.toCSSObject().transform || "";
371
504
  }).on("scaleEnd", (e) => {
372
- this.dragStatus = "end" /* END */;
505
+ this.dragStatus = StageDragStatus.END;
373
506
  const frame = this.moveableHelper?.getFrame(e.target);
374
507
  this.emit("update", {
375
508
  el: this.target,
@@ -398,17 +531,32 @@ class StageDragResize extends EventEmitter {
398
531
  });
399
532
  }
400
533
  }
401
- update(isResize = false) {
534
+ update(isResize = false, parentEl = null) {
402
535
  if (!this.target)
403
536
  return;
537
+ const { contentWindow } = this.core.renderer;
538
+ const doc = contentWindow?.document;
539
+ if (!doc)
540
+ return;
404
541
  const offset = this.mode === Mode.SORTABLE ? { left: 0, top: 0 } : { left: this.target.offsetLeft, top: this.target.offsetTop };
405
- const left = this.calcValueByFontsize(offset.left);
406
- const top = this.calcValueByFontsize(offset.top);
407
- const width = this.calcValueByFontsize(this.target.clientWidth);
408
- const height = this.calcValueByFontsize(this.target.clientHeight);
542
+ let left = calcValueByFontsize(doc, offset.left);
543
+ let top = calcValueByFontsize(doc, offset.top);
544
+ const width = calcValueByFontsize(doc, this.target.clientWidth);
545
+ const height = calcValueByFontsize(doc, this.target.clientHeight);
546
+ if (parentEl && this.mode === Mode.ABSOLUTE && this.dragEl) {
547
+ const [translateX, translateY] = this.moveableHelper?.getFrame(this.dragEl).properties.transform.translate.value;
548
+ const { left: parentLeft, top: parentTop } = getOffset(parentEl);
549
+ left = calcValueByFontsize(doc, this.dragEl.offsetLeft) + parseFloat(translateX) - calcValueByFontsize(doc, parentLeft);
550
+ top = calcValueByFontsize(doc, this.dragEl.offsetTop) + parseFloat(translateY) - calcValueByFontsize(doc, parentTop);
551
+ }
409
552
  this.emit("update", {
410
- el: this.target,
411
- style: isResize ? { left, top, width, height } : { left, top }
553
+ data: [
554
+ {
555
+ el: this.target,
556
+ style: isResize ? { left, top, width, height } : { left, top }
557
+ }
558
+ ],
559
+ parentEl
412
560
  });
413
561
  }
414
562
  generateGhostEl(el) {
@@ -416,6 +564,7 @@ class StageDragResize extends EventEmitter {
416
564
  this.destroyGhostEl();
417
565
  }
418
566
  const ghostEl = el.cloneNode(true);
567
+ this.setGhostElChildrenId(ghostEl);
419
568
  const { top, left } = getAbsolutePosition(el, getOffset(el));
420
569
  ghostEl.id = `${GHOST_EL_ID_PREFIX}${el.id}`;
421
570
  ghostEl.style.zIndex = ZIndex.GHOST_EL;
@@ -426,30 +575,20 @@ class StageDragResize extends EventEmitter {
426
575
  el.after(ghostEl);
427
576
  return ghostEl;
428
577
  }
578
+ setGhostElChildrenId(el) {
579
+ for (const child of Array.from(el.children)) {
580
+ if (child.id) {
581
+ child.id = `${GHOST_EL_ID_PREFIX}${child.id}`;
582
+ }
583
+ if (child.children.length) {
584
+ this.setGhostElChildrenId(child);
585
+ }
586
+ }
587
+ }
429
588
  destroyGhostEl() {
430
589
  this.ghostEl?.remove();
431
590
  this.ghostEl = void 0;
432
591
  }
433
- updateDragEl(el) {
434
- const offset = getOffset(el);
435
- const { transform } = getComputedStyle(el);
436
- this.dragEl.style.cssText = `
437
- position: absolute;
438
- transform: ${transform};
439
- left: ${offset.left}px;
440
- top: ${offset.top}px;
441
- width: ${el.clientWidth}px;
442
- height: ${el.clientHeight}px;
443
- z-index: ${ZIndex.DRAG_EL};
444
- `;
445
- this.dragEl.id = `${DRAG_EL_ID_PREFIX}${el.id}`;
446
- if (typeof this.core.config.updateDragEl === "function") {
447
- this.core.config.updateDragEl(this.dragEl, el);
448
- }
449
- }
450
- destroyDragEl() {
451
- this.dragEl?.remove();
452
- }
453
592
  getOptions(options = {}) {
454
593
  if (!this.target)
455
594
  return {};
@@ -500,7 +639,7 @@ class StageDragResize extends EventEmitter {
500
639
  bounds: {
501
640
  top: 0,
502
641
  left: -1,
503
- right: this.container.clientWidth,
642
+ right: this.container.clientWidth - 1,
504
643
  bottom: this.container.clientHeight,
505
644
  ...moveableOptions.bounds || {}
506
645
  },
@@ -508,64 +647,17 @@ class StageDragResize extends EventEmitter {
508
647
  ...moveableOptions
509
648
  };
510
649
  }
511
- calcValueByFontsize(value) {
512
- const { contentWindow } = this.core.renderer;
513
- const fontSize = contentWindow?.document.documentElement.style.fontSize;
514
- if (fontSize) {
515
- const times = globalThis.parseFloat(fontSize) / 100;
516
- return (value / times).toFixed(2);
517
- }
518
- return value;
650
+ canContainerHighlight() {
651
+ return this.core.containerHighlightType === ContainerHighlightType.DEFAULT || this.core.containerHighlightType === ContainerHighlightType.ALT && this.isContainerHighlight;
519
652
  }
520
653
  }
521
- const down = (deltaTop, target) => {
522
- let swapIndex = 0;
523
- let addUpH = target.clientHeight;
524
- const brothers = Array.from(target.parentNode?.children || []).filter((node) => !node.id.startsWith(GHOST_EL_ID_PREFIX));
525
- const index = brothers.indexOf(target);
526
- const downEls = brothers.slice(index + 1);
527
- for (let i = 0; i < downEls.length; i++) {
528
- const ele = downEls[i];
529
- if (ele.style?.position === "fixed") {
530
- continue;
531
- }
532
- addUpH += ele.clientHeight / 2;
533
- if (deltaTop <= addUpH) {
534
- break;
535
- }
536
- addUpH += ele.clientHeight / 2;
537
- swapIndex = i;
538
- }
539
- return {
540
- src: target.id,
541
- dist: downEls.length && swapIndex > -1 ? downEls[swapIndex].id : target.id
542
- };
543
- };
544
- const up = (deltaTop, target) => {
545
- const brothers = Array.from(target.parentNode?.children || []).filter((node) => !node.id.startsWith(GHOST_EL_ID_PREFIX));
546
- const index = brothers.indexOf(target);
547
- const upEls = brothers.slice(0, index);
548
- let addUpH = target.clientHeight;
549
- let swapIndex = upEls.length - 1;
550
- for (let i = upEls.length - 1; i >= 0; i--) {
551
- const ele = upEls[i];
552
- if (!ele)
553
- continue;
554
- if (ele.style.position === "fixed")
555
- continue;
556
- addUpH += ele.clientHeight / 2;
557
- if (-deltaTop <= addUpH)
558
- break;
559
- addUpH += ele.clientHeight / 2;
560
- swapIndex = i;
561
- }
562
- return {
563
- src: target.id,
564
- dist: upEls.length && swapIndex > -1 ? upEls[swapIndex].id : target.id
565
- };
566
- };
567
654
 
568
655
  class TargetCalibrate extends EventEmitter {
656
+ parent;
657
+ mask;
658
+ dr;
659
+ core;
660
+ operationEl;
569
661
  constructor(config) {
570
662
  super();
571
663
  this.parent = config.parent;
@@ -585,6 +677,7 @@ class TargetCalibrate extends EventEmitter {
585
677
  top: ${top}px;
586
678
  width: ${el.clientWidth}px;
587
679
  height: ${el.clientHeight}px;
680
+ z-index: ${ZIndex.DRAG_EL};
588
681
  `;
589
682
  this.operationEl.id = `${prefix}${el.id}`;
590
683
  if (typeof this.core.config.updateDragEl === "function") {
@@ -632,6 +725,11 @@ class TargetCalibrate extends EventEmitter {
632
725
  }
633
726
 
634
727
  class StageHighlight extends EventEmitter {
728
+ core;
729
+ container;
730
+ target;
731
+ moveable;
732
+ calibrationTarget;
635
733
  constructor(config) {
636
734
  super();
637
735
  this.core = config.core;
@@ -652,7 +750,7 @@ class StageHighlight extends EventEmitter {
652
750
  target: this.calibrationTarget.update(el, HIGHLIGHT_EL_ID_PREFIX),
653
751
  origin: false,
654
752
  rootContainer: this.core.container,
655
- zoom: 1
753
+ zoom: 2
656
754
  });
657
755
  }
658
756
  clearHighlight() {
@@ -669,41 +767,14 @@ class StageHighlight extends EventEmitter {
669
767
  }
670
768
 
671
769
  class Rule extends EventEmitter$1 {
770
+ hGuides;
771
+ vGuides;
772
+ horizontalGuidelines = [];
773
+ verticalGuidelines = [];
774
+ container;
775
+ containerResizeObserver;
672
776
  constructor(container) {
673
777
  super();
674
- this.horizontalGuidelines = [];
675
- this.verticalGuidelines = [];
676
- this.getGuidesStyle = (type) => ({
677
- position: "fixed",
678
- zIndex: 1,
679
- left: type === GuidesType.HORIZONTAL ? 0 : "-30px",
680
- top: type === GuidesType.HORIZONTAL ? "-30px" : 0,
681
- width: type === GuidesType.HORIZONTAL ? "100%" : "30px",
682
- height: type === GuidesType.HORIZONTAL ? "30px" : "100%"
683
- });
684
- this.createGuides = (type, defaultGuides = []) => new Guides(this.container, {
685
- type,
686
- defaultGuides,
687
- displayDragPos: true,
688
- backgroundColor: "#fff",
689
- lineColor: "#000",
690
- textColor: "#000",
691
- style: this.getGuidesStyle(type)
692
- });
693
- this.hGuidesChangeGuidesHandler = (e) => {
694
- this.horizontalGuidelines = e.guides;
695
- this.emit("changeGuides", {
696
- type: GuidesType.HORIZONTAL,
697
- guides: this.horizontalGuidelines
698
- });
699
- };
700
- this.vGuidesChangeGuidesHandler = (e) => {
701
- this.verticalGuidelines = e.guides;
702
- this.emit("changeGuides", {
703
- type: GuidesType.VERTICAL,
704
- guides: this.verticalGuidelines
705
- });
706
- };
707
778
  this.container = container;
708
779
  this.hGuides = this.createGuides(GuidesType.HORIZONTAL, this.horizontalGuidelines);
709
780
  this.vGuides = this.createGuides(GuidesType.VERTICAL, this.verticalGuidelines);
@@ -775,16 +846,43 @@ class Rule extends EventEmitter$1 {
775
846
  this.containerResizeObserver.disconnect();
776
847
  this.removeAllListeners();
777
848
  }
849
+ getGuidesStyle = (type) => ({
850
+ position: "fixed",
851
+ zIndex: 1,
852
+ left: type === GuidesType.HORIZONTAL ? 0 : "-30px",
853
+ top: type === GuidesType.HORIZONTAL ? "-30px" : 0,
854
+ width: type === GuidesType.HORIZONTAL ? "100%" : "30px",
855
+ height: type === GuidesType.HORIZONTAL ? "30px" : "100%"
856
+ });
857
+ createGuides = (type, defaultGuides = []) => new Guides(this.container, {
858
+ type,
859
+ defaultGuides,
860
+ displayDragPos: true,
861
+ backgroundColor: "#fff",
862
+ lineColor: "#000",
863
+ textColor: "#000",
864
+ style: this.getGuidesStyle(type)
865
+ });
866
+ hGuidesChangeGuidesHandler = (e) => {
867
+ this.horizontalGuidelines = e.guides;
868
+ this.emit("changeGuides", {
869
+ type: GuidesType.HORIZONTAL,
870
+ guides: this.horizontalGuidelines
871
+ });
872
+ };
873
+ vGuidesChangeGuidesHandler = (e) => {
874
+ this.verticalGuidelines = e.guides;
875
+ this.emit("changeGuides", {
876
+ type: GuidesType.VERTICAL,
877
+ guides: this.verticalGuidelines
878
+ });
879
+ };
778
880
  }
779
881
 
780
882
  const wrapperClassName = "editor-mask-wrapper";
781
883
  const throttleTime = 100;
782
884
  const hideScrollbar = () => {
783
- const style = globalThis.document.createElement("style");
784
- style.innerHTML = `
785
- .${wrapperClassName}::-webkit-scrollbar { width: 0 !important; display: none }
786
- `;
787
- globalThis.document.head.appendChild(style);
885
+ injectStyle(globalThis.document, `.${wrapperClassName}::-webkit-scrollbar { width: 0 !important; display: none }`);
788
886
  };
789
887
  const createContent = () => createDiv({
790
888
  className: "editor-mask",
@@ -812,66 +910,30 @@ const createWrapper = () => {
812
910
  return el;
813
911
  };
814
912
  class StageMask extends Rule {
913
+ content = createContent();
914
+ wrapper;
915
+ core;
916
+ page = null;
917
+ pageScrollParent = null;
918
+ scrollTop = 0;
919
+ scrollLeft = 0;
920
+ width = 0;
921
+ height = 0;
922
+ wrapperHeight = 0;
923
+ wrapperWidth = 0;
924
+ maxScrollTop = 0;
925
+ maxScrollLeft = 0;
926
+ intersectionObserver = null;
927
+ isMultiSelectStatus = false;
928
+ mode = Mode.ABSOLUTE;
929
+ pageResizeObserver = null;
930
+ wrapperResizeObserver = null;
931
+ highlightHandler = throttle((event) => {
932
+ this.emit("highlight", event);
933
+ }, throttleTime);
815
934
  constructor(config) {
816
935
  const wrapper = createWrapper();
817
936
  super(wrapper);
818
- this.content = createContent();
819
- this.page = null;
820
- this.pageScrollParent = null;
821
- this.scrollTop = 0;
822
- this.scrollLeft = 0;
823
- this.width = 0;
824
- this.height = 0;
825
- this.wrapperHeight = 0;
826
- this.wrapperWidth = 0;
827
- this.maxScrollTop = 0;
828
- this.maxScrollLeft = 0;
829
- this.intersectionObserver = null;
830
- this.mode = Mode.ABSOLUTE;
831
- this.pageResizeObserver = null;
832
- this.wrapperResizeObserver = null;
833
- this.highlightHandler = throttle((event) => {
834
- this.emit("highlight", event);
835
- }, throttleTime);
836
- this.mouseDownHandler = (event) => {
837
- this.emit("clearHighlight");
838
- event.stopImmediatePropagation();
839
- event.stopPropagation();
840
- if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT)
841
- return;
842
- if (event.target.className.indexOf("moveable-control") !== -1) {
843
- return;
844
- }
845
- this.content.removeEventListener("mousemove", this.highlightHandler);
846
- this.emit("beforeSelect", event);
847
- globalThis.document.addEventListener("mouseup", this.mouseUpHandler);
848
- };
849
- this.mouseUpHandler = () => {
850
- globalThis.document.removeEventListener("mouseup", this.mouseUpHandler);
851
- this.content.addEventListener("mousemove", this.highlightHandler);
852
- this.emit("select");
853
- };
854
- this.mouseWheelHandler = (event) => {
855
- this.emit("clearHighlight");
856
- if (!this.page)
857
- throw new Error("page \u672A\u521D\u59CB\u5316");
858
- const { deltaY, deltaX } = event;
859
- if (this.page.clientHeight < this.wrapperHeight && deltaY)
860
- return;
861
- if (this.page.clientWidth < this.wrapperWidth && deltaX)
862
- return;
863
- if (this.maxScrollTop > 0) {
864
- this.scrollTop = this.scrollTop + deltaY;
865
- }
866
- if (this.maxScrollLeft > 0) {
867
- this.scrollLeft = this.scrollLeft + deltaX;
868
- }
869
- this.scroll();
870
- this.emit("scroll", event);
871
- };
872
- this.mouseLeaveHandler = () => {
873
- setTimeout(() => this.emit("clearHighlight"), throttleTime);
874
- };
875
937
  this.wrapper = wrapper;
876
938
  this.core = config.core;
877
939
  this.content.addEventListener("mousedown", this.mouseDownHandler);
@@ -879,6 +941,16 @@ class StageMask extends Rule {
879
941
  this.content.addEventListener("wheel", this.mouseWheelHandler);
880
942
  this.content.addEventListener("mousemove", this.highlightHandler);
881
943
  this.content.addEventListener("mouseleave", this.mouseLeaveHandler);
944
+ const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
945
+ const ctrl = isMac ? "meta" : "ctrl";
946
+ KeyController.global.keydown(ctrl, (e) => {
947
+ e.inputEvent.preventDefault();
948
+ this.isMultiSelectStatus = true;
949
+ });
950
+ KeyController.global.keyup(ctrl, (e) => {
951
+ e.inputEvent.preventDefault();
952
+ this.isMultiSelectStatus = false;
953
+ });
882
954
  }
883
955
  setMode(mode) {
884
956
  this.mode = mode;
@@ -1007,46 +1079,237 @@ class StageMask extends Rule {
1007
1079
  if (this.maxScrollLeft < this.scrollLeft)
1008
1080
  this.scrollLeft = this.maxScrollLeft;
1009
1081
  }
1082
+ mouseDownHandler = (event) => {
1083
+ this.emit("clearHighlight");
1084
+ event.stopImmediatePropagation();
1085
+ event.stopPropagation();
1086
+ if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT)
1087
+ return;
1088
+ if (!this.isMultiSelectStatus && event.target.className.indexOf("moveable-area") !== -1) {
1089
+ return;
1090
+ }
1091
+ if (event.target.className.indexOf("moveable-control") !== -1) {
1092
+ return;
1093
+ }
1094
+ this.content.removeEventListener("mousemove", this.highlightHandler);
1095
+ if (this.isMultiSelectStatus) {
1096
+ this.emit("beforeMultiSelect", event);
1097
+ } else {
1098
+ this.emit("beforeSelect", event);
1099
+ }
1100
+ globalThis.document.addEventListener("mouseup", this.mouseUpHandler);
1101
+ };
1102
+ mouseUpHandler = () => {
1103
+ globalThis.document.removeEventListener("mouseup", this.mouseUpHandler);
1104
+ this.content.addEventListener("mousemove", this.highlightHandler);
1105
+ if (!this.isMultiSelectStatus) {
1106
+ this.emit("select");
1107
+ }
1108
+ };
1109
+ mouseWheelHandler = (event) => {
1110
+ this.emit("clearHighlight");
1111
+ if (!this.page)
1112
+ throw new Error("page \u672A\u521D\u59CB\u5316");
1113
+ const { deltaY, deltaX } = event;
1114
+ if (this.page.clientHeight < this.wrapperHeight && deltaY)
1115
+ return;
1116
+ if (this.page.clientWidth < this.wrapperWidth && deltaX)
1117
+ return;
1118
+ if (this.maxScrollTop > 0) {
1119
+ this.scrollTop = this.scrollTop + deltaY;
1120
+ }
1121
+ if (this.maxScrollLeft > 0) {
1122
+ this.scrollLeft = this.scrollLeft + deltaX;
1123
+ }
1124
+ this.scroll();
1125
+ this.emit("scroll", event);
1126
+ };
1127
+ mouseLeaveHandler = () => {
1128
+ setTimeout(() => this.emit("clearHighlight"), throttleTime);
1129
+ };
1010
1130
  }
1011
1131
 
1012
- class StageRender extends EventEmitter {
1013
- constructor({ core }) {
1132
+ class StageMultiDragResize extends EventEmitter {
1133
+ core;
1134
+ mask;
1135
+ container;
1136
+ targetList = [];
1137
+ dragElList = [];
1138
+ moveableForMulti;
1139
+ dragStatus = StageDragStatus.END;
1140
+ multiMoveableHelper;
1141
+ constructor(config) {
1014
1142
  super();
1015
- this.contentWindow = null;
1016
- this.runtime = null;
1017
- this.getMagicApi = () => ({
1018
- onPageElUpdate: (el) => this.emit("page-el-update", el),
1019
- onRuntimeReady: (runtime) => {
1020
- this.runtime = runtime;
1021
- globalThis.runtime = runtime;
1022
- this.emit("runtime-ready", runtime);
1143
+ this.core = config.core;
1144
+ this.container = config.container;
1145
+ this.mask = config.mask;
1146
+ }
1147
+ multiSelect(els) {
1148
+ this.targetList = els;
1149
+ this.core.dr.destroyDragEl();
1150
+ this.destroyDragElList();
1151
+ this.dragElList = els.map((elItem) => {
1152
+ const dragElDiv = globalThis.document.createElement("div");
1153
+ this.container.append(dragElDiv);
1154
+ dragElDiv.style.cssText = getTargetElStyle(elItem);
1155
+ dragElDiv.id = `${DRAG_EL_ID_PREFIX}${elItem.id}`;
1156
+ if (typeof this.core.config.updateDragEl === "function") {
1157
+ this.core.config.updateDragEl(dragElDiv, elItem);
1023
1158
  }
1159
+ return dragElDiv;
1024
1160
  });
1025
- this.getRuntime = () => {
1026
- if (this.runtime)
1027
- return Promise.resolve(this.runtime);
1028
- return new Promise((resolve) => {
1029
- const listener = (runtime) => {
1030
- this.off("runtime-ready", listener);
1031
- resolve(runtime);
1032
- };
1033
- this.on("runtime-ready", listener);
1161
+ this.moveableForMulti?.destroy();
1162
+ this.multiMoveableHelper?.clear();
1163
+ this.moveableForMulti = new Moveable(this.container, this.getOptions({
1164
+ target: this.dragElList
1165
+ }));
1166
+ this.multiMoveableHelper = MoveableHelper.create({
1167
+ useBeforeRender: true,
1168
+ useRender: false,
1169
+ createAuto: true
1170
+ });
1171
+ const frames = [];
1172
+ this.moveableForMulti.on("dragGroupStart", (params) => {
1173
+ const { events } = params;
1174
+ this.multiMoveableHelper?.onDragGroupStart(params);
1175
+ events.forEach((ev) => {
1176
+ const matchEventTarget = this.targetList.find((targetItem) => targetItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ""));
1177
+ if (!matchEventTarget)
1178
+ return;
1179
+ frames.push({
1180
+ left: matchEventTarget.offsetLeft,
1181
+ top: matchEventTarget.offsetTop,
1182
+ id: matchEventTarget.id
1183
+ });
1034
1184
  });
1035
- };
1036
- this.loadHandler = async () => {
1037
- this.contentWindow = this.iframe?.contentWindow;
1038
- this.contentWindow.magic = this.getMagicApi();
1039
- if (this.render) {
1040
- const el = await this.render(this.core);
1041
- if (el) {
1042
- this.iframe?.contentDocument?.body?.appendChild(el);
1185
+ this.dragStatus = StageDragStatus.START;
1186
+ }).on("dragGroup", (params) => {
1187
+ const { events } = params;
1188
+ events.forEach((ev) => {
1189
+ const frameSnapShot = frames.find((frameItem) => frameItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ""));
1190
+ if (!frameSnapShot)
1191
+ return;
1192
+ const targeEl = this.targetList.find((targetItem) => targetItem.id === ev.target.id.replace(DRAG_EL_ID_PREFIX, ""));
1193
+ if (!targeEl)
1194
+ return;
1195
+ const isParentIncluded = this.targetList.find((targetItem) => targetItem.id === targeEl.parentElement?.id);
1196
+ if (!isParentIncluded) {
1197
+ targeEl.style.left = `${frameSnapShot.left + ev.beforeTranslate[0]}px`;
1198
+ targeEl.style.top = `${frameSnapShot.top + ev.beforeTranslate[1]}px`;
1043
1199
  }
1200
+ });
1201
+ this.multiMoveableHelper?.onDragGroup(params);
1202
+ this.dragStatus = StageDragStatus.ING;
1203
+ }).on("dragGroupEnd", () => {
1204
+ this.update();
1205
+ this.dragStatus = StageDragStatus.END;
1206
+ }).on("clickGroup", (params) => {
1207
+ const { inputTarget, targets } = params;
1208
+ if (!this.mask.isMultiSelectStatus && targets.length > 1 && targets.includes(inputTarget)) {
1209
+ this.emit("select", inputTarget.id.replace(DRAG_EL_ID_PREFIX, ""));
1044
1210
  }
1045
- this.emit("onload");
1046
- this.contentWindow.postMessage({
1047
- tmagicRuntimeReady: true
1048
- }, "*");
1211
+ });
1212
+ }
1213
+ canSelect(el, stop) {
1214
+ if (el.className.includes(PAGE_CLASS)) {
1215
+ this.core.highlightedDom = void 0;
1216
+ this.core.highlightLayer.clearHighlight();
1217
+ stop();
1218
+ return false;
1219
+ }
1220
+ const currentTargetMode = getMode(el);
1221
+ let selectedDomMode = "";
1222
+ if (this.core.selectedDom?.className.includes(PAGE_CLASS)) {
1223
+ return true;
1224
+ }
1225
+ if (this.targetList.length === 0 && this.core.selectedDom) {
1226
+ selectedDomMode = getMode(this.core.selectedDom);
1227
+ } else if (this.targetList.length > 0) {
1228
+ selectedDomMode = getMode(this.targetList[0]);
1229
+ }
1230
+ if (currentTargetMode !== selectedDomMode) {
1231
+ return false;
1232
+ }
1233
+ return true;
1234
+ }
1235
+ clearSelectStatus() {
1236
+ if (!this.moveableForMulti)
1237
+ return;
1238
+ this.destroyDragElList();
1239
+ this.moveableForMulti.target = null;
1240
+ this.moveableForMulti.updateTarget();
1241
+ this.targetList = [];
1242
+ }
1243
+ destroy() {
1244
+ this.moveableForMulti?.destroy();
1245
+ this.destroyDragElList();
1246
+ }
1247
+ destroyDragElList() {
1248
+ this.dragElList.forEach((dragElItem) => dragElItem?.remove());
1249
+ }
1250
+ update(isResize = false) {
1251
+ if (this.targetList.length === 0)
1252
+ return;
1253
+ const { contentWindow } = this.core.renderer;
1254
+ const doc = contentWindow?.document;
1255
+ if (!doc)
1256
+ return;
1257
+ this.emit("update", {
1258
+ data: this.targetList.map((targetItem) => {
1259
+ const offset = { left: targetItem.offsetLeft, top: targetItem.offsetTop };
1260
+ const left = calcValueByFontsize(doc, offset.left);
1261
+ const top = calcValueByFontsize(doc, offset.top);
1262
+ const width = calcValueByFontsize(doc, targetItem.clientWidth);
1263
+ const height = calcValueByFontsize(doc, targetItem.clientHeight);
1264
+ return {
1265
+ el: targetItem,
1266
+ style: isResize ? { left, top, width, height } : { left, top }
1267
+ };
1268
+ }),
1269
+ parentEl: null
1270
+ });
1271
+ }
1272
+ getOptions(options = {}) {
1273
+ let { multiMoveableOptions = {} } = this.core.config;
1274
+ if (typeof multiMoveableOptions === "function") {
1275
+ multiMoveableOptions = multiMoveableOptions(this.core);
1276
+ }
1277
+ return {
1278
+ defaultGroupRotate: 0,
1279
+ defaultGroupOrigin: "50% 50%",
1280
+ draggable: true,
1281
+ resizable: false,
1282
+ throttleDrag: 0,
1283
+ startDragRotate: 0,
1284
+ throttleDragRotate: 0,
1285
+ zoom: 1,
1286
+ origin: true,
1287
+ padding: { left: 0, top: 0, right: 0, bottom: 0 },
1288
+ snappable: true,
1289
+ bounds: {
1290
+ top: 0,
1291
+ left: -1,
1292
+ right: this.container.clientWidth - 1,
1293
+ bottom: this.container.clientHeight,
1294
+ ...multiMoveableOptions.bounds || {}
1295
+ },
1296
+ ...options,
1297
+ ...multiMoveableOptions
1049
1298
  };
1299
+ }
1300
+ }
1301
+
1302
+ const style = ".tmagic-stage-container-highlight::after {\n content: '';\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n background-color: #000;\n opacity: .1;\n pointer-events: none;\n}\n";
1303
+
1304
+ class StageRender extends EventEmitter {
1305
+ contentWindow = null;
1306
+ runtime = null;
1307
+ iframe;
1308
+ runtimeUrl;
1309
+ core;
1310
+ render;
1311
+ constructor({ core }) {
1312
+ super();
1050
1313
  this.core = core;
1051
1314
  this.runtimeUrl = core.config.runtimeUrl || "";
1052
1315
  this.render = core.config.render;
@@ -1059,20 +1322,39 @@ class StageRender extends EventEmitter {
1059
1322
  `;
1060
1323
  this.iframe.addEventListener("load", this.loadHandler);
1061
1324
  }
1325
+ getMagicApi = () => ({
1326
+ onPageElUpdate: (el) => this.emit("page-el-update", el),
1327
+ onRuntimeReady: (runtime) => {
1328
+ this.runtime = runtime;
1329
+ globalThis.runtime = runtime;
1330
+ this.emit("runtime-ready", runtime);
1331
+ }
1332
+ });
1062
1333
  async mount(el) {
1063
- if (this.iframe) {
1064
- if (!isSameDomain(this.runtimeUrl) && this.runtimeUrl) {
1065
- let html = await fetch(this.runtimeUrl).then((res) => res.text());
1066
- const base = `${location.protocol}//${getHost(this.runtimeUrl)}`;
1067
- html = html.replace("<head>", `<head>
1068
- <base href="${base}">`);
1069
- this.iframe.srcdoc = html;
1070
- }
1071
- el.appendChild(this.iframe);
1072
- } else {
1334
+ if (!this.iframe) {
1073
1335
  throw Error("mount \u5931\u8D25");
1074
1336
  }
1075
- }
1337
+ if (!isSameDomain(this.runtimeUrl) && this.runtimeUrl) {
1338
+ let html = await fetch(this.runtimeUrl).then((res) => res.text());
1339
+ const base = `${location.protocol}//${getHost(this.runtimeUrl)}`;
1340
+ html = html.replace("<head>", `<head>
1341
+ <base href="${base}">`);
1342
+ this.iframe.srcdoc = html;
1343
+ }
1344
+ el.appendChild(this.iframe);
1345
+ this.postTmagicRuntimeReady();
1346
+ }
1347
+ getRuntime = () => {
1348
+ if (this.runtime)
1349
+ return Promise.resolve(this.runtime);
1350
+ return new Promise((resolve) => {
1351
+ const listener = (runtime) => {
1352
+ this.off("runtime-ready", listener);
1353
+ resolve(runtime);
1354
+ };
1355
+ this.on("runtime-ready", listener);
1356
+ });
1357
+ };
1076
1358
  destroy() {
1077
1359
  this.iframe?.removeEventListener("load", this.loadHandler);
1078
1360
  this.contentWindow = null;
@@ -1080,18 +1362,60 @@ class StageRender extends EventEmitter {
1080
1362
  this.iframe = void 0;
1081
1363
  this.removeAllListeners();
1082
1364
  }
1365
+ loadHandler = async () => {
1366
+ if (!this.contentWindow?.magic) {
1367
+ this.postTmagicRuntimeReady();
1368
+ }
1369
+ if (!this.contentWindow)
1370
+ return;
1371
+ if (this.render) {
1372
+ const el = await this.render(this.core);
1373
+ if (el) {
1374
+ this.contentWindow.document?.body?.appendChild(el);
1375
+ }
1376
+ }
1377
+ this.emit("onload");
1378
+ injectStyle(this.contentWindow.document, style);
1379
+ };
1380
+ postTmagicRuntimeReady() {
1381
+ this.contentWindow = this.iframe?.contentWindow;
1382
+ this.contentWindow.magic = this.getMagicApi();
1383
+ this.contentWindow.postMessage({
1384
+ tmagicRuntimeReady: true
1385
+ }, "*");
1386
+ }
1083
1387
  }
1084
1388
 
1085
1389
  class StageCore extends EventEmitter {
1390
+ container;
1391
+ selectedDom;
1392
+ selectedDomList = [];
1393
+ highlightedDom;
1394
+ renderer;
1395
+ mask;
1396
+ dr;
1397
+ multiDr;
1398
+ highlightLayer;
1399
+ config;
1400
+ zoom = DEFAULT_ZOOM;
1401
+ containerHighlightClassName;
1402
+ containerHighlightDuration;
1403
+ containerHighlightType;
1404
+ isContainer;
1405
+ canSelect;
1086
1406
  constructor(config) {
1087
1407
  super();
1088
- this.zoom = DEFAULT_ZOOM;
1089
1408
  this.config = config;
1090
1409
  this.setZoom(config.zoom);
1091
1410
  this.canSelect = config.canSelect || ((el) => !!el.id);
1411
+ this.isContainer = config.isContainer;
1412
+ this.containerHighlightClassName = config.containerHighlightClassName || CONTAINER_HIGHLIGHT_CLASS;
1413
+ this.containerHighlightDuration = config.containerHighlightDuration || 800;
1414
+ this.containerHighlightType = config.containerHighlightType;
1092
1415
  this.renderer = new StageRender({ core: this });
1093
1416
  this.mask = new StageMask({ core: this });
1094
- this.dr = new StageDragResize({ core: this, container: this.mask.content });
1417
+ this.dr = new StageDragResize({ core: this, container: this.mask.content, mask: this.mask });
1418
+ this.multiDr = new StageMultiDragResize({ core: this, container: this.mask.content, mask: this.mask });
1095
1419
  this.highlightLayer = new StageHighlight({ core: this, container: this.mask.wrapper });
1096
1420
  this.renderer.on("runtime-ready", (runtime) => {
1097
1421
  this.emit("runtime-ready", runtime);
@@ -1099,31 +1423,61 @@ class StageCore extends EventEmitter {
1099
1423
  this.renderer.on("page-el-update", (el) => {
1100
1424
  this.mask?.observe(el);
1101
1425
  });
1102
- this.mask.on("beforeSelect", (event) => {
1103
- this.setElementFromPoint(event);
1426
+ this.mask.on("beforeSelect", async (event) => {
1427
+ this.clearSelectStatus("multiSelect");
1428
+ const el = await this.getElementFromPoint(event);
1429
+ if (!el)
1430
+ return;
1431
+ this.select(el, event);
1104
1432
  }).on("select", () => {
1105
1433
  this.emit("select", this.selectedDom);
1106
1434
  }).on("changeGuides", (data) => {
1107
1435
  this.dr.setGuidelines(data.type, data.guides);
1108
1436
  this.emit("changeGuides", data);
1109
1437
  }).on("highlight", async (event) => {
1110
- await this.setElementFromPoint(event);
1438
+ const el = await this.getElementFromPoint(event);
1439
+ if (!el)
1440
+ return;
1441
+ if (this.multiDr.dragStatus === StageDragStatus.ING)
1442
+ return;
1443
+ await this.highlight(el);
1111
1444
  if (this.highlightedDom === this.selectedDom) {
1112
1445
  this.highlightLayer.clearHighlight();
1113
1446
  return;
1114
1447
  }
1115
- this.highlightLayer.highlight(this.highlightedDom);
1116
1448
  this.emit("highlight", this.highlightedDom);
1117
1449
  }).on("clearHighlight", async () => {
1118
1450
  this.highlightLayer.clearHighlight();
1451
+ }).on("beforeMultiSelect", async (event) => {
1452
+ const el = await this.getElementFromPoint(event);
1453
+ if (!el)
1454
+ return;
1455
+ if (this.selectedDom && !this.selectedDom.className.includes(PAGE_CLASS)) {
1456
+ this.selectedDomList.push(this.selectedDom);
1457
+ this.selectedDom = void 0;
1458
+ }
1459
+ const existIndex = this.selectedDomList.findIndex((selectedDom) => selectedDom.id === el.id);
1460
+ if (existIndex !== -1) {
1461
+ this.selectedDomList.splice(existIndex, 1);
1462
+ } else {
1463
+ this.selectedDomList.push(el);
1464
+ }
1465
+ this.multiSelect(this.selectedDomList);
1119
1466
  });
1120
1467
  this.dr.on("update", (data) => {
1121
1468
  setTimeout(() => this.emit("update", data));
1122
1469
  }).on("sort", (data) => {
1123
1470
  setTimeout(() => this.emit("sort", data));
1124
1471
  });
1472
+ this.multiDr.on("update", (data) => {
1473
+ setTimeout(() => this.emit("update", data));
1474
+ }).on("select", async (id) => {
1475
+ const el = await this.getTargetElement(id);
1476
+ this.select(el);
1477
+ setTimeout(() => this.emit("select", el));
1478
+ });
1125
1479
  }
1126
- async setElementFromPoint(event) {
1480
+ getElementsFromPoint(event) {
1127
1481
  const { renderer, zoom } = this;
1128
1482
  const doc = renderer.contentWindow?.document;
1129
1483
  let x = event.clientX;
@@ -1135,23 +1489,31 @@ class StageCore extends EventEmitter {
1135
1489
  y = y - rect.top;
1136
1490
  }
1137
1491
  }
1138
- const els = doc?.elementsFromPoint(x / zoom, y / zoom);
1492
+ return doc?.elementsFromPoint(x / zoom, y / zoom);
1493
+ }
1494
+ async getElementFromPoint(event) {
1495
+ const els = this.getElementsFromPoint(event);
1139
1496
  let stopped = false;
1140
1497
  const stop = () => stopped = true;
1141
1498
  for (const el of els) {
1142
- if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && await this.canSelect(el, event, stop)) {
1499
+ if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && await this.isElCanSelect(el, event, stop)) {
1143
1500
  if (stopped)
1144
1501
  break;
1145
- if (event.type === "mousemove") {
1146
- this.highlight(el);
1147
- break;
1148
- }
1149
- this.select(el, event);
1150
- break;
1502
+ return el;
1151
1503
  }
1152
1504
  }
1153
1505
  }
1506
+ async isElCanSelect(el, event, stop) {
1507
+ const canSelectByProp = await this.canSelect(el, event, stop);
1508
+ if (!canSelectByProp)
1509
+ return false;
1510
+ if (this.mask.isMultiSelectStatus) {
1511
+ return this.multiDr.canSelect(el, stop);
1512
+ }
1513
+ return true;
1514
+ }
1154
1515
  async select(idOrEl, event) {
1516
+ this.clearSelectStatus("multiSelect");
1155
1517
  const el = await this.getTargetElement(idOrEl);
1156
1518
  if (el === this.selectedDom)
1157
1519
  return;
@@ -1173,6 +1535,12 @@ class StageCore extends EventEmitter {
1173
1535
  }
1174
1536
  }
1175
1537
  }
1538
+ async multiSelect(idOrElList) {
1539
+ this.clearSelectStatus("select");
1540
+ const elList = await Promise.all(idOrElList.map(async (idOrEl) => await this.getTargetElement(idOrEl)));
1541
+ this.multiDr.multiSelect(elList);
1542
+ this.emit("multiSelect", elList);
1543
+ }
1176
1544
  update(data) {
1177
1545
  const { config } = data;
1178
1546
  return this.renderer?.getRuntime().then((runtime) => {
@@ -1195,7 +1563,7 @@ class StageCore extends EventEmitter {
1195
1563
  this.highlightLayer.clearHighlight();
1196
1564
  return;
1197
1565
  }
1198
- if (el === this.highlightedDom)
1566
+ if (el === this.highlightedDom || !el)
1199
1567
  return;
1200
1568
  this.highlightLayer.highlight(el);
1201
1569
  this.highlightedDom = el;
@@ -1212,6 +1580,14 @@ class StageCore extends EventEmitter {
1212
1580
  setZoom(zoom = DEFAULT_ZOOM) {
1213
1581
  this.zoom = zoom;
1214
1582
  }
1583
+ clearSelectStatus(selectType) {
1584
+ if (selectType === "multiSelect") {
1585
+ this.multiDr.clearSelectStatus();
1586
+ this.selectedDomList = [];
1587
+ } else {
1588
+ this.dr.clearSelectStatus();
1589
+ }
1590
+ }
1215
1591
  async mount(el) {
1216
1592
  this.container = el;
1217
1593
  const { mask, renderer } = this;
@@ -1223,6 +1599,24 @@ class StageCore extends EventEmitter {
1223
1599
  this.mask.clearGuides();
1224
1600
  this.dr.clearGuides();
1225
1601
  }
1602
+ async addContainerHighlightClassName(event, exclude) {
1603
+ const els = this.getElementsFromPoint(event);
1604
+ const { renderer } = this;
1605
+ const doc = renderer.contentWindow?.document;
1606
+ if (!doc)
1607
+ return;
1608
+ for (const el of els) {
1609
+ if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && await this.isContainer(el) && !exclude.includes(el)) {
1610
+ addClassName(el, doc, this.containerHighlightClassName);
1611
+ break;
1612
+ }
1613
+ }
1614
+ }
1615
+ getAddContainerHighlightClassNameTimeout(event, exclude = []) {
1616
+ return globalThis.setTimeout(() => {
1617
+ this.addContainerHighlightClassName(event, exclude);
1618
+ }, this.containerHighlightDuration);
1619
+ }
1226
1620
  destroy() {
1227
1621
  const { mask, renderer, dr, highlightLayer } = this;
1228
1622
  renderer.destroy();
@@ -1243,5 +1637,5 @@ class StageCore extends EventEmitter {
1243
1637
  }
1244
1638
  }
1245
1639
 
1246
- export { DEFAULT_ZOOM, DRAG_EL_ID_PREFIX, GHOST_EL_ID_PREFIX, GuidesType, HIGHLIGHT_EL_ID_PREFIX, Mode, MouseButton, SELECTED_CLASS, StageDragResize, StageMask, StageRender, ZIndex, StageCore as default };
1247
- //# sourceMappingURL=tmagic-stage.es.js.map
1640
+ export { CONTAINER_HIGHLIGHT_CLASS, ContainerHighlightType, DEFAULT_ZOOM, DRAG_EL_ID_PREFIX, GHOST_EL_ID_PREFIX, GuidesType, HIGHLIGHT_EL_ID_PREFIX, Mode, MouseButton, PAGE_CLASS, SELECTED_CLASS, StageDragResize, StageDragStatus, StageMask, StageRender, ZIndex, addSelectedClassName, calcValueByFontsize, StageCore as default, down, getAbsolutePosition, getMode, getOffset, getScrollParent, getTargetElStyle, isAbsolute, isFixed, isFixedParent, isRelative, isStatic, removeSelectedClassName, up };
1641
+ //# sourceMappingURL=tmagic-stage.mjs.map