@tmagic/stage 1.0.0-beta.9 → 1.0.0-rc.3

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.
@@ -5,6 +5,8 @@ import { throttle } from 'lodash-es';
5
5
  import Guides from '@scena/guides';
6
6
 
7
7
  const GHOST_EL_ID_PREFIX = "ghost_el_";
8
+ const DRAG_EL_ID_PREFIX = "drag_el_";
9
+ const HIGHLIGHT_EL_ID_PREFIX = "highlight_el_";
8
10
  const DEFAULT_ZOOM = 1;
9
11
  var GuidesType = /* @__PURE__ */ ((GuidesType2) => {
10
12
  GuidesType2["HORIZONTAL"] = "horizontal";
@@ -30,6 +32,15 @@ var Mode = /* @__PURE__ */ ((Mode2) => {
30
32
  })(Mode || {});
31
33
  const SELECTED_CLASS = "tmagic-stage-selected-area";
32
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
+ };
33
44
  const getOffset = (el) => {
34
45
  const { transform } = getComputedStyle(el);
35
46
  const { offsetParent } = el;
@@ -154,58 +165,59 @@ const removeSelectedClassName = (doc) => {
154
165
  if (oldEl) {
155
166
  oldEl.classList.remove(SELECTED_CLASS);
156
167
  oldEl.parentNode?.classList.remove(`${SELECTED_CLASS}-parent`);
168
+ doc.querySelectorAll(`.${SELECTED_CLASS}-parents`).forEach((item) => {
169
+ item.classList.remove(`${SELECTED_CLASS}-parents`);
170
+ });
157
171
  }
158
172
  };
159
- const addSelectedClassName = (el) => {
173
+ const addSelectedClassName = (el, doc) => {
160
174
  el.classList.add(SELECTED_CLASS);
161
175
  el.parentNode?.classList.add(`${SELECTED_CLASS}-parent`);
176
+ getParents(el, doc.body).forEach((item) => {
177
+ item.classList.add(`${SELECTED_CLASS}-parents`);
178
+ });
162
179
  };
163
180
 
164
181
  class StageDragResize extends EventEmitter {
165
- core;
166
- container;
167
- target;
168
- dragEl;
169
- moveable;
170
- horizontalGuidelines = [];
171
- verticalGuidelines = [];
172
- moveableOptions = {};
173
- dragStatus = "end" /* END */;
174
- ghostEl;
175
- mode = Mode.ABSOLUTE;
176
- moveableHelper;
177
182
  constructor(config) {
178
183
  super();
184
+ this.horizontalGuidelines = [];
185
+ this.verticalGuidelines = [];
186
+ this.elementGuidelines = [];
187
+ this.mode = Mode.ABSOLUTE;
188
+ this.moveableOptions = {};
189
+ this.dragStatus = "end" /* END */;
179
190
  this.core = config.core;
180
191
  this.container = config.container;
192
+ this.dragEl = globalThis.document.createElement("div");
193
+ this.container.append(this.dragEl);
181
194
  }
182
- async select(el, event) {
195
+ select(el, event) {
196
+ const oldTarget = this.target;
183
197
  this.target = el;
184
- if (/(auto|scroll)/.test(this.target.style.overflow)) {
185
- 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();
186
208
  }
187
- this.mode = getMode(el);
188
- this.destroyDragEl();
189
- this.destroyGhostEl();
190
- this.dragEl = this.generateDragEl(el);
191
- this.moveableOptions = await this.getOptions({
192
- target: this.dragEl || this.target
193
- });
194
- this.moveableHelper = MoveableHelper.create({
195
- useBeforeRender: true,
196
- useRender: false,
197
- createAuto: true
198
- });
199
- this.initMoveable();
200
209
  if (event) {
201
210
  this.moveable?.dragStart(event);
202
211
  }
203
212
  }
204
- async refresh() {
213
+ updateMoveable(el = this.target) {
205
214
  if (!this.moveable)
206
215
  throw new Error("\u672A\u521D\u59CB\u5316moveable");
207
- const options = await this.getOptions();
208
- 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]) => {
209
221
  this.moveable[key] = value;
210
222
  });
211
223
  this.moveable.updateTarget();
@@ -213,15 +225,19 @@ class StageDragResize extends EventEmitter {
213
225
  setGuidelines(type, guidelines) {
214
226
  if (type === GuidesType.HORIZONTAL) {
215
227
  this.horizontalGuidelines = guidelines;
228
+ this.moveableOptions.horizontalGuidelines = guidelines;
216
229
  } else if (type === GuidesType.VERTICAL) {
217
230
  this.verticalGuidelines = guidelines;
231
+ this.moveableOptions.verticalGuidelines = guidelines;
218
232
  }
219
- this.refresh();
233
+ this.updateMoveable();
220
234
  }
221
235
  clearGuides() {
222
- this.verticalGuidelines = [];
223
236
  this.horizontalGuidelines = [];
224
- this.refresh();
237
+ this.verticalGuidelines = [];
238
+ this.moveableOptions.horizontalGuidelines = [];
239
+ this.moveableOptions.verticalGuidelines = [];
240
+ this.updateMoveable();
225
241
  }
226
242
  destroy() {
227
243
  this.moveable?.destroy();
@@ -230,6 +246,17 @@ class StageDragResize extends EventEmitter {
230
246
  this.dragStatus = "end" /* END */;
231
247
  this.removeAllListeners();
232
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
+ }
233
260
  initMoveable() {
234
261
  this.moveable?.destroy();
235
262
  this.moveable = new Moveable(this.container, {
@@ -274,22 +301,21 @@ class StageDragResize extends EventEmitter {
274
301
  bindDragEvent() {
275
302
  if (!this.moveable)
276
303
  throw new Error("moveable \u4E3A\u521D\u59CB\u5316");
277
- let offset = {
278
- left: 0,
279
- top: 0
280
- };
304
+ let offset = null;
281
305
  this.moveable.on("dragStart", (e) => {
282
306
  if (!this.target)
283
307
  throw new Error("\u672A\u9009\u4E2D\u7EC4\u4EF6");
284
308
  this.dragStatus = "start" /* START */;
285
309
  this.moveableHelper?.onDragStart(e);
286
- offset = getAbsolutePosition(this.target, { left: 0, top: 0 });
287
310
  if (this.mode === Mode.SORTABLE) {
288
311
  this.ghostEl = this.generateGhostEl(this.target);
289
312
  }
290
313
  }).on("drag", (e) => {
291
314
  if (!this.target || !this.dragEl)
292
315
  return;
316
+ if (!offset) {
317
+ offset = getAbsolutePosition(this.target, { left: 0, top: 0 });
318
+ }
293
319
  this.dragStatus = "ing" /* ING */;
294
320
  const { left, top } = e;
295
321
  if (this.ghostEl) {
@@ -310,17 +336,18 @@ class StageDragResize extends EventEmitter {
310
336
  this.update();
311
337
  }
312
338
  }
339
+ offset = null;
313
340
  this.dragStatus = "end" /* END */;
314
341
  this.destroyGhostEl();
315
342
  });
316
343
  }
317
- async getSnapElements(el) {
318
- const { renderer } = this.core;
319
- const getSnapElements = (await renderer.getRuntime())?.getSnapElements || (() => {
344
+ getSnapElements(runtime, el) {
345
+ const { renderer, mask } = this.core;
346
+ const getSnapElements = runtime?.getSnapElements || (() => {
320
347
  const doc = renderer.contentWindow?.document;
321
348
  return doc ? Array.from(doc.querySelectorAll("[id]")) : [];
322
349
  });
323
- 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);
324
351
  }
325
352
  sort() {
326
353
  if (!this.target || !this.ghostEl)
@@ -359,7 +386,7 @@ class StageDragResize extends EventEmitter {
359
386
  }
360
387
  const ghostEl = el.cloneNode(true);
361
388
  const { top, left } = getAbsolutePosition(el, getOffset(el));
362
- ghostEl.id = `${GHOST_EL_ID_PREFIX}${ghostEl.id}`;
389
+ ghostEl.id = `${GHOST_EL_ID_PREFIX}${el.id}`;
363
390
  ghostEl.style.zIndex = "5";
364
391
  ghostEl.style.opacity = ".5";
365
392
  ghostEl.style.position = "absolute";
@@ -372,44 +399,40 @@ class StageDragResize extends EventEmitter {
372
399
  this.ghostEl?.remove();
373
400
  this.ghostEl = void 0;
374
401
  }
375
- generateDragEl(el) {
376
- if (this.dragEl) {
377
- this.destroyDragEl();
378
- }
402
+ updateDragEl(el) {
379
403
  const { width, height } = el.getBoundingClientRect();
380
404
  const offset = getOffset(el);
381
- const dragEl = globalThis.document.createElement("div");
382
- dragEl.style.cssText = `
405
+ this.dragEl.style.cssText = `
383
406
  position: absolute;
384
407
  left: ${offset.left}px;
385
408
  top: ${offset.top}px;
386
409
  width: ${width}px;
387
410
  height: ${height}px;
411
+ z-index: 9;
388
412
  `;
389
- this.container.append(dragEl);
390
- return dragEl;
413
+ this.dragEl.id = `${DRAG_EL_ID_PREFIX}${el.id}`;
391
414
  }
392
415
  destroyDragEl() {
393
416
  this.dragEl?.remove();
394
- this.dragEl = void 0;
395
417
  }
396
- async getOptions(options = {}) {
418
+ getOptions(options = {}) {
397
419
  if (!this.target)
398
420
  return {};
399
421
  const isAbsolute = this.mode === Mode.ABSOLUTE;
422
+ const isFixed = this.mode === Mode.FIXED;
400
423
  let { moveableOptions = {} } = this.core.config;
401
424
  if (typeof moveableOptions === "function") {
402
425
  moveableOptions = moveableOptions(this.core);
403
426
  }
404
427
  return {
405
- origin: true,
428
+ origin: false,
406
429
  rootContainer: this.core.container,
407
430
  zoom: 1,
408
431
  dragArea: false,
409
432
  draggable: true,
410
433
  resizable: true,
411
- snappable: isAbsolute,
412
- snapGap: isAbsolute,
434
+ snappable: isAbsolute || isFixed,
435
+ snapGap: isAbsolute || isFixed,
413
436
  snapThreshold: 5,
414
437
  snapDigit: 0,
415
438
  throttleDrag: 0,
@@ -422,8 +445,16 @@ class StageDragResize extends EventEmitter {
422
445
  center: isAbsolute,
423
446
  middle: isAbsolute
424
447
  },
448
+ elementSnapDirections: {
449
+ top: isAbsolute,
450
+ right: isAbsolute,
451
+ bottom: isAbsolute,
452
+ left: isAbsolute
453
+ },
454
+ isDisplayInnerSnapDigit: true,
425
455
  horizontalGuidelines: this.horizontalGuidelines,
426
456
  verticalGuidelines: this.verticalGuidelines,
457
+ elementGuidelines: this.elementGuidelines,
427
458
  bounds: {
428
459
  top: 0,
429
460
  left: 0,
@@ -492,15 +523,97 @@ const up = (deltaTop, target) => {
492
523
  };
493
524
  };
494
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
+
495
607
  class StageHighlight extends EventEmitter {
496
- core;
497
- container;
498
- target;
499
- moveable;
500
608
  constructor(config) {
501
609
  super();
502
610
  this.core = config.core;
503
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
+ });
504
617
  }
505
618
  highlight(el) {
506
619
  if (!el || el === this.target)
@@ -508,29 +621,61 @@ class StageHighlight extends EventEmitter {
508
621
  this.target = el;
509
622
  this.moveable?.destroy();
510
623
  this.moveable = new Moveable(this.container, {
511
- target: this.target
624
+ target: this.calibrationTarget.update(el, HIGHLIGHT_EL_ID_PREFIX),
625
+ origin: false,
626
+ rootContainer: this.core.container,
627
+ zoom: 1
512
628
  });
513
629
  }
514
630
  clearHighlight() {
515
631
  if (!this.moveable)
516
632
  return;
633
+ this.target = void 0;
517
634
  this.moveable.target = null;
518
635
  this.moveable.updateTarget();
519
636
  }
520
637
  destroy() {
521
638
  this.moveable?.destroy();
639
+ this.calibrationTarget.destroy();
522
640
  }
523
641
  }
524
642
 
525
643
  class Rule extends EventEmitter$1 {
526
- hGuides;
527
- vGuides;
528
- horizontalGuidelines = [];
529
- verticalGuidelines = [];
530
- container;
531
- containerResizeObserver;
532
644
  constructor(container) {
533
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
+ };
534
679
  this.container = container;
535
680
  this.hGuides = this.createGuides(GuidesType.HORIZONTAL);
536
681
  this.vGuides = this.createGuides(GuidesType.VERTICAL);
@@ -599,37 +744,6 @@ class Rule extends EventEmitter$1 {
599
744
  this.containerResizeObserver.disconnect();
600
745
  this.removeAllListeners();
601
746
  }
602
- getGuidesStyle = (type) => ({
603
- position: "fixed",
604
- zIndex: 1,
605
- left: type === GuidesType.HORIZONTAL ? 0 : "-30px",
606
- top: type === GuidesType.HORIZONTAL ? "-30px" : 0,
607
- width: type === GuidesType.HORIZONTAL ? "100%" : "30px",
608
- height: type === GuidesType.HORIZONTAL ? "30px" : "100%"
609
- });
610
- createGuides = (type, defaultGuides = []) => new Guides(this.container, {
611
- type,
612
- defaultGuides,
613
- displayDragPos: true,
614
- backgroundColor: "#fff",
615
- lineColor: "#000",
616
- textColor: "#000",
617
- style: this.getGuidesStyle(type)
618
- });
619
- hGuidesChangeGuidesHandler = (e) => {
620
- this.horizontalGuidelines = e.guides;
621
- this.emit("changeGuides", {
622
- type: GuidesType.HORIZONTAL,
623
- guides: this.horizontalGuidelines
624
- });
625
- };
626
- vGuidesChangeGuidesHandler = (e) => {
627
- this.verticalGuidelines = e.guides;
628
- this.emit("changeGuides", {
629
- type: GuidesType.VERTICAL,
630
- guides: this.verticalGuidelines
631
- });
632
- };
633
747
  }
634
748
 
635
749
  const wrapperClassName = "editor-mask-wrapper";
@@ -667,32 +781,74 @@ const createWrapper = () => {
667
781
  return el;
668
782
  };
669
783
  class StageMask extends Rule {
670
- content = createContent();
671
- wrapper;
672
- core;
673
- page = null;
674
- pageScrollParent = null;
675
- scrollTop = 0;
676
- scrollLeft = 0;
677
- width = 0;
678
- height = 0;
679
- wrapperHeight = 0;
680
- wrapperWidth = 0;
681
- mode = Mode.ABSOLUTE;
682
- pageResizeObserver = null;
683
- wrapperResizeObserver = null;
684
- highlightHandler = throttle((event) => {
685
- this.emit("highlight", event);
686
- }, throttleTime);
687
784
  constructor(config) {
688
785
  const wrapper = createWrapper();
689
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.intersectionObserver = null;
799
+ this.mode = Mode.ABSOLUTE;
800
+ this.pageResizeObserver = null;
801
+ this.wrapperResizeObserver = null;
802
+ this.highlightHandler = throttle((event) => {
803
+ this.emit("highlight", event);
804
+ }, throttleTime);
805
+ this.mouseDownHandler = (event) => {
806
+ this.emit("clearHighlight");
807
+ event.stopImmediatePropagation();
808
+ event.stopPropagation();
809
+ if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT)
810
+ return;
811
+ if (event.target.className.indexOf("moveable-control") !== -1) {
812
+ return;
813
+ }
814
+ this.content.removeEventListener("mousemove", this.highlightHandler);
815
+ this.emit("beforeSelect", event);
816
+ globalThis.document.addEventListener("mouseup", this.mouseUpHandler);
817
+ };
818
+ this.mouseUpHandler = () => {
819
+ globalThis.document.removeEventListener("mouseup", this.mouseUpHandler);
820
+ this.content.addEventListener("mousemove", this.highlightHandler);
821
+ this.emit("select");
822
+ };
823
+ this.mouseWheelHandler = (event) => {
824
+ this.emit("clearHighlight");
825
+ if (!this.page)
826
+ throw new Error("page \u672A\u521D\u59CB\u5316");
827
+ const { deltaY, deltaX } = event;
828
+ if (this.page.clientHeight < this.wrapperHeight && deltaY)
829
+ return;
830
+ if (this.page.clientWidth < this.wrapperWidth && deltaX)
831
+ return;
832
+ if (this.maxScrollTop > 0) {
833
+ this.scrollTop = this.scrollTop + deltaY;
834
+ }
835
+ if (this.maxScrollLeft > 0) {
836
+ this.scrollLeft = this.scrollLeft + deltaX;
837
+ }
838
+ this.fixScrollValue();
839
+ this.scroll();
840
+ this.emit("scroll", event);
841
+ };
842
+ this.mouseLeaveHandler = () => {
843
+ setTimeout(() => this.emit("clearHighlight"), throttleTime);
844
+ };
690
845
  this.wrapper = wrapper;
691
846
  this.core = config.core;
692
847
  this.content.addEventListener("mousedown", this.mouseDownHandler);
693
848
  this.wrapper.appendChild(this.content);
694
849
  this.content.addEventListener("wheel", this.mouseWheelHandler);
695
850
  this.content.addEventListener("mousemove", this.highlightHandler);
851
+ this.content.addEventListener("mouseleave", this.mouseLeaveHandler);
696
852
  }
697
853
  setMode(mode) {
698
854
  this.mode = mode;
@@ -711,12 +867,32 @@ class StageMask extends Rule {
711
867
  this.page = page;
712
868
  this.pageScrollParent = getScrollParent(page) || this.core.renderer.contentWindow?.document.documentElement || null;
713
869
  this.pageResizeObserver?.disconnect();
870
+ this.wrapperResizeObserver?.disconnect();
871
+ this.intersectionObserver?.disconnect();
872
+ if (typeof IntersectionObserver !== "undefined") {
873
+ this.intersectionObserver = new IntersectionObserver((entries) => {
874
+ entries.forEach((entry) => {
875
+ const { target, intersectionRatio } = entry;
876
+ if (intersectionRatio <= 0) {
877
+ this.scrollIntoView(target);
878
+ }
879
+ this.intersectionObserver?.unobserve(target);
880
+ });
881
+ }, {
882
+ root: this.pageScrollParent,
883
+ rootMargin: "0px",
884
+ threshold: 1
885
+ });
886
+ }
714
887
  if (typeof ResizeObserver !== "undefined") {
715
888
  this.pageResizeObserver = new ResizeObserver((entries) => {
716
889
  const [entry] = entries;
717
890
  const { clientHeight, clientWidth } = entry.target;
718
891
  this.setHeight(clientHeight);
719
892
  this.setWidth(clientWidth);
893
+ this.fixScrollValue();
894
+ this.scroll();
895
+ this.core.dr.updateMoveable();
720
896
  });
721
897
  this.pageResizeObserver.observe(page);
722
898
  this.wrapperResizeObserver = new ResizeObserver((entries) => {
@@ -724,6 +900,8 @@ class StageMask extends Rule {
724
900
  const { clientHeight, clientWidth } = entry.target;
725
901
  this.wrapperHeight = clientHeight;
726
902
  this.wrapperWidth = clientWidth;
903
+ this.setMaxScrollLeft();
904
+ this.setMaxScrollTop();
727
905
  });
728
906
  this.wrapperResizeObserver.observe(this.wrapper);
729
907
  }
@@ -736,16 +914,31 @@ class StageMask extends Rule {
736
914
  setLayout(el) {
737
915
  this.setMode(isFixedParent(el) ? Mode.FIXED : Mode.ABSOLUTE);
738
916
  }
917
+ scrollIntoView(el) {
918
+ el.scrollIntoView();
919
+ if (!this.pageScrollParent)
920
+ return;
921
+ this.scrollLeft = this.pageScrollParent.scrollLeft;
922
+ this.scrollTop = this.pageScrollParent.scrollTop;
923
+ this.scroll();
924
+ }
739
925
  destroy() {
740
926
  this.content?.remove();
741
927
  this.page = null;
742
928
  this.pageScrollParent = null;
743
929
  this.pageResizeObserver?.disconnect();
744
930
  this.wrapperResizeObserver?.disconnect();
931
+ this.content.removeEventListener("mouseleave", this.mouseLeaveHandler);
745
932
  super.destroy();
746
933
  }
747
934
  scroll() {
748
935
  let { scrollLeft, scrollTop } = this;
936
+ if (this.pageScrollParent) {
937
+ this.pageScrollParent.scrollTo({
938
+ top: scrollTop,
939
+ left: scrollLeft
940
+ });
941
+ }
749
942
  if (this.mode === Mode.FIXED) {
750
943
  scrollLeft = 0;
751
944
  scrollTop = 0;
@@ -758,74 +951,75 @@ class StageMask extends Rule {
758
951
  }
759
952
  setHeight(height) {
760
953
  this.height = height;
954
+ this.setMaxScrollTop();
761
955
  this.content.style.height = `${height}px`;
762
956
  }
763
957
  setWidth(width) {
764
958
  this.width = width;
959
+ this.setMaxScrollLeft();
765
960
  this.content.style.width = `${width}px`;
766
961
  }
767
- mouseDownHandler = (event) => {
768
- event.stopImmediatePropagation();
769
- event.stopPropagation();
770
- if (event.button !== MouseButton.LEFT && event.button !== MouseButton.RIGHT)
771
- return;
772
- if (event.target.className.indexOf("moveable-control") !== -1) {
773
- return;
774
- }
775
- this.emit("beforeSelect", event);
776
- globalThis.document.addEventListener("mouseup", this.mouseUpHandler);
777
- this.content.removeEventListener("mousemove", this.highlightHandler);
778
- this.emit("clearHighlight");
779
- };
780
- mouseUpHandler = () => {
781
- globalThis.document.removeEventListener("mouseup", this.mouseUpHandler);
782
- this.content.addEventListener("mousemove", this.highlightHandler);
783
- this.emit("select");
784
- };
785
- mouseWheelHandler = (event) => {
786
- if (!this.page)
787
- throw new Error("page \u672A\u521D\u59CB\u5316");
788
- const { deltaY, deltaX } = event;
789
- const { height, wrapperHeight, width, wrapperWidth } = this;
790
- const maxScrollTop = height - wrapperHeight;
791
- const maxScrollLeft = width - wrapperWidth;
792
- if (maxScrollTop > 0) {
793
- if (deltaY > 0) {
794
- this.scrollTop = this.scrollTop + Math.min(maxScrollTop - this.scrollTop, deltaY);
795
- } else {
796
- this.scrollTop = Math.max(this.scrollTop + deltaY, 0);
797
- }
798
- }
799
- if (width > wrapperWidth) {
800
- if (deltaX > 0) {
801
- this.scrollLeft = this.scrollLeft + Math.min(maxScrollLeft - this.scrollLeft, deltaX);
802
- } else {
803
- this.scrollLeft = Math.max(this.scrollLeft + deltaX, 0);
804
- }
805
- }
806
- if (this.mode !== Mode.FIXED) {
807
- this.scrollTo(this.scrollLeft, this.scrollTop);
808
- }
809
- if (this.pageScrollParent) {
810
- this.pageScrollParent.scrollTo({
811
- top: this.scrollTop,
812
- left: this.scrollLeft
813
- });
814
- }
815
- this.scroll();
816
- this.emit("scroll", event);
817
- };
962
+ setMaxScrollLeft() {
963
+ this.maxScrollLeft = this.width - this.wrapperWidth;
964
+ }
965
+ setMaxScrollTop() {
966
+ this.maxScrollTop = this.height - this.wrapperHeight;
967
+ }
968
+ fixScrollValue() {
969
+ if (this.scrollTop < 0)
970
+ this.scrollTop = 0;
971
+ if (this.scrollLeft < 0)
972
+ this.scrollLeft = 0;
973
+ if (this.maxScrollTop < this.scrollTop)
974
+ this.scrollTop = this.maxScrollTop;
975
+ if (this.maxScrollLeft < this.scrollLeft)
976
+ this.scrollLeft = this.maxScrollLeft;
977
+ }
818
978
  }
819
979
 
820
980
  class StageRender extends EventEmitter {
821
- contentWindow = null;
822
- runtime = null;
823
- iframe;
824
- runtimeUrl;
825
- core;
826
- render;
827
981
  constructor({ core }) {
828
982
  super();
983
+ this.contentWindow = null;
984
+ this.runtime = null;
985
+ this.getMagicApi = () => ({
986
+ onPageElUpdate: (el) => this.emit("page-el-update", el),
987
+ onRuntimeReady: (runtime) => {
988
+ this.runtime = runtime;
989
+ globalThis.runtime = runtime;
990
+ this.emit("runtime-ready", runtime);
991
+ }
992
+ });
993
+ this.getRuntime = () => {
994
+ if (this.runtime)
995
+ return Promise.resolve(this.runtime);
996
+ return new Promise((resolve) => {
997
+ const listener = (runtime) => {
998
+ this.off("runtime-ready", listener);
999
+ resolve(runtime);
1000
+ };
1001
+ this.on("runtime-ready", listener);
1002
+ });
1003
+ };
1004
+ this.loadHandler = async () => {
1005
+ this.emit("onload");
1006
+ if (this.render) {
1007
+ const el = await this.render(this.core);
1008
+ if (el) {
1009
+ this.iframe?.contentDocument?.body?.appendChild(el);
1010
+ }
1011
+ }
1012
+ if (this.contentWindow) {
1013
+ const style = this.contentWindow.document.createElement("style");
1014
+ style.id = "tmagic-stage-render";
1015
+ style.innerHTML = `
1016
+ .${SELECTED_CLASS}, .${SELECTED_CLASS}-parent {
1017
+ z-index: ${ZIndex.SELECTED_EL};
1018
+ }
1019
+ `;
1020
+ this.contentWindow.document.head.appendChild(style);
1021
+ }
1022
+ };
829
1023
  this.core = core;
830
1024
  this.runtimeUrl = core.config.runtimeUrl || "";
831
1025
  this.render = core.config.render;
@@ -838,14 +1032,6 @@ class StageRender extends EventEmitter {
838
1032
  `;
839
1033
  this.iframe.addEventListener("load", this.loadHandler);
840
1034
  }
841
- getMagicApi = () => ({
842
- onPageElUpdate: (el) => this.emit("page-el-update", el),
843
- onRuntimeReady: (runtime) => {
844
- this.runtime = runtime;
845
- globalThis.runtime = runtime;
846
- this.emit("runtime-ready", runtime);
847
- }
848
- });
849
1035
  async mount(el) {
850
1036
  if (this.iframe) {
851
1037
  if (!isSameDomain(this.runtimeUrl) && this.runtimeUrl) {
@@ -862,17 +1048,6 @@ class StageRender extends EventEmitter {
862
1048
  throw Error("mount \u5931\u8D25");
863
1049
  }
864
1050
  }
865
- getRuntime = () => {
866
- if (this.runtime)
867
- return Promise.resolve(this.runtime);
868
- return new Promise((resolve) => {
869
- const listener = (runtime) => {
870
- this.off("runtime-ready", listener);
871
- resolve(runtime);
872
- };
873
- this.on("runtime-ready", listener);
874
- });
875
- };
876
1051
  destroy() {
877
1052
  this.iframe?.removeEventListener("load", this.loadHandler);
878
1053
  this.contentWindow = null;
@@ -880,49 +1055,25 @@ class StageRender extends EventEmitter {
880
1055
  this.iframe = void 0;
881
1056
  this.removeAllListeners();
882
1057
  }
883
- loadHandler = async () => {
884
- this.emit("onload");
885
- if (this.render) {
886
- const el = await this.render(this.core);
887
- if (el) {
888
- this.iframe?.contentDocument?.body?.appendChild(el);
889
- }
890
- }
891
- if (this.contentWindow) {
892
- const style = this.contentWindow.document.createElement("style");
893
- style.id = "tmagic-stage-render";
894
- style.innerHTML = `
895
- .${SELECTED_CLASS}, .${SELECTED_CLASS}-parent {
896
- z-index: ${ZIndex.SELECTED_EL};
897
- }
898
- `;
899
- this.contentWindow.document.head.appendChild(style);
900
- }
901
- };
902
1058
  }
903
1059
 
904
1060
  class StageCore extends EventEmitter {
905
- selectedDom;
906
- highlightedDom;
907
- renderer;
908
- mask;
909
- dr;
910
- highlightLayer;
911
- config;
912
- zoom = DEFAULT_ZOOM;
913
- container;
914
- canSelect;
915
1061
  constructor(config) {
916
1062
  super();
1063
+ this.zoom = DEFAULT_ZOOM;
917
1064
  this.config = config;
918
1065
  this.setZoom(config.zoom);
919
1066
  this.canSelect = config.canSelect || ((el) => !!el.id);
920
1067
  this.renderer = new StageRender({ core: this });
921
1068
  this.mask = new StageMask({ core: this });
922
1069
  this.dr = new StageDragResize({ core: this, container: this.mask.content });
923
- this.highlightLayer = new StageHighlight({ core: this, container: this.mask.content });
924
- this.renderer.on("runtime-ready", (runtime) => this.emit("runtime-ready", runtime));
925
- this.renderer.on("page-el-update", (el) => this.mask?.observe(el));
1070
+ this.highlightLayer = new StageHighlight({ core: this, container: this.mask.wrapper });
1071
+ this.renderer.on("runtime-ready", (runtime) => {
1072
+ this.emit("runtime-ready", runtime);
1073
+ });
1074
+ this.renderer.on("page-el-update", (el) => {
1075
+ this.mask?.observe(el);
1076
+ });
926
1077
  this.mask.on("beforeSelect", (event) => {
927
1078
  this.setElementFromPoint(event);
928
1079
  }).on("select", () => {
@@ -932,6 +1083,10 @@ class StageCore extends EventEmitter {
932
1083
  this.emit("changeGuides", data);
933
1084
  }).on("highlight", async (event) => {
934
1085
  await this.setElementFromPoint(event);
1086
+ if (this.highlightedDom === this.selectedDom) {
1087
+ this.highlightLayer.clearHighlight();
1088
+ return;
1089
+ }
935
1090
  this.highlightLayer.highlight(this.highlightedDom);
936
1091
  this.emit("highlight", this.highlightedDom);
937
1092
  }).on("clearHighlight", async () => {
@@ -959,7 +1114,7 @@ class StageCore extends EventEmitter {
959
1114
  let stopped = false;
960
1115
  const stop = () => stopped = true;
961
1116
  for (const el of els) {
962
- if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && await this.canSelect(el, stop)) {
1117
+ if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && await this.canSelect(el, event, stop)) {
963
1118
  if (stopped)
964
1119
  break;
965
1120
  if (event.type === "mousemove") {
@@ -976,47 +1131,58 @@ class StageCore extends EventEmitter {
976
1131
  if (el === this.selectedDom)
977
1132
  return;
978
1133
  const runtime = await this.renderer.getRuntime();
1134
+ await runtime?.select?.(el.id);
979
1135
  if (runtime?.beforeSelect) {
980
1136
  await runtime.beforeSelect(el);
981
1137
  }
982
1138
  this.mask.setLayout(el);
983
- this.dr?.select(el, event);
1139
+ this.dr.select(el, event);
1140
+ if (this.config.autoScrollIntoView || el.dataset.autoScrollIntoView) {
1141
+ this.mask.intersectionObserver?.observe(el);
1142
+ }
984
1143
  this.selectedDom = el;
985
1144
  if (this.renderer.contentWindow) {
986
1145
  removeSelectedClassName(this.renderer.contentWindow.document);
987
1146
  if (this.selectedDom) {
988
- addSelectedClassName(this.selectedDom);
1147
+ addSelectedClassName(this.selectedDom, this.renderer.contentWindow.document);
989
1148
  }
990
1149
  }
991
1150
  }
992
1151
  update(data) {
993
1152
  const { config } = data;
994
- this.renderer?.getRuntime().then((runtime) => {
1153
+ return this.renderer?.getRuntime().then((runtime) => {
995
1154
  runtime?.update?.(data);
996
1155
  setTimeout(() => {
997
1156
  const el = this.renderer.contentWindow?.document.getElementById(`${config.id}`);
998
- if (el) {
1157
+ if (el && el.id === this.selectedDom?.id) {
1158
+ this.selectedDom = el;
999
1159
  this.mask.setLayout(el);
1000
- this.dr?.select(el);
1160
+ this.dr.updateMoveable(el);
1001
1161
  }
1002
1162
  }, 0);
1003
1163
  });
1004
1164
  }
1005
1165
  async highlight(idOrEl) {
1006
- const el = await this.getTargetElement(idOrEl);
1166
+ let el;
1167
+ try {
1168
+ el = await this.getTargetElement(idOrEl);
1169
+ } catch (error) {
1170
+ this.highlightLayer.clearHighlight();
1171
+ return;
1172
+ }
1007
1173
  if (el === this.highlightedDom)
1008
1174
  return;
1009
1175
  this.highlightLayer.highlight(el);
1010
1176
  this.highlightedDom = el;
1011
1177
  }
1012
1178
  sortNode(data) {
1013
- this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
1179
+ return this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
1014
1180
  }
1015
1181
  add(data) {
1016
- this.renderer?.getRuntime().then((runtime) => runtime?.add?.(data));
1182
+ return this.renderer?.getRuntime().then((runtime) => runtime?.add?.(data));
1017
1183
  }
1018
1184
  remove(data) {
1019
- this.renderer?.getRuntime().then((runtime) => runtime?.remove?.(data));
1185
+ return this.renderer?.getRuntime().then((runtime) => runtime?.remove?.(data));
1020
1186
  }
1021
1187
  setZoom(zoom = DEFAULT_ZOOM) {
1022
1188
  this.zoom = zoom;
@@ -1042,16 +1208,13 @@ class StageCore extends EventEmitter {
1042
1208
  this.container = void 0;
1043
1209
  }
1044
1210
  async getTargetElement(idOrEl) {
1045
- let el;
1046
1211
  if (typeof idOrEl === "string" || typeof idOrEl === "number") {
1047
- const runtime = await this.renderer?.getRuntime();
1048
- el = await runtime?.select?.(`${idOrEl}`);
1212
+ const el = this.renderer.contentWindow?.document.getElementById(`${idOrEl}`);
1049
1213
  if (!el)
1050
1214
  throw new Error(`\u4E0D\u5B58\u5728ID\u4E3A${idOrEl}\u7684\u5143\u7D20`);
1051
- } else {
1052
- el = idOrEl;
1215
+ return el;
1053
1216
  }
1054
- return el;
1217
+ return idOrEl;
1055
1218
  }
1056
1219
  }
1057
1220