@plait/core 0.54.0 → 0.55.0

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.
Files changed (71) hide show
  1. package/board/board.component.d.ts +6 -3
  2. package/constants/index.d.ts +2 -0
  3. package/core/element/context.d.ts +6 -2
  4. package/core/element/plugin-element.d.ts +13 -4
  5. package/core/list-render.d.ts +16 -0
  6. package/esm2022/board/board.component.mjs +24 -21
  7. package/esm2022/constants/index.mjs +3 -1
  8. package/esm2022/core/element/context.mjs +1 -1
  9. package/esm2022/core/element/plugin-element.mjs +79 -12
  10. package/esm2022/core/list-render.mjs +209 -0
  11. package/esm2022/interfaces/board.mjs +3 -3
  12. package/esm2022/interfaces/element.mjs +28 -2
  13. package/esm2022/interfaces/node.mjs +18 -1
  14. package/esm2022/interfaces/path.mjs +56 -57
  15. package/esm2022/plugins/create-board.mjs +10 -10
  16. package/esm2022/plugins/with-hotkey.mjs +32 -3
  17. package/esm2022/plugins/with-moving.mjs +12 -12
  18. package/esm2022/plugins/with-related-fragment.mjs +5 -5
  19. package/esm2022/public-api.mjs +1 -3
  20. package/esm2022/transforms/group.mjs +23 -6
  21. package/esm2022/transforms/index.mjs +6 -3
  22. package/esm2022/transforms/z-index.mjs +20 -0
  23. package/esm2022/utils/angle.mjs +17 -3
  24. package/esm2022/utils/clipboard/clipboard.mjs +5 -5
  25. package/esm2022/utils/clipboard/common.mjs +5 -5
  26. package/esm2022/utils/clipboard/types.mjs +1 -1
  27. package/esm2022/utils/common.mjs +27 -1
  28. package/esm2022/utils/fragment.mjs +20 -1
  29. package/esm2022/utils/group.mjs +27 -1
  30. package/esm2022/utils/helper.mjs +37 -1
  31. package/esm2022/utils/index.mjs +4 -1
  32. package/esm2022/utils/math.mjs +37 -1
  33. package/esm2022/utils/position.mjs +3 -3
  34. package/esm2022/utils/snap/snap-moving.mjs +199 -0
  35. package/esm2022/utils/snap/snap.mjs +208 -0
  36. package/esm2022/utils/to-image.mjs +2 -2
  37. package/esm2022/utils/weak-maps.mjs +3 -1
  38. package/esm2022/utils/z-index.mjs +166 -0
  39. package/fesm2022/plait-core.mjs +1655 -1080
  40. package/fesm2022/plait-core.mjs.map +1 -1
  41. package/interfaces/board.d.ts +5 -5
  42. package/interfaces/element.d.ts +5 -0
  43. package/interfaces/node.d.ts +1 -0
  44. package/package.json +1 -1
  45. package/public-api.d.ts +0 -2
  46. package/styles/styles.scss +9 -0
  47. package/transforms/group.d.ts +4 -0
  48. package/transforms/index.d.ts +3 -2
  49. package/transforms/z-index.d.ts +13 -0
  50. package/utils/angle.d.ts +2 -0
  51. package/utils/clipboard/common.d.ts +1 -1
  52. package/utils/clipboard/types.d.ts +1 -1
  53. package/utils/common.d.ts +8 -0
  54. package/utils/fragment.d.ts +3 -1
  55. package/utils/group.d.ts +3 -1
  56. package/utils/helper.d.ts +4 -1
  57. package/utils/index.d.ts +3 -0
  58. package/utils/math.d.ts +1 -0
  59. package/utils/position.d.ts +1 -1
  60. package/utils/snap/snap-moving.d.ts +5 -0
  61. package/utils/snap/snap.d.ts +31 -0
  62. package/utils/weak-maps.d.ts +2 -0
  63. package/utils/z-index.d.ts +5 -0
  64. package/core/children/children.component.d.ts +0 -17
  65. package/core/children/effect.d.ts +0 -2
  66. package/core/element/element.component.d.ts +0 -30
  67. package/esm2022/core/children/children.component.mjs +0 -60
  68. package/esm2022/core/children/effect.mjs +0 -2
  69. package/esm2022/core/element/element.component.mjs +0 -105
  70. package/esm2022/utils/moving-snap.mjs +0 -372
  71. package/utils/moving-snap.d.ts +0 -41
@@ -1,17 +1,18 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Directive, Input, Injectable, Component, ChangeDetectionStrategy, EventEmitter, ElementRef, Output, HostBinding, ViewChild, ContentChildren } from '@angular/core';
2
+ import { IterableDiffers, inject, ViewContainerRef, Directive, Input, Injectable, EventEmitter, ElementRef, Component, ChangeDetectionStrategy, Output, HostBinding, ViewChild, ContentChildren } from '@angular/core';
3
3
  import rough from 'roughjs/bin/rough';
4
4
  import { timer, Subject, fromEvent } from 'rxjs';
5
5
  import { takeUntil, filter, tap } from 'rxjs/operators';
6
6
  import { isKeyHotkey, isHotkey } from 'is-hotkey';
7
- import { produce, createDraft, finishDraft, isDraft } from 'immer';
8
- import { NgFor } from '@angular/common';
7
+ import { createDraft, finishDraft, isDraft } from 'immer';
9
8
 
10
9
  // record richtext type status
11
10
  const IS_BOARD_CACHE = new WeakMap();
12
11
  const FLUSHING = new WeakMap();
13
12
  const NODE_TO_INDEX = new WeakMap();
14
13
  const NODE_TO_PARENT = new WeakMap();
14
+ const NODE_TO_G = new WeakMap();
15
+ const NODE_TO_CONTAINER_G = new WeakMap();
15
16
  const IS_TEXT_EDITABLE = new WeakMap();
16
17
  const BOARD_TO_ON_CHANGE = new WeakMap();
17
18
  const BOARD_TO_AFTER_CHANGE = new WeakMap();
@@ -77,11 +78,11 @@ const Selection = {
77
78
  }
78
79
  };
79
80
 
80
- const sortElements = (board, elements) => {
81
+ const sortElements = (board, elements, ascendingOrder = true) => {
81
82
  return [...elements].sort((a, b) => {
82
83
  const pathA = PlaitBoard.findPath(board, a);
83
84
  const pathB = PlaitBoard.findPath(board, b);
84
- return pathA[0] - pathB[0];
85
+ return ascendingOrder ? pathA[0] - pathB[0] : pathB[0] - pathA[0];
85
86
  });
86
87
  };
87
88
 
@@ -506,6 +507,8 @@ const SELECTION_RECTANGLE_CLASS_NAME = 'selection-rectangle';
506
507
 
507
508
  const HOST_CLASS_NAME = 'plait-board-container';
508
509
  const ACTIVE_MOVING_CLASS_NAME = 'active-with-moving';
510
+ const ROTATE_HANDLE_CLASS_NAME = 'rotate-handle';
511
+ const RESIZE_HANDLE_CLASS_NAME = 'resize-handle';
509
512
  const SCROLL_BAR_WIDTH = 20;
510
513
  const MAX_RADIUS = 16;
511
514
  const POINTER_BUTTON = {
@@ -621,7 +624,215 @@ function hasOnContextChanged(value) {
621
624
  return false;
622
625
  }
623
626
 
627
+ class ListRender {
628
+ constructor(board, viewContainerRef) {
629
+ this.board = board;
630
+ this.viewContainerRef = viewContainerRef;
631
+ this.children = [];
632
+ this.componentRefs = [];
633
+ this.contexts = [];
634
+ this.differ = null;
635
+ this.initialized = false;
636
+ }
637
+ initialize(children, childrenContext) {
638
+ this.initialized = true;
639
+ this.children = children;
640
+ children.forEach((descendant, index) => {
641
+ NODE_TO_INDEX.set(descendant, index);
642
+ NODE_TO_PARENT.set(descendant, childrenContext.parent);
643
+ const context = getContext(this.board, descendant, index, childrenContext.parent);
644
+ const componentType = getComponentType(this.board, context);
645
+ const componentRef = createPluginComponent(componentType, context, this.viewContainerRef, childrenContext);
646
+ this.componentRefs.push(componentRef);
647
+ this.contexts.push(context);
648
+ });
649
+ const newDiffers = this.viewContainerRef.injector.get(IterableDiffers);
650
+ this.differ = newDiffers.find(children).create(trackBy);
651
+ this.differ.diff(children);
652
+ }
653
+ update(children, childrenContext) {
654
+ if (!this.initialized) {
655
+ this.initialize(children, childrenContext);
656
+ return;
657
+ }
658
+ if (!this.differ) {
659
+ throw new Error('Exception: Can not find differ ');
660
+ }
661
+ const { board, parent } = childrenContext;
662
+ const diffResult = this.differ.diff(children);
663
+ if (diffResult) {
664
+ const newContexts = [];
665
+ const newComponentRefs = [];
666
+ let currentIndexForFirstElement = null;
667
+ diffResult.forEachItem((record) => {
668
+ NODE_TO_INDEX.set(record.item, record.currentIndex);
669
+ NODE_TO_PARENT.set(record.item, childrenContext.parent);
670
+ const previousContext = record.previousIndex === null ? undefined : this.contexts[record.previousIndex];
671
+ const context = getContext(board, record.item, record.currentIndex, parent, previousContext);
672
+ if (record.previousIndex === null) {
673
+ const componentType = getComponentType(board, context);
674
+ const componentRef = createPluginComponent(componentType, context, this.viewContainerRef, childrenContext);
675
+ newContexts.push(context);
676
+ newComponentRefs.push(componentRef);
677
+ }
678
+ else {
679
+ const componentRef = this.componentRefs[record.previousIndex];
680
+ componentRef.instance.context = context;
681
+ newComponentRefs.push(componentRef);
682
+ newContexts.push(context);
683
+ }
684
+ if (record.item === this.children[0]) {
685
+ currentIndexForFirstElement = record.currentIndex;
686
+ }
687
+ });
688
+ diffResult.forEachOperation(record => {
689
+ // removed
690
+ if (record.currentIndex === null) {
691
+ const componentRef = this.componentRefs[record.previousIndex];
692
+ componentRef?.destroy();
693
+ }
694
+ // moved
695
+ if (record.previousIndex !== null && record.currentIndex !== null) {
696
+ mountOnItemMove(record.item, record.currentIndex, childrenContext, currentIndexForFirstElement);
697
+ }
698
+ });
699
+ this.componentRefs = newComponentRefs;
700
+ this.contexts = newContexts;
701
+ this.children = children;
702
+ }
703
+ else {
704
+ const newContexts = [];
705
+ this.children.forEach((element, index) => {
706
+ NODE_TO_INDEX.set(element, index);
707
+ NODE_TO_PARENT.set(element, childrenContext.parent);
708
+ const previousContext = this.contexts[index];
709
+ const previousComponentRef = this.componentRefs[index];
710
+ const context = getContext(board, element, index, parent, previousContext);
711
+ previousComponentRef.instance.context = context;
712
+ newContexts.push(context);
713
+ });
714
+ this.contexts = newContexts;
715
+ }
716
+ }
717
+ destroy() {
718
+ this.children.forEach((element, index) => {
719
+ if (this.componentRefs[index]) {
720
+ this.componentRefs[index].destroy();
721
+ }
722
+ });
723
+ this.componentRefs = [];
724
+ this.children = [];
725
+ this.contexts = [];
726
+ this.initialized = false;
727
+ this.differ = null;
728
+ }
729
+ }
730
+ const trackBy = (index, element) => {
731
+ return element.id;
732
+ };
733
+ const createPluginComponent = (componentType, context, viewContainerRef, childrenContext) => {
734
+ const componentRef = viewContainerRef.createComponent(componentType);
735
+ const instance = componentRef.instance;
736
+ instance.context = context;
737
+ componentRef.changeDetectorRef.detectChanges();
738
+ const g = componentRef.instance.getContainerG();
739
+ mountElementG(context.index, g, childrenContext);
740
+ componentRef.instance.initializeListRender();
741
+ return componentRef;
742
+ };
743
+ const getComponentType = (board, context) => {
744
+ const result = board.drawElement(context);
745
+ return result;
746
+ };
747
+ const getContext = (board, element, index, parent, previousContext) => {
748
+ let isSelected = isSelectedElement(board, element);
749
+ const previousElement = previousContext && previousContext.element;
750
+ if (previousElement && previousElement !== element && isSelectedElement(board, previousElement)) {
751
+ isSelected = true;
752
+ removeSelectedElement(board, previousElement);
753
+ addSelectedElement(board, element);
754
+ }
755
+ const context = {
756
+ element: element,
757
+ parent: parent,
758
+ board: board,
759
+ selected: isSelected,
760
+ index
761
+ };
762
+ return context;
763
+ };
764
+ // the g depth of root element:[1]-[2]-[3]-[4]
765
+ // the g depth of root element and children element(the [2] element has children):
766
+ // [1]-
767
+ // [2]([2-1-1][2-1-2][2-1][2-2][2-3-1][2-3-2][2-3][2])-
768
+ // [3]-
769
+ // [4]
770
+ const mountElementG = (index, g, childrenContext,
771
+ // for moving scene: the current index for first element before moving
772
+ currentIndexForFirstElement = null) => {
773
+ const { parent, parentG } = childrenContext;
774
+ if (PlaitBoard.isBoard(parent)) {
775
+ if (index > 0) {
776
+ const previousElement = parent.children[index - 1];
777
+ const previousContainerG = PlaitElement.getContainerG(previousElement, { suppressThrow: false });
778
+ previousContainerG.insertAdjacentElement('afterend', g);
779
+ }
780
+ else {
781
+ if (currentIndexForFirstElement !== null) {
782
+ const firstElement = parent.children[currentIndexForFirstElement];
783
+ const firstContainerG = firstElement && PlaitElement.getContainerG(firstElement, { suppressThrow: true });
784
+ if (firstElement && firstContainerG) {
785
+ parentG.insertBefore(g, firstContainerG);
786
+ }
787
+ else {
788
+ throw new Error('fail to mount container on moving');
789
+ }
790
+ }
791
+ else {
792
+ parentG.append(g);
793
+ }
794
+ }
795
+ }
796
+ else {
797
+ if (index > 0) {
798
+ const previousElement = parent.children[index - 1];
799
+ const previousElementG = PlaitElement.getElementG(previousElement);
800
+ previousElementG.insertAdjacentElement('afterend', g);
801
+ }
802
+ else {
803
+ if (currentIndexForFirstElement) {
804
+ const nextElement = parent.children[currentIndexForFirstElement];
805
+ const nextPath = nextElement && PlaitBoard.findPath(childrenContext.board, nextElement);
806
+ const first = nextPath && PlaitNode.first(childrenContext.board, nextPath);
807
+ const firstContainerG = first && PlaitElement.getContainerG(first, { suppressThrow: false });
808
+ if (firstContainerG) {
809
+ parentG.insertBefore(g, firstContainerG);
810
+ }
811
+ else {
812
+ throw new Error('fail to mount container on moving');
813
+ }
814
+ }
815
+ else {
816
+ let parentElementG = PlaitElement.getElementG(parent);
817
+ parentG.insertBefore(g, parentElementG);
818
+ }
819
+ }
820
+ }
821
+ };
822
+ const mountOnItemMove = (element, index, childrenContext, currentIndexForFirstElement) => {
823
+ const containerG = PlaitElement.getContainerG(element, { suppressThrow: false });
824
+ mountElementG(index, containerG, childrenContext, currentIndexForFirstElement);
825
+ if (element.children && !PlaitElement.isRootElement(element)) {
826
+ element.children.forEach((child, index) => {
827
+ mountOnItemMove(child, index, { ...childrenContext, parent: element }, null);
828
+ });
829
+ }
830
+ };
831
+
624
832
  class PlaitPluginElementComponent {
833
+ get hasChildren() {
834
+ return !!this.element.children;
835
+ }
625
836
  set context(value) {
626
837
  if (hasBeforeContextChange(this)) {
627
838
  this.beforeContextChange(value);
@@ -632,20 +843,28 @@ class PlaitPluginElementComponent {
632
843
  ELEMENT_TO_COMPONENT.set(this.element, this);
633
844
  }
634
845
  if (this.initialized) {
846
+ const elementG = this.getElementG();
847
+ const containerG = this.getContainerG();
848
+ NODE_TO_G.set(this.element, elementG);
849
+ NODE_TO_CONTAINER_G.set(this.element, containerG);
850
+ this.updateListRender();
635
851
  this.cdr.markForCheck();
636
852
  if (hasOnContextChanged(this)) {
637
853
  this.onContextChanged(value, previousContext);
638
854
  }
639
855
  }
640
856
  else {
641
- if (PlaitElement.isRootElement(this.element) && this.element.children) {
642
- this.g = createG();
643
- this.rootG = createG();
644
- this.rootG.append(this.g);
857
+ if (PlaitElement.isRootElement(this.element) && this.hasChildren) {
858
+ this._g = createG();
859
+ this._containerG = createG();
860
+ this._containerG.append(this._g);
645
861
  }
646
862
  else {
647
- this.g = createG();
863
+ this._g = createG();
864
+ this._containerG = this._g;
648
865
  }
866
+ NODE_TO_G.set(this.element, this._g);
867
+ NODE_TO_CONTAINER_G.set(this.element, this._containerG);
649
868
  }
650
869
  }
651
870
  get context() {
@@ -660,25 +879,79 @@ class PlaitPluginElementComponent {
660
879
  get selected() {
661
880
  return this.context && this.context.selected;
662
881
  }
663
- get effect() {
664
- return this.context && this.context.effect;
882
+ getContainerG() {
883
+ return this._containerG;
884
+ }
885
+ getElementG() {
886
+ return this._g;
665
887
  }
666
888
  constructor(cdr) {
667
889
  this.cdr = cdr;
890
+ this.viewContainerRef = inject(ViewContainerRef);
668
891
  this.initialized = false;
669
892
  }
670
893
  ngOnInit() {
671
894
  if (this.element.type) {
672
- (this.rootG || this.g).setAttribute(`plait-${this.element.type}`, 'true');
895
+ this.getContainerG().setAttribute(`plait-${this.element.type}`, 'true');
896
+ }
897
+ if (this.hasChildren) {
898
+ if (PlaitElement.isRootElement(this.element)) {
899
+ this._rootContainerG = this._containerG;
900
+ }
901
+ else {
902
+ const path = PlaitBoard.findPath(this.board, this.element);
903
+ const rootNode = PlaitNode.get(this.board, path.slice(0, 1));
904
+ this._rootContainerG = PlaitElement.getContainerG(rootNode, { suppressThrow: false });
905
+ }
673
906
  }
907
+ this.getContainerG().setAttribute('plait-data-id', this.element.id);
674
908
  this.initialized = true;
675
909
  }
910
+ initializeListRender() {
911
+ if (this.hasChildren) {
912
+ this.listRender = new ListRender(this.board, this.viewContainerRef);
913
+ if (this.board.isExpanded(this.element)) {
914
+ this.listRender.initialize(this.element.children, this.initializeChildrenContext());
915
+ }
916
+ }
917
+ }
918
+ updateListRender() {
919
+ if (this.hasChildren) {
920
+ if (!this.listRender) {
921
+ throw new Error('incorrectly initialize list render');
922
+ }
923
+ if (this.board.isExpanded(this.element)) {
924
+ this.listRender.update(this.element.children, this.initializeChildrenContext());
925
+ }
926
+ else {
927
+ if (this.listRender.initialized) {
928
+ this.listRender.destroy();
929
+ }
930
+ }
931
+ }
932
+ }
933
+ initializeChildrenContext() {
934
+ if (!this._rootContainerG) {
935
+ throw new Error('can not resolve root container g');
936
+ }
937
+ return {
938
+ board: this.board,
939
+ parent: this.element,
940
+ parentG: this._rootContainerG
941
+ };
942
+ }
676
943
  ngOnDestroy() {
677
944
  if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
678
945
  ELEMENT_TO_COMPONENT.delete(this.element);
679
946
  }
947
+ if (NODE_TO_G.get(this.element) === this._g) {
948
+ NODE_TO_G.delete(this.element);
949
+ }
950
+ if (NODE_TO_CONTAINER_G.get(this.element) === this._containerG) {
951
+ NODE_TO_CONTAINER_G.delete(this.element);
952
+ }
680
953
  removeSelectedElement(this.board, this.element);
681
- (this.rootG || this.g).remove();
954
+ this.getContainerG().remove();
682
955
  }
683
956
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitPluginElementComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
684
957
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.4", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
@@ -968,6 +1241,42 @@ function toFixed(v) {
968
1241
  function approximately(a, b, precision = 0.000001) {
969
1242
  return Math.abs(a - b) <= precision;
970
1243
  }
1244
+ // https://medium.com/@steveruiz/find-the-points-where-a-line-segment-intercepts-an-angled-ellipse-in-javascript-typescript-e451524beece
1245
+ function getCrossingPointsBetweenEllipseAndSegment(startPoint, endPoint, cx, cy, rx, ry, segment_only = true) {
1246
+ // If the ellipse or line segment are empty, return no tValues.
1247
+ if (rx === 0 || ry === 0 || (startPoint[0] === endPoint[0] && startPoint[1] === endPoint[1])) {
1248
+ return [];
1249
+ }
1250
+ rx = rx < 0 ? rx : -rx;
1251
+ ry = ry < 0 ? ry : -ry;
1252
+ startPoint[0] -= cx;
1253
+ startPoint[1] -= cy;
1254
+ endPoint[0] -= cx;
1255
+ endPoint[1] -= cy;
1256
+ // Calculate the quadratic parameters.
1257
+ var A = ((endPoint[0] - startPoint[0]) * (endPoint[0] - startPoint[0])) / rx / rx +
1258
+ ((endPoint[1] - startPoint[1]) * (endPoint[1] - startPoint[1])) / ry / ry;
1259
+ var B = (2 * startPoint[0] * (endPoint[0] - startPoint[0])) / rx / rx + (2 * startPoint[1] * (endPoint[1] - startPoint[1])) / ry / ry;
1260
+ var C = (startPoint[0] * startPoint[0]) / rx / rx + (startPoint[1] * startPoint[1]) / ry / ry - 1;
1261
+ // Make a list of t values (normalized points on the line where intersections occur).
1262
+ var tValues = [];
1263
+ // Calculate the discriminant.
1264
+ var discriminant = B * B - 4 * A * C;
1265
+ if (discriminant === 0) {
1266
+ // One real solution.
1267
+ tValues.push(-B / 2 / A);
1268
+ }
1269
+ else if (discriminant > 0) {
1270
+ // Two real solutions.
1271
+ tValues.push((-B + Math.sqrt(discriminant)) / 2 / A);
1272
+ tValues.push((-B - Math.sqrt(discriminant)) / 2 / A);
1273
+ }
1274
+ return (tValues
1275
+ // Filter to only points that are on the segment.
1276
+ .filter(t => !segment_only || (t >= 0 && t <= 1))
1277
+ // Solve for points.
1278
+ .map(t => [startPoint[0] + (endPoint[0] - startPoint[0]) * t + cx, startPoint[1] + (endPoint[1] - startPoint[1]) * t + cy]));
1279
+ }
971
1280
 
972
1281
  function isInPlaitBoard(board, x, y) {
973
1282
  const plaitBoardElement = PlaitBoard.getBoardContainer(board);
@@ -1055,6 +1364,42 @@ function uniqueById(elements) {
1055
1364
  });
1056
1365
  return Array.from(uniqueMap.values());
1057
1366
  }
1367
+ const findLastIndex = (array, cb, fromIndex = array.length - 1) => {
1368
+ if (fromIndex < 0) {
1369
+ fromIndex = array.length + fromIndex;
1370
+ }
1371
+ fromIndex = Math.min(array.length - 1, Math.max(fromIndex, 0));
1372
+ let index = fromIndex + 1;
1373
+ while (--index > -1) {
1374
+ if (cb(array[index], index, array)) {
1375
+ return index;
1376
+ }
1377
+ }
1378
+ return -1;
1379
+ };
1380
+ const findIndex = (array, cb, fromIndex = 0) => {
1381
+ // fromIndex = 2
1382
+ if (fromIndex < 0) {
1383
+ fromIndex = array.length + fromIndex;
1384
+ }
1385
+ fromIndex = Math.min(array.length, Math.max(fromIndex, 0));
1386
+ let index = fromIndex - 1;
1387
+ while (++index < array.length) {
1388
+ if (cb(array[index], index, array)) {
1389
+ return index;
1390
+ }
1391
+ }
1392
+ return -1;
1393
+ };
1394
+ const isIndicesContinuous = (indexes) => {
1395
+ indexes.sort((a, b) => a - b);
1396
+ for (let i = 1; i < indexes.length; i++) {
1397
+ if (indexes[i] !== indexes[i - 1] + 1) {
1398
+ return false;
1399
+ }
1400
+ }
1401
+ return true;
1402
+ };
1058
1403
 
1059
1404
  /**
1060
1405
  * Check whether to merge an operation into the previous operation.
@@ -1694,157 +2039,456 @@ const setIsFromViewportChange = (board, state) => {
1694
2039
  };
1695
2040
  function scrollToRectangle(board, client) { }
1696
2041
 
1697
- const BOARD_TO_RAF = new WeakMap();
1698
- const getTimerId = (board, key) => {
1699
- const state = getRAFState(board);
1700
- return state[key] || null;
1701
- };
1702
- const getRAFState = (board) => {
1703
- return BOARD_TO_RAF.get(board) || {};
1704
- };
1705
- const throttleRAF = (board, key, fn) => {
1706
- const scheduleFunc = () => {
1707
- let timerId = requestAnimationFrame(() => {
1708
- const value = BOARD_TO_RAF.get(board) || {};
1709
- value[key] = null;
1710
- BOARD_TO_RAF.set(board, value);
1711
- PlaitBoard.isAlive(board) && fn();
1712
- });
1713
- const state = getRAFState(board);
1714
- state[key] = timerId;
1715
- BOARD_TO_RAF.set(board, state);
1716
- };
1717
- let timerId = getTimerId(board, key);
1718
- if (timerId !== null) {
1719
- cancelAnimationFrame(timerId);
1720
- }
1721
- scheduleFunc();
1722
- };
1723
- const debounce = (func, wait, options) => {
1724
- let timerSubscription = null;
1725
- return () => {
1726
- if (timerSubscription && !timerSubscription.closed) {
1727
- timerSubscription.unsubscribe();
1728
- timerSubscription = timer(wait).subscribe(() => {
1729
- func();
1730
- });
2042
+ const Path = {
2043
+ /**
2044
+ * Get a list of ancestor paths for a given path.
2045
+ *
2046
+ * The paths are sorted from shallowest to deepest ancestor. However, if the
2047
+ * `reverse: true` option is passed, they are reversed.
2048
+ */
2049
+ ancestors(path, options = {}) {
2050
+ const { reverse = false } = options;
2051
+ let paths = Path.levels(path, options);
2052
+ if (reverse) {
2053
+ paths = paths.slice(1);
1731
2054
  }
1732
2055
  else {
1733
- if (options?.leading) {
1734
- timer(0).subscribe(() => {
1735
- func();
1736
- });
1737
- }
1738
- timerSubscription = timer(wait).subscribe();
2056
+ paths = paths.slice(0, -1);
1739
2057
  }
1740
- };
1741
- };
1742
-
1743
- const IS_DRAGGING = new WeakMap();
1744
- const isDragging = (board) => {
1745
- return !!IS_DRAGGING.get(board);
1746
- };
1747
- const setDragging = (board, state) => {
1748
- IS_DRAGGING.set(board, state);
1749
- };
1750
-
1751
- const getMovingElements = (board) => {
1752
- return BOARD_TO_MOVING_ELEMENT.get(board) || [];
1753
- };
1754
- const isMovingElements = (board) => {
1755
- return (BOARD_TO_MOVING_ELEMENT.get(board) || []).length > 0;
1756
- };
1757
- const removeMovingElements = (board) => {
1758
- BOARD_TO_MOVING_ELEMENT.delete(board);
1759
- setDragging(board, false);
1760
- };
1761
- const cacheMovingElements = (board, elements) => {
1762
- BOARD_TO_MOVING_ELEMENT.set(board, elements);
1763
- setDragging(board, true);
1764
- };
1765
-
1766
- const IMAGE_CONTAINER = 'plait-image-container';
1767
- /**
1768
- * Is element node
1769
- * @param node
1770
- * @returns
1771
- */
1772
- function isElementNode(node) {
1773
- return node.nodeType === Node.ELEMENT_NODE;
1774
- }
1775
- /**
1776
- * load image resources
1777
- * @param url image url
1778
- * @returns image element
1779
- */
1780
- function loadImage(src) {
1781
- return new Promise((resolve, reject) => {
1782
- const img = new Image();
1783
- img.crossOrigin = 'Anonymous';
1784
- img.onload = () => resolve(img);
1785
- img.onerror = () => reject(new Error('Failed to load image'));
1786
- img.src = src;
1787
- });
1788
- }
1789
- /**
1790
- * create and return canvas and context
1791
- * @param width canvas width
1792
- * @param height canvas height
1793
- * @param fillStyle fill style
1794
- * @returns canvas and context
1795
- */
1796
- function createCanvas(width, height, fillStyle = 'transparent') {
1797
- const canvas = document.createElement('canvas');
1798
- const ctx = canvas.getContext('2d');
1799
- canvas.width = width;
1800
- canvas.height = height;
1801
- canvas.style.width = `${width}px`;
1802
- canvas.style.height = `${height}px`;
1803
- ctx.strokeStyle = '#ffffff';
1804
- ctx.fillStyle = fillStyle;
1805
- ctx.fillRect(0, 0, width, height);
1806
- return {
1807
- canvas,
1808
- ctx
1809
- };
1810
- }
1811
- /**
1812
- * convert image to base64
1813
- * @param url image url
1814
- * @returns image base64
1815
- */
1816
- function convertImageToBase64(url) {
1817
- return loadImage(url).then(img => {
1818
- const { canvas, ctx } = createCanvas(img.width, img.height);
1819
- ctx?.drawImage(img, 0, 0);
1820
- return canvas.toDataURL('image/png');
1821
- });
1822
- }
1823
- /**
1824
- * clone node style
1825
- * @param nativeNode source node
1826
- * @param clonedNode clone node
1827
- */
1828
- function cloneCSSStyle(nativeNode, clonedNode) {
1829
- const targetStyle = clonedNode?.style;
1830
- if (!targetStyle) {
1831
- return;
1832
- }
1833
- const sourceStyle = window.getComputedStyle(nativeNode);
1834
- if (sourceStyle.cssText) {
1835
- targetStyle.cssText = sourceStyle.cssText;
1836
- targetStyle.transformOrigin = sourceStyle.transformOrigin;
1837
- }
1838
- else {
1839
- Array.from(sourceStyle).forEach(name => {
1840
- let value = sourceStyle.getPropertyValue(name);
1841
- targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
1842
- });
1843
- }
1844
- }
1845
- /**
1846
- * batch clone target styles
1847
- * @param sourceNode
2058
+ return paths;
2059
+ },
2060
+ /**
2061
+ * Get a list of paths at every level down to a path. Note: this is the same
2062
+ * as `Path.ancestors`, but including the path itself.
2063
+ *
2064
+ * The paths are sorted from shallowest to deepest. However, if the `reverse:
2065
+ * true` option is passed, they are reversed.
2066
+ */
2067
+ levels(path, options = {}) {
2068
+ const { reverse = false } = options;
2069
+ const list = [];
2070
+ for (let i = 0; i <= path.length; i++) {
2071
+ list.push(path.slice(0, i));
2072
+ }
2073
+ if (reverse) {
2074
+ list.reverse();
2075
+ }
2076
+ return list;
2077
+ },
2078
+ parent(path) {
2079
+ if (path.length === 0) {
2080
+ throw new Error(`Cannot get the parent path of the root path [${path}].`);
2081
+ }
2082
+ return path.slice(0, -1);
2083
+ },
2084
+ next(path) {
2085
+ if (path.length === 0) {
2086
+ throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
2087
+ }
2088
+ const last = path[path.length - 1];
2089
+ return path.slice(0, -1).concat(last + 1);
2090
+ },
2091
+ hasPrevious(path) {
2092
+ return path[path.length - 1] > 0;
2093
+ },
2094
+ previous(path) {
2095
+ if (path.length === 0) {
2096
+ throw new Error(`Cannot get the previous path of a root path [${path}], because it has no previous index.`);
2097
+ }
2098
+ const last = path[path.length - 1];
2099
+ if (last <= 0) {
2100
+ throw new Error(`Cannot get the previous path of a first child path [${path}] because it would result in a negative index.`);
2101
+ }
2102
+ return path.slice(0, -1).concat(last - 1);
2103
+ },
2104
+ /**
2105
+ * Check if a path is an ancestor of another.
2106
+ */
2107
+ isAncestor(path, another) {
2108
+ return path.length < another.length && Path.compare(path, another) === 0;
2109
+ },
2110
+ /**
2111
+ * Compare a path to another, returning an integer indicating whether the path
2112
+ * was before, at, or after the other.
2113
+ *
2114
+ * Note: Two paths of unequal length can still receive a `0` result if one is
2115
+ * directly above or below the other. If you want exact matching, use
2116
+ * [[Path.equals]] instead.
2117
+ */
2118
+ compare(path, another) {
2119
+ const min = Math.min(path.length, another.length);
2120
+ for (let i = 0; i < min; i++) {
2121
+ if (path[i] < another[i])
2122
+ return -1;
2123
+ if (path[i] > another[i])
2124
+ return 1;
2125
+ }
2126
+ return 0;
2127
+ },
2128
+ /**
2129
+ * Check if a path is exactly equal to another.
2130
+ */
2131
+ equals(path, another) {
2132
+ return path.length === another.length && path.every((n, i) => n === another[i]);
2133
+ },
2134
+ /**
2135
+ * Check if a path ends before one of the indexes in another.
2136
+ */
2137
+ endsBefore(path, another) {
2138
+ const i = path.length - 1;
2139
+ const as = path.slice(0, i);
2140
+ const bs = another.slice(0, i);
2141
+ const av = path[i];
2142
+ const bv = another[i];
2143
+ return Path.equals(as, bs) && av < bv;
2144
+ },
2145
+ /**
2146
+ * Check if a path is a sibling of another.
2147
+ */
2148
+ isSibling(path, another) {
2149
+ if (path.length !== another.length) {
2150
+ return false;
2151
+ }
2152
+ const as = path.slice(0, -1);
2153
+ const bs = another.slice(0, -1);
2154
+ const al = path[path.length - 1];
2155
+ const bl = another[another.length - 1];
2156
+ return al !== bl && Path.equals(as, bs);
2157
+ },
2158
+ transform(path, operation) {
2159
+ if (!path)
2160
+ return null;
2161
+ // PERF: use destructing instead of immer
2162
+ const p = [...path];
2163
+ // PERF: Exit early if the operation is guaranteed not to have an effect.
2164
+ if (path.length === 0) {
2165
+ return p;
2166
+ }
2167
+ switch (operation.type) {
2168
+ case 'insert_node': {
2169
+ const { path: op } = operation;
2170
+ if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
2171
+ p[op.length - 1] += 1;
2172
+ }
2173
+ break;
2174
+ }
2175
+ case 'remove_node': {
2176
+ const { path: op } = operation;
2177
+ if (Path.equals(op, p) || Path.isAncestor(op, p)) {
2178
+ return null;
2179
+ }
2180
+ else if (Path.endsBefore(op, p)) {
2181
+ p[op.length - 1] -= 1;
2182
+ }
2183
+ break;
2184
+ }
2185
+ case 'move_node': {
2186
+ const { path: op, newPath: onp } = operation;
2187
+ // If the old and new path are the same, it's a no-op.
2188
+ if (Path.equals(op, onp)) {
2189
+ return p;
2190
+ }
2191
+ if (Path.isAncestor(op, p) || Path.equals(op, p)) {
2192
+ const copy = onp.slice();
2193
+ if (Path.endsBefore(op, onp) && op.length < onp.length) {
2194
+ copy[op.length - 1] -= 1;
2195
+ }
2196
+ return copy.concat(p.slice(op.length));
2197
+ }
2198
+ else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
2199
+ if (Path.endsBefore(op, p)) {
2200
+ p[op.length - 1] -= 1;
2201
+ }
2202
+ else {
2203
+ p[op.length - 1] += 1;
2204
+ }
2205
+ }
2206
+ else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
2207
+ if (Path.endsBefore(op, p)) {
2208
+ p[op.length - 1] -= 1;
2209
+ }
2210
+ p[onp.length - 1] += 1;
2211
+ }
2212
+ else if (Path.endsBefore(op, p)) {
2213
+ if (Path.equals(onp, p)) {
2214
+ p[onp.length - 1] += 1;
2215
+ }
2216
+ p[op.length - 1] -= 1;
2217
+ }
2218
+ break;
2219
+ }
2220
+ }
2221
+ return p;
2222
+ }
2223
+ };
2224
+
2225
+ const PlaitNode = {
2226
+ parent: (board, path) => {
2227
+ const parentPath = Path.parent(path);
2228
+ const p = PlaitNode.get(board, parentPath);
2229
+ return p;
2230
+ },
2231
+ /**
2232
+ * Return a generator of all the ancestor nodes above a specific path.
2233
+ *
2234
+ * By default the order is top-down, from highest to lowest ancestor in
2235
+ * the tree, but you can pass the `reverse: true` option to go bottom-up.
2236
+ */
2237
+ *parents(root, path, options = {}) {
2238
+ for (const p of Path.ancestors(path, options)) {
2239
+ const n = PlaitNode.get(root, p);
2240
+ yield n;
2241
+ }
2242
+ },
2243
+ get(root, path) {
2244
+ let node = root;
2245
+ for (let i = 0; i < path.length; i++) {
2246
+ const p = path[i];
2247
+ if (!node || !node.children || !node.children[p]) {
2248
+ throw new Error(`Cannot find a descendant at path [${path}]`);
2249
+ }
2250
+ node = node.children[p];
2251
+ }
2252
+ return node;
2253
+ },
2254
+ last(board, path) {
2255
+ let n = PlaitNode.get(board, path);
2256
+ while (n && n.children && n.children.length > 0) {
2257
+ const i = n.children.length - 1;
2258
+ n = n.children[i];
2259
+ }
2260
+ return n;
2261
+ },
2262
+ first(board, path) {
2263
+ const p = path.slice();
2264
+ let n = PlaitNode.get(board, p);
2265
+ if (!n.children) {
2266
+ return n;
2267
+ }
2268
+ while (n) {
2269
+ if (n.children.length === 0) {
2270
+ break;
2271
+ }
2272
+ else {
2273
+ n = n.children[0];
2274
+ p.push(0);
2275
+ }
2276
+ }
2277
+ return n;
2278
+ }
2279
+ };
2280
+
2281
+ function insertNode(board, node, path) {
2282
+ const operation = { type: 'insert_node', node, path };
2283
+ board.apply(operation);
2284
+ }
2285
+ function setNode(board, props, path) {
2286
+ const properties = {};
2287
+ const newProperties = {};
2288
+ const node = PlaitNode.get(board, path);
2289
+ for (const k in props) {
2290
+ if (node[k] !== props[k]) {
2291
+ if (node.hasOwnProperty(k)) {
2292
+ properties[k] = node[k];
2293
+ }
2294
+ if (props[k] != null)
2295
+ newProperties[k] = props[k];
2296
+ }
2297
+ }
2298
+ const operation = { type: 'set_node', properties, newProperties, path };
2299
+ board.apply(operation);
2300
+ }
2301
+ function removeNode(board, path) {
2302
+ const node = PlaitNode.get(board, path);
2303
+ const operation = { type: 'remove_node', path, node };
2304
+ board.apply(operation);
2305
+ }
2306
+ function moveNode(board, path, newPath) {
2307
+ const operation = { type: 'move_node', path, newPath };
2308
+ board.apply(operation);
2309
+ }
2310
+ const NodeTransforms = {
2311
+ insertNode,
2312
+ setNode,
2313
+ removeNode,
2314
+ moveNode
2315
+ };
2316
+
2317
+ const BOARD_TO_RAF = new WeakMap();
2318
+ const getTimerId = (board, key) => {
2319
+ const state = getRAFState(board);
2320
+ return state[key] || null;
2321
+ };
2322
+ const getRAFState = (board) => {
2323
+ return BOARD_TO_RAF.get(board) || {};
2324
+ };
2325
+ const throttleRAF = (board, key, fn) => {
2326
+ const scheduleFunc = () => {
2327
+ let timerId = requestAnimationFrame(() => {
2328
+ const value = BOARD_TO_RAF.get(board) || {};
2329
+ value[key] = null;
2330
+ BOARD_TO_RAF.set(board, value);
2331
+ PlaitBoard.isAlive(board) && fn();
2332
+ });
2333
+ const state = getRAFState(board);
2334
+ state[key] = timerId;
2335
+ BOARD_TO_RAF.set(board, state);
2336
+ };
2337
+ let timerId = getTimerId(board, key);
2338
+ if (timerId !== null) {
2339
+ cancelAnimationFrame(timerId);
2340
+ }
2341
+ scheduleFunc();
2342
+ };
2343
+ const debounce = (func, wait, options) => {
2344
+ let timerSubscription = null;
2345
+ return () => {
2346
+ if (timerSubscription && !timerSubscription.closed) {
2347
+ timerSubscription.unsubscribe();
2348
+ timerSubscription = timer(wait).subscribe(() => {
2349
+ func();
2350
+ });
2351
+ }
2352
+ else {
2353
+ if (options?.leading) {
2354
+ timer(0).subscribe(() => {
2355
+ func();
2356
+ });
2357
+ }
2358
+ timerSubscription = timer(wait).subscribe();
2359
+ }
2360
+ };
2361
+ };
2362
+ const getElementsIndices = (board, elements) => {
2363
+ sortElements(board, elements);
2364
+ return elements.map(item => {
2365
+ return board.children.map(item => item.id).indexOf(item.id);
2366
+ });
2367
+ };
2368
+ const getHighestIndexOfElement = (board, elements) => {
2369
+ const indices = getElementsIndices(board, elements);
2370
+ return indices[indices.length - 1];
2371
+ };
2372
+ const moveElementsToNewPath = (board, moveOptions) => {
2373
+ moveOptions
2374
+ .map(item => {
2375
+ const path = PlaitBoard.findPath(board, item.element);
2376
+ const ref = board.pathRef(path);
2377
+ return () => {
2378
+ ref.current && NodeTransforms.moveNode(board, ref.current, item.newPath);
2379
+ ref.unref();
2380
+ };
2381
+ })
2382
+ .forEach(action => {
2383
+ action();
2384
+ });
2385
+ };
2386
+
2387
+ const IS_DRAGGING = new WeakMap();
2388
+ const isDragging = (board) => {
2389
+ return !!IS_DRAGGING.get(board);
2390
+ };
2391
+ const setDragging = (board, state) => {
2392
+ IS_DRAGGING.set(board, state);
2393
+ };
2394
+
2395
+ const getMovingElements = (board) => {
2396
+ return BOARD_TO_MOVING_ELEMENT.get(board) || [];
2397
+ };
2398
+ const isMovingElements = (board) => {
2399
+ return (BOARD_TO_MOVING_ELEMENT.get(board) || []).length > 0;
2400
+ };
2401
+ const removeMovingElements = (board) => {
2402
+ BOARD_TO_MOVING_ELEMENT.delete(board);
2403
+ setDragging(board, false);
2404
+ };
2405
+ const cacheMovingElements = (board, elements) => {
2406
+ BOARD_TO_MOVING_ELEMENT.set(board, elements);
2407
+ setDragging(board, true);
2408
+ };
2409
+
2410
+ const IMAGE_CONTAINER = 'plait-image-container';
2411
+ /**
2412
+ * Is element node
2413
+ * @param node
2414
+ * @returns
2415
+ */
2416
+ function isElementNode(node) {
2417
+ return node.nodeType === Node.ELEMENT_NODE;
2418
+ }
2419
+ /**
2420
+ * load image resources
2421
+ * @param url image url
2422
+ * @returns image element
2423
+ */
2424
+ function loadImage(src) {
2425
+ return new Promise((resolve, reject) => {
2426
+ const img = new Image();
2427
+ img.crossOrigin = 'Anonymous';
2428
+ img.onload = () => resolve(img);
2429
+ img.onerror = () => reject(new Error('Failed to load image'));
2430
+ img.src = src;
2431
+ });
2432
+ }
2433
+ /**
2434
+ * create and return canvas and context
2435
+ * @param width canvas width
2436
+ * @param height canvas height
2437
+ * @param fillStyle fill style
2438
+ * @returns canvas and context
2439
+ */
2440
+ function createCanvas(width, height, fillStyle = 'transparent') {
2441
+ const canvas = document.createElement('canvas');
2442
+ const ctx = canvas.getContext('2d');
2443
+ canvas.width = width;
2444
+ canvas.height = height;
2445
+ canvas.style.width = `${width}px`;
2446
+ canvas.style.height = `${height}px`;
2447
+ ctx.strokeStyle = '#ffffff';
2448
+ ctx.fillStyle = fillStyle;
2449
+ ctx.fillRect(0, 0, width, height);
2450
+ return {
2451
+ canvas,
2452
+ ctx
2453
+ };
2454
+ }
2455
+ /**
2456
+ * convert image to base64
2457
+ * @param url image url
2458
+ * @returns image base64
2459
+ */
2460
+ function convertImageToBase64(url) {
2461
+ return loadImage(url).then(img => {
2462
+ const { canvas, ctx } = createCanvas(img.width, img.height);
2463
+ ctx?.drawImage(img, 0, 0);
2464
+ return canvas.toDataURL('image/png');
2465
+ });
2466
+ }
2467
+ /**
2468
+ * clone node style
2469
+ * @param nativeNode source node
2470
+ * @param clonedNode clone node
2471
+ */
2472
+ function cloneCSSStyle(nativeNode, clonedNode) {
2473
+ const targetStyle = clonedNode?.style;
2474
+ if (!targetStyle) {
2475
+ return;
2476
+ }
2477
+ const sourceStyle = window.getComputedStyle(nativeNode);
2478
+ if (sourceStyle.cssText) {
2479
+ targetStyle.cssText = sourceStyle.cssText;
2480
+ targetStyle.transformOrigin = sourceStyle.transformOrigin;
2481
+ }
2482
+ else {
2483
+ Array.from(sourceStyle).forEach(name => {
2484
+ let value = sourceStyle.getPropertyValue(name);
2485
+ targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
2486
+ });
2487
+ }
2488
+ }
2489
+ /**
2490
+ * batch clone target styles
2491
+ * @param sourceNode
1848
2492
  * @param cloneNode
1849
2493
  * @param inlineStyleClassNames
1850
2494
  */
@@ -1899,7 +2543,7 @@ async function cloneSvg(board, elements, rectangle, options) {
1899
2543
  const { width, height, x, y } = rectangle;
1900
2544
  const { padding = 4, inlineStyleClassNames } = options;
1901
2545
  const sourceSvg = PlaitBoard.getHost(board);
1902
- const selectedGElements = elements.map(value => PlaitElement.getComponent(value).g);
2546
+ const selectedGElements = elements.map(value => PlaitElement.getElementG(value));
1903
2547
  const cloneSvgElement = sourceSvg.cloneNode();
1904
2548
  const newHostElement = PlaitBoard.getElementHost(board).cloneNode();
1905
2549
  cloneSvgElement.style.width = `${width}px`;
@@ -2013,19 +2657,19 @@ const getProbablySupportsClipboardWriteText = () => {
2013
2657
  const getProbablySupportsClipboardRead = () => {
2014
2658
  return 'clipboard' in navigator && 'read' in navigator.clipboard;
2015
2659
  };
2016
- const createClipboardContext = (type, data, text) => {
2660
+ const createClipboardContext = (type, elements, text) => {
2017
2661
  return {
2018
2662
  type,
2019
- data,
2663
+ elements,
2020
2664
  text
2021
2665
  };
2022
2666
  };
2023
2667
  const addClipboardContext = (clipboardContext, addition) => {
2024
- const { type, data, text } = clipboardContext;
2668
+ const { type, elements, text } = clipboardContext;
2025
2669
  if (type === addition.type) {
2026
2670
  return {
2027
2671
  type,
2028
- data: data.concat(addition.data),
2672
+ elements: elements.concat(addition.elements),
2029
2673
  text: text + ' ' + addition.text
2030
2674
  };
2031
2675
  }
@@ -2138,309 +2782,87 @@ const getClipboardData = async (dataTransfer) => {
2138
2782
  let clipboardData = {};
2139
2783
  if (dataTransfer) {
2140
2784
  if (dataTransfer.files.length) {
2141
- return { files: Array.from(dataTransfer.files) };
2142
- }
2143
- clipboardData = getDataTransferClipboard(dataTransfer);
2144
- if (Object.keys(clipboardData).length === 0) {
2145
- clipboardData = getDataTransferClipboardText(dataTransfer);
2146
- }
2147
- return clipboardData;
2148
- }
2149
- if (getProbablySupportsClipboardRead()) {
2150
- return await getNavigatorClipboard();
2151
- }
2152
- return clipboardData;
2153
- };
2154
- const setClipboardData = async (dataTransfer, clipboardContext) => {
2155
- if (!clipboardContext) {
2156
- return;
2157
- }
2158
- const { type, data, text } = clipboardContext;
2159
- if (getProbablySupportsClipboardWrite()) {
2160
- return await setNavigatorClipboard(type, data, text);
2161
- }
2162
- if (dataTransfer) {
2163
- setDataTransferClipboard(dataTransfer, type, data);
2164
- setDataTransferClipboardText(dataTransfer, text);
2165
- return;
2166
- }
2167
- // Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
2168
- // Such as contextmenu copy in Firefox.
2169
- if (getProbablySupportsClipboardWriteText()) {
2170
- return await navigator.clipboard.writeText(buildPlaitHtml(type, data));
2171
- }
2172
- };
2173
-
2174
- const BOARD_TO_TOUCH_REF = new WeakMap();
2175
- const isPreventTouchMove = (board) => {
2176
- return !!BOARD_TO_TOUCH_REF.get(board);
2177
- };
2178
- const preventTouchMove = (board, event, state) => {
2179
- const hostElement = PlaitBoard.getElementHost(board);
2180
- const activeHostElement = PlaitBoard.getElementActiveHost(board);
2181
- if (state) {
2182
- if ((event.target instanceof HTMLElement || event.target instanceof SVGElement) &&
2183
- (hostElement.contains(event.target) || activeHostElement.contains(event.target))) {
2184
- BOARD_TO_TOUCH_REF.set(board, { state, target: event.target instanceof SVGElement ? event.target : undefined });
2185
- }
2186
- else {
2187
- BOARD_TO_TOUCH_REF.set(board, { state, target: undefined });
2188
- }
2189
- }
2190
- else {
2191
- const ref = BOARD_TO_TOUCH_REF.get(board);
2192
- if (ref) {
2193
- BOARD_TO_TOUCH_REF.delete(board);
2194
- ref.host?.remove();
2195
- }
2196
- }
2197
- };
2198
- /**
2199
- * some intersection maybe cause target is removed from current browser window,
2200
- * after it was removed touch move event will not be fired
2201
- * so scroll behavior will can not be prevented in mobile browser device
2202
- * this function will prevent target element being remove.
2203
- */
2204
- const handleTouchTarget = (board) => {
2205
- const touchRef = BOARD_TO_TOUCH_REF.get(board);
2206
- if (touchRef &&
2207
- touchRef.target &&
2208
- !PlaitBoard.getElementHost(board).contains(touchRef.target) &&
2209
- !PlaitBoard.getElementActiveHost(board).contains(touchRef.target)) {
2210
- touchRef.target.style.opacity = '0';
2211
- const host = createG();
2212
- host.appendChild(touchRef.target);
2213
- touchRef.host = host;
2214
- host.classList.add('touch-target');
2215
- PlaitBoard.getElementActiveHost(board).append(host);
2216
- }
2217
- };
2218
-
2219
- const Viewport = {
2220
- isViewport: (value) => {
2221
- return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
2222
- }
2223
- };
2224
-
2225
- const Path = {
2226
- /**
2227
- * Get a list of ancestor paths for a given path.
2228
- *
2229
- * The paths are sorted from shallowest to deepest ancestor. However, if the
2230
- * `reverse: true` option is passed, they are reversed.
2231
- */
2232
- ancestors(path, options = {}) {
2233
- const { reverse = false } = options;
2234
- let paths = Path.levels(path, options);
2235
- if (reverse) {
2236
- paths = paths.slice(1);
2237
- }
2238
- else {
2239
- paths = paths.slice(0, -1);
2240
- }
2241
- return paths;
2242
- },
2243
- /**
2244
- * Get a list of paths at every level down to a path. Note: this is the same
2245
- * as `Path.ancestors`, but including the path itself.
2246
- *
2247
- * The paths are sorted from shallowest to deepest. However, if the `reverse:
2248
- * true` option is passed, they are reversed.
2249
- */
2250
- levels(path, options = {}) {
2251
- const { reverse = false } = options;
2252
- const list = [];
2253
- for (let i = 0; i <= path.length; i++) {
2254
- list.push(path.slice(0, i));
2255
- }
2256
- if (reverse) {
2257
- list.reverse();
2258
- }
2259
- return list;
2260
- },
2261
- parent(path) {
2262
- if (path.length === 0) {
2263
- throw new Error(`Cannot get the parent path of the root path [${path}].`);
2264
- }
2265
- return path.slice(0, -1);
2266
- },
2267
- next(path) {
2268
- if (path.length === 0) {
2269
- throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
2270
- }
2271
- const last = path[path.length - 1];
2272
- return path.slice(0, -1).concat(last + 1);
2273
- },
2274
- hasPrevious(path) {
2275
- return path[path.length - 1] > 0;
2276
- },
2277
- previous(path) {
2278
- if (path.length === 0) {
2279
- throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
2280
- }
2281
- const last = path[path.length - 1];
2282
- return path.slice(0, -1).concat(last - 1);
2283
- },
2284
- /**
2285
- * Check if a path is an ancestor of another.
2286
- */
2287
- isAncestor(path, another) {
2288
- return path.length < another.length && Path.compare(path, another) === 0;
2289
- },
2290
- /**
2291
- * Compare a path to another, returning an integer indicating whether the path
2292
- * was before, at, or after the other.
2293
- *
2294
- * Note: Two paths of unequal length can still receive a `0` result if one is
2295
- * directly above or below the other. If you want exact matching, use
2296
- * [[Path.equals]] instead.
2297
- */
2298
- compare(path, another) {
2299
- const min = Math.min(path.length, another.length);
2300
- for (let i = 0; i < min; i++) {
2301
- if (path[i] < another[i])
2302
- return -1;
2303
- if (path[i] > another[i])
2304
- return 1;
2305
- }
2306
- return 0;
2307
- },
2308
- /**
2309
- * Check if a path is exactly equal to another.
2310
- */
2311
- equals(path, another) {
2312
- return path.length === another.length && path.every((n, i) => n === another[i]);
2313
- },
2314
- /**
2315
- * Check if a path ends before one of the indexes in another.
2316
- */
2317
- endsBefore(path, another) {
2318
- const i = path.length - 1;
2319
- const as = path.slice(0, i);
2320
- const bs = another.slice(0, i);
2321
- const av = path[i];
2322
- const bv = another[i];
2323
- return Path.equals(as, bs) && av < bv;
2324
- },
2325
- /**
2326
- * Check if a path is a sibling of another.
2327
- */
2328
- isSibling(path, another) {
2329
- if (path.length !== another.length) {
2330
- return false;
2331
- }
2332
- const as = path.slice(0, -1);
2333
- const bs = another.slice(0, -1);
2334
- const al = path[path.length - 1];
2335
- const bl = another[another.length - 1];
2336
- return al !== bl && Path.equals(as, bs);
2337
- },
2338
- transform(path, operation) {
2339
- return produce(path, p => {
2340
- // PERF: Exit early if the operation is guaranteed not to have an effect.
2341
- if (!path || path?.length === 0) {
2342
- return;
2343
- }
2344
- if (p === null) {
2345
- return null;
2346
- }
2347
- switch (operation.type) {
2348
- case 'insert_node': {
2349
- const { path: op } = operation;
2350
- if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
2351
- p[op.length - 1] += 1;
2352
- }
2353
- break;
2354
- }
2355
- case 'remove_node': {
2356
- const { path: op } = operation;
2357
- if (Path.equals(op, p) || Path.isAncestor(op, p)) {
2358
- return null;
2359
- }
2360
- else if (Path.endsBefore(op, p)) {
2361
- p[op.length - 1] -= 1;
2362
- }
2363
- break;
2364
- }
2365
- case 'move_node': {
2366
- const { path: op, newPath: onp } = operation;
2367
- // If the old and new path are the same, it's a no-op.
2368
- if (Path.equals(op, onp)) {
2369
- return;
2370
- }
2371
- if (Path.isAncestor(op, p) || Path.equals(op, p)) {
2372
- const copy = onp.slice();
2373
- // op.length <= onp.length is different for slate
2374
- // resolve drag from [0, 0] to [0, 3] issue
2375
- if (Path.endsBefore(op, onp) && op.length <= onp.length) {
2376
- copy[op.length - 1] -= 1;
2377
- }
2378
- return copy.concat(p.slice(op.length));
2379
- }
2380
- else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
2381
- if (Path.endsBefore(op, p)) {
2382
- p[op.length - 1] -= 1;
2383
- }
2384
- else {
2385
- p[op.length - 1] += 1;
2386
- }
2387
- }
2388
- else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
2389
- if (Path.endsBefore(op, p)) {
2390
- p[op.length - 1] -= 1;
2391
- }
2392
- p[onp.length - 1] += 1;
2393
- }
2394
- else if (Path.endsBefore(op, p)) {
2395
- if (Path.equals(onp, p)) {
2396
- p[onp.length - 1] += 1;
2397
- }
2398
- p[op.length - 1] -= 1;
2399
- }
2400
- break;
2401
- }
2402
- }
2403
- return p;
2404
- });
2785
+ return { files: Array.from(dataTransfer.files) };
2786
+ }
2787
+ clipboardData = getDataTransferClipboard(dataTransfer);
2788
+ if (Object.keys(clipboardData).length === 0) {
2789
+ clipboardData = getDataTransferClipboardText(dataTransfer);
2790
+ }
2791
+ return clipboardData;
2792
+ }
2793
+ if (getProbablySupportsClipboardRead()) {
2794
+ return await getNavigatorClipboard();
2795
+ }
2796
+ return clipboardData;
2797
+ };
2798
+ const setClipboardData = async (dataTransfer, clipboardContext) => {
2799
+ if (!clipboardContext) {
2800
+ return;
2801
+ }
2802
+ const { type, elements, text } = clipboardContext;
2803
+ if (getProbablySupportsClipboardWrite()) {
2804
+ return await setNavigatorClipboard(type, elements, text);
2805
+ }
2806
+ if (dataTransfer) {
2807
+ setDataTransferClipboard(dataTransfer, type, elements);
2808
+ setDataTransferClipboardText(dataTransfer, text);
2809
+ return;
2810
+ }
2811
+ // Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
2812
+ // Such as contextmenu copy in Firefox.
2813
+ if (getProbablySupportsClipboardWriteText()) {
2814
+ return await navigator.clipboard.writeText(buildPlaitHtml(type, elements));
2405
2815
  }
2406
2816
  };
2407
2817
 
2408
- const PlaitNode = {
2409
- parent: (board, path) => {
2410
- const parentPath = Path.parent(path);
2411
- const p = PlaitNode.get(board, parentPath);
2412
- return p;
2413
- },
2414
- /**
2415
- * Return a generator of all the ancestor nodes above a specific path.
2416
- *
2417
- * By default the order is top-down, from highest to lowest ancestor in
2418
- * the tree, but you can pass the `reverse: true` option to go bottom-up.
2419
- */
2420
- *parents(root, path, options = {}) {
2421
- for (const p of Path.ancestors(path, options)) {
2422
- const n = PlaitNode.get(root, p);
2423
- yield n;
2818
+ const BOARD_TO_TOUCH_REF = new WeakMap();
2819
+ const isPreventTouchMove = (board) => {
2820
+ return !!BOARD_TO_TOUCH_REF.get(board);
2821
+ };
2822
+ const preventTouchMove = (board, event, state) => {
2823
+ const hostElement = PlaitBoard.getElementHost(board);
2824
+ const activeHostElement = PlaitBoard.getElementActiveHost(board);
2825
+ if (state) {
2826
+ if ((event.target instanceof HTMLElement || event.target instanceof SVGElement) &&
2827
+ (hostElement.contains(event.target) || activeHostElement.contains(event.target))) {
2828
+ BOARD_TO_TOUCH_REF.set(board, { state, target: event.target instanceof SVGElement ? event.target : undefined });
2424
2829
  }
2425
- },
2426
- get(root, path) {
2427
- let node = root;
2428
- for (let i = 0; i < path.length; i++) {
2429
- const p = path[i];
2430
- if (!node || !node.children || !node.children[p]) {
2431
- throw new Error(`Cannot find a descendant at path [${path}]`);
2432
- }
2433
- node = node.children[p];
2830
+ else {
2831
+ BOARD_TO_TOUCH_REF.set(board, { state, target: undefined });
2434
2832
  }
2435
- return node;
2436
- },
2437
- last(board, path) {
2438
- let n = PlaitNode.get(board, path);
2439
- while (n && n.children && n.children.length > 0) {
2440
- const i = n.children.length - 1;
2441
- n = n.children[i];
2833
+ }
2834
+ else {
2835
+ const ref = BOARD_TO_TOUCH_REF.get(board);
2836
+ if (ref) {
2837
+ BOARD_TO_TOUCH_REF.delete(board);
2838
+ ref.host?.remove();
2442
2839
  }
2443
- return n;
2840
+ }
2841
+ };
2842
+ /**
2843
+ * some intersection maybe cause target is removed from current browser window,
2844
+ * after it was removed touch move event will not be fired
2845
+ * so scroll behavior will can not be prevented in mobile browser device
2846
+ * this function will prevent target element being remove.
2847
+ */
2848
+ const handleTouchTarget = (board) => {
2849
+ const touchRef = BOARD_TO_TOUCH_REF.get(board);
2850
+ if (touchRef &&
2851
+ touchRef.target &&
2852
+ !PlaitBoard.getElementHost(board).contains(touchRef.target) &&
2853
+ !PlaitBoard.getElementActiveHost(board).contains(touchRef.target)) {
2854
+ touchRef.target.style.opacity = '0';
2855
+ const host = createG();
2856
+ host.appendChild(touchRef.target);
2857
+ touchRef.host = host;
2858
+ host.classList.add('touch-target');
2859
+ PlaitBoard.getElementActiveHost(board).append(host);
2860
+ }
2861
+ };
2862
+
2863
+ const Viewport = {
2864
+ isViewport: (value) => {
2865
+ return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
2444
2866
  }
2445
2867
  };
2446
2868
 
@@ -2586,40 +3008,65 @@ const GeneralTransforms = {
2586
3008
  }
2587
3009
  };
2588
3010
 
2589
- function insertNode(board, node, path) {
2590
- const operation = { type: 'insert_node', node, path };
2591
- board.apply(operation);
2592
- }
2593
- function setNode(board, props, path) {
2594
- const properties = {};
2595
- const newProperties = {};
2596
- const node = PlaitNode.get(board, path);
2597
- for (const k in props) {
2598
- if (node[k] !== props[k]) {
2599
- if (node.hasOwnProperty(k)) {
2600
- properties[k] = node[k];
2601
- }
2602
- if (props[k] != null)
2603
- newProperties[k] = props[k];
3011
+ const addGroup = (board, elements) => {
3012
+ const selectedGroups = getHighestSelectedGroups(board, elements);
3013
+ const selectedIsolatedElements = getSelectedIsolatedElementsCanAddToGroup(board);
3014
+ const highestSelectedElements = [...selectedGroups, ...selectedIsolatedElements];
3015
+ const group = createGroup();
3016
+ if (canAddGroup(board)) {
3017
+ highestSelectedElements.forEach(item => {
3018
+ const path = PlaitBoard.findPath(board, item);
3019
+ NodeTransforms.setNode(board, { groupId: group.id }, path);
3020
+ });
3021
+ const selectedElements = getSelectedElements(board);
3022
+ const highestIndexOfSelectedElement = getHighestIndexOfElement(board, selectedElements);
3023
+ const indices = getElementsIndices(board, highestSelectedElements);
3024
+ const isContinuous = isIndicesContinuous(indices);
3025
+ if (!isContinuous) {
3026
+ moveElementsToNewPathAfterAddGroup(board, selectedElements, [highestIndexOfSelectedElement - 1]);
3027
+ }
3028
+ if (hasSelectedElementsInSameGroup(highestSelectedElements)) {
3029
+ const newGroupId = selectedIsolatedElements[0].groupId;
3030
+ NodeTransforms.insertNode(board, {
3031
+ ...group,
3032
+ groupId: newGroupId
3033
+ }, [board.children.length]);
3034
+ }
3035
+ else {
3036
+ NodeTransforms.insertNode(board, group, [board.children.length]);
2604
3037
  }
2605
3038
  }
2606
- const operation = { type: 'set_node', properties, newProperties, path };
2607
- board.apply(operation);
2608
- }
2609
- function removeNode(board, path) {
2610
- const node = PlaitNode.get(board, path);
2611
- const operation = { type: 'remove_node', path, node };
2612
- board.apply(operation);
2613
- }
2614
- function moveNode(board, path, newPath) {
2615
- const operation = { type: 'move_node', path, newPath };
2616
- board.apply(operation);
2617
- }
2618
- const NodeTransforms = {
2619
- insertNode,
2620
- setNode,
2621
- removeNode,
2622
- moveNode
3039
+ };
3040
+ const removeGroup = (board, elements) => {
3041
+ const selectedGroups = getHighestSelectedGroups(board, elements);
3042
+ if (canRemoveGroup(board)) {
3043
+ selectedGroups.forEach(group => {
3044
+ const elementsInGroup = findElements(board, {
3045
+ match: item => item.groupId === group.id,
3046
+ recursion: () => false
3047
+ });
3048
+ elementsInGroup.forEach(element => {
3049
+ const path = PlaitBoard.findPath(board, element);
3050
+ NodeTransforms.setNode(board, { groupId: group.groupId || undefined }, path);
3051
+ });
3052
+ });
3053
+ selectedGroups
3054
+ .map(group => {
3055
+ const groupPath = PlaitBoard.findPath(board, group);
3056
+ const groupRef = board.pathRef(groupPath);
3057
+ return () => {
3058
+ groupRef.current && NodeTransforms.removeNode(board, groupRef.current);
3059
+ groupRef.unref();
3060
+ };
3061
+ })
3062
+ .forEach(action => {
3063
+ action();
3064
+ });
3065
+ }
3066
+ };
3067
+ const GroupTransforms = {
3068
+ addGroup,
3069
+ removeGroup
2623
3070
  };
2624
3071
 
2625
3072
  function setSelection(board, selection) {
@@ -2646,6 +3093,184 @@ function addSelectionWithTemporaryElements(board, elements) {
2646
3093
  }
2647
3094
  }
2648
3095
 
3096
+ const getOneMoveOptions = (board, direction) => {
3097
+ const indicesToMove = getElementsIndices(board, getSelectedElements(board));
3098
+ let groupedIndices = toContiguousGroups(board, indicesToMove);
3099
+ if (direction === 'up') {
3100
+ groupedIndices = groupedIndices.reverse();
3101
+ }
3102
+ let moveContents = [];
3103
+ groupedIndices.forEach((indices, i) => {
3104
+ const leadingIndex = indices[0];
3105
+ const trailingIndex = indices[indices.length - 1];
3106
+ const boundaryIndex = direction === 'down' ? leadingIndex : trailingIndex;
3107
+ const targetIndex = getTargetIndex(board, boundaryIndex, direction);
3108
+ if (targetIndex === -1 || boundaryIndex === targetIndex) {
3109
+ return;
3110
+ }
3111
+ if (direction === 'down') {
3112
+ indices = indices.reverse();
3113
+ }
3114
+ moveContents.push(...indices.map(path => {
3115
+ return {
3116
+ element: board.children[path],
3117
+ newPath: [targetIndex]
3118
+ };
3119
+ }));
3120
+ });
3121
+ return moveContents;
3122
+ };
3123
+ const getAllMoveOptions = (board, direction) => {
3124
+ const indicesToMove = getElementsIndices(board, getSelectedElements(board));
3125
+ let groupedIndices = toContiguousGroups(board, indicesToMove);
3126
+ let moveContents = [];
3127
+ if (direction === 'down') {
3128
+ groupedIndices = groupedIndices.reverse();
3129
+ }
3130
+ groupedIndices.forEach(indices => {
3131
+ const leadingIndex = indices[0];
3132
+ const trailingIndex = indices[indices.length - 1];
3133
+ const boundaryIndex = direction === 'down' ? leadingIndex : trailingIndex;
3134
+ const sourceElement = board.children[boundaryIndex];
3135
+ const editingGroup = getEditingGroup(board, sourceElement);
3136
+ let targetIndex = direction === 'down' ? 0 : board.children.length - 1;
3137
+ if (editingGroup) {
3138
+ const elementsInGroup = sortElements(board, getElementsInGroup(board, editingGroup, true, true));
3139
+ targetIndex =
3140
+ direction === 'down'
3141
+ ? board.children.indexOf(elementsInGroup[0])
3142
+ : board.children.indexOf(elementsInGroup[elementsInGroup.length - 1]);
3143
+ }
3144
+ if (direction === 'down') {
3145
+ indices = indices.reverse();
3146
+ }
3147
+ moveContents.push(...indices.map(path => {
3148
+ return {
3149
+ element: board.children[path],
3150
+ newPath: [targetIndex]
3151
+ };
3152
+ }));
3153
+ });
3154
+ return moveContents;
3155
+ };
3156
+ const canSetZIndex = (board) => {
3157
+ const selectedElements = getSelectedElements(board).filter(item => board.canSetZIndex(item));
3158
+ return selectedElements.length > 0;
3159
+ };
3160
+ const toContiguousGroups = (board, array) => {
3161
+ let cursor = 0;
3162
+ return array.reduce((acc, value, index) => {
3163
+ if (index > 0) {
3164
+ const currentElement = board.children[value];
3165
+ const previousElement = board.children[array[index - 1]];
3166
+ const isContiguous = value - 1 === array[index - 1]
3167
+ ? true
3168
+ : board.children.every((item, childIndex) => {
3169
+ if (childIndex > array[index - 1] && childIndex <= value - 1) {
3170
+ return PlaitGroupElement.isGroup(item);
3171
+ }
3172
+ return true;
3173
+ });
3174
+ let isPartialSelectGroupElement = false;
3175
+ if (previousElement.groupId || (currentElement.groupId && previousElement.groupId !== currentElement.groupId)) {
3176
+ let isPartialSelectPreviousGroup = false;
3177
+ let isPartialSelectCurrentElement = false;
3178
+ if (previousElement.groupId) {
3179
+ const highestGroup = getHighestGroup(board, previousElement);
3180
+ isPartialSelectPreviousGroup = !isSelectedAllElementsInGroup(board, highestGroup);
3181
+ }
3182
+ if (currentElement.groupId) {
3183
+ const highestGroup = getHighestGroup(board, currentElement);
3184
+ isPartialSelectCurrentElement = !isSelectedAllElementsInGroup(board, highestGroup);
3185
+ }
3186
+ isPartialSelectGroupElement = isPartialSelectPreviousGroup || isPartialSelectCurrentElement;
3187
+ }
3188
+ if (!isContiguous || isPartialSelectGroupElement) {
3189
+ cursor = ++cursor;
3190
+ }
3191
+ }
3192
+ (acc[cursor] || (acc[cursor] = [])).push(value);
3193
+ return acc;
3194
+ }, []);
3195
+ };
3196
+ /**
3197
+ * Returns next candidate index that's available to be moved to. Currently that
3198
+ * is a non-deleted element, and not inside a group (unless we're editing it).
3199
+ */
3200
+ const getTargetIndex = (board, boundaryIndex, direction) => {
3201
+ if ((boundaryIndex === 0 && direction === 'down') || (boundaryIndex === board.children.length - 1 && direction === 'up')) {
3202
+ return -1;
3203
+ }
3204
+ const indexFilter = (element) => {
3205
+ if (element.isDeleted || PlaitGroupElement.isGroup(element)) {
3206
+ return false;
3207
+ }
3208
+ return true;
3209
+ };
3210
+ const candidateIndex = direction === 'down'
3211
+ ? findLastIndex(board.children, el => indexFilter(el), Math.max(0, boundaryIndex - 1))
3212
+ : findIndex(board.children, el => indexFilter(el), boundaryIndex + 1);
3213
+ const nextElement = board.children[candidateIndex];
3214
+ if (!nextElement) {
3215
+ return -1;
3216
+ }
3217
+ const elements = [...board.children];
3218
+ const sourceElement = elements[boundaryIndex];
3219
+ const editingGroup = getEditingGroup(board, sourceElement);
3220
+ const nextElementGroups = (getGroupByElement(board, nextElement, true) || []);
3221
+ // candidate element is a sibling in current editing group → return
3222
+ if (editingGroup && sourceElement?.groupId !== nextElement?.groupId) {
3223
+ // candidate element is outside current editing group → prevent
3224
+ if (!nextElementGroups.find(item => item.id === editingGroup.id)) {
3225
+ return -1;
3226
+ }
3227
+ }
3228
+ if (!nextElement.groupId) {
3229
+ return candidateIndex;
3230
+ }
3231
+ let siblingGroup;
3232
+ if (editingGroup) {
3233
+ siblingGroup = nextElementGroups[nextElementGroups.indexOf(editingGroup) - 1];
3234
+ }
3235
+ else {
3236
+ siblingGroup = nextElementGroups[nextElementGroups.length - 1];
3237
+ }
3238
+ if (siblingGroup) {
3239
+ let elementsInSiblingGroup = getElementsInGroup(board, siblingGroup, true, false);
3240
+ if (elementsInSiblingGroup.length) {
3241
+ elementsInSiblingGroup.sort((a, b) => {
3242
+ const indexA = board.children.findIndex(child => child.id === a.id);
3243
+ const indexB = board.children.findIndex(child => child.id === b.id);
3244
+ return indexA - indexB;
3245
+ });
3246
+ // assumes getElementsInGroup() returned elements are sorted
3247
+ // by zIndex (ascending)
3248
+ return direction === 'down'
3249
+ ? elements.indexOf(elementsInSiblingGroup[0])
3250
+ : elements.indexOf(elementsInSiblingGroup[elementsInSiblingGroup.length - 1]);
3251
+ }
3252
+ }
3253
+ return candidateIndex;
3254
+ };
3255
+
3256
+ const moveToTop = (board) => {
3257
+ const moveOptions = getAllMoveOptions(board, 'up');
3258
+ moveElementsToNewPath(board, moveOptions);
3259
+ };
3260
+ const moveToBottom = (board) => {
3261
+ const moveOptions = getAllMoveOptions(board, 'down');
3262
+ moveElementsToNewPath(board, moveOptions);
3263
+ };
3264
+ const moveUp = (board) => {
3265
+ const moveOptions = getOneMoveOptions(board, 'up');
3266
+ moveElementsToNewPath(board, moveOptions);
3267
+ };
3268
+ const moveDown = (board) => {
3269
+ const moveOptions = getOneMoveOptions(board, 'down');
3270
+ moveElementsToNewPath(board, moveOptions);
3271
+ };
3272
+ const ZIndexTransforms = { moveUp, moveDown, moveToTop, moveToBottom };
3273
+
2649
3274
  const removeElements = (board, elements) => {
2650
3275
  elements
2651
3276
  .map(element => {
@@ -2665,55 +3290,13 @@ const CoreTransforms = {
2665
3290
  removeElements
2666
3291
  };
2667
3292
 
2668
- const addGroup = (board, elements) => {
2669
- const selectedGroups = getHighestSelectedGroups(board, elements);
2670
- const selectedIsolatedElements = getSelectedIsolatedElementsCanAddToGroup(board);
2671
- const highestSelectedElements = [...selectedGroups, ...selectedIsolatedElements];
2672
- const group = createGroup();
2673
- if (canAddGroup(board)) {
2674
- highestSelectedElements.forEach(item => {
2675
- const path = PlaitBoard.findPath(board, item);
2676
- NodeTransforms.setNode(board, { groupId: group.id }, path);
2677
- });
2678
- if (hasSelectedElementsInSameGroup(highestSelectedElements)) {
2679
- const newGroupId = selectedIsolatedElements[0].groupId;
2680
- NodeTransforms.insertNode(board, {
2681
- ...group,
2682
- groupId: newGroupId
2683
- }, [board.children.length]);
2684
- }
2685
- else {
2686
- NodeTransforms.insertNode(board, group, [board.children.length]);
2687
- }
2688
- }
2689
- };
2690
- const removeGroup = (board, elements) => {
2691
- const selectedGroups = getHighestSelectedGroups(board, elements);
2692
- if (canRemoveGroup(board)) {
2693
- selectedGroups.map(group => {
2694
- const elementsInGroup = findElements(board, {
2695
- match: item => item.groupId === group.id,
2696
- recursion: () => false
2697
- });
2698
- elementsInGroup.forEach(item => {
2699
- const path = PlaitBoard.findPath(board, item);
2700
- NodeTransforms.setNode(board, { groupId: group.groupId || undefined }, path);
2701
- });
2702
- const groupPath = PlaitBoard.findPath(board, group);
2703
- NodeTransforms.removeNode(board, groupPath);
2704
- });
2705
- }
2706
- };
2707
- const GroupTransforms = {
2708
- addGroup,
2709
- removeGroup
2710
- };
2711
-
2712
3293
  const Transforms = {
2713
3294
  ...GeneralTransforms,
2714
3295
  ...ViewportTransforms$1,
2715
3296
  ...SelectionTransforms,
2716
- ...NodeTransforms
3297
+ ...NodeTransforms,
3298
+ ...GroupTransforms,
3299
+ ...ZIndexTransforms
2717
3300
  };
2718
3301
 
2719
3302
  const rotatePoints = (points, centerPoint, angle) => {
@@ -2820,11 +3403,25 @@ function rotateElements(board, elements, angle) {
2820
3403
  const selectionCenterPoint = RectangleClient.getCenterPoint(selectionRectangle);
2821
3404
  elements.forEach(item => {
2822
3405
  const originAngle = item.angle;
2823
- const points = rotatedDataPoints(item.points, selectionCenterPoint, angle);
3406
+ const points = rotatedDataPoints(item.points, selectionCenterPoint, normalizeAngle(angle));
2824
3407
  const path = PlaitBoard.findPath(board, item);
2825
- Transforms.setNode(board, { points, angle: originAngle + angle }, path);
3408
+ Transforms.setNode(board, { points, angle: normalizeAngle(originAngle + angle) }, path);
2826
3409
  });
2827
3410
  }
3411
+ const normalizeAngle = (angle) => {
3412
+ if (angle < 0) {
3413
+ return angle + 2 * Math.PI;
3414
+ }
3415
+ if (angle >= 2 * Math.PI) {
3416
+ return angle - 2 * Math.PI;
3417
+ }
3418
+ return angle;
3419
+ };
3420
+ const getAngleBetweenPoints = (startPoint, endPoint, centerPoint) => {
3421
+ const startAngle = (5 * Math.PI) / 2 + Math.atan2(startPoint[1] - centerPoint[1], startPoint[0] - centerPoint[0]);
3422
+ const endAngle = (5 * Math.PI) / 2 + Math.atan2(endPoint[1] - centerPoint[1], endPoint[0] - centerPoint[0]);
3423
+ return normalizeAngle(endAngle - startAngle);
3424
+ };
2828
3425
 
2829
3426
  function isSelectionMoving(board) {
2830
3427
  return !!BOARD_TO_IS_SELECTION_MOVING.get(board);
@@ -3156,11 +3753,255 @@ const canRemoveGroup = (board, elements) => {
3156
3753
  const selectedElements = elements || getSelectedElements(board);
3157
3754
  return selectedElements.length > 0 && selectedGroups.length > 0;
3158
3755
  };
3756
+ const getEditingGroup = (board, element) => {
3757
+ const groups = getGroupByElement(board, element, true);
3758
+ let editingGroup = null;
3759
+ if (groups?.length) {
3760
+ for (let i = 0; i < groups?.length; i++) {
3761
+ if (!isSelectedAllElementsInGroup(board, groups[i])) {
3762
+ editingGroup = groups[i];
3763
+ break;
3764
+ }
3765
+ }
3766
+ }
3767
+ return editingGroup;
3768
+ };
3769
+ const moveElementsToNewPathAfterAddGroup = (board, selectedElements, newPath) => {
3770
+ const moveElements = [...selectedElements];
3771
+ sortElements(board, moveElements);
3772
+ moveElements.pop();
3773
+ moveElementsToNewPath(board, moveElements.map(element => {
3774
+ return {
3775
+ element,
3776
+ newPath
3777
+ };
3778
+ }));
3779
+ };
3159
3780
 
3160
3781
  const deleteFragment = (board) => {
3161
3782
  const elements = board.getDeletedFragment([]);
3162
3783
  board.deleteFragment(elements);
3163
3784
  };
3785
+ const setFragment = (board, type, clipboardData) => {
3786
+ const selectedElements = getSelectedElements(board);
3787
+ const rectangle = getRectangleByElements(board, selectedElements, false);
3788
+ const clipboardContext = board.buildFragment(null, rectangle, type);
3789
+ clipboardContext && setClipboardData(clipboardData, clipboardContext);
3790
+ };
3791
+ const duplicateElements = (board, elements) => {
3792
+ const selectedElements = elements || getSelectedElements(board);
3793
+ const rectangle = getRectangleByElements(board, selectedElements, false);
3794
+ const clipboardContext = board.buildFragment(null, rectangle, 'copy');
3795
+ clipboardContext &&
3796
+ board.insertFragment({
3797
+ ...clipboardContext,
3798
+ text: undefined
3799
+ }, [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2]);
3800
+ };
3801
+
3802
+ const SNAP_TOLERANCE = 2;
3803
+ const SNAP_SPACING = 24;
3804
+ function getSnapRectangles(board, activeElements) {
3805
+ const elements = findElements(board, {
3806
+ match: element => board.isAlign(element) && !activeElements.some(item => item.id === element.id),
3807
+ recursion: () => true,
3808
+ isReverse: false
3809
+ });
3810
+ return elements.map(item => getRectangleByAngle(board.getRectangle(item), item.angle) || board.getRectangle(item));
3811
+ }
3812
+ function getBarPoint(point, isHorizontal) {
3813
+ return isHorizontal
3814
+ ? [
3815
+ [point[0], point[1] - 4],
3816
+ [point[0], point[1] + 4]
3817
+ ]
3818
+ : [
3819
+ [point[0] - 4, point[1]],
3820
+ [point[0] + 4, point[1]]
3821
+ ];
3822
+ }
3823
+ function getMinPointDelta(pointRectangles, axis, isHorizontal) {
3824
+ let delta = SNAP_TOLERANCE;
3825
+ pointRectangles.forEach(item => {
3826
+ const distance = getNearestDelta(axis, item, isHorizontal);
3827
+ if (Math.abs(distance) < Math.abs(delta)) {
3828
+ delta = distance;
3829
+ }
3830
+ });
3831
+ return delta;
3832
+ }
3833
+ const getNearestDelta = (axis, rectangle, isHorizontal) => {
3834
+ const pointAxis = getTripleAxis(rectangle, isHorizontal);
3835
+ const deltas = pointAxis.map(item => item - axis);
3836
+ const absDeltas = deltas.map(item => Math.abs(item));
3837
+ const index = absDeltas.indexOf(Math.min(...absDeltas));
3838
+ return deltas[index];
3839
+ };
3840
+ const getTripleAxis = (rectangle, isHorizontal) => {
3841
+ const axis = isHorizontal ? 'x' : 'y';
3842
+ const side = isHorizontal ? 'width' : 'height';
3843
+ return [rectangle[axis], rectangle[axis] + rectangle[side] / 2, rectangle[axis] + rectangle[side]];
3844
+ };
3845
+ function getNearestPointRectangle(snapRectangles, activeRectangle) {
3846
+ let minDistance = Infinity;
3847
+ let nearestRectangle = snapRectangles[0];
3848
+ snapRectangles.forEach(item => {
3849
+ const distance = Math.sqrt(Math.pow(activeRectangle.x - item.x, 2) + Math.pow(activeRectangle.y - item.y, 2));
3850
+ if (distance < minDistance) {
3851
+ minDistance = distance;
3852
+ nearestRectangle = item;
3853
+ }
3854
+ });
3855
+ return nearestRectangle;
3856
+ }
3857
+ const isSnapPoint = (axis, rectangle, isHorizontal) => {
3858
+ const pointAxis = getTripleAxis(rectangle, isHorizontal);
3859
+ return pointAxis.includes(axis);
3860
+ };
3861
+ function drawPointSnapLines(board, activeRectangle, snapRectangles, drawHorizontal = true, drawVertical = true, snapMiddle = false) {
3862
+ let pointLinePoints = [];
3863
+ const pointAxisX = getTripleAxis(activeRectangle, true);
3864
+ const pointAxisY = getTripleAxis(activeRectangle, false);
3865
+ const pointLineRefs = [
3866
+ {
3867
+ axis: pointAxisX[0],
3868
+ isHorizontal: true,
3869
+ pointRectangles: []
3870
+ },
3871
+ {
3872
+ axis: pointAxisX[1],
3873
+ isHorizontal: true,
3874
+ pointRectangles: []
3875
+ },
3876
+ {
3877
+ axis: pointAxisX[2],
3878
+ isHorizontal: true,
3879
+ pointRectangles: []
3880
+ },
3881
+ {
3882
+ axis: pointAxisY[0],
3883
+ isHorizontal: false,
3884
+ pointRectangles: []
3885
+ },
3886
+ {
3887
+ axis: pointAxisY[1],
3888
+ isHorizontal: false,
3889
+ pointRectangles: []
3890
+ },
3891
+ {
3892
+ axis: pointAxisY[2],
3893
+ isHorizontal: false,
3894
+ pointRectangles: []
3895
+ }
3896
+ ];
3897
+ for (let index = 0; index < snapRectangles.length; index++) {
3898
+ const element = snapRectangles[index];
3899
+ if (isSnapPoint(pointLineRefs[0].axis, element, pointLineRefs[0].isHorizontal)) {
3900
+ pointLineRefs[0].pointRectangles.push(element);
3901
+ }
3902
+ if (isSnapPoint(pointLineRefs[1].axis, element, pointLineRefs[1].isHorizontal)) {
3903
+ pointLineRefs[1].pointRectangles.push(element);
3904
+ }
3905
+ if (isSnapPoint(pointLineRefs[2].axis, element, pointLineRefs[2].isHorizontal)) {
3906
+ pointLineRefs[2].pointRectangles.push(element);
3907
+ }
3908
+ if (isSnapPoint(pointLineRefs[3].axis, element, pointLineRefs[3].isHorizontal)) {
3909
+ pointLineRefs[3].pointRectangles.push(element);
3910
+ }
3911
+ if (isSnapPoint(pointLineRefs[4].axis, element, pointLineRefs[4].isHorizontal)) {
3912
+ pointLineRefs[4].pointRectangles.push(element);
3913
+ }
3914
+ if (isSnapPoint(pointLineRefs[5].axis, element, pointLineRefs[5].isHorizontal)) {
3915
+ pointLineRefs[5].pointRectangles.push(element);
3916
+ }
3917
+ }
3918
+ const setResizePointSnapLine = (axis, pointRectangle, isHorizontal) => {
3919
+ const boundingRectangle = RectangleClient.inflate(RectangleClient.getBoundingRectangle([activeRectangle, pointRectangle]), SNAP_SPACING);
3920
+ if (isHorizontal) {
3921
+ const pointStart = [axis, boundingRectangle.y];
3922
+ const pointEnd = [axis, boundingRectangle.y + boundingRectangle.height];
3923
+ pointLinePoints.push([pointStart, pointEnd]);
3924
+ }
3925
+ else {
3926
+ const pointStart = [boundingRectangle.x, axis];
3927
+ const pointEnd = [boundingRectangle.x + boundingRectangle.width, axis];
3928
+ pointLinePoints.push([pointStart, pointEnd]);
3929
+ }
3930
+ };
3931
+ if (drawHorizontal && pointLineRefs[0].pointRectangles.length) {
3932
+ const leftRectangle = pointLineRefs[0].pointRectangles.length === 1
3933
+ ? pointLineRefs[0].pointRectangles[0]
3934
+ : getNearestPointRectangle(pointLineRefs[0].pointRectangles, activeRectangle);
3935
+ setResizePointSnapLine(pointLineRefs[0].axis, leftRectangle, pointLineRefs[0].isHorizontal);
3936
+ }
3937
+ if (drawHorizontal && snapMiddle && pointLineRefs[1].pointRectangles.length) {
3938
+ const middleRectangle = pointLineRefs[1].pointRectangles.length === 1
3939
+ ? pointLineRefs[1].pointRectangles[0]
3940
+ : getNearestPointRectangle(pointLineRefs[1].pointRectangles, activeRectangle);
3941
+ setResizePointSnapLine(pointLineRefs[1].axis, middleRectangle, pointLineRefs[1].isHorizontal);
3942
+ }
3943
+ if (drawHorizontal && pointLineRefs[2].pointRectangles.length) {
3944
+ const rightRectangle = pointLineRefs[2].pointRectangles.length === 1
3945
+ ? pointLineRefs[2].pointRectangles[0]
3946
+ : getNearestPointRectangle(pointLineRefs[2].pointRectangles, activeRectangle);
3947
+ setResizePointSnapLine(pointLineRefs[2].axis, rightRectangle, pointLineRefs[2].isHorizontal);
3948
+ }
3949
+ if (drawVertical && pointLineRefs[3].pointRectangles.length) {
3950
+ const topRectangle = pointLineRefs[3].pointRectangles.length === 1
3951
+ ? pointLineRefs[3].pointRectangles[0]
3952
+ : getNearestPointRectangle(pointLineRefs[3].pointRectangles, activeRectangle);
3953
+ setResizePointSnapLine(pointLineRefs[3].axis, topRectangle, pointLineRefs[3].isHorizontal);
3954
+ }
3955
+ if (drawVertical && snapMiddle && pointLineRefs[4].pointRectangles.length) {
3956
+ const middleRectangle = pointLineRefs[4].pointRectangles.length === 1
3957
+ ? pointLineRefs[4].pointRectangles[0]
3958
+ : getNearestPointRectangle(pointLineRefs[4].pointRectangles, activeRectangle);
3959
+ setResizePointSnapLine(pointLineRefs[4].axis, middleRectangle, pointLineRefs[4].isHorizontal);
3960
+ }
3961
+ if (drawVertical && pointLineRefs[5].pointRectangles.length) {
3962
+ const rightRectangle = pointLineRefs[5].pointRectangles.length === 1
3963
+ ? pointLineRefs[5].pointRectangles[0]
3964
+ : getNearestPointRectangle(pointLineRefs[5].pointRectangles, activeRectangle);
3965
+ setResizePointSnapLine(pointLineRefs[5].axis, rightRectangle, pointLineRefs[5].isHorizontal);
3966
+ }
3967
+ return drawDashedLines(board, pointLinePoints);
3968
+ }
3969
+ function drawDashedLines(board, lines) {
3970
+ const g = createG();
3971
+ lines.forEach(points => {
3972
+ if (!points.length)
3973
+ return;
3974
+ const line = PlaitBoard.getRoughSVG(board).line(points[0][0], points[0][1], points[1][0], points[1][1], {
3975
+ stroke: SELECTION_BORDER_COLOR,
3976
+ strokeWidth: 1,
3977
+ strokeLineDash: [4, 4]
3978
+ });
3979
+ g.appendChild(line);
3980
+ });
3981
+ return g;
3982
+ }
3983
+ function drawSolidLines(board, lines) {
3984
+ const g = createG();
3985
+ lines.forEach(points => {
3986
+ if (!points.length)
3987
+ return;
3988
+ let isHorizontal = points[0][1] === points[1][1];
3989
+ const line = PlaitBoard.getRoughSVG(board).line(points[0][0], points[0][1], points[1][0], points[1][1], {
3990
+ stroke: SELECTION_BORDER_COLOR,
3991
+ strokeWidth: 1
3992
+ });
3993
+ g.appendChild(line);
3994
+ points.forEach(point => {
3995
+ const barPoint = getBarPoint(point, isHorizontal);
3996
+ const bar = PlaitBoard.getRoughSVG(board).line(barPoint[0][0], barPoint[0][1], barPoint[1][0], barPoint[1][1], {
3997
+ stroke: SELECTION_BORDER_COLOR,
3998
+ strokeWidth: 1
3999
+ });
4000
+ g.appendChild(bar);
4001
+ });
4002
+ });
4003
+ return g;
4004
+ }
3164
4005
 
3165
4006
  const PlaitElement = {
3166
4007
  isRootElement(value) {
@@ -3174,6 +4015,32 @@ const PlaitElement = {
3174
4015
  },
3175
4016
  getComponent(value) {
3176
4017
  return ELEMENT_TO_COMPONENT.get(value);
4018
+ },
4019
+ getElementG(value) {
4020
+ const g = NODE_TO_G.get(value);
4021
+ if (!g) {
4022
+ throw new Error(`can not resolve element g: ${JSON.stringify(value)}`);
4023
+ }
4024
+ return g;
4025
+ },
4026
+ hasMounted(element) {
4027
+ const containerG = PlaitElement.getContainerG(element, { suppressThrow: true });
4028
+ if (containerG) {
4029
+ return true;
4030
+ }
4031
+ else {
4032
+ return false;
4033
+ }
4034
+ },
4035
+ getContainerG(value, options) {
4036
+ const containerG = NODE_TO_CONTAINER_G.get(value) || null;
4037
+ if (!containerG) {
4038
+ if (options.suppressThrow) {
4039
+ return null;
4040
+ }
4041
+ throw new Error('can not resolve container g');
4042
+ }
4043
+ return containerG;
3177
4044
  }
3178
4045
  };
3179
4046
 
@@ -3437,7 +4304,7 @@ const PlaitBoard = {
3437
4304
  return isBoard;
3438
4305
  },
3439
4306
  isAlive(board) {
3440
- const isAlive = IS_BOARD_CACHE.get(board);
4307
+ const isAlive = IS_BOARD_ALIVE.get(board);
3441
4308
  return !!isAlive;
3442
4309
  },
3443
4310
  findPath(board, node) {
@@ -3615,18 +4482,16 @@ function createBoard(children, options) {
3615
4482
  globalKeyDown: (event) => { },
3616
4483
  keyUp: (event) => { },
3617
4484
  dblClick: (event) => { },
3618
- setFragment: (data, clipboardContext) => {
3619
- setClipboardData(data, clipboardContext);
3620
- },
3621
- insertFragment: (data) => { },
4485
+ buildFragment: (clipboardContext) => clipboardContext,
4486
+ insertFragment: () => { },
3622
4487
  deleteFragment: (elements) => {
3623
4488
  CoreTransforms.removeElements(board, elements);
3624
4489
  },
3625
4490
  getDeletedFragment: (data) => data,
3626
4491
  getRelatedFragment: (data, originData) => data,
3627
- drawElement: (context) => [],
3628
- redrawElement: (context, previousContext) => { },
3629
- destroyElement: (context) => { },
4492
+ drawElement: (context) => {
4493
+ throw new Error(`can not resolve plugin element component type: ${context.element.type}`);
4494
+ },
3630
4495
  isWithinSelection: element => false,
3631
4496
  isRectangleHit: element => false,
3632
4497
  isHit: element => false,
@@ -3645,7 +4510,9 @@ function createBoard(children, options) {
3645
4510
  globalPointerMove: pointer => { },
3646
4511
  globalPointerUp: pointer => { },
3647
4512
  isImageBindingAllowed: (element) => false,
3648
- canAddToGroup: (element) => true
4513
+ canAddToGroup: (element) => true,
4514
+ canSetZIndex: (element) => true,
4515
+ isExpanded: (element) => true
3649
4516
  };
3650
4517
  return board;
3651
4518
  }
@@ -4039,354 +4906,194 @@ function withViewport(board) {
4039
4906
  return board;
4040
4907
  }
4041
4908
 
4042
- const SNAP_TOLERANCE = 2;
4043
- class MovingSnapReaction {
4044
- constructor(board, activeElements, activeRectangle) {
4045
- this.board = board;
4046
- this.activeElements = activeElements;
4047
- this.activeRectangle = activeRectangle;
4048
- this.alignRectangles = this.getAlignRectangle();
4049
- }
4050
- getAlignRectangle() {
4051
- const result = [];
4052
- depthFirstRecursion(this.board, node => {
4053
- if (PlaitBoard.isBoard(node) || this.activeElements.some(element => node.id === element.id) || !this.board.isAlign(node)) {
4054
- return;
4055
- }
4056
- const rectangle = this.board.getRectangle(node);
4057
- rectangle && result.push(getRectangleByAngle(rectangle, node.angle) || rectangle);
4058
- }, node => {
4059
- if (node && (PlaitBoard.isBoard(node) || this.board.isRecursion(node))) {
4060
- return true;
4061
- }
4062
- else {
4063
- return false;
4909
+ function getSnapMovingRef(board, activeRectangle, activeElements) {
4910
+ const snapRectangles = getSnapRectangles(board, activeElements);
4911
+ const snapG = createG();
4912
+ let snapDelta = getPointLineDelta(activeRectangle, snapRectangles);
4913
+ const pointLinesG = drawMovingPointSnapLines(board, snapDelta, activeRectangle, snapRectangles);
4914
+ snapG.append(pointLinesG);
4915
+ const result = getGapSnapLinesAndDelta(board, snapDelta, activeRectangle, snapRectangles);
4916
+ snapDelta = result.snapDelta;
4917
+ snapG.append(result.snapG);
4918
+ return { ...snapDelta, snapG };
4919
+ }
4920
+ function getPointLineDeltas(activeRectangle, snapRectangles, isHorizontal) {
4921
+ const axis = getTripleAxis(activeRectangle, isHorizontal);
4922
+ const deltaStart = getMinPointDelta(snapRectangles, axis[0], isHorizontal);
4923
+ const deltaMiddle = getMinPointDelta(snapRectangles, axis[1], isHorizontal);
4924
+ const deltaEnd = getMinPointDelta(snapRectangles, axis[2], isHorizontal);
4925
+ return [deltaStart, deltaMiddle, deltaEnd];
4926
+ }
4927
+ function getPointLineDelta(activeRectangle, snapRectangles) {
4928
+ let snapDelta = {
4929
+ deltaX: 0,
4930
+ deltaY: 0
4931
+ };
4932
+ function getDelta(isHorizontal) {
4933
+ let delta = 0;
4934
+ const deltas = getPointLineDeltas(activeRectangle, snapRectangles, isHorizontal);
4935
+ for (let i = 0; i < deltas.length; i++) {
4936
+ if (Math.abs(deltas[i]) < SNAP_TOLERANCE) {
4937
+ delta = deltas[i];
4938
+ break;
4064
4939
  }
4065
- }, true);
4066
- return result;
4940
+ }
4941
+ return delta;
4067
4942
  }
4068
- handleSnapping() {
4069
- const alignRectangles = this.getAlignRectangle();
4070
- const g = createG();
4071
- let alignLines = [];
4072
- const offset = 12;
4073
- let deltaX = 0;
4074
- let deltaY = 0;
4075
- let isCorrectX = false;
4076
- let isCorrectY = false;
4077
- for (let alignRectangle of alignRectangles) {
4078
- const closestDistances = this.calculateClosestDistances(this.activeRectangle, alignRectangle);
4079
- let canDrawHorizontal = false;
4080
- if (!isCorrectX && closestDistances.absXDistance < SNAP_TOLERANCE) {
4081
- deltaX = closestDistances.xDistance;
4082
- this.activeRectangle.x -= deltaX;
4083
- isCorrectX = true;
4084
- canDrawHorizontal = true;
4085
- }
4086
- if (closestDistances.absXDistance === 0) {
4087
- canDrawHorizontal = true;
4943
+ snapDelta.deltaX = getDelta(true);
4944
+ snapDelta.deltaY = getDelta(false);
4945
+ return snapDelta;
4946
+ }
4947
+ function updateActiveRectangle(snapDelta, activeRectangle) {
4948
+ const { deltaX, deltaY } = snapDelta;
4949
+ const { x, y, width, height } = activeRectangle;
4950
+ return {
4951
+ x: x + deltaX,
4952
+ y: y + deltaY,
4953
+ width,
4954
+ height
4955
+ };
4956
+ }
4957
+ function drawMovingPointSnapLines(board, snapDelta, activeRectangle, snapRectangles) {
4958
+ const newActiveRectangle = updateActiveRectangle(snapDelta, activeRectangle);
4959
+ return drawPointSnapLines(board, newActiveRectangle, snapRectangles, true, true, true);
4960
+ }
4961
+ function getGapSnapLinesAndDelta(board, snapDelta, activeRectangle, snapRectangles) {
4962
+ let deltaX = snapDelta.deltaX;
4963
+ let deltaY = snapDelta.deltaY;
4964
+ const gapHorizontalResult = getGapLinesAndDelta(activeRectangle, snapRectangles, true);
4965
+ const gapVerticalResult = getGapLinesAndDelta(activeRectangle, snapRectangles, false);
4966
+ const gapSnapLines = [...gapHorizontalResult.lines, ...gapVerticalResult.lines];
4967
+ if (gapHorizontalResult.delta) {
4968
+ deltaX = gapHorizontalResult.delta;
4969
+ }
4970
+ if (gapVerticalResult.delta) {
4971
+ deltaY = gapVerticalResult.delta;
4972
+ }
4973
+ return {
4974
+ snapDelta: { deltaX, deltaY },
4975
+ snapG: drawSolidLines(board, gapSnapLines)
4976
+ };
4977
+ }
4978
+ function getGapLinesAndDelta(activeRectangle, snapRectangles, isHorizontal) {
4979
+ let lines = [];
4980
+ let delta = 0;
4981
+ let rectangles = [];
4982
+ const axis = isHorizontal ? 'x' : 'y';
4983
+ const side = isHorizontal ? 'width' : 'height';
4984
+ const activeRectangleCenter = activeRectangle[axis] + activeRectangle[side] / 2;
4985
+ snapRectangles.forEach(rec => {
4986
+ const isCross = isHorizontal ? isHorizontalCross(rec, activeRectangle) : isVerticalCross(rec, activeRectangle);
4987
+ if (isCross && !RectangleClient.isHit(rec, activeRectangle)) {
4988
+ rectangles.push(rec);
4989
+ }
4990
+ });
4991
+ rectangles = [...rectangles, activeRectangle].sort((a, b) => a[axis] - b[axis]);
4992
+ const refArray = [];
4993
+ let gapDistance = 0;
4994
+ let beforeIndex = undefined;
4995
+ let afterIndex = undefined;
4996
+ for (let i = 0; i < rectangles.length; i++) {
4997
+ for (let j = i + 1; j < rectangles.length; j++) {
4998
+ const before = rectangles[i];
4999
+ const after = rectangles[j];
5000
+ const distance = after[axis] - (before[axis] + before[side]);
5001
+ let dif = Infinity;
5002
+ if (refArray[i]?.after) {
5003
+ refArray[i].after.push({ distance, index: j });
4088
5004
  }
4089
- if (canDrawHorizontal) {
4090
- const verticalY = [
4091
- alignRectangle.y,
4092
- alignRectangle.y + alignRectangle.height,
4093
- this.activeRectangle.y,
4094
- this.activeRectangle.y + this.activeRectangle.height
4095
- ];
4096
- const lineTopY = Math.min(...verticalY) - offset;
4097
- const lineBottomY = Math.max(...verticalY) + offset;
4098
- const leftLine = [this.activeRectangle.x, lineTopY, this.activeRectangle.x, lineBottomY];
4099
- const middleLine = [
4100
- this.activeRectangle.x + this.activeRectangle.width / 2,
4101
- lineTopY,
4102
- this.activeRectangle.x + this.activeRectangle.width / 2,
4103
- lineBottomY
4104
- ];
4105
- const rightLine = [
4106
- this.activeRectangle.x + this.activeRectangle.width,
4107
- lineTopY,
4108
- this.activeRectangle.x + this.activeRectangle.width,
4109
- lineBottomY
4110
- ];
4111
- const shouldDrawLeftLine = closestDistances.indexX === 0 ||
4112
- closestDistances.indexX === 1 ||
4113
- (closestDistances.indexX === 2 && this.activeRectangle.width === alignRectangle.width);
4114
- if (shouldDrawLeftLine && !alignLines[0]) {
4115
- alignLines[0] = leftLine;
4116
- }
4117
- const shouldDrawRightLine = closestDistances.indexX === 2 ||
4118
- closestDistances.indexX === 3 ||
4119
- (closestDistances.indexX === 0 && this.activeRectangle.width === alignRectangle.width);
4120
- if (shouldDrawRightLine && !alignLines[2]) {
4121
- alignLines[2] = rightLine;
4122
- }
4123
- const shouldDrawMiddleLine = closestDistances.indexX === 4 || (!shouldDrawLeftLine && !shouldDrawRightLine);
4124
- if (shouldDrawMiddleLine && !alignLines[1]) {
4125
- alignLines[1] = middleLine;
4126
- }
4127
- isCorrectX = true;
5005
+ else {
5006
+ refArray[i] = { ...refArray[i], after: [{ distance, index: j }] };
4128
5007
  }
4129
- let canDrawVertical = false;
4130
- if (!isCorrectY && closestDistances.absYDistance < SNAP_TOLERANCE) {
4131
- deltaY = closestDistances.yDistance;
4132
- this.activeRectangle.y -= deltaY;
4133
- isCorrectY = true;
4134
- canDrawVertical = true;
5008
+ if (refArray[j]?.before) {
5009
+ refArray[j].before.push({ distance, index: i });
4135
5010
  }
4136
- if (closestDistances.absYDistance === 0) {
4137
- canDrawVertical = true;
5011
+ else {
5012
+ refArray[j] = { ...refArray[j], before: [{ distance, index: i }] };
4138
5013
  }
4139
- if (canDrawVertical) {
4140
- const horizontalX = [
4141
- alignRectangle.x,
4142
- alignRectangle.x + alignRectangle.width,
4143
- this.activeRectangle.x,
4144
- this.activeRectangle.x + this.activeRectangle.width
4145
- ];
4146
- const lineLeftX = Math.min(...horizontalX) - offset;
4147
- const lineRightX = Math.max(...horizontalX) + offset;
4148
- const topLine = [lineLeftX, this.activeRectangle.y, lineRightX, this.activeRectangle.y];
4149
- const horizontalMiddleLine = [
4150
- lineLeftX,
4151
- this.activeRectangle.y + this.activeRectangle.height / 2,
4152
- lineRightX,
4153
- this.activeRectangle.y + this.activeRectangle.height / 2
4154
- ];
4155
- const bottomLine = [
4156
- lineLeftX,
4157
- this.activeRectangle.y + this.activeRectangle.height,
4158
- lineRightX,
4159
- this.activeRectangle.y + this.activeRectangle.height
4160
- ];
4161
- const shouldDrawTopLine = closestDistances.indexY === 0 ||
4162
- closestDistances.indexY === 1 ||
4163
- (closestDistances.indexY === 2 && this.activeRectangle.height === alignRectangle.height);
4164
- if (shouldDrawTopLine && !alignLines[3]) {
4165
- alignLines[3] = topLine;
4166
- }
4167
- const shouldDrawBottomLine = closestDistances.indexY === 2 ||
4168
- closestDistances.indexY === 3 ||
4169
- (closestDistances.indexY === 0 && this.activeRectangle.width === alignRectangle.width);
4170
- if (shouldDrawBottomLine && !alignLines[5]) {
4171
- alignLines[5] = bottomLine;
4172
- }
4173
- const shouldDrawMiddleLine = closestDistances.indexY === 4 || (!shouldDrawTopLine && !shouldDrawBottomLine);
4174
- if (shouldDrawMiddleLine && !alignLines[4]) {
4175
- alignLines[4] = horizontalMiddleLine;
4176
- }
5014
+ //middle
5015
+ let _center = (before[axis] + before[side] + after[axis]) / 2;
5016
+ dif = Math.abs(_center - activeRectangleCenter);
5017
+ if (dif < SNAP_TOLERANCE) {
5018
+ gapDistance = (after[axis] - (before[axis] + before[side]) - activeRectangle[side]) / 2;
5019
+ delta = _center - activeRectangleCenter;
5020
+ beforeIndex = i;
5021
+ afterIndex = j;
4177
5022
  }
4178
- }
4179
- const alignDeltaX = deltaX;
4180
- const alignDeltaY = deltaY;
4181
- this.activeRectangle.x += deltaX;
4182
- this.activeRectangle.y += deltaY;
4183
- const distributeHorizontalResult = this.alignDistribute(alignRectangles, true);
4184
- const distributeVerticalResult = this.alignDistribute(alignRectangles, false);
4185
- const distributeLines = [...distributeHorizontalResult.distributeLines, ...distributeVerticalResult.distributeLines];
4186
- if (distributeHorizontalResult.delta) {
4187
- deltaX = distributeHorizontalResult.delta;
4188
- if (alignDeltaX !== deltaX) {
4189
- alignLines[0] = [];
4190
- alignLines[1] = [];
4191
- alignLines[2] = [];
5023
+ //after
5024
+ const distanceRight = after[axis] - (before[axis] + before[side]);
5025
+ _center = after[axis] + after[side] + distanceRight + activeRectangle[side] / 2;
5026
+ dif = Math.abs(_center - activeRectangleCenter);
5027
+ if (!gapDistance && dif < SNAP_TOLERANCE) {
5028
+ gapDistance = distanceRight;
5029
+ beforeIndex = j;
5030
+ delta = _center - activeRectangleCenter;
4192
5031
  }
4193
- }
4194
- if (distributeVerticalResult.delta) {
4195
- deltaY = distributeVerticalResult.delta;
4196
- if (alignDeltaY !== deltaY) {
4197
- alignLines[3] = [];
4198
- alignLines[4] = [];
4199
- alignLines[5] = [];
5032
+ //before
5033
+ const distanceBefore = after[axis] - (before[axis] + before[side]);
5034
+ _center = before[axis] - distanceBefore - activeRectangle[side] / 2;
5035
+ dif = Math.abs(_center - activeRectangleCenter);
5036
+ if (!gapDistance && dif < SNAP_TOLERANCE) {
5037
+ gapDistance = distanceBefore;
5038
+ afterIndex = i;
5039
+ delta = _center - activeRectangleCenter;
4200
5040
  }
4201
5041
  }
4202
- if (alignLines.length) {
4203
- this.drawAlignLines(alignLines, g);
4204
- }
4205
- if (distributeLines.length) {
4206
- this.drawDistributeLines(distributeLines, g);
4207
- }
4208
- return { deltaX, deltaY, g };
4209
- }
4210
- calculateClosestDistances(activeRectangle, alignRectangle) {
4211
- const activeRectangleCenter = [activeRectangle.x + activeRectangle.width / 2, activeRectangle.y + activeRectangle.height / 2];
4212
- const alignRectangleCenter = [alignRectangle.x + alignRectangle.width / 2, alignRectangle.y + alignRectangle.height / 2];
4213
- const centerXDistance = activeRectangleCenter[0] - alignRectangleCenter[0];
4214
- const centerYDistance = activeRectangleCenter[1] - alignRectangleCenter[1];
4215
- const leftToLeft = activeRectangle.x - alignRectangle.x;
4216
- const leftToRight = activeRectangle.x - (alignRectangle.x + alignRectangle.width);
4217
- const rightToRight = activeRectangle.x + activeRectangle.width - (alignRectangle.x + alignRectangle.width);
4218
- const rightToLeft = activeRectangle.x + activeRectangle.width - alignRectangle.x;
4219
- const topToTop = activeRectangle.y - alignRectangle.y;
4220
- const topToBottom = activeRectangle.y - (alignRectangle.y + alignRectangle.height);
4221
- const bottomToTop = activeRectangle.y + activeRectangle.height - alignRectangle.y;
4222
- const bottomToBottom = activeRectangle.y + activeRectangle.height - (alignRectangle.y + alignRectangle.height);
4223
- const xDistances = [leftToLeft, leftToRight, rightToRight, rightToLeft, centerXDistance];
4224
- const yDistances = [topToTop, topToBottom, bottomToBottom, bottomToTop, centerYDistance];
4225
- const xDistancesAbs = xDistances.map(distance => Math.abs(distance));
4226
- const yDistancesAbs = yDistances.map(distance => Math.abs(distance));
4227
- const indexX = xDistancesAbs.indexOf(Math.min(...xDistancesAbs));
4228
- const indexY = yDistancesAbs.indexOf(Math.min(...yDistancesAbs));
4229
- return {
4230
- absXDistance: xDistancesAbs[indexX],
4231
- xDistance: xDistances[indexX],
4232
- absYDistance: yDistancesAbs[indexY],
4233
- yDistance: yDistances[indexY],
4234
- indexX,
4235
- indexY
4236
- };
4237
5042
  }
4238
- alignDistribute(alignRectangles, isHorizontal) {
4239
- let distributeLines = [];
4240
- let delta = 0;
4241
- let rectangles = [];
4242
- const axis = isHorizontal ? 'x' : 'y';
4243
- const side = isHorizontal ? 'width' : 'height';
4244
- const activeRectangleCenter = this.activeRectangle[axis] + this.activeRectangle[side] / 2;
4245
- alignRectangles.forEach(rec => {
4246
- const isCross = isHorizontal ? isHorizontalCross(rec, this.activeRectangle) : isVerticalCross(rec, this.activeRectangle);
4247
- if (isCross && !RectangleClient.isHit(rec, this.activeRectangle)) {
4248
- rectangles.push(rec);
4249
- }
4250
- });
4251
- rectangles = [...rectangles, this.activeRectangle].sort((a, b) => a[axis] - b[axis]);
4252
- const refArray = [];
4253
- let distributeDistance = 0;
4254
- let beforeIndex = undefined;
4255
- let afterIndex = undefined;
4256
- for (let i = 0; i < rectangles.length; i++) {
4257
- for (let j = i + 1; j < rectangles.length; j++) {
4258
- const before = rectangles[i];
4259
- const after = rectangles[j];
4260
- const distance = after[axis] - (before[axis] + before[side]);
4261
- let dif = Infinity;
4262
- if (refArray[i]?.after) {
4263
- refArray[i].after.push({ distance, index: j });
4264
- }
4265
- else {
4266
- refArray[i] = { ...refArray[i], after: [{ distance, index: j }] };
4267
- }
4268
- if (refArray[j]?.before) {
4269
- refArray[j].before.push({ distance, index: i });
4270
- }
4271
- else {
4272
- refArray[j] = { ...refArray[j], before: [{ distance, index: i }] };
4273
- }
4274
- //middle
4275
- let _center = (before[axis] + before[side] + after[axis]) / 2;
4276
- dif = Math.abs(activeRectangleCenter - _center);
4277
- if (dif < SNAP_TOLERANCE) {
4278
- distributeDistance = (after[axis] - (before[axis] + before[side]) - this.activeRectangle[side]) / 2;
4279
- delta = activeRectangleCenter - _center;
4280
- beforeIndex = i;
4281
- afterIndex = j;
4282
- }
4283
- //after
4284
- const distanceRight = after[axis] - (before[axis] + before[side]);
4285
- _center = after[axis] + after[side] + distanceRight + this.activeRectangle[side] / 2;
4286
- dif = Math.abs(activeRectangleCenter - _center);
4287
- if (!distributeDistance && dif < SNAP_TOLERANCE) {
4288
- distributeDistance = distanceRight;
4289
- beforeIndex = j;
4290
- delta = activeRectangleCenter - _center;
4291
- }
4292
- //before
4293
- const distanceBefore = after[axis] - (before[axis] + before[side]);
4294
- _center = before[axis] - distanceBefore - this.activeRectangle[side] / 2;
4295
- dif = Math.abs(activeRectangleCenter - _center);
4296
- if (!distributeDistance && dif < SNAP_TOLERANCE) {
4297
- distributeDistance = distanceBefore;
4298
- afterIndex = i;
4299
- delta = activeRectangleCenter - _center;
4300
- }
4301
- }
4302
- }
4303
- const activeIndex = rectangles.indexOf(this.activeRectangle);
4304
- let beforeIndexes = [];
4305
- let afterIndexes = [];
4306
- if (beforeIndex !== undefined) {
4307
- beforeIndexes.push(beforeIndex);
4308
- findRectangle(distributeDistance, refArray[beforeIndex], 'before', beforeIndexes);
4309
- }
4310
- if (afterIndex !== undefined) {
4311
- afterIndexes.push(afterIndex);
4312
- findRectangle(distributeDistance, refArray[afterIndex], 'after', afterIndexes);
4313
- }
4314
- if (beforeIndexes.length || afterIndexes.length) {
4315
- const indexArr = [...beforeIndexes.reverse(), activeIndex, ...afterIndexes];
4316
- this.activeRectangle[axis] -= delta;
4317
- for (let i = 1; i < indexArr.length; i++) {
4318
- distributeLines.push(getLinePoints(rectangles[indexArr[i - 1]], rectangles[indexArr[i]]));
4319
- }
5043
+ const activeIndex = rectangles.indexOf(activeRectangle);
5044
+ let beforeIndexes = [];
5045
+ let afterIndexes = [];
5046
+ if (beforeIndex !== undefined) {
5047
+ beforeIndexes.push(beforeIndex);
5048
+ findRectangle(gapDistance, refArray[beforeIndex], 'before', beforeIndexes);
5049
+ }
5050
+ if (afterIndex !== undefined) {
5051
+ afterIndexes.push(afterIndex);
5052
+ findRectangle(gapDistance, refArray[afterIndex], 'after', afterIndexes);
5053
+ }
5054
+ if (beforeIndexes.length || afterIndexes.length) {
5055
+ const indexArr = [...beforeIndexes.reverse(), activeIndex, ...afterIndexes];
5056
+ activeRectangle[axis] += delta;
5057
+ for (let i = 1; i < indexArr.length; i++) {
5058
+ lines.push(getLinePoints(rectangles[indexArr[i - 1]], rectangles[indexArr[i]]));
4320
5059
  }
4321
- function findRectangle(distance, ref, direction, rectangleIndexes) {
4322
- const arr = ref[direction];
4323
- const index = refArray.indexOf(ref);
4324
- if ((index === 0 && direction === 'before') || (index === refArray.length - 1 && direction === 'after'))
5060
+ }
5061
+ function findRectangle(distance, ref, direction, rectangleIndexes) {
5062
+ const arr = ref[direction];
5063
+ const index = refArray.indexOf(ref);
5064
+ if ((index === 0 && direction === 'before') || (index === refArray.length - 1 && direction === 'after'))
5065
+ return;
5066
+ for (let i = 0; i < arr.length; i++) {
5067
+ if (Math.abs(arr[i].distance - distance) < 0.1) {
5068
+ rectangleIndexes.push(arr[i].index);
5069
+ findRectangle(distance, refArray[arr[i].index], direction, rectangleIndexes);
4325
5070
  return;
4326
- for (let i = 0; i < arr.length; i++) {
4327
- if (Math.abs(arr[i].distance - distance) < 0.1) {
4328
- rectangleIndexes.push(arr[i].index);
4329
- findRectangle(distance, refArray[arr[i].index], direction, rectangleIndexes);
4330
- return;
4331
- }
4332
5071
  }
4333
5072
  }
4334
- function getLinePoints(beforeRectangle, afterRectangle) {
4335
- const oppositeAxis = axis === 'x' ? 'y' : 'x';
4336
- const oppositeSide = side === 'width' ? 'height' : 'width';
4337
- const align = [
4338
- beforeRectangle[oppositeAxis],
4339
- beforeRectangle[oppositeAxis] + beforeRectangle[oppositeSide],
4340
- afterRectangle[oppositeAxis],
4341
- afterRectangle[oppositeAxis] + afterRectangle[oppositeSide]
4342
- ];
4343
- const sortArr = align.sort((a, b) => a - b);
4344
- const average = (sortArr[1] + sortArr[2]) / 2;
4345
- const offset = 3;
4346
- return isHorizontal
4347
- ? [
4348
- [beforeRectangle.x + beforeRectangle.width + offset, average],
4349
- [afterRectangle.x - offset, average]
4350
- ]
4351
- : [
4352
- [average, beforeRectangle.y + beforeRectangle.height + offset],
4353
- [average, afterRectangle.y - offset]
4354
- ];
4355
- }
4356
- return { delta, distributeLines };
4357
- }
4358
- drawAlignLines(lines, g) {
4359
- lines.forEach(points => {
4360
- if (!points.length)
4361
- return;
4362
- const xAlign = PlaitBoard.getRoughSVG(this.board).line(points[0], points[1], points[2], points[3], {
4363
- stroke: SELECTION_BORDER_COLOR,
4364
- strokeWidth: 1,
4365
- strokeLineDash: [4, 4]
4366
- });
4367
- g.appendChild(xAlign);
4368
- });
4369
5073
  }
4370
- drawDistributeLines(lines, g) {
4371
- lines.forEach(line => {
4372
- if (!line.length)
4373
- return;
4374
- let isHorizontal = line[0][1] === line[1][1];
4375
- const yAlign = PlaitBoard.getRoughSVG(this.board).line(line[0][0], line[0][1], line[1][0], line[1][1], {
4376
- stroke: SELECTION_BORDER_COLOR,
4377
- strokeWidth: 1
4378
- });
4379
- g.appendChild(yAlign);
4380
- line.forEach(point => {
4381
- const barPoint = getBarPoint(point, isHorizontal);
4382
- const bar = PlaitBoard.getRoughSVG(this.board).line(barPoint[0][0], barPoint[0][1], barPoint[1][0], barPoint[1][1], {
4383
- stroke: SELECTION_BORDER_COLOR,
4384
- strokeWidth: 1
4385
- });
4386
- g.appendChild(bar);
4387
- });
4388
- });
5074
+ function getLinePoints(beforeRectangle, afterRectangle) {
5075
+ const oppositeAxis = axis === 'x' ? 'y' : 'x';
5076
+ const oppositeSide = side === 'width' ? 'height' : 'width';
5077
+ const snap = [
5078
+ beforeRectangle[oppositeAxis],
5079
+ beforeRectangle[oppositeAxis] + beforeRectangle[oppositeSide],
5080
+ afterRectangle[oppositeAxis],
5081
+ afterRectangle[oppositeAxis] + afterRectangle[oppositeSide]
5082
+ ];
5083
+ const sortArr = snap.sort((a, b) => a - b);
5084
+ const average = (sortArr[1] + sortArr[2]) / 2;
5085
+ const offset = 3;
5086
+ return isHorizontal
5087
+ ? [
5088
+ [beforeRectangle.x + beforeRectangle.width + offset, average],
5089
+ [afterRectangle.x - offset, average]
5090
+ ]
5091
+ : [
5092
+ [average, beforeRectangle.y + beforeRectangle.height + offset],
5093
+ [average, afterRectangle.y - offset]
5094
+ ];
4389
5095
  }
5096
+ return { delta, lines };
4390
5097
  }
4391
5098
  function isHorizontalCross(rectangle, other) {
4392
5099
  return !(rectangle.y + rectangle.height < other.y || rectangle.y > other.y + other.height);
@@ -4394,17 +5101,6 @@ function isHorizontalCross(rectangle, other) {
4394
5101
  function isVerticalCross(rectangle, other) {
4395
5102
  return !(rectangle.x + rectangle.width < other.x || rectangle.x > other.x + other.width);
4396
5103
  }
4397
- function getBarPoint(point, isHorizontal) {
4398
- return isHorizontal
4399
- ? [
4400
- [point[0], point[1] - 4],
4401
- [point[0], point[1] + 4]
4402
- ]
4403
- : [
4404
- [point[0] - 4, point[1]],
4405
- [point[0] + 4, point[1]]
4406
- ];
4407
- }
4408
5104
 
4409
5105
  function withMoving(board) {
4410
5106
  const { pointerDown, pointerMove, globalPointerUp, globalPointerMove } = board;
@@ -4413,7 +5109,7 @@ function withMoving(board) {
4413
5109
  let isPreventDefault = false;
4414
5110
  let startPoint;
4415
5111
  let activeElements = [];
4416
- let alignG = null;
5112
+ let snapG = null;
4417
5113
  let activeElementsRectangle = null;
4418
5114
  let selectedTargetElements = null;
4419
5115
  let hitTargetElement = undefined;
@@ -4462,7 +5158,7 @@ function withMoving(board) {
4462
5158
  if (!isPreventDefault) {
4463
5159
  isPreventDefault = true;
4464
5160
  }
4465
- alignG?.remove();
5161
+ snapG?.remove();
4466
5162
  const endPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4467
5163
  offsetX = endPoint[0] - startPoint[0];
4468
5164
  offsetY = endPoint[1] - startPoint[1];
@@ -4483,13 +5179,13 @@ function withMoving(board) {
4483
5179
  x: activeElementsRectangle.x + offsetX,
4484
5180
  y: activeElementsRectangle.y + offsetY
4485
5181
  };
4486
- const movingSnapReaction = new MovingSnapReaction(board, activeElements, getRectangleByAngle(newRectangle, getSelectionAngle(activeElements)) || newRectangle);
4487
- const ref = movingSnapReaction.handleSnapping();
4488
- offsetX -= ref.deltaX;
4489
- offsetY -= ref.deltaY;
4490
- alignG = ref.g;
4491
- alignG.classList.add(ACTIVE_MOVING_CLASS_NAME);
4492
- PlaitBoard.getElementActiveHost(board).append(alignG);
5182
+ const activeRectangle = getRectangleByAngle(newRectangle, getSelectionAngle(activeElements)) || newRectangle;
5183
+ const ref = getSnapMovingRef(board, activeRectangle, activeElements);
5184
+ offsetX += ref.deltaX;
5185
+ offsetY += ref.deltaY;
5186
+ snapG = ref.snapG;
5187
+ snapG.classList.add(ACTIVE_MOVING_CLASS_NAME);
5188
+ PlaitBoard.getElementActiveHost(board).append(snapG);
4493
5189
  handleTouchTarget(board);
4494
5190
  const currentElements = updatePoints(board, activeElements, offsetX, offsetY);
4495
5191
  PlaitBoard.getBoardContainer(board).classList.add('element-moving');
@@ -4524,7 +5220,7 @@ function withMoving(board) {
4524
5220
  globalPointerUp(event);
4525
5221
  };
4526
5222
  function cancelMove(board) {
4527
- alignG?.remove();
5223
+ snapG?.remove();
4528
5224
  startPoint = null;
4529
5225
  activeElementsRectangle = null;
4530
5226
  offsetX = 0;
@@ -4708,7 +5404,36 @@ const withHotkey = (board) => {
4708
5404
  Transforms.addSelectionWithTemporaryElements(board, elements);
4709
5405
  return;
4710
5406
  }
5407
+ if (!PlaitBoard.isReadonly(board)) {
5408
+ if (isKeyHotkey('mod+]', event)) {
5409
+ event.preventDefault();
5410
+ Transforms.moveUp(board);
5411
+ return;
5412
+ }
5413
+ if (isKeyHotkey('mod+[', event)) {
5414
+ event.preventDefault();
5415
+ Transforms.moveDown(board);
5416
+ return;
5417
+ }
5418
+ if (isKeyHotkey('mod+option+‘', event)) {
5419
+ event.preventDefault();
5420
+ Transforms.moveToTop(board);
5421
+ return;
5422
+ }
5423
+ if (isKeyHotkey('mod+option+“', event)) {
5424
+ event.preventDefault();
5425
+ Transforms.moveToBottom(board);
5426
+ return;
5427
+ }
5428
+ }
4711
5429
  const selectedElements = getSelectedElements(board);
5430
+ if (!PlaitBoard.isReadonly(board) && selectedElements.length > 0) {
5431
+ if (isKeyHotkey('mod+d', event)) {
5432
+ event.preventDefault();
5433
+ duplicateElements(board, selectedElements);
5434
+ return;
5435
+ }
5436
+ }
4712
5437
  if (!PlaitBoard.isReadonly(board) &&
4713
5438
  selectedElements.length > 0 &&
4714
5439
  (hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event))) {
@@ -4768,162 +5493,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
4768
5493
  type: Injectable
4769
5494
  }] });
4770
5495
 
4771
- class PlaitElementComponent {
4772
- constructor(renderer2, viewContainerRef) {
4773
- this.renderer2 = renderer2;
4774
- this.viewContainerRef = viewContainerRef;
4775
- this.initialized = false;
4776
- }
4777
- ngOnInit() {
4778
- this.initialize();
4779
- this.drawElement();
4780
- }
4781
- initialize() {
4782
- NODE_TO_INDEX.set(this.element, this.index);
4783
- NODE_TO_PARENT.set(this.element, this.parent);
4784
- this.initialized = true;
4785
- }
4786
- drawElement() {
4787
- const context = this.getContext();
4788
- const result = this.board.drawElement(context);
4789
- if (Array.isArray(result)) {
4790
- }
4791
- else {
4792
- const componentRef = this.viewContainerRef.createComponent(result);
4793
- const instance = componentRef.instance;
4794
- instance.context = context;
4795
- this.insertG(instance.rootG ? instance.rootG : instance.g);
4796
- this.instance = instance;
4797
- }
4798
- }
4799
- insertG(g) {
4800
- if (PlaitBoard.isBoard(this.parent)) {
4801
- this.parentG.append(g);
4802
- }
4803
- else {
4804
- let siblingG = PlaitElement.getComponent(this.parent).g;
4805
- if (this.index > 0) {
4806
- const brotherElement = this.parent.children[this.index - 1];
4807
- const lastElement = PlaitNode.last(this.board, PlaitBoard.findPath(this.board, brotherElement));
4808
- let component = PlaitElement.getComponent(lastElement) || PlaitElement.getComponent(brotherElement);
4809
- siblingG = component.g;
4810
- }
4811
- this.parentG.insertBefore(g, siblingG);
4812
- }
4813
- }
4814
- ngOnChanges(simpleChanges) {
4815
- if (this.initialized) {
4816
- NODE_TO_INDEX.set(this.element, this.index);
4817
- NODE_TO_PARENT.set(this.element, this.parent);
4818
- const elementChanged = simpleChanges['element'];
4819
- const context = this.getContext();
4820
- if (elementChanged && isSelectedElement(this.board, elementChanged.previousValue)) {
4821
- context.selected = true;
4822
- removeSelectedElement(this.board, elementChanged.previousValue);
4823
- addSelectedElement(this.board, this.element);
4824
- }
4825
- if (this.instance) {
4826
- this.instance.context = context;
4827
- }
4828
- }
4829
- }
4830
- getContext() {
4831
- const isSelected = isSelectedElement(this.board, this.element);
4832
- const context = {
4833
- element: this.element,
4834
- parent: this.parent,
4835
- board: this.board,
4836
- selected: isSelected,
4837
- effect: this.effect
4838
- };
4839
- return context;
4840
- }
4841
- ngOnDestroy() {
4842
- this.board.destroyElement(this.getContext());
4843
- }
4844
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitElementComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Component }); }
4845
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.4", type: PlaitElementComponent, isStandalone: true, selector: "plait-element", inputs: { index: "index", element: "element", parent: "parent", board: "board", effect: "effect", parentG: "parentG" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4846
- }
4847
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitElementComponent, decorators: [{
4848
- type: Component,
4849
- args: [{
4850
- selector: 'plait-element',
4851
- template: '',
4852
- changeDetection: ChangeDetectionStrategy.OnPush,
4853
- standalone: true
4854
- }]
4855
- }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ViewContainerRef }], propDecorators: { index: [{
4856
- type: Input
4857
- }], element: [{
4858
- type: Input
4859
- }], parent: [{
4860
- type: Input
4861
- }], board: [{
4862
- type: Input
4863
- }], effect: [{
4864
- type: Input
4865
- }], parentG: [{
4866
- type: Input
4867
- }] } });
4868
-
4869
- class PlaitChildrenElementComponent {
4870
- constructor() {
4871
- this.trackBy = (index, element) => {
4872
- return element.id;
4873
- };
4874
- }
4875
- ngOnInit() {
4876
- if (!this.parent) {
4877
- this.parent = this.board;
4878
- }
4879
- if (!this.parentG) {
4880
- this.parentG = PlaitBoard.getElementHost(this.board);
4881
- }
4882
- }
4883
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitChildrenElementComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4884
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.4", type: PlaitChildrenElementComponent, isStandalone: true, selector: "plait-children", inputs: { board: "board", parent: "parent", effect: "effect", parentG: "parentG" }, ngImport: i0, template: `
4885
- <plait-element
4886
- *ngFor="let item of parent.children; let index = index; trackBy: trackBy"
4887
- [index]="index"
4888
- [element]="item"
4889
- [parent]="parent"
4890
- [board]="board"
4891
- [effect]="effect"
4892
- [parentG]="parentG"
4893
- ></plait-element>
4894
- `, isInline: true, dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: PlaitElementComponent, selector: "plait-element", inputs: ["index", "element", "parent", "board", "effect", "parentG"] }] }); }
4895
- }
4896
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitChildrenElementComponent, decorators: [{
4897
- type: Component,
4898
- args: [{
4899
- selector: 'plait-children',
4900
- template: `
4901
- <plait-element
4902
- *ngFor="let item of parent.children; let index = index; trackBy: trackBy"
4903
- [index]="index"
4904
- [element]="item"
4905
- [parent]="parent"
4906
- [board]="board"
4907
- [effect]="effect"
4908
- [parentG]="parentG"
4909
- ></plait-element>
4910
- `,
4911
- standalone: true,
4912
- imports: [NgFor, PlaitElementComponent]
4913
- }]
4914
- }], ctorParameters: () => [], propDecorators: { board: [{
4915
- type: Input
4916
- }], parent: [{
4917
- type: Input
4918
- }], effect: [{
4919
- type: Input
4920
- }], parentG: [{
4921
- type: Input
4922
- }] } });
4923
-
4924
5496
  function withRelatedFragment(board) {
4925
- const { setFragment } = board;
4926
- board.setFragment = (data, clipboardContext, rectangle, type) => {
5497
+ const { buildFragment } = board;
5498
+ board.buildFragment = (clipboardContext, rectangle, type) => {
4927
5499
  const relatedFragment = board.getRelatedFragment([]);
4928
5500
  if (!clipboardContext) {
4929
5501
  clipboardContext = createClipboardContext(WritableClipboardType.elements, relatedFragment, '');
@@ -4932,10 +5504,10 @@ function withRelatedFragment(board) {
4932
5504
  clipboardContext = addClipboardContext(clipboardContext, {
4933
5505
  text: '',
4934
5506
  type: WritableClipboardType.elements,
4935
- data: relatedFragment
5507
+ elements: relatedFragment
4936
5508
  });
4937
5509
  }
4938
- setFragment(data, clipboardContext, rectangle, type);
5510
+ return buildFragment(clipboardContext, rectangle, type);
4939
5511
  };
4940
5512
  return board;
4941
5513
  }
@@ -4980,7 +5552,6 @@ class PlaitBoardComponent {
4980
5552
  this.elementRef = elementRef;
4981
5553
  this.ngZone = ngZone;
4982
5554
  this.hasInitialized = false;
4983
- this.effect = {};
4984
5555
  this.destroy$ = new Subject();
4985
5556
  this.plaitValue = [];
4986
5557
  this.plaitPlugins = [];
@@ -5023,7 +5594,7 @@ class PlaitBoardComponent {
5023
5594
  });
5024
5595
  BOARD_TO_ON_CHANGE.set(this.board, () => {
5025
5596
  this.ngZone.run(() => {
5026
- this.update();
5597
+ this.updateListRender();
5027
5598
  });
5028
5599
  });
5029
5600
  BOARD_TO_AFTER_CHANGE.set(this.board, () => {
@@ -5039,15 +5610,12 @@ class PlaitBoardComponent {
5039
5610
  this.plaitChange.emit(changeEvent);
5040
5611
  });
5041
5612
  });
5613
+ this.initializeListRender();
5042
5614
  this.hasInitialized = true;
5043
5615
  }
5044
5616
  ngAfterContentInit() {
5045
5617
  this.initializeIslands();
5046
5618
  }
5047
- update() {
5048
- this.effect = {};
5049
- this.cdr.detectChanges();
5050
- }
5051
5619
  ngOnChanges(changes) {
5052
5620
  if (this.hasInitialized) {
5053
5621
  const valueChange = changes['plaitValue'];
@@ -5165,10 +5733,8 @@ class PlaitBoardComponent {
5165
5733
  fromEvent(document, 'copy')
5166
5734
  .pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.hasBeenTextEditing(this.board)))
5167
5735
  .subscribe((event) => {
5168
- const selectedElements = getSelectedElements(this.board);
5169
5736
  event.preventDefault();
5170
- const rectangle = getRectangleByElements(this.board, selectedElements, false);
5171
- this.board.setFragment(event.clipboardData, null, rectangle, 'copy');
5737
+ setFragment(this.board, 'copy', event.clipboardData);
5172
5738
  });
5173
5739
  fromEvent(document, 'paste')
5174
5740
  .pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.isReadonly(this.board) && !PlaitBoard.hasBeenTextEditing(this.board)))
@@ -5177,19 +5743,31 @@ class PlaitBoardComponent {
5177
5743
  if (mousePoint) {
5178
5744
  const targetPoint = toViewBoxPoint(this.board, toHostPoint(this.board, mousePoint[0], mousePoint[1]));
5179
5745
  const clipboardData = await getClipboardData(clipboardEvent.clipboardData);
5180
- this.board.insertFragment(clipboardEvent.clipboardData, clipboardData, targetPoint);
5746
+ this.board.insertFragment(clipboardData, targetPoint);
5181
5747
  }
5182
5748
  });
5183
5749
  fromEvent(document, 'cut')
5184
5750
  .pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.isReadonly(this.board) && !PlaitBoard.hasBeenTextEditing(this.board)))
5185
5751
  .subscribe((event) => {
5186
- const selectedElements = getSelectedElements(this.board);
5187
5752
  event.preventDefault();
5188
- const rectangle = getRectangleByElements(this.board, selectedElements, false);
5189
- this.board.setFragment(event.clipboardData, null, rectangle, 'cut');
5753
+ setFragment(this.board, 'cut', event.clipboardData);
5190
5754
  deleteFragment(this.board);
5191
5755
  });
5192
5756
  }
5757
+ initializeListRender() {
5758
+ this.listRender = new ListRender(this.board, this.viewContainerRef);
5759
+ this.listRender.initialize(this.board.children, this.initializeChildrenContext());
5760
+ }
5761
+ updateListRender() {
5762
+ this.listRender.update(this.board.children, this.initializeChildrenContext());
5763
+ }
5764
+ initializeChildrenContext() {
5765
+ return {
5766
+ board: this.board,
5767
+ parent: this.board,
5768
+ parentG: PlaitBoard.getElementHost(this.board)
5769
+ };
5770
+ }
5193
5771
  viewportScrollListener() {
5194
5772
  fromEvent(this.viewportContainer.nativeElement, 'scroll')
5195
5773
  .pipe(takeUntil(this.destroy$), filter(() => {
@@ -5287,10 +5865,9 @@ class PlaitBoardComponent {
5287
5865
  <g class="element-upper-host"></g>
5288
5866
  <g class="element-active-host"></g>
5289
5867
  </svg>
5290
- <plait-children [board]="board" [effect]="effect"></plait-children>
5291
5868
  </div>
5292
5869
  <ng-content></ng-content>
5293
- `, isInline: true, dependencies: [{ kind: "component", type: PlaitChildrenElementComponent, selector: "plait-children", inputs: ["board", "parent", "effect", "parentG"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5870
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5294
5871
  }
5295
5872
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitBoardComponent, decorators: [{
5296
5873
  type: Component,
@@ -5303,14 +5880,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
5303
5880
  <g class="element-upper-host"></g>
5304
5881
  <g class="element-active-host"></g>
5305
5882
  </svg>
5306
- <plait-children [board]="board" [effect]="effect"></plait-children>
5307
5883
  </div>
5308
5884
  <ng-content></ng-content>
5309
5885
  `,
5310
5886
  changeDetection: ChangeDetectionStrategy.OnPush,
5311
5887
  providers: [PlaitContextService],
5312
- standalone: true,
5313
- imports: [PlaitChildrenElementComponent]
5888
+ standalone: true
5314
5889
  }]
5315
5890
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ViewContainerRef }, { type: i0.ElementRef }, { type: i0.NgZone }], propDecorators: { plaitValue: [{
5316
5891
  type: Input
@@ -5607,5 +6182,5 @@ const isDebug = (key) => {
5607
6182
  * Generated bundle index. Do not edit.
5608
6183
  */
5609
6184
 
5610
- export { A, ACTIVE_MOVING_CLASS_NAME, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, BOARD_TO_AFTER_CHANGE, BOARD_TO_COMPONENT, BOARD_TO_ELEMENT_HOST, BOARD_TO_HOST, BOARD_TO_IS_SELECTION_MOVING, BOARD_TO_MOVING_ELEMENT, BOARD_TO_MOVING_POINT, BOARD_TO_MOVING_POINT_IN_BOARD, BOARD_TO_ON_CHANGE, BOARD_TO_ROUGH_SVG, BOARD_TO_SELECTED_ELEMENT, BOARD_TO_TEMPORARY_ELEMENTS, BOARD_TO_TOUCH_REF, BOARD_TO_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, CursorClass, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DebugGenerator, DefaultThemeColor, Direction, E, EIGHT, ELEMENT_TO_COMPONENT, END, ENTER, EQUALS, ESCAPE, F, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, FF_EQUALS, FF_MINUS, FF_MUTE, FF_SEMICOLON, FF_VOLUME_DOWN, FF_VOLUME_UP, FIRST_MEDIA, FIVE, FLUSHING, FOUR, G, GroupTransforms, H, HIT_DISTANCE_BUFFER, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_ALIVE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_DRAGGING, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_SAFARI, IS_TEXT_EDITABLE, J, K, L, LAST_MEDIA, LEFT_ARROW, M, MAC_ENTER, MAC_META, MAC_WK_CMD_LEFT, MAC_WK_CMD_RIGHT, MAX_RADIUS, MERGING, META, MUTE, N, NINE, NODE_TO_INDEX, NODE_TO_PARENT, NS, NUMPAD_DIVIDE, NUMPAD_EIGHT, NUMPAD_FIVE, NUMPAD_FOUR, NUMPAD_MINUS, NUMPAD_MULTIPLY, NUMPAD_NINE, NUMPAD_ONE, NUMPAD_PERIOD, NUMPAD_PLUS, NUMPAD_SEVEN, NUMPAD_SIX, NUMPAD_THREE, NUMPAD_TWO, NUMPAD_ZERO, NUM_CENTER, NUM_LOCK, O, ONE, OPEN_SQUARE_BRACKET, P, PAGE_DOWN, PAGE_UP, PATH_REFS, PAUSE, PERIOD, PLUS_SIGN, POINTER_BUTTON, PRESS_AND_MOVE_BUFFER, PRINT_SCREEN, Path, PlaitBoard, PlaitBoardComponent, PlaitChildrenElementComponent, PlaitContextService, PlaitElement, PlaitElementComponent, PlaitGroupElement, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RESIZE_CURSORS, RIGHT_ARROW, RectangleClient, ResizeCursorClass, RetroThemeColor, RgbaToHEX, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SELECTION_RECTANGLE_CLASS_NAME, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SNAPPING_STROKE_WIDTH, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, WritableClipboardType, X, Y, Z, ZERO, addClipboardContext, addSelectedElement, approximately, arrowPoints, buildPlaitHtml, cacheMovingElements, cacheSelectedElements, cacheSelectedElementsWithGroup, cacheSelectedElementsWithGroupOnShift, calcNewViewBox, canAddGroup, canRemoveGroup, catmullRomFitting, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createClipboardContext, createDebugGenerator, createFakeEvent, createForeignObject, createG, createGroup, createGroupRectangleG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createTestingBoard, createText, createTouchEvent, debounce, degreesToRadians, deleteFragment, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawEntireActiveRectangleG, drawLine, drawLinearPath, drawRectangle, drawRoundRectangle, fakeNodeWeakMap, filterSelectedGroups, findElements, getAllElementsInGroup, getBoardRectangle, getClipboardData, getClipboardFromHtml, getDataTransferClipboard, getDataTransferClipboardText, getElementById, getElementHostBBox, getElementsInGroup, getElementsInGroupByElement, getEllipseTangentSlope, getGroupByElement, getHighestGroup, getHighestSelectedElements, getHighestSelectedGroup, getHighestSelectedGroups, getHitElementByPoint, getHitElementsBySelection, getHitSelectedElements, getIsRecursionFunc, getMovingElements, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getOffsetAfterRotate, getProbablySupportsClipboardRead, getProbablySupportsClipboardWrite, getProbablySupportsClipboardWriteText, getRealScrollBarWidth, getRectangleByAngle, getRectangleByElements, getRectangleByGroup, getRotatedBoundingRectangle, getSelectedElements, getSelectedGroups, getSelectedIsolatedElements, getSelectedIsolatedElementsCanAddToGroup, getSelectedTargetElements, getSelectionAngle, getTemporaryElements, getTemporaryRef, getVectorFromPointAndSlope, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, handleTouchTarget, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hasSameAngle, hasSelectedElementsInSameGroup, hasValidAngle, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isAxisChangedByAngle, isContextmenu, isDOMElement, isDOMNode, isDebug, isDragging, isFromScrolling, isFromViewportChange, isHandleSelection, isInPlaitBoard, isLineHitLine, isMainPointer, isMovingElements, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedAllElementsInGroup, isSelectedElement, isSelectedElementOrGroup, isSelectionMoving, isSetSelectionOperation, isSetViewportOperation, nonGroupInHighestSelectedElements, normalizePoint, preventTouchMove, radiansToDegrees, removeMovingElements, removeSelectedElement, rotate, rotateAntiPointsByElement, rotateElements, rotatePoints, rotatePointsByElement, rotatedDataPoints, scrollToRectangle, setAngleForG, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setDragging, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectedElementsWithGroup, setSelectionMoving, setStrokeLinecap, shouldClear, shouldMerge, shouldSave, stripHtml, temporaryDisableSelection, throttleRAF, toDomPrecision, toFixed, toHostPoint, toHostPointFromViewBoxPoint, toImage, toScreenPointFromHostPoint, toViewBoxPoint, toViewBoxPoints, uniqueById, updateForeignObject, updateForeignObjectWidth, updatePoints, updateViewportByScrolling, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withArrowMoving, withMoving, withOptions, withSelection };
6185
+ export { A, ACTIVE_MOVING_CLASS_NAME, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, BOARD_TO_AFTER_CHANGE, BOARD_TO_COMPONENT, BOARD_TO_ELEMENT_HOST, BOARD_TO_HOST, BOARD_TO_IS_SELECTION_MOVING, BOARD_TO_MOVING_ELEMENT, BOARD_TO_MOVING_POINT, BOARD_TO_MOVING_POINT_IN_BOARD, BOARD_TO_ON_CHANGE, BOARD_TO_ROUGH_SVG, BOARD_TO_SELECTED_ELEMENT, BOARD_TO_TEMPORARY_ELEMENTS, BOARD_TO_TOUCH_REF, BOARD_TO_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, CursorClass, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DebugGenerator, DefaultThemeColor, Direction, E, EIGHT, ELEMENT_TO_COMPONENT, END, ENTER, EQUALS, ESCAPE, F, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, FF_EQUALS, FF_MINUS, FF_MUTE, FF_SEMICOLON, FF_VOLUME_DOWN, FF_VOLUME_UP, FIRST_MEDIA, FIVE, FLUSHING, FOUR, G, H, HIT_DISTANCE_BUFFER, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_ALIVE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_DRAGGING, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_SAFARI, IS_TEXT_EDITABLE, J, K, L, LAST_MEDIA, LEFT_ARROW, M, MAC_ENTER, MAC_META, MAC_WK_CMD_LEFT, MAC_WK_CMD_RIGHT, MAX_RADIUS, MERGING, META, MUTE, N, NINE, NODE_TO_CONTAINER_G, NODE_TO_G, NODE_TO_INDEX, NODE_TO_PARENT, NS, NUMPAD_DIVIDE, NUMPAD_EIGHT, NUMPAD_FIVE, NUMPAD_FOUR, NUMPAD_MINUS, NUMPAD_MULTIPLY, NUMPAD_NINE, NUMPAD_ONE, NUMPAD_PERIOD, NUMPAD_PLUS, NUMPAD_SEVEN, NUMPAD_SIX, NUMPAD_THREE, NUMPAD_TWO, NUMPAD_ZERO, NUM_CENTER, NUM_LOCK, O, ONE, OPEN_SQUARE_BRACKET, P, PAGE_DOWN, PAGE_UP, PATH_REFS, PAUSE, PERIOD, PLUS_SIGN, POINTER_BUTTON, PRESS_AND_MOVE_BUFFER, PRINT_SCREEN, Path, PlaitBoard, PlaitBoardComponent, PlaitContextService, PlaitElement, PlaitGroupElement, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RESIZE_CURSORS, RESIZE_HANDLE_CLASS_NAME, RIGHT_ARROW, ROTATE_HANDLE_CLASS_NAME, RectangleClient, ResizeCursorClass, RetroThemeColor, RgbaToHEX, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SELECTION_RECTANGLE_CLASS_NAME, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SNAPPING_STROKE_WIDTH, SNAP_TOLERANCE, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, WritableClipboardType, X, Y, Z, ZERO, addClipboardContext, addSelectedElement, approximately, arrowPoints, buildPlaitHtml, cacheMovingElements, cacheSelectedElements, cacheSelectedElementsWithGroup, cacheSelectedElementsWithGroupOnShift, calcNewViewBox, canAddGroup, canRemoveGroup, canSetZIndex, catmullRomFitting, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createClipboardContext, createDebugGenerator, createFakeEvent, createForeignObject, createG, createGroup, createGroupRectangleG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createTestingBoard, createText, createTouchEvent, debounce, degreesToRadians, deleteFragment, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawDashedLines, drawEntireActiveRectangleG, drawLine, drawLinearPath, drawPointSnapLines, drawRectangle, drawRoundRectangle, drawSolidLines, duplicateElements, fakeNodeWeakMap, filterSelectedGroups, findElements, findIndex, findLastIndex, getAllElementsInGroup, getAllMoveOptions, getAngleBetweenPoints, getBarPoint, getBoardRectangle, getClipboardData, getClipboardFromHtml, getCrossingPointsBetweenEllipseAndSegment, getDataTransferClipboard, getDataTransferClipboardText, getEditingGroup, getElementById, getElementHostBBox, getElementsInGroup, getElementsInGroupByElement, getElementsIndices, getEllipseTangentSlope, getGroupByElement, getHighestGroup, getHighestIndexOfElement, getHighestSelectedElements, getHighestSelectedGroup, getHighestSelectedGroups, getHitElementByPoint, getHitElementsBySelection, getHitSelectedElements, getIsRecursionFunc, getMinPointDelta, getMovingElements, getNearestDelta, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getNearestPointRectangle, getOffsetAfterRotate, getOneMoveOptions, getProbablySupportsClipboardRead, getProbablySupportsClipboardWrite, getProbablySupportsClipboardWriteText, getRealScrollBarWidth, getRectangleByAngle, getRectangleByElements, getRectangleByGroup, getRotatedBoundingRectangle, getSelectedElements, getSelectedGroups, getSelectedIsolatedElements, getSelectedIsolatedElementsCanAddToGroup, getSelectedTargetElements, getSelectionAngle, getSnapRectangles, getTemporaryElements, getTemporaryRef, getTripleAxis, getVectorFromPointAndSlope, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, handleTouchTarget, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hasSameAngle, hasSelectedElementsInSameGroup, hasValidAngle, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isAxisChangedByAngle, isContextmenu, isDOMElement, isDOMNode, isDebug, isDragging, isFromScrolling, isFromViewportChange, isHandleSelection, isInPlaitBoard, isIndicesContinuous, isLineHitLine, isMainPointer, isMovingElements, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedAllElementsInGroup, isSelectedElement, isSelectedElementOrGroup, isSelectionMoving, isSetSelectionOperation, isSetViewportOperation, isSnapPoint, moveElementsToNewPath, moveElementsToNewPathAfterAddGroup, nonGroupInHighestSelectedElements, normalizeAngle, normalizePoint, preventTouchMove, radiansToDegrees, removeMovingElements, removeSelectedElement, rotate, rotateAntiPointsByElement, rotateElements, rotatePoints, rotatePointsByElement, rotatedDataPoints, scrollToRectangle, setAngleForG, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setDragging, setFragment, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectedElementsWithGroup, setSelectionMoving, setStrokeLinecap, shouldClear, shouldMerge, shouldSave, sortElements, stripHtml, temporaryDisableSelection, throttleRAF, toDomPrecision, toFixed, toHostPoint, toHostPointFromViewBoxPoint, toImage, toScreenPointFromHostPoint, toViewBoxPoint, toViewBoxPoints, uniqueById, updateForeignObject, updateForeignObjectWidth, updatePoints, updateViewportByScrolling, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withArrowMoving, withMoving, withOptions, withSelection };
5611
6186
  //# sourceMappingURL=plait-core.mjs.map