@tmagic/stage 1.0.0-beta.8 → 1.0.0-rc.2

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,8 +1,12 @@
1
1
  import EventEmitter$1, { EventEmitter } from 'events';
2
2
  import Moveable from 'moveable';
3
+ import MoveableHelper from 'moveable-helper';
4
+ import { throttle } from 'lodash-es';
3
5
  import Guides from '@scena/guides';
4
6
 
5
7
  const GHOST_EL_ID_PREFIX = "ghost_el_";
8
+ const DRAG_EL_ID_PREFIX = "drag_el_";
9
+ const HIGHLIGHT_EL_ID_PREFIX = "highlight_el_";
6
10
  const DEFAULT_ZOOM = 1;
7
11
  var GuidesType = /* @__PURE__ */ ((GuidesType2) => {
8
12
  GuidesType2["HORIZONTAL"] = "horizontal";
@@ -11,6 +15,7 @@ var GuidesType = /* @__PURE__ */ ((GuidesType2) => {
11
15
  })(GuidesType || {});
12
16
  var ZIndex = /* @__PURE__ */ ((ZIndex2) => {
13
17
  ZIndex2["MASK"] = "99999";
18
+ ZIndex2["SELECTED_EL"] = "666";
14
19
  return ZIndex2;
15
20
  })(ZIndex || {});
16
21
  var MouseButton = /* @__PURE__ */ ((MouseButton2) => {
@@ -25,7 +30,17 @@ var Mode = /* @__PURE__ */ ((Mode2) => {
25
30
  Mode2["SORTABLE"] = "sortable";
26
31
  return Mode2;
27
32
  })(Mode || {});
33
+ const SELECTED_CLASS = "tmagic-stage-selected-area";
28
34
 
35
+ const getParents = (el, relative) => {
36
+ let cur = el.parentElement;
37
+ const parents = [];
38
+ while (cur && cur !== relative) {
39
+ parents.push(cur);
40
+ cur = cur.parentElement;
41
+ }
42
+ return parents;
43
+ };
29
44
  const getOffset = (el) => {
30
45
  const { transform } = getComputedStyle(el);
31
46
  const { offsetParent } = el;
@@ -145,46 +160,64 @@ const createDiv = ({ className, cssText }) => {
145
160
  el.style.cssText = cssText;
146
161
  return el;
147
162
  };
163
+ const removeSelectedClassName = (doc) => {
164
+ const oldEl = doc.querySelector(`.${SELECTED_CLASS}`);
165
+ if (oldEl) {
166
+ oldEl.classList.remove(SELECTED_CLASS);
167
+ oldEl.parentNode?.classList.remove(`${SELECTED_CLASS}-parent`);
168
+ doc.querySelectorAll(`.${SELECTED_CLASS}-parents`).forEach((item) => {
169
+ item.classList.remove(`${SELECTED_CLASS}-parents`);
170
+ });
171
+ }
172
+ };
173
+ const addSelectedClassName = (el, doc) => {
174
+ el.classList.add(SELECTED_CLASS);
175
+ el.parentNode?.classList.add(`${SELECTED_CLASS}-parent`);
176
+ getParents(el, doc.body).forEach((item) => {
177
+ item.classList.add(`${SELECTED_CLASS}-parents`);
178
+ });
179
+ };
148
180
 
149
181
  class StageDragResize extends EventEmitter {
150
- core;
151
- container;
152
- target;
153
- dragEl;
154
- moveable;
155
- horizontalGuidelines = [];
156
- verticalGuidelines = [];
157
- moveableOptions = {};
158
- dragStatus = "end" /* END */;
159
- ghostEl;
160
- mode = Mode.ABSOLUTE;
161
182
  constructor(config) {
162
183
  super();
184
+ this.horizontalGuidelines = [];
185
+ this.verticalGuidelines = [];
186
+ this.elementGuidelines = [];
187
+ this.mode = Mode.ABSOLUTE;
188
+ this.moveableOptions = {};
189
+ this.dragStatus = "end" /* END */;
163
190
  this.core = config.core;
164
191
  this.container = config.container;
192
+ this.dragEl = globalThis.document.createElement("div");
193
+ this.container.append(this.dragEl);
165
194
  }
166
- async select(el, event) {
195
+ select(el, event) {
196
+ const oldTarget = this.target;
167
197
  this.target = el;
168
- if (/(auto|scroll)/.test(this.target.style.overflow)) {
169
- this.target.style.overflow = "hidden";
198
+ this.init(el);
199
+ if (!this.moveable || this.target !== oldTarget) {
200
+ this.moveableHelper = MoveableHelper.create({
201
+ useBeforeRender: true,
202
+ useRender: false,
203
+ createAuto: true
204
+ });
205
+ this.initMoveable();
206
+ } else {
207
+ this.updateMoveable();
170
208
  }
171
- this.mode = getMode(el);
172
- this.destroyDragEl();
173
- this.destroyGhostEl();
174
- this.dragEl = this.generateDragEl(el);
175
- this.moveableOptions = await this.getOptions({
176
- target: this.dragEl || this.target
177
- });
178
- this.initMoveable();
179
209
  if (event) {
180
210
  this.moveable?.dragStart(event);
181
211
  }
182
212
  }
183
- async refresh() {
213
+ updateMoveable(el = this.target) {
184
214
  if (!this.moveable)
185
215
  throw new Error("\u672A\u521D\u59CB\u5316moveable");
186
- const options = await this.getOptions();
187
- Object.entries(options).forEach(([key, value]) => {
216
+ if (!el)
217
+ throw new Error("\u4E3A\u9009\u4E2D\u4EFB\u4F55\u8282\u70B9");
218
+ this.target = el;
219
+ this.init(el);
220
+ Object.entries(this.moveableOptions).forEach(([key, value]) => {
188
221
  this.moveable[key] = value;
189
222
  });
190
223
  this.moveable.updateTarget();
@@ -192,15 +225,19 @@ class StageDragResize extends EventEmitter {
192
225
  setGuidelines(type, guidelines) {
193
226
  if (type === GuidesType.HORIZONTAL) {
194
227
  this.horizontalGuidelines = guidelines;
228
+ this.moveableOptions.horizontalGuidelines = guidelines;
195
229
  } else if (type === GuidesType.VERTICAL) {
196
230
  this.verticalGuidelines = guidelines;
231
+ this.moveableOptions.verticalGuidelines = guidelines;
197
232
  }
198
- this.refresh();
233
+ this.updateMoveable();
199
234
  }
200
235
  clearGuides() {
201
- this.verticalGuidelines = [];
202
236
  this.horizontalGuidelines = [];
203
- this.refresh();
237
+ this.verticalGuidelines = [];
238
+ this.moveableOptions.horizontalGuidelines = [];
239
+ this.moveableOptions.verticalGuidelines = [];
240
+ this.updateMoveable();
204
241
  }
205
242
  destroy() {
206
243
  this.moveable?.destroy();
@@ -209,6 +246,17 @@ class StageDragResize extends EventEmitter {
209
246
  this.dragStatus = "end" /* END */;
210
247
  this.removeAllListeners();
211
248
  }
249
+ init(el) {
250
+ if (/(auto|scroll)/.test(el.style.overflow)) {
251
+ el.style.overflow = "hidden";
252
+ }
253
+ this.mode = getMode(el);
254
+ this.destroyGhostEl();
255
+ this.updateDragEl(el);
256
+ this.moveableOptions = this.getOptions({
257
+ target: this.dragEl
258
+ });
259
+ }
212
260
  initMoveable() {
213
261
  this.moveable?.destroy();
214
262
  this.moveable = new Moveable(this.container, {
@@ -253,27 +301,31 @@ class StageDragResize extends EventEmitter {
253
301
  bindDragEvent() {
254
302
  if (!this.moveable)
255
303
  throw new Error("moveable \u4E3A\u521D\u59CB\u5316");
256
- this.moveable.on("dragStart", () => {
304
+ let offset = null;
305
+ this.moveable.on("dragStart", (e) => {
257
306
  if (!this.target)
258
307
  throw new Error("\u672A\u9009\u4E2D\u7EC4\u4EF6");
259
308
  this.dragStatus = "start" /* START */;
309
+ this.moveableHelper?.onDragStart(e);
260
310
  if (this.mode === Mode.SORTABLE) {
261
311
  this.ghostEl = this.generateGhostEl(this.target);
262
312
  }
263
- }).on("drag", ({ left, top }) => {
313
+ }).on("drag", (e) => {
264
314
  if (!this.target || !this.dragEl)
265
315
  return;
316
+ if (!offset) {
317
+ offset = getAbsolutePosition(this.target, { left: 0, top: 0 });
318
+ }
266
319
  this.dragStatus = "ing" /* ING */;
267
- const offset = getAbsolutePosition(this.target, { left, top });
320
+ const { left, top } = e;
268
321
  if (this.ghostEl) {
269
322
  this.dragEl.style.top = `${top}px`;
270
- this.ghostEl.style.top = `${offset.top}px`;
323
+ this.ghostEl.style.top = `${top + offset.top}px`;
271
324
  return;
272
325
  }
273
- this.dragEl.style.left = `${left}px`;
274
- this.dragEl.style.top = `${top}px`;
275
- this.target.style.left = `${offset.left}px`;
276
- this.target.style.top = `${offset.top}px`;
326
+ this.moveableHelper?.onDrag(e);
327
+ this.target.style.left = `${left + offset.left}px`;
328
+ this.target.style.top = `${top + offset.top}px`;
277
329
  }).on("dragEnd", () => {
278
330
  if (this.dragStatus === "ing" /* ING */) {
279
331
  switch (this.mode) {
@@ -284,17 +336,18 @@ class StageDragResize extends EventEmitter {
284
336
  this.update();
285
337
  }
286
338
  }
339
+ offset = null;
287
340
  this.dragStatus = "end" /* END */;
288
341
  this.destroyGhostEl();
289
342
  });
290
343
  }
291
- async getSnapElements(el) {
292
- const { renderer } = this.core;
293
- const getSnapElements = (await renderer.getRuntime())?.getSnapElements || (() => {
344
+ getSnapElements(runtime, el) {
345
+ const { renderer, mask } = this.core;
346
+ const getSnapElements = runtime?.getSnapElements || (() => {
294
347
  const doc = renderer.contentWindow?.document;
295
348
  return doc ? Array.from(doc.querySelectorAll("[id]")) : [];
296
349
  });
297
- return getSnapElements(el).filter((element) => element !== this.target && !this.target?.contains(element));
350
+ return getSnapElements(el).filter((element) => element !== this.target && !this.target?.contains(element) && element !== mask.page);
298
351
  }
299
352
  sort() {
300
353
  if (!this.target || !this.ghostEl)
@@ -333,7 +386,7 @@ class StageDragResize extends EventEmitter {
333
386
  }
334
387
  const ghostEl = el.cloneNode(true);
335
388
  const { top, left } = getAbsolutePosition(el, getOffset(el));
336
- ghostEl.id = `${GHOST_EL_ID_PREFIX}${ghostEl.id}`;
389
+ ghostEl.id = `${GHOST_EL_ID_PREFIX}${el.id}`;
337
390
  ghostEl.style.zIndex = "5";
338
391
  ghostEl.style.opacity = ".5";
339
392
  ghostEl.style.position = "absolute";
@@ -346,49 +399,62 @@ class StageDragResize extends EventEmitter {
346
399
  this.ghostEl?.remove();
347
400
  this.ghostEl = void 0;
348
401
  }
349
- generateDragEl(el) {
350
- if (this.dragEl) {
351
- this.destroyDragEl();
352
- }
402
+ updateDragEl(el) {
353
403
  const { width, height } = el.getBoundingClientRect();
354
404
  const offset = getOffset(el);
355
- const dragEl = globalThis.document.createElement("div");
356
- dragEl.style.cssText = `
405
+ this.dragEl.style.cssText = `
357
406
  position: absolute;
358
407
  left: ${offset.left}px;
359
408
  top: ${offset.top}px;
360
409
  width: ${width}px;
361
410
  height: ${height}px;
411
+ z-index: 9;
362
412
  `;
363
- this.container.append(dragEl);
364
- return dragEl;
413
+ this.dragEl.id = `${DRAG_EL_ID_PREFIX}${el.id}`;
365
414
  }
366
415
  destroyDragEl() {
367
416
  this.dragEl?.remove();
368
- this.dragEl = void 0;
369
417
  }
370
- async getOptions(options = {}) {
418
+ getOptions(options = {}) {
371
419
  if (!this.target)
372
420
  return {};
373
421
  const isAbsolute = this.mode === Mode.ABSOLUTE;
422
+ const isFixed = this.mode === Mode.FIXED;
374
423
  let { moveableOptions = {} } = this.core.config;
375
424
  if (typeof moveableOptions === "function") {
376
425
  moveableOptions = moveableOptions(this.core);
377
426
  }
378
427
  return {
379
- scrollable: true,
380
- origin: true,
428
+ origin: false,
429
+ rootContainer: this.core.container,
381
430
  zoom: 1,
382
- dragArea: true,
431
+ dragArea: false,
383
432
  draggable: true,
384
433
  resizable: true,
385
- snappable: isAbsolute,
386
- snapGap: isAbsolute,
387
- snapDirections: { center: isAbsolute, middle: isAbsolute },
388
- elementSnapDirections: { center: isAbsolute, middle: isAbsolute },
389
- elementGuidelines: isAbsolute ? await this.getSnapElements(this.target) : [],
434
+ snappable: isAbsolute || isFixed,
435
+ snapGap: isAbsolute || isFixed,
436
+ snapThreshold: 5,
437
+ snapDigit: 0,
438
+ throttleDrag: 0,
439
+ isDisplaySnapDigit: isAbsolute,
440
+ snapDirections: {
441
+ top: isAbsolute,
442
+ right: isAbsolute,
443
+ bottom: isAbsolute,
444
+ left: isAbsolute,
445
+ center: isAbsolute,
446
+ middle: isAbsolute
447
+ },
448
+ elementSnapDirections: {
449
+ top: isAbsolute,
450
+ right: isAbsolute,
451
+ bottom: isAbsolute,
452
+ left: isAbsolute
453
+ },
454
+ isDisplayInnerSnapDigit: true,
390
455
  horizontalGuidelines: this.horizontalGuidelines,
391
456
  verticalGuidelines: this.verticalGuidelines,
457
+ elementGuidelines: this.elementGuidelines,
392
458
  bounds: {
393
459
  top: 0,
394
460
  left: 0,
@@ -457,15 +523,159 @@ const up = (deltaTop, target) => {
457
523
  };
458
524
  };
459
525
 
526
+ class TargetCalibrate extends EventEmitter {
527
+ constructor(config) {
528
+ super();
529
+ this.parent = config.parent;
530
+ this.mask = config.mask;
531
+ this.dr = config.dr;
532
+ this.operationEl = globalThis.document.createElement("div");
533
+ this.parent.append(this.operationEl);
534
+ }
535
+ update(el, prefix) {
536
+ const { width, height } = el.getBoundingClientRect();
537
+ const { left, top } = this.getOffset(el);
538
+ this.operationEl.style.cssText = `
539
+ position: absolute;
540
+ left: ${left}px;
541
+ top: ${top}px;
542
+ width: ${width}px;
543
+ height: ${height}px;
544
+ `;
545
+ this.operationEl.id = `${prefix}${el.id}`;
546
+ return this.operationEl;
547
+ }
548
+ destroy() {
549
+ this.operationEl?.remove();
550
+ }
551
+ getOffset(el) {
552
+ const { transform } = getComputedStyle(el);
553
+ const { offsetParent } = el;
554
+ let left = el.offsetLeft;
555
+ let top = el.offsetTop;
556
+ if (transform.indexOf("matrix") > -1) {
557
+ let a = 1;
558
+ let b = 1;
559
+ let c = 1;
560
+ let d = 1;
561
+ let e = 0;
562
+ let f = 0;
563
+ transform.replace(/matrix\((.+), (.+), (.+), (.+), (.+), (.+)\)/, ($0, $1, $2, $3, $4, $5, $6) => {
564
+ a = +$1;
565
+ b = +$2;
566
+ c = +$3;
567
+ d = +$4;
568
+ e = +$5;
569
+ f = +$6;
570
+ return transform;
571
+ });
572
+ left = a * left + c * top + e;
573
+ top = b * left + d * top + f;
574
+ }
575
+ if (offsetParent) {
576
+ const parentOffset = this.getOffset(offsetParent);
577
+ return {
578
+ left: left + parentOffset.left,
579
+ top: top + parentOffset.top
580
+ };
581
+ }
582
+ if (this.dr.mode === Mode.FIXED) {
583
+ if (getMode(el) === Mode.FIXED) {
584
+ return {
585
+ left,
586
+ top
587
+ };
588
+ }
589
+ return {
590
+ left: left - this.mask.scrollLeft,
591
+ top: top - this.mask.scrollTop
592
+ };
593
+ }
594
+ if (getMode(el) === Mode.FIXED) {
595
+ return {
596
+ left: left + this.mask.scrollLeft,
597
+ top: top + this.mask.scrollTop
598
+ };
599
+ }
600
+ return {
601
+ left,
602
+ top
603
+ };
604
+ }
605
+ }
606
+
607
+ class StageHighlight extends EventEmitter {
608
+ constructor(config) {
609
+ super();
610
+ this.core = config.core;
611
+ this.container = config.container;
612
+ this.calibrationTarget = new TargetCalibrate({
613
+ parent: this.core.mask.content,
614
+ mask: this.core.mask,
615
+ dr: this.core.dr
616
+ });
617
+ }
618
+ highlight(el) {
619
+ if (!el || el === this.target)
620
+ return;
621
+ this.target = el;
622
+ this.moveable?.destroy();
623
+ this.moveable = new Moveable(this.container, {
624
+ target: this.calibrationTarget.update(el, HIGHLIGHT_EL_ID_PREFIX),
625
+ origin: false,
626
+ rootContainer: this.core.container,
627
+ zoom: 1
628
+ });
629
+ }
630
+ clearHighlight() {
631
+ if (!this.moveable)
632
+ return;
633
+ this.target = void 0;
634
+ this.moveable.target = null;
635
+ this.moveable.updateTarget();
636
+ }
637
+ destroy() {
638
+ this.moveable?.destroy();
639
+ this.calibrationTarget.destroy();
640
+ }
641
+ }
642
+
460
643
  class Rule extends EventEmitter$1 {
461
- hGuides;
462
- vGuides;
463
- horizontalGuidelines = [];
464
- verticalGuidelines = [];
465
- container;
466
- containerResizeObserver;
467
644
  constructor(container) {
468
645
  super();
646
+ this.horizontalGuidelines = [];
647
+ this.verticalGuidelines = [];
648
+ this.getGuidesStyle = (type) => ({
649
+ position: "fixed",
650
+ zIndex: 1,
651
+ left: type === GuidesType.HORIZONTAL ? 0 : "-30px",
652
+ top: type === GuidesType.HORIZONTAL ? "-30px" : 0,
653
+ width: type === GuidesType.HORIZONTAL ? "100%" : "30px",
654
+ height: type === GuidesType.HORIZONTAL ? "30px" : "100%"
655
+ });
656
+ this.createGuides = (type, defaultGuides = []) => new Guides(this.container, {
657
+ type,
658
+ defaultGuides,
659
+ displayDragPos: true,
660
+ backgroundColor: "#fff",
661
+ lineColor: "#000",
662
+ textColor: "#000",
663
+ style: this.getGuidesStyle(type)
664
+ });
665
+ this.hGuidesChangeGuidesHandler = (e) => {
666
+ this.horizontalGuidelines = e.guides;
667
+ this.emit("changeGuides", {
668
+ type: GuidesType.HORIZONTAL,
669
+ guides: this.horizontalGuidelines
670
+ });
671
+ };
672
+ this.vGuidesChangeGuidesHandler = (e) => {
673
+ this.verticalGuidelines = e.guides;
674
+ this.emit("changeGuides", {
675
+ type: GuidesType.VERTICAL,
676
+ guides: this.verticalGuidelines
677
+ });
678
+ };
469
679
  this.container = container;
470
680
  this.hGuides = this.createGuides(GuidesType.HORIZONTAL);
471
681
  this.vGuides = this.createGuides(GuidesType.VERTICAL);
@@ -534,40 +744,10 @@ class Rule extends EventEmitter$1 {
534
744
  this.containerResizeObserver.disconnect();
535
745
  this.removeAllListeners();
536
746
  }
537
- getGuidesStyle = (type) => ({
538
- position: "fixed",
539
- zIndex: 1,
540
- left: type === GuidesType.HORIZONTAL ? 0 : "-30px",
541
- top: type === GuidesType.HORIZONTAL ? "-30px" : 0,
542
- width: type === GuidesType.HORIZONTAL ? "100%" : "30px",
543
- height: type === GuidesType.HORIZONTAL ? "30px" : "100%"
544
- });
545
- createGuides = (type, defaultGuides = []) => new Guides(this.container, {
546
- type,
547
- defaultGuides,
548
- displayDragPos: true,
549
- backgroundColor: "#fff",
550
- lineColor: "#000",
551
- textColor: "#000",
552
- style: this.getGuidesStyle(type)
553
- });
554
- hGuidesChangeGuidesHandler = (e) => {
555
- this.horizontalGuidelines = e.guides;
556
- this.emit("changeGuides", {
557
- type: GuidesType.HORIZONTAL,
558
- guides: this.horizontalGuidelines
559
- });
560
- };
561
- vGuidesChangeGuidesHandler = (e) => {
562
- this.verticalGuidelines = e.guides;
563
- this.emit("changeGuides", {
564
- type: GuidesType.VERTICAL,
565
- guides: this.verticalGuidelines
566
- });
567
- };
568
747
  }
569
748
 
570
749
  const wrapperClassName = "editor-mask-wrapper";
750
+ const throttleTime = 100;
571
751
  const hideScrollbar = () => {
572
752
  const style = globalThis.document.createElement("style");
573
753
  style.innerHTML = `
@@ -601,28 +781,73 @@ const createWrapper = () => {
601
781
  return el;
602
782
  };
603
783
  class StageMask extends Rule {
604
- content = createContent();
605
- wrapper;
606
- core;
607
- page = null;
608
- pageScrollParent = null;
609
- scrollTop = 0;
610
- scrollLeft = 0;
611
- width = 0;
612
- height = 0;
613
- wrapperHeight = 0;
614
- wrapperWidth = 0;
615
- mode = Mode.ABSOLUTE;
616
- pageResizeObserver = null;
617
- wrapperResizeObserver = null;
618
784
  constructor(config) {
619
785
  const wrapper = createWrapper();
620
786
  super(wrapper);
787
+ this.content = createContent();
788
+ this.page = null;
789
+ this.pageScrollParent = null;
790
+ this.scrollTop = 0;
791
+ this.scrollLeft = 0;
792
+ this.width = 0;
793
+ this.height = 0;
794
+ this.wrapperHeight = 0;
795
+ this.wrapperWidth = 0;
796
+ this.maxScrollTop = 0;
797
+ this.maxScrollLeft = 0;
798
+ this.mode = Mode.ABSOLUTE;
799
+ this.pageResizeObserver = null;
800
+ this.wrapperResizeObserver = null;
801
+ this.highlightHandler = throttle((event) => {
802
+ this.emit("highlight", event);
803
+ }, throttleTime);
804
+ this.mouseDownHandler = (event) => {
805
+ this.emit("clearHighlight");
806
+ event.stopImmediatePropagation();
807
+ event.stopPropagation();
808
+ if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT)
809
+ return;
810
+ if (event.target.className.indexOf("moveable-control") !== -1) {
811
+ return;
812
+ }
813
+ this.content.removeEventListener("mousemove", this.highlightHandler);
814
+ this.emit("beforeSelect", event);
815
+ globalThis.document.addEventListener("mouseup", this.mouseUpHandler);
816
+ };
817
+ this.mouseUpHandler = () => {
818
+ globalThis.document.removeEventListener("mouseup", this.mouseUpHandler);
819
+ this.content.addEventListener("mousemove", this.highlightHandler);
820
+ this.emit("select");
821
+ };
822
+ this.mouseWheelHandler = (event) => {
823
+ this.emit("clearHighlight");
824
+ if (!this.page)
825
+ throw new Error("page \u672A\u521D\u59CB\u5316");
826
+ const { deltaY, deltaX } = event;
827
+ if (this.page.clientHeight < this.wrapperHeight && deltaY)
828
+ return;
829
+ if (this.page.clientWidth < this.wrapperWidth && deltaX)
830
+ return;
831
+ if (this.maxScrollTop > 0) {
832
+ this.scrollTop = this.scrollTop + deltaY;
833
+ }
834
+ if (this.maxScrollLeft > 0) {
835
+ this.scrollLeft = this.scrollLeft + deltaX;
836
+ }
837
+ this.fixScrollValue();
838
+ this.scroll();
839
+ this.emit("scroll", event);
840
+ };
841
+ this.mouseLeaveHandler = () => {
842
+ setTimeout(() => this.emit("clearHighlight"), throttleTime);
843
+ };
621
844
  this.wrapper = wrapper;
622
845
  this.core = config.core;
623
846
  this.content.addEventListener("mousedown", this.mouseDownHandler);
624
847
  this.wrapper.appendChild(this.content);
625
848
  this.content.addEventListener("wheel", this.mouseWheelHandler);
849
+ this.content.addEventListener("mousemove", this.highlightHandler);
850
+ this.content.addEventListener("mouseleave", this.mouseLeaveHandler);
626
851
  }
627
852
  setMode(mode) {
628
853
  this.mode = mode;
@@ -647,6 +872,9 @@ class StageMask extends Rule {
647
872
  const { clientHeight, clientWidth } = entry.target;
648
873
  this.setHeight(clientHeight);
649
874
  this.setWidth(clientWidth);
875
+ this.fixScrollValue();
876
+ this.scroll();
877
+ this.core.dr.updateMoveable();
650
878
  });
651
879
  this.pageResizeObserver.observe(page);
652
880
  this.wrapperResizeObserver = new ResizeObserver((entries) => {
@@ -654,6 +882,8 @@ class StageMask extends Rule {
654
882
  const { clientHeight, clientWidth } = entry.target;
655
883
  this.wrapperHeight = clientHeight;
656
884
  this.wrapperWidth = clientWidth;
885
+ this.setMaxScrollLeft();
886
+ this.setMaxScrollTop();
657
887
  });
658
888
  this.wrapperResizeObserver.observe(this.wrapper);
659
889
  }
@@ -666,16 +896,33 @@ class StageMask extends Rule {
666
896
  setLayout(el) {
667
897
  this.setMode(isFixedParent(el) ? Mode.FIXED : Mode.ABSOLUTE);
668
898
  }
899
+ scrollIntoView(el) {
900
+ if (this.mode === Mode.FIXED)
901
+ return;
902
+ el.scrollIntoView();
903
+ if (!this.pageScrollParent)
904
+ return;
905
+ this.scrollLeft = this.pageScrollParent.scrollLeft;
906
+ this.scrollTop = this.pageScrollParent.scrollTop;
907
+ this.scroll();
908
+ }
669
909
  destroy() {
670
910
  this.content?.remove();
671
911
  this.page = null;
672
912
  this.pageScrollParent = null;
673
913
  this.pageResizeObserver?.disconnect();
674
914
  this.wrapperResizeObserver?.disconnect();
915
+ this.content.removeEventListener("mouseleave", this.mouseLeaveHandler);
675
916
  super.destroy();
676
917
  }
677
918
  scroll() {
678
919
  let { scrollLeft, scrollTop } = this;
920
+ if (this.pageScrollParent) {
921
+ this.pageScrollParent.scrollTo({
922
+ top: scrollTop,
923
+ left: scrollLeft
924
+ });
925
+ }
679
926
  if (this.mode === Mode.FIXED) {
680
927
  scrollLeft = 0;
681
928
  scrollTop = 0;
@@ -688,71 +935,75 @@ class StageMask extends Rule {
688
935
  }
689
936
  setHeight(height) {
690
937
  this.height = height;
938
+ this.setMaxScrollTop();
691
939
  this.content.style.height = `${height}px`;
692
940
  }
693
941
  setWidth(width) {
694
942
  this.width = width;
943
+ this.setMaxScrollLeft();
695
944
  this.content.style.width = `${width}px`;
696
945
  }
697
- mouseDownHandler = async (event) => {
698
- event.stopImmediatePropagation();
699
- event.stopPropagation();
700
- if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT)
701
- return;
702
- if (event.target.className.indexOf("moveable-control") !== -1) {
703
- return;
704
- }
705
- this.emit("beforeSelect", event);
706
- globalThis.document.addEventListener("mouseup", this.mouseUpHandler);
707
- };
708
- mouseUpHandler = () => {
709
- globalThis.document.removeEventListener("mouseup", this.mouseUpHandler);
710
- this.emit("select");
711
- };
712
- mouseWheelHandler = (event) => {
713
- if (!this.page)
714
- throw new Error("page \u672A\u521D\u59CB\u5316");
715
- const { deltaY, deltaX } = event;
716
- const { height, wrapperHeight, width, wrapperWidth } = this;
717
- const maxScrollTop = height - wrapperHeight;
718
- const maxScrollLeft = width - wrapperWidth;
719
- if (maxScrollTop > 0) {
720
- if (deltaY > 0) {
721
- this.scrollTop = this.scrollTop + Math.min(maxScrollTop - this.scrollTop, deltaY);
722
- } else {
723
- this.scrollTop = Math.max(this.scrollTop + deltaY, 0);
724
- }
725
- }
726
- if (width > wrapperWidth) {
727
- if (deltaX > 0) {
728
- this.scrollLeft = this.scrollLeft + Math.min(maxScrollLeft - this.scrollLeft, deltaX);
729
- } else {
730
- this.scrollLeft = Math.max(this.scrollLeft + deltaX, 0);
731
- }
732
- }
733
- if (this.mode !== Mode.FIXED) {
734
- this.scrollTo(this.scrollLeft, this.scrollTop);
735
- }
736
- if (this.pageScrollParent) {
737
- this.pageScrollParent.scrollTo({
738
- top: this.scrollTop,
739
- left: this.scrollLeft
740
- });
741
- }
742
- this.scroll();
743
- this.emit("scroll", event);
744
- };
946
+ setMaxScrollLeft() {
947
+ this.maxScrollLeft = this.width - this.wrapperWidth;
948
+ }
949
+ setMaxScrollTop() {
950
+ this.maxScrollTop = this.height - this.wrapperHeight;
951
+ }
952
+ fixScrollValue() {
953
+ if (this.scrollTop < 0)
954
+ this.scrollTop = 0;
955
+ if (this.scrollLeft < 0)
956
+ this.scrollLeft = 0;
957
+ if (this.maxScrollTop < this.scrollTop)
958
+ this.scrollTop = this.maxScrollTop;
959
+ if (this.maxScrollLeft < this.scrollLeft)
960
+ this.scrollLeft = this.maxScrollLeft;
961
+ }
745
962
  }
746
963
 
747
964
  class StageRender extends EventEmitter {
748
- contentWindow = null;
749
- runtime = null;
750
- iframe;
751
- runtimeUrl;
752
- core;
753
- render;
754
965
  constructor({ core }) {
755
966
  super();
967
+ this.contentWindow = null;
968
+ this.runtime = null;
969
+ this.getMagicApi = () => ({
970
+ onPageElUpdate: (el) => this.emit("page-el-update", el),
971
+ onRuntimeReady: (runtime) => {
972
+ this.runtime = runtime;
973
+ globalThis.runtime = runtime;
974
+ this.emit("runtime-ready", runtime);
975
+ }
976
+ });
977
+ this.getRuntime = () => {
978
+ if (this.runtime)
979
+ return Promise.resolve(this.runtime);
980
+ return new Promise((resolve) => {
981
+ const listener = (runtime) => {
982
+ this.off("runtime-ready", listener);
983
+ resolve(runtime);
984
+ };
985
+ this.on("runtime-ready", listener);
986
+ });
987
+ };
988
+ this.loadHandler = async () => {
989
+ this.emit("onload");
990
+ if (this.render) {
991
+ const el = await this.render(this.core);
992
+ if (el) {
993
+ this.iframe?.contentDocument?.body?.appendChild(el);
994
+ }
995
+ }
996
+ if (this.contentWindow) {
997
+ const style = this.contentWindow.document.createElement("style");
998
+ style.id = "tmagic-stage-render";
999
+ style.innerHTML = `
1000
+ .${SELECTED_CLASS}, .${SELECTED_CLASS}-parent {
1001
+ z-index: ${ZIndex.SELECTED_EL};
1002
+ }
1003
+ `;
1004
+ this.contentWindow.document.head.appendChild(style);
1005
+ }
1006
+ };
756
1007
  this.core = core;
757
1008
  this.runtimeUrl = core.config.runtimeUrl || "";
758
1009
  this.render = core.config.render;
@@ -765,14 +1016,6 @@ class StageRender extends EventEmitter {
765
1016
  `;
766
1017
  this.iframe.addEventListener("load", this.loadHandler);
767
1018
  }
768
- getMagicApi = () => ({
769
- onPageElUpdate: (el) => this.emit("page-el-update", el),
770
- onRuntimeReady: (runtime) => {
771
- this.runtime = runtime;
772
- globalThis.runtime = runtime;
773
- this.emit("runtime-ready", runtime);
774
- }
775
- });
776
1019
  async mount(el) {
777
1020
  if (this.iframe) {
778
1021
  if (!isSameDomain(this.runtimeUrl) && this.runtimeUrl) {
@@ -789,17 +1032,6 @@ class StageRender extends EventEmitter {
789
1032
  throw Error("mount \u5931\u8D25");
790
1033
  }
791
1034
  }
792
- getRuntime = () => {
793
- if (this.runtime)
794
- return Promise.resolve(this.runtime);
795
- return new Promise((resolve) => {
796
- const listener = (runtime) => {
797
- this.off("runtime-ready", listener);
798
- resolve(runtime);
799
- };
800
- this.on("runtime-ready", listener);
801
- });
802
- };
803
1035
  destroy() {
804
1036
  this.iframe?.removeEventListener("load", this.loadHandler);
805
1037
  this.contentWindow = null;
@@ -807,35 +1039,25 @@ class StageRender extends EventEmitter {
807
1039
  this.iframe = void 0;
808
1040
  this.removeAllListeners();
809
1041
  }
810
- loadHandler = async () => {
811
- this.emit("onload");
812
- if (this.render) {
813
- const el = await this.render(this.core);
814
- if (el) {
815
- this.iframe?.contentDocument?.body?.appendChild(el);
816
- }
817
- }
818
- };
819
1042
  }
820
1043
 
821
1044
  class StageCore extends EventEmitter {
822
- selectedDom;
823
- renderer;
824
- mask;
825
- dr;
826
- config;
827
- zoom = DEFAULT_ZOOM;
828
- canSelect;
829
1045
  constructor(config) {
830
1046
  super();
1047
+ this.zoom = DEFAULT_ZOOM;
831
1048
  this.config = config;
832
1049
  this.setZoom(config.zoom);
833
1050
  this.canSelect = config.canSelect || ((el) => !!el.id);
834
1051
  this.renderer = new StageRender({ core: this });
835
1052
  this.mask = new StageMask({ core: this });
836
1053
  this.dr = new StageDragResize({ core: this, container: this.mask.content });
837
- this.renderer.on("runtime-ready", (runtime) => this.emit("runtime-ready", runtime));
838
- this.renderer.on("page-el-update", (el) => this.mask?.observe(el));
1054
+ this.highlightLayer = new StageHighlight({ core: this, container: this.mask.wrapper });
1055
+ this.renderer.on("runtime-ready", (runtime) => {
1056
+ this.emit("runtime-ready", runtime);
1057
+ });
1058
+ this.renderer.on("page-el-update", (el) => {
1059
+ this.mask?.observe(el);
1060
+ });
839
1061
  this.mask.on("beforeSelect", (event) => {
840
1062
  this.setElementFromPoint(event);
841
1063
  }).on("select", () => {
@@ -843,6 +1065,16 @@ class StageCore extends EventEmitter {
843
1065
  }).on("changeGuides", (data) => {
844
1066
  this.dr.setGuidelines(data.type, data.guides);
845
1067
  this.emit("changeGuides", data);
1068
+ }).on("highlight", async (event) => {
1069
+ await this.setElementFromPoint(event);
1070
+ if (this.highlightedDom === this.selectedDom) {
1071
+ this.highlightLayer.clearHighlight();
1072
+ return;
1073
+ }
1074
+ this.highlightLayer.highlight(this.highlightedDom);
1075
+ this.emit("highlight", this.highlightedDom);
1076
+ }).on("clearHighlight", async () => {
1077
+ this.highlightLayer.clearHighlight();
846
1078
  });
847
1079
  this.dr.on("update", (data) => {
848
1080
  setTimeout(() => this.emit("update", data));
@@ -866,60 +1098,79 @@ class StageCore extends EventEmitter {
866
1098
  let stopped = false;
867
1099
  const stop = () => stopped = true;
868
1100
  for (const el of els) {
869
- if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && await this.canSelect(el, stop)) {
1101
+ if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && await this.canSelect(el, event, stop)) {
870
1102
  if (stopped)
871
1103
  break;
1104
+ if (event.type === "mousemove") {
1105
+ this.highlight(el);
1106
+ break;
1107
+ }
872
1108
  this.select(el, event);
873
1109
  break;
874
1110
  }
875
1111
  }
876
1112
  }
877
1113
  async select(idOrEl, event) {
878
- let el;
879
- if (typeof idOrEl === "string" || typeof idOrEl === "number") {
880
- const runtime2 = await this.renderer?.getRuntime();
881
- el = await runtime2?.select?.(`${idOrEl}`);
882
- if (!el)
883
- throw new Error(`\u4E0D\u5B58\u5728ID\u4E3A${idOrEl}\u7684\u5143\u7D20`);
884
- } else {
885
- el = idOrEl;
886
- }
1114
+ const el = await this.getTargetElement(idOrEl);
887
1115
  if (el === this.selectedDom)
888
1116
  return;
889
- const runtime = await this.renderer?.getRuntime();
1117
+ const runtime = await this.renderer.getRuntime();
1118
+ await runtime?.select?.(el.id);
890
1119
  if (runtime?.beforeSelect) {
891
1120
  await runtime.beforeSelect(el);
892
1121
  }
893
1122
  this.mask.setLayout(el);
894
- this.dr?.select(el, event);
1123
+ this.dr.select(el, event);
1124
+ this.mask.scrollIntoView(el);
895
1125
  this.selectedDom = el;
1126
+ if (this.renderer.contentWindow) {
1127
+ removeSelectedClassName(this.renderer.contentWindow.document);
1128
+ if (this.selectedDom) {
1129
+ addSelectedClassName(this.selectedDom, this.renderer.contentWindow.document);
1130
+ }
1131
+ }
896
1132
  }
897
1133
  update(data) {
898
1134
  const { config } = data;
899
- this.renderer?.getRuntime().then((runtime) => {
1135
+ return this.renderer?.getRuntime().then((runtime) => {
900
1136
  runtime?.update?.(data);
901
1137
  setTimeout(() => {
902
1138
  const el = this.renderer.contentWindow?.document.getElementById(`${config.id}`);
903
- if (el) {
1139
+ if (el && el.id === this.selectedDom?.id) {
1140
+ this.selectedDom = el;
904
1141
  this.mask.setLayout(el);
905
- this.dr?.select(el);
1142
+ this.dr.updateMoveable(el);
906
1143
  }
907
1144
  }, 0);
908
1145
  });
909
1146
  }
1147
+ async highlight(idOrEl) {
1148
+ let el;
1149
+ try {
1150
+ el = await this.getTargetElement(idOrEl);
1151
+ } catch (error) {
1152
+ this.highlightLayer.clearHighlight();
1153
+ return;
1154
+ }
1155
+ if (el === this.highlightedDom)
1156
+ return;
1157
+ this.highlightLayer.highlight(el);
1158
+ this.highlightedDom = el;
1159
+ }
910
1160
  sortNode(data) {
911
- this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
1161
+ return this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
912
1162
  }
913
1163
  add(data) {
914
- this.renderer?.getRuntime().then((runtime) => runtime?.add?.(data));
1164
+ return this.renderer?.getRuntime().then((runtime) => runtime?.add?.(data));
915
1165
  }
916
1166
  remove(data) {
917
- this.renderer?.getRuntime().then((runtime) => runtime?.remove?.(data));
1167
+ return this.renderer?.getRuntime().then((runtime) => runtime?.remove?.(data));
918
1168
  }
919
1169
  setZoom(zoom = DEFAULT_ZOOM) {
920
1170
  this.zoom = zoom;
921
1171
  }
922
1172
  mount(el) {
1173
+ this.container = el;
923
1174
  const { mask, renderer } = this;
924
1175
  renderer.mount(el);
925
1176
  mask.mount(el);
@@ -930,11 +1181,22 @@ class StageCore extends EventEmitter {
930
1181
  this.dr.clearGuides();
931
1182
  }
932
1183
  destroy() {
933
- const { mask, renderer, dr } = this;
1184
+ const { mask, renderer, dr, highlightLayer } = this;
934
1185
  renderer.destroy();
935
1186
  mask.destroy();
936
1187
  dr.destroy();
1188
+ highlightLayer.destroy();
937
1189
  this.removeAllListeners();
1190
+ this.container = void 0;
1191
+ }
1192
+ async getTargetElement(idOrEl) {
1193
+ if (typeof idOrEl === "string" || typeof idOrEl === "number") {
1194
+ const el = this.renderer.contentWindow?.document.getElementById(`${idOrEl}`);
1195
+ if (!el)
1196
+ throw new Error(`\u4E0D\u5B58\u5728ID\u4E3A${idOrEl}\u7684\u5143\u7D20`);
1197
+ return el;
1198
+ }
1199
+ return idOrEl;
938
1200
  }
939
1201
  }
940
1202