@plait/core 0.53.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 (84) hide show
  1. package/board/board.component.d.ts +5 -2
  2. package/constants/index.d.ts +2 -0
  3. package/constants/selection.d.ts +1 -0
  4. package/core/element/context.d.ts +6 -2
  5. package/core/element/plugin-element.d.ts +13 -4
  6. package/core/list-render.d.ts +16 -0
  7. package/esm2022/board/board.component.mjs +28 -23
  8. package/esm2022/constants/index.mjs +3 -1
  9. package/esm2022/constants/selection.mjs +2 -1
  10. package/esm2022/core/element/context.mjs +1 -1
  11. package/esm2022/core/element/plugin-element.mjs +79 -12
  12. package/esm2022/core/list-render.mjs +209 -0
  13. package/esm2022/interfaces/board.mjs +6 -2
  14. package/esm2022/interfaces/element.mjs +28 -2
  15. package/esm2022/interfaces/node.mjs +18 -1
  16. package/esm2022/interfaces/path.mjs +56 -57
  17. package/esm2022/interfaces/rectangle-client.mjs +4 -1
  18. package/esm2022/interfaces/selection.mjs +2 -2
  19. package/esm2022/plugins/create-board.mjs +15 -13
  20. package/esm2022/plugins/with-hotkey.mjs +33 -4
  21. package/esm2022/plugins/with-moving.mjs +15 -14
  22. package/esm2022/plugins/with-related-fragment.mjs +5 -5
  23. package/esm2022/plugins/with-selection.mjs +7 -5
  24. package/esm2022/public-api.mjs +1 -3
  25. package/esm2022/transforms/element.mjs +2 -2
  26. package/esm2022/transforms/group.mjs +23 -6
  27. package/esm2022/transforms/index.mjs +6 -3
  28. package/esm2022/transforms/z-index.mjs +20 -0
  29. package/esm2022/utils/angle.mjs +56 -3
  30. package/esm2022/utils/clipboard/clipboard.mjs +5 -5
  31. package/esm2022/utils/clipboard/common.mjs +5 -5
  32. package/esm2022/utils/clipboard/types.mjs +1 -1
  33. package/esm2022/utils/common.mjs +29 -2
  34. package/esm2022/utils/debug.mjs +16 -1
  35. package/esm2022/utils/element.mjs +3 -3
  36. package/esm2022/utils/fragment.mjs +24 -0
  37. package/esm2022/utils/group.mjs +37 -5
  38. package/esm2022/utils/helper.mjs +37 -1
  39. package/esm2022/utils/index.mjs +5 -1
  40. package/esm2022/utils/math.mjs +37 -1
  41. package/esm2022/utils/position.mjs +3 -3
  42. package/esm2022/utils/selection.mjs +2 -3
  43. package/esm2022/utils/snap/snap-moving.mjs +199 -0
  44. package/esm2022/utils/snap/snap.mjs +208 -0
  45. package/esm2022/utils/to-image.mjs +2 -2
  46. package/esm2022/utils/weak-maps.mjs +4 -1
  47. package/esm2022/utils/z-index.mjs +166 -0
  48. package/fesm2022/plait-core.mjs +2110 -1458
  49. package/fesm2022/plait-core.mjs.map +1 -1
  50. package/interfaces/board.d.ts +9 -7
  51. package/interfaces/element.d.ts +5 -0
  52. package/interfaces/node.d.ts +1 -0
  53. package/interfaces/rectangle-client.d.ts +1 -0
  54. package/interfaces/selection.d.ts +1 -1
  55. package/package.json +1 -1
  56. package/public-api.d.ts +0 -2
  57. package/styles/styles.scss +9 -0
  58. package/transforms/group.d.ts +4 -0
  59. package/transforms/index.d.ts +3 -2
  60. package/transforms/z-index.d.ts +13 -0
  61. package/utils/angle.d.ts +8 -1
  62. package/utils/clipboard/common.d.ts +1 -1
  63. package/utils/clipboard/types.d.ts +1 -1
  64. package/utils/common.d.ts +8 -0
  65. package/utils/debug.d.ts +1 -0
  66. package/utils/fragment.d.ts +4 -0
  67. package/utils/group.d.ts +4 -2
  68. package/utils/helper.d.ts +4 -1
  69. package/utils/index.d.ts +4 -0
  70. package/utils/math.d.ts +1 -0
  71. package/utils/position.d.ts +1 -1
  72. package/utils/selection.d.ts +1 -1
  73. package/utils/snap/snap-moving.d.ts +5 -0
  74. package/utils/snap/snap.d.ts +31 -0
  75. package/utils/weak-maps.d.ts +3 -0
  76. package/utils/z-index.d.ts +5 -0
  77. package/core/children/children.component.d.ts +0 -17
  78. package/core/children/effect.d.ts +0 -2
  79. package/core/element/element.component.d.ts +0 -30
  80. package/esm2022/core/children/children.component.mjs +0 -60
  81. package/esm2022/core/children/effect.mjs +0 -2
  82. package/esm2022/core/element/element.component.mjs +0 -105
  83. package/esm2022/utils/reaction-manager.mjs +0 -371
  84. package/utils/reaction-manager.d.ts +0 -41
@@ -1,23 +1,25 @@
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();
18
19
  const BOARD_TO_COMPONENT = new WeakMap();
19
20
  const BOARD_TO_ROUGH_SVG = new WeakMap();
20
21
  const BOARD_TO_HOST = new WeakMap();
22
+ const IS_BOARD_ALIVE = new WeakMap();
21
23
  const BOARD_TO_ELEMENT_HOST = new WeakMap();
22
24
  const BOARD_TO_SELECTED_ELEMENT = new WeakMap();
23
25
  const BOARD_TO_MOVING_POINT_IN_BOARD = new WeakMap();
@@ -64,7 +66,7 @@ const getIsRecursionFunc = (board) => {
64
66
  };
65
67
 
66
68
  const SELECTION_BORDER_COLOR = '#6698FF';
67
- const SELECTION_FILL_COLOR = '#6698FF19'; // 主色 0.1 透明度
69
+ const SELECTION_FILL_COLOR = '#6698FF25'; // opacity 0.25
68
70
  const Selection = {
69
71
  isCollapsed(selection) {
70
72
  if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
@@ -76,11 +78,11 @@ const Selection = {
76
78
  }
77
79
  };
78
80
 
79
- const sortElements = (board, elements) => {
81
+ const sortElements = (board, elements, ascendingOrder = true) => {
80
82
  return [...elements].sort((a, b) => {
81
83
  const pathA = PlaitBoard.findPath(board, a);
82
84
  const pathB = PlaitBoard.findPath(board, b);
83
- return pathA[0] - pathB[0];
85
+ return ascendingOrder ? pathA[0] - pathB[0] : pathB[0] - pathA[0];
84
86
  });
85
87
  };
86
88
 
@@ -185,6 +187,9 @@ const RectangleClient = {
185
187
  getCenterPoint: (rectangle) => {
186
188
  return [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
187
189
  },
190
+ getCenterPointByPoints: (points) => {
191
+ return RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(points));
192
+ },
188
193
  getEdgeCenterPoints: (rectangle) => {
189
194
  return [
190
195
  [rectangle.x + rectangle.width / 2, rectangle.y],
@@ -497,10 +502,13 @@ const RESIZE_CURSORS = [ResizeCursorClass.ns, ResizeCursorClass.nesw, ResizeCurs
497
502
 
498
503
  const ATTACHED_ELEMENT_CLASS_NAME = 'plait-board-attached';
499
504
  const ACTIVE_STROKE_WIDTH = 1;
505
+ const SNAPPING_STROKE_WIDTH = 2;
500
506
  const SELECTION_RECTANGLE_CLASS_NAME = 'selection-rectangle';
501
507
 
502
508
  const HOST_CLASS_NAME = 'plait-board-container';
503
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';
504
512
  const SCROLL_BAR_WIDTH = 20;
505
513
  const MAX_RADIUS = 16;
506
514
  const POINTER_BUTTON = {
@@ -616,7 +624,215 @@ function hasOnContextChanged(value) {
616
624
  return false;
617
625
  }
618
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
+
619
832
  class PlaitPluginElementComponent {
833
+ get hasChildren() {
834
+ return !!this.element.children;
835
+ }
620
836
  set context(value) {
621
837
  if (hasBeforeContextChange(this)) {
622
838
  this.beforeContextChange(value);
@@ -627,20 +843,28 @@ class PlaitPluginElementComponent {
627
843
  ELEMENT_TO_COMPONENT.set(this.element, this);
628
844
  }
629
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();
630
851
  this.cdr.markForCheck();
631
852
  if (hasOnContextChanged(this)) {
632
853
  this.onContextChanged(value, previousContext);
633
854
  }
634
855
  }
635
856
  else {
636
- if (PlaitElement.isRootElement(this.element) && this.element.children) {
637
- this.g = createG();
638
- this.rootG = createG();
639
- 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);
640
861
  }
641
862
  else {
642
- this.g = createG();
863
+ this._g = createG();
864
+ this._containerG = this._g;
643
865
  }
866
+ NODE_TO_G.set(this.element, this._g);
867
+ NODE_TO_CONTAINER_G.set(this.element, this._containerG);
644
868
  }
645
869
  }
646
870
  get context() {
@@ -655,25 +879,79 @@ class PlaitPluginElementComponent {
655
879
  get selected() {
656
880
  return this.context && this.context.selected;
657
881
  }
658
- get effect() {
659
- return this.context && this.context.effect;
882
+ getContainerG() {
883
+ return this._containerG;
884
+ }
885
+ getElementG() {
886
+ return this._g;
660
887
  }
661
888
  constructor(cdr) {
662
889
  this.cdr = cdr;
890
+ this.viewContainerRef = inject(ViewContainerRef);
663
891
  this.initialized = false;
664
892
  }
665
893
  ngOnInit() {
666
894
  if (this.element.type) {
667
- (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
+ }
668
906
  }
907
+ this.getContainerG().setAttribute('plait-data-id', this.element.id);
669
908
  this.initialized = true;
670
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
+ }
671
943
  ngOnDestroy() {
672
944
  if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
673
945
  ELEMENT_TO_COMPONENT.delete(this.element);
674
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
+ }
675
953
  removeSelectedElement(this.board, this.element);
676
- (this.rootG || this.g).remove();
954
+ this.getContainerG().remove();
677
955
  }
678
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 }); }
679
957
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.4", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
@@ -963,6 +1241,42 @@ function toFixed(v) {
963
1241
  function approximately(a, b, precision = 0.000001) {
964
1242
  return Math.abs(a - b) <= precision;
965
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
+ }
966
1280
 
967
1281
  function isInPlaitBoard(board, x, y) {
968
1282
  const plaitBoardElement = PlaitBoard.getBoardContainer(board);
@@ -1050,6 +1364,42 @@ function uniqueById(elements) {
1050
1364
  });
1051
1365
  return Array.from(uniqueMap.values());
1052
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
+ };
1053
1403
 
1054
1404
  /**
1055
1405
  * Check whether to merge an operation into the previous operation.
@@ -1689,157 +2039,456 @@ const setIsFromViewportChange = (board, state) => {
1689
2039
  };
1690
2040
  function scrollToRectangle(board, client) { }
1691
2041
 
1692
- const BOARD_TO_RAF = new WeakMap();
1693
- const getTimerId = (board, key) => {
1694
- const state = getRAFState(board);
1695
- return state[key] || null;
1696
- };
1697
- const getRAFState = (board) => {
1698
- return BOARD_TO_RAF.get(board) || {};
1699
- };
1700
- const throttleRAF = (board, key, fn) => {
1701
- const scheduleFunc = () => {
1702
- let timerId = requestAnimationFrame(() => {
1703
- const value = BOARD_TO_RAF.get(board) || {};
1704
- value[key] = null;
1705
- BOARD_TO_RAF.set(board, value);
1706
- fn();
1707
- });
1708
- const state = getRAFState(board);
1709
- state[key] = timerId;
1710
- BOARD_TO_RAF.set(board, state);
1711
- };
1712
- let timerId = getTimerId(board, key);
1713
- if (timerId !== null) {
1714
- cancelAnimationFrame(timerId);
1715
- }
1716
- scheduleFunc();
1717
- };
1718
- const debounce = (func, wait, options) => {
1719
- let timerSubscription = null;
1720
- return () => {
1721
- if (timerSubscription && !timerSubscription.closed) {
1722
- timerSubscription.unsubscribe();
1723
- timerSubscription = timer(wait).subscribe(() => {
1724
- func();
1725
- });
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);
1726
2054
  }
1727
2055
  else {
1728
- if (options?.leading) {
1729
- timer(0).subscribe(() => {
1730
- func();
1731
- });
1732
- }
1733
- timerSubscription = timer(wait).subscribe();
2056
+ paths = paths.slice(0, -1);
1734
2057
  }
1735
- };
1736
- };
1737
-
1738
- const IS_DRAGGING = new WeakMap();
1739
- const isDragging = (board) => {
1740
- return !!IS_DRAGGING.get(board);
1741
- };
1742
- const setDragging = (board, state) => {
1743
- IS_DRAGGING.set(board, state);
1744
- };
1745
-
1746
- const getMovingElements = (board) => {
1747
- return BOARD_TO_MOVING_ELEMENT.get(board) || [];
1748
- };
1749
- const isMovingElements = (board) => {
1750
- return (BOARD_TO_MOVING_ELEMENT.get(board) || []).length > 0;
1751
- };
1752
- const removeMovingElements = (board) => {
1753
- BOARD_TO_MOVING_ELEMENT.delete(board);
1754
- setDragging(board, false);
1755
- };
1756
- const cacheMovingElements = (board, elements) => {
1757
- BOARD_TO_MOVING_ELEMENT.set(board, elements);
1758
- setDragging(board, true);
1759
- };
1760
-
1761
- const IMAGE_CONTAINER = 'plait-image-container';
1762
- /**
1763
- * Is element node
1764
- * @param node
1765
- * @returns
1766
- */
1767
- function isElementNode(node) {
1768
- return node.nodeType === Node.ELEMENT_NODE;
1769
- }
1770
- /**
1771
- * load image resources
1772
- * @param url image url
1773
- * @returns image element
1774
- */
1775
- function loadImage(src) {
1776
- return new Promise((resolve, reject) => {
1777
- const img = new Image();
1778
- img.crossOrigin = 'Anonymous';
1779
- img.onload = () => resolve(img);
1780
- img.onerror = () => reject(new Error('Failed to load image'));
1781
- img.src = src;
1782
- });
1783
- }
1784
- /**
1785
- * create and return canvas and context
1786
- * @param width canvas width
1787
- * @param height canvas height
1788
- * @param fillStyle fill style
1789
- * @returns canvas and context
1790
- */
1791
- function createCanvas(width, height, fillStyle = 'transparent') {
1792
- const canvas = document.createElement('canvas');
1793
- const ctx = canvas.getContext('2d');
1794
- canvas.width = width;
1795
- canvas.height = height;
1796
- canvas.style.width = `${width}px`;
1797
- canvas.style.height = `${height}px`;
1798
- ctx.strokeStyle = '#ffffff';
1799
- ctx.fillStyle = fillStyle;
1800
- ctx.fillRect(0, 0, width, height);
1801
- return {
1802
- canvas,
1803
- ctx
1804
- };
1805
- }
1806
- /**
1807
- * convert image to base64
1808
- * @param url image url
1809
- * @returns image base64
1810
- */
1811
- function convertImageToBase64(url) {
1812
- return loadImage(url).then(img => {
1813
- const { canvas, ctx } = createCanvas(img.width, img.height);
1814
- ctx?.drawImage(img, 0, 0);
1815
- return canvas.toDataURL('image/png');
1816
- });
1817
- }
1818
- /**
1819
- * clone node style
1820
- * @param nativeNode source node
1821
- * @param clonedNode clone node
1822
- */
1823
- function cloneCSSStyle(nativeNode, clonedNode) {
1824
- const targetStyle = clonedNode?.style;
1825
- if (!targetStyle) {
1826
- return;
1827
- }
1828
- const sourceStyle = window.getComputedStyle(nativeNode);
1829
- if (sourceStyle.cssText) {
1830
- targetStyle.cssText = sourceStyle.cssText;
1831
- targetStyle.transformOrigin = sourceStyle.transformOrigin;
1832
- }
1833
- else {
1834
- Array.from(sourceStyle).forEach(name => {
1835
- let value = sourceStyle.getPropertyValue(name);
1836
- targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
1837
- });
1838
- }
1839
- }
1840
- /**
1841
- * batch clone target styles
1842
- * @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
1843
2492
  * @param cloneNode
1844
2493
  * @param inlineStyleClassNames
1845
2494
  */
@@ -1894,7 +2543,7 @@ async function cloneSvg(board, elements, rectangle, options) {
1894
2543
  const { width, height, x, y } = rectangle;
1895
2544
  const { padding = 4, inlineStyleClassNames } = options;
1896
2545
  const sourceSvg = PlaitBoard.getHost(board);
1897
- const selectedGElements = elements.map(value => PlaitElement.getComponent(value).g);
2546
+ const selectedGElements = elements.map(value => PlaitElement.getElementG(value));
1898
2547
  const cloneSvgElement = sourceSvg.cloneNode();
1899
2548
  const newHostElement = PlaitBoard.getElementHost(board).cloneNode();
1900
2549
  cloneSvgElement.style.width = `${width}px`;
@@ -2008,19 +2657,19 @@ const getProbablySupportsClipboardWriteText = () => {
2008
2657
  const getProbablySupportsClipboardRead = () => {
2009
2658
  return 'clipboard' in navigator && 'read' in navigator.clipboard;
2010
2659
  };
2011
- const createClipboardContext = (type, data, text) => {
2660
+ const createClipboardContext = (type, elements, text) => {
2012
2661
  return {
2013
2662
  type,
2014
- data,
2663
+ elements,
2015
2664
  text
2016
2665
  };
2017
2666
  };
2018
2667
  const addClipboardContext = (clipboardContext, addition) => {
2019
- const { type, data, text } = clipboardContext;
2668
+ const { type, elements, text } = clipboardContext;
2020
2669
  if (type === addition.type) {
2021
2670
  return {
2022
2671
  type,
2023
- data: data.concat(addition.data),
2672
+ elements: elements.concat(addition.elements),
2024
2673
  text: text + ' ' + addition.text
2025
2674
  };
2026
2675
  }
@@ -2092,123 +2741,562 @@ const getNavigatorClipboard = async () => {
2092
2741
  files
2093
2742
  };
2094
2743
  }
2095
- if (item.types.includes('text/html')) {
2096
- const htmlContent = await blobAsString(await item.getType('text/html'));
2097
- const htmlClipboardData = getClipboardFromHtml(htmlContent);
2098
- if (htmlClipboardData) {
2099
- return htmlClipboardData;
2744
+ if (item.types.includes('text/html')) {
2745
+ const htmlContent = await blobAsString(await item.getType('text/html'));
2746
+ const htmlClipboardData = getClipboardFromHtml(htmlContent);
2747
+ if (htmlClipboardData) {
2748
+ return htmlClipboardData;
2749
+ }
2750
+ if (htmlContent && htmlContent.trim()) {
2751
+ clipboardData = { text: stripHtml(htmlContent) };
2752
+ }
2753
+ }
2754
+ if (item.types.includes('text/plain')) {
2755
+ const textContent = await blobAsString(await item.getType('text/plain'));
2756
+ clipboardData = {
2757
+ text: stripHtml(textContent)
2758
+ };
2759
+ }
2760
+ }
2761
+ }
2762
+ return clipboardData;
2763
+ };
2764
+ const isFile = (item) => {
2765
+ return item.types.find(i => i.match(/^image\//));
2766
+ };
2767
+ const blobAsString = (blob) => {
2768
+ return new Promise((resolve, reject) => {
2769
+ const reader = new FileReader();
2770
+ reader.addEventListener('loadend', () => {
2771
+ const text = reader.result;
2772
+ resolve(text);
2773
+ });
2774
+ reader.addEventListener('error', () => {
2775
+ reject(reader.error);
2776
+ });
2777
+ reader.readAsText(blob);
2778
+ });
2779
+ };
2780
+
2781
+ const getClipboardData = async (dataTransfer) => {
2782
+ let clipboardData = {};
2783
+ if (dataTransfer) {
2784
+ if (dataTransfer.files.length) {
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));
2815
+ }
2816
+ };
2817
+
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 });
2829
+ }
2830
+ else {
2831
+ BOARD_TO_TOUCH_REF.set(board, { state, target: undefined });
2832
+ }
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();
2839
+ }
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);
2866
+ }
2867
+ };
2868
+
2869
+ const applyToDraft = (board, selection, viewport, theme, op) => {
2870
+ switch (op.type) {
2871
+ case 'insert_node': {
2872
+ const { path, node } = op;
2873
+ const parent = PlaitNode.parent(board, path);
2874
+ const index = path[path.length - 1];
2875
+ if (!parent.children || index > parent.children.length) {
2876
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
2877
+ }
2878
+ parent.children.splice(index, 0, node);
2879
+ break;
2880
+ }
2881
+ case 'remove_node': {
2882
+ const { path } = op;
2883
+ const parent = PlaitNode.parent(board, path);
2884
+ const index = path[path.length - 1];
2885
+ if (!parent.children || index > parent.children.length) {
2886
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
2887
+ }
2888
+ parent.children.splice(index, 1);
2889
+ break;
2890
+ }
2891
+ case 'move_node': {
2892
+ const { path, newPath } = op;
2893
+ if (Path.isAncestor(path, newPath)) {
2894
+ throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
2895
+ }
2896
+ const node = PlaitNode.get(board, path);
2897
+ const parent = PlaitNode.parent(board, path);
2898
+ const index = path[path.length - 1];
2899
+ // This is tricky, but since the `path` and `newPath` both refer to
2900
+ // the same snapshot in time, there's a mismatch. After either
2901
+ // removing the original position, the second step's path can be out
2902
+ // of date. So instead of using the `op.newPath` directly, we
2903
+ // transform `op.path` to ascertain what the `newPath` would be after
2904
+ // the operation was applied.
2905
+ parent.children?.splice(index, 1);
2906
+ const truePath = Path.transform(path, op);
2907
+ const newParent = PlaitNode.get(board, Path.parent(truePath));
2908
+ const newIndex = truePath[truePath.length - 1];
2909
+ newParent.children?.splice(newIndex, 0, node);
2910
+ break;
2911
+ }
2912
+ case 'set_node': {
2913
+ const { path, properties, newProperties } = op;
2914
+ if (path.length === 0) {
2915
+ throw new Error(`Cannot set properties on the root node!`);
2916
+ }
2917
+ const node = PlaitNode.get(board, path);
2918
+ for (const key in newProperties) {
2919
+ const value = newProperties[key];
2920
+ if (value == null) {
2921
+ delete node[key];
2922
+ }
2923
+ else {
2924
+ node[key] = value;
2925
+ }
2926
+ }
2927
+ // properties that were previously defined, but are now missing, must be deleted
2928
+ for (const key in properties) {
2929
+ if (!newProperties.hasOwnProperty(key)) {
2930
+ delete node[key];
2931
+ }
2932
+ }
2933
+ break;
2934
+ }
2935
+ case 'set_viewport': {
2936
+ const { newProperties } = op;
2937
+ if (newProperties == null) {
2938
+ viewport = newProperties;
2939
+ }
2940
+ else {
2941
+ if (viewport == null) {
2942
+ if (!Viewport.isViewport(newProperties)) {
2943
+ throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
2944
+ }
2945
+ viewport = { ...newProperties };
2946
+ }
2947
+ for (const key in newProperties) {
2948
+ const value = newProperties[key];
2949
+ if (value == null) {
2950
+ delete viewport[key];
2951
+ }
2952
+ else {
2953
+ viewport[key] = value;
2954
+ }
2955
+ }
2956
+ }
2957
+ break;
2958
+ }
2959
+ case 'set_selection': {
2960
+ const { newProperties } = op;
2961
+ if (newProperties == null) {
2962
+ selection = newProperties;
2963
+ }
2964
+ else {
2965
+ if (selection === null) {
2966
+ selection = op.newProperties;
2100
2967
  }
2101
- if (htmlContent && htmlContent.trim()) {
2102
- clipboardData = { text: stripHtml(htmlContent) };
2968
+ else {
2969
+ selection = newProperties;
2103
2970
  }
2104
2971
  }
2105
- if (item.types.includes('text/plain')) {
2106
- const textContent = await blobAsString(await item.getType('text/plain'));
2107
- clipboardData = {
2108
- text: stripHtml(textContent)
2109
- };
2110
- }
2972
+ break;
2973
+ }
2974
+ case 'set_theme': {
2975
+ const { newProperties } = op;
2976
+ theme = newProperties;
2977
+ break;
2111
2978
  }
2112
2979
  }
2113
- return clipboardData;
2114
- };
2115
- const isFile = (item) => {
2116
- return item.types.find(i => i.match(/^image\//));
2980
+ return { selection, viewport, theme };
2117
2981
  };
2118
- const blobAsString = (blob) => {
2119
- return new Promise((resolve, reject) => {
2120
- const reader = new FileReader();
2121
- reader.addEventListener('loadend', () => {
2122
- const text = reader.result;
2123
- resolve(text);
2124
- });
2125
- reader.addEventListener('error', () => {
2126
- reject(reader.error);
2127
- });
2128
- reader.readAsText(blob);
2129
- });
2982
+ const GeneralTransforms = {
2983
+ /**
2984
+ * Transform the board by an operation.
2985
+ */
2986
+ transform(board, op) {
2987
+ board.children = createDraft(board.children);
2988
+ let viewport = board.viewport && createDraft(board.viewport);
2989
+ let selection = board.selection && createDraft(board.selection);
2990
+ let theme = board.theme && createDraft(board.theme);
2991
+ try {
2992
+ const state = applyToDraft(board, selection, viewport, theme, op);
2993
+ viewport = state.viewport;
2994
+ selection = state.selection;
2995
+ theme = state.theme;
2996
+ }
2997
+ finally {
2998
+ board.children = finishDraft(board.children);
2999
+ if (selection) {
3000
+ board.selection = isDraft(selection) ? finishDraft(selection) : selection;
3001
+ }
3002
+ else {
3003
+ board.selection = null;
3004
+ }
3005
+ board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
3006
+ board.theme = isDraft(theme) ? finishDraft(theme) : theme;
3007
+ }
3008
+ }
2130
3009
  };
2131
3010
 
2132
- const getClipboardData = async (dataTransfer) => {
2133
- let clipboardData = {};
2134
- if (dataTransfer) {
2135
- if (dataTransfer.files.length) {
2136
- return { files: Array.from(dataTransfer.files) };
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]);
2137
3027
  }
2138
- clipboardData = getDataTransferClipboard(dataTransfer);
2139
- if (Object.keys(clipboardData).length === 0) {
2140
- clipboardData = getDataTransferClipboardText(dataTransfer);
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]);
2141
3037
  }
2142
- return clipboardData;
2143
- }
2144
- if (getProbablySupportsClipboardRead()) {
2145
- return await getNavigatorClipboard();
2146
3038
  }
2147
- return clipboardData;
2148
3039
  };
2149
- const setClipboardData = async (dataTransfer, clipboardContext) => {
2150
- if (!clipboardContext) {
2151
- return;
2152
- }
2153
- const { type, data, text } = clipboardContext;
2154
- if (getProbablySupportsClipboardWrite()) {
2155
- return await setNavigatorClipboard(type, data, text);
2156
- }
2157
- if (dataTransfer) {
2158
- setDataTransferClipboard(dataTransfer, type, data);
2159
- setDataTransferClipboardText(dataTransfer, text);
2160
- return;
2161
- }
2162
- // Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
2163
- // Such as contextmenu copy in Firefox.
2164
- if (getProbablySupportsClipboardWriteText()) {
2165
- return await navigator.clipboard.writeText(buildPlaitHtml(type, data));
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
+ });
2166
3065
  }
2167
3066
  };
3067
+ const GroupTransforms = {
3068
+ addGroup,
3069
+ removeGroup
3070
+ };
2168
3071
 
2169
- const BOARD_TO_TOUCH_REF = new WeakMap();
2170
- const isPreventTouchMove = (board) => {
2171
- return !!BOARD_TO_TOUCH_REF.get(board);
3072
+ function setSelection(board, selection) {
3073
+ const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
3074
+ board.apply(operation);
3075
+ }
3076
+ const SelectionTransforms = {
3077
+ setSelection,
3078
+ addSelectionWithTemporaryElements
2172
3079
  };
2173
- const preventTouchMove = (board, event, state) => {
2174
- const hostElement = PlaitBoard.getElementHost(board);
2175
- const activeHostElement = PlaitBoard.getElementActiveHost(board);
2176
- if (state) {
2177
- if ((event.target instanceof HTMLElement || event.target instanceof SVGElement) &&
2178
- (hostElement.contains(event.target) || activeHostElement.contains(event.target))) {
2179
- BOARD_TO_TOUCH_REF.set(board, { state, target: event.target instanceof SVGElement ? event.target : undefined });
2180
- }
2181
- else {
2182
- BOARD_TO_TOUCH_REF.set(board, { state, target: undefined });
2183
- }
3080
+ function addSelectionWithTemporaryElements(board, elements) {
3081
+ const timeoutId = setTimeout(() => {
3082
+ setSelection(board, { anchor: [0, 0], focus: [0, 0] });
3083
+ }, 0);
3084
+ let ref = getTemporaryRef(board);
3085
+ if (ref) {
3086
+ clearTimeout(ref.timeoutId);
3087
+ const currentElements = ref.elements;
3088
+ ref.elements.push(...elements.filter(element => !currentElements.includes(element)));
3089
+ ref.timeoutId = timeoutId;
2184
3090
  }
2185
3091
  else {
2186
- const ref = BOARD_TO_TOUCH_REF.get(board);
2187
- if (ref) {
2188
- BOARD_TO_TOUCH_REF.delete(board);
2189
- ref.host?.remove();
2190
- }
3092
+ BOARD_TO_TEMPORARY_ELEMENTS.set(board, { timeoutId, elements });
2191
3093
  }
3094
+ }
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
+ }, []);
2192
3195
  };
2193
3196
  /**
2194
- * some intersection maybe cause target is removed from current browser window,
2195
- * after it was removed touch move event will not be fired
2196
- * so scroll behavior will can not be prevented in mobile browser device
2197
- * this function will prevent target element being remove.
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).
2198
3199
  */
2199
- const handleTouchTarget = (board) => {
2200
- const touchRef = BOARD_TO_TOUCH_REF.get(board);
2201
- if (touchRef &&
2202
- touchRef.target &&
2203
- !PlaitBoard.getElementHost(board).contains(touchRef.target) &&
2204
- !PlaitBoard.getElementActiveHost(board).contains(touchRef.target)) {
2205
- touchRef.target.style.opacity = '0';
2206
- const host = createG();
2207
- host.appendChild(touchRef.target);
2208
- touchRef.host = host;
2209
- host.classList.add('touch-target');
2210
- PlaitBoard.getElementActiveHost(board).append(host);
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
+ }
2211
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
+
3274
+ const removeElements = (board, elements) => {
3275
+ elements
3276
+ .map(element => {
3277
+ const path = PlaitBoard.findPath(board, element);
3278
+ const ref = board.pathRef(path);
3279
+ return () => {
3280
+ ref.current && removeNode(board, ref.current);
3281
+ ref.unref();
3282
+ removeSelectedElement(board, element, true);
3283
+ };
3284
+ })
3285
+ .forEach(action => {
3286
+ action();
3287
+ });
3288
+ };
3289
+ const CoreTransforms = {
3290
+ removeElements
3291
+ };
3292
+
3293
+ const Transforms = {
3294
+ ...GeneralTransforms,
3295
+ ...ViewportTransforms$1,
3296
+ ...SelectionTransforms,
3297
+ ...NodeTransforms,
3298
+ ...GroupTransforms,
3299
+ ...ZIndexTransforms
2212
3300
  };
2213
3301
 
2214
3302
  const rotatePoints = (points, centerPoint, angle) => {
@@ -2234,7 +3322,14 @@ const getSelectionAngle = (elements) => {
2234
3322
  return angle;
2235
3323
  };
2236
3324
  const hasSameAngle = (elements) => {
2237
- return !!getSelectionAngle(elements);
3325
+ if (!elements.length) {
3326
+ return false;
3327
+ }
3328
+ const angle = elements[0].angle;
3329
+ if (angle === undefined) {
3330
+ return false;
3331
+ }
3332
+ return !elements.some(item => item.angle !== angle);
2238
3333
  };
2239
3334
  const getRotatedBoundingRectangle = (rectanglesCornerPoints, angle) => {
2240
3335
  let rectanglesFromOrigin = [];
@@ -2273,16 +3368,60 @@ const rotatePointsByElement = (points, element) => {
2273
3368
  return null;
2274
3369
  }
2275
3370
  };
2276
- const rotateAntiPointsByElement = (points, element) => {
2277
- if (hasValidAngle(element)) {
2278
- let rectangle = RectangleClient.getRectangleByPoints(element.points);
3371
+ const rotateAntiPointsByElement = (points, element) => {
3372
+ if (hasValidAngle(element)) {
3373
+ let rectangle = RectangleClient.getRectangleByPoints(element.points);
3374
+ const centerPoint = RectangleClient.getCenterPoint(rectangle);
3375
+ return rotatePoints(points, centerPoint, -element.angle);
3376
+ }
3377
+ else {
3378
+ return null;
3379
+ }
3380
+ };
3381
+ const getRectangleByAngle = (rectangle, angle) => {
3382
+ if (angle) {
3383
+ const cornerPoints = RectangleClient.getCornerPoints(rectangle);
2279
3384
  const centerPoint = RectangleClient.getCenterPoint(rectangle);
2280
- return rotatePoints(points, centerPoint, -element.angle);
3385
+ return RectangleClient.getRectangleByPoints(rotatePoints(cornerPoints, centerPoint, angle));
2281
3386
  }
2282
3387
  else {
2283
3388
  return null;
2284
3389
  }
2285
3390
  };
3391
+ const isAxisChangedByAngle = (angle) => {
3392
+ const unitAngle = Math.abs(angle) % Math.PI;
3393
+ return unitAngle >= (1 / 4) * Math.PI && unitAngle <= (3 / 4) * Math.PI;
3394
+ };
3395
+ function degreesToRadians(d) {
3396
+ return (d / 180) * Math.PI;
3397
+ }
3398
+ function radiansToDegrees(r) {
3399
+ return (r / Math.PI) * 180;
3400
+ }
3401
+ function rotateElements(board, elements, angle) {
3402
+ const selectionRectangle = getRectangleByElements(board, elements, false);
3403
+ const selectionCenterPoint = RectangleClient.getCenterPoint(selectionRectangle);
3404
+ elements.forEach(item => {
3405
+ const originAngle = item.angle;
3406
+ const points = rotatedDataPoints(item.points, selectionCenterPoint, normalizeAngle(angle));
3407
+ const path = PlaitBoard.findPath(board, item);
3408
+ Transforms.setNode(board, { points, angle: normalizeAngle(originAngle + angle) }, path);
3409
+ });
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
+ };
2286
3425
 
2287
3426
  function isSelectionMoving(board) {
2288
3427
  return !!BOARD_TO_IS_SELECTION_MOVING.get(board);
@@ -2319,7 +3458,7 @@ function getTemporaryRef(board) {
2319
3458
  function deleteTemporaryElements(board) {
2320
3459
  BOARD_TO_TEMPORARY_ELEMENTS.delete(board);
2321
3460
  }
2322
- function createSelectionRectangleG(board) {
3461
+ function drawEntireActiveRectangleG(board) {
2323
3462
  const elements = getSelectedElements(board);
2324
3463
  const rectangle = getRectangleByElements(board, elements, false);
2325
3464
  if (rectangle.width > 0 && rectangle.height > 0 && elements.length > 1) {
@@ -2329,7 +3468,6 @@ function createSelectionRectangleG(board) {
2329
3468
  fillStyle: 'solid'
2330
3469
  });
2331
3470
  selectionRectangleG.classList.add(SELECTION_RECTANGLE_CLASS_NAME);
2332
- PlaitBoard.getElementActiveHost(board).append(selectionRectangleG);
2333
3471
  const angle = getSelectionAngle(elements);
2334
3472
  if (angle) {
2335
3473
  setAngleForG(selectionRectangleG, RectangleClient.getCenterPoint(rectangle), angle);
@@ -2570,11 +3708,16 @@ const createGroupRectangleG = (board, elements) => {
2570
3708
  if (item.groupId && isRender) {
2571
3709
  const elements = getElementsInGroupByElement(board, item);
2572
3710
  const rectangle = getRectangleByElements(board, elements, false);
2573
- groupRectangleG.append(drawRectangle(board, rectangle, {
3711
+ const rectangleG = drawRectangle(board, rectangle, {
2574
3712
  stroke: SELECTION_BORDER_COLOR,
2575
3713
  strokeWidth: ACTIVE_STROKE_WIDTH,
2576
3714
  strokeLineDash: [5]
2577
- }));
3715
+ });
3716
+ const angle = getSelectionAngle(elements);
3717
+ if (angle) {
3718
+ setAngleForG(rectangleG, RectangleClient.getCenterPoint(rectangle), angle);
3719
+ }
3720
+ groupRectangleG.append(rectangleG);
2578
3721
  }
2579
3722
  });
2580
3723
  return groupRectangleG;
@@ -2610,6 +3753,255 @@ const canRemoveGroup = (board, elements) => {
2610
3753
  const selectedElements = elements || getSelectedElements(board);
2611
3754
  return selectedElements.length > 0 && selectedGroups.length > 0;
2612
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
+ };
3780
+
3781
+ const deleteFragment = (board) => {
3782
+ const elements = board.getDeletedFragment([]);
3783
+ board.deleteFragment(elements);
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
+ }
2613
4005
 
2614
4006
  const PlaitElement = {
2615
4007
  isRootElement(value) {
@@ -2623,228 +4015,32 @@ const PlaitElement = {
2623
4015
  },
2624
4016
  getComponent(value) {
2625
4017
  return ELEMENT_TO_COMPONENT.get(value);
2626
- }
2627
- };
2628
-
2629
- const Path = {
2630
- /**
2631
- * Get a list of ancestor paths for a given path.
2632
- *
2633
- * The paths are sorted from shallowest to deepest ancestor. However, if the
2634
- * `reverse: true` option is passed, they are reversed.
2635
- */
2636
- ancestors(path, options = {}) {
2637
- const { reverse = false } = options;
2638
- let paths = Path.levels(path, options);
2639
- if (reverse) {
2640
- paths = paths.slice(1);
2641
- }
2642
- else {
2643
- paths = paths.slice(0, -1);
2644
- }
2645
- return paths;
2646
- },
2647
- /**
2648
- * Get a list of paths at every level down to a path. Note: this is the same
2649
- * as `Path.ancestors`, but including the path itself.
2650
- *
2651
- * The paths are sorted from shallowest to deepest. However, if the `reverse:
2652
- * true` option is passed, they are reversed.
2653
- */
2654
- levels(path, options = {}) {
2655
- const { reverse = false } = options;
2656
- const list = [];
2657
- for (let i = 0; i <= path.length; i++) {
2658
- list.push(path.slice(0, i));
2659
- }
2660
- if (reverse) {
2661
- list.reverse();
2662
- }
2663
- return list;
2664
- },
2665
- parent(path) {
2666
- if (path.length === 0) {
2667
- throw new Error(`Cannot get the parent path of the root path [${path}].`);
2668
- }
2669
- return path.slice(0, -1);
2670
- },
2671
- next(path) {
2672
- if (path.length === 0) {
2673
- throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
2674
- }
2675
- const last = path[path.length - 1];
2676
- return path.slice(0, -1).concat(last + 1);
2677
- },
2678
- hasPrevious(path) {
2679
- return path[path.length - 1] > 0;
2680
- },
2681
- previous(path) {
2682
- if (path.length === 0) {
2683
- throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
2684
- }
2685
- const last = path[path.length - 1];
2686
- return path.slice(0, -1).concat(last - 1);
2687
- },
2688
- /**
2689
- * Check if a path is an ancestor of another.
2690
- */
2691
- isAncestor(path, another) {
2692
- return path.length < another.length && Path.compare(path, another) === 0;
2693
4018
  },
2694
- /**
2695
- * Compare a path to another, returning an integer indicating whether the path
2696
- * was before, at, or after the other.
2697
- *
2698
- * Note: Two paths of unequal length can still receive a `0` result if one is
2699
- * directly above or below the other. If you want exact matching, use
2700
- * [[Path.equals]] instead.
2701
- */
2702
- compare(path, another) {
2703
- const min = Math.min(path.length, another.length);
2704
- for (let i = 0; i < min; i++) {
2705
- if (path[i] < another[i])
2706
- return -1;
2707
- if (path[i] > another[i])
2708
- return 1;
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)}`);
2709
4023
  }
2710
- return 0;
2711
- },
2712
- /**
2713
- * Check if a path is exactly equal to another.
2714
- */
2715
- equals(path, another) {
2716
- return path.length === another.length && path.every((n, i) => n === another[i]);
2717
- },
2718
- /**
2719
- * Check if a path ends before one of the indexes in another.
2720
- */
2721
- endsBefore(path, another) {
2722
- const i = path.length - 1;
2723
- const as = path.slice(0, i);
2724
- const bs = another.slice(0, i);
2725
- const av = path[i];
2726
- const bv = another[i];
2727
- return Path.equals(as, bs) && av < bv;
4024
+ return g;
2728
4025
  },
2729
- /**
2730
- * Check if a path is a sibling of another.
2731
- */
2732
- isSibling(path, another) {
2733
- if (path.length !== another.length) {
2734
- return false;
4026
+ hasMounted(element) {
4027
+ const containerG = PlaitElement.getContainerG(element, { suppressThrow: true });
4028
+ if (containerG) {
4029
+ return true;
2735
4030
  }
2736
- const as = path.slice(0, -1);
2737
- const bs = another.slice(0, -1);
2738
- const al = path[path.length - 1];
2739
- const bl = another[another.length - 1];
2740
- return al !== bl && Path.equals(as, bs);
2741
- },
2742
- transform(path, operation) {
2743
- return produce(path, p => {
2744
- // PERF: Exit early if the operation is guaranteed not to have an effect.
2745
- if (!path || path?.length === 0) {
2746
- return;
2747
- }
2748
- if (p === null) {
2749
- return null;
2750
- }
2751
- switch (operation.type) {
2752
- case 'insert_node': {
2753
- const { path: op } = operation;
2754
- if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
2755
- p[op.length - 1] += 1;
2756
- }
2757
- break;
2758
- }
2759
- case 'remove_node': {
2760
- const { path: op } = operation;
2761
- if (Path.equals(op, p) || Path.isAncestor(op, p)) {
2762
- return null;
2763
- }
2764
- else if (Path.endsBefore(op, p)) {
2765
- p[op.length - 1] -= 1;
2766
- }
2767
- break;
2768
- }
2769
- case 'move_node': {
2770
- const { path: op, newPath: onp } = operation;
2771
- // If the old and new path are the same, it's a no-op.
2772
- if (Path.equals(op, onp)) {
2773
- return;
2774
- }
2775
- if (Path.isAncestor(op, p) || Path.equals(op, p)) {
2776
- const copy = onp.slice();
2777
- // op.length <= onp.length is different for slate
2778
- // resolve drag from [0, 0] to [0, 3] issue
2779
- if (Path.endsBefore(op, onp) && op.length <= onp.length) {
2780
- copy[op.length - 1] -= 1;
2781
- }
2782
- return copy.concat(p.slice(op.length));
2783
- }
2784
- else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
2785
- if (Path.endsBefore(op, p)) {
2786
- p[op.length - 1] -= 1;
2787
- }
2788
- else {
2789
- p[op.length - 1] += 1;
2790
- }
2791
- }
2792
- else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
2793
- if (Path.endsBefore(op, p)) {
2794
- p[op.length - 1] -= 1;
2795
- }
2796
- p[onp.length - 1] += 1;
2797
- }
2798
- else if (Path.endsBefore(op, p)) {
2799
- if (Path.equals(onp, p)) {
2800
- p[onp.length - 1] += 1;
2801
- }
2802
- p[op.length - 1] -= 1;
2803
- }
2804
- break;
2805
- }
2806
- }
2807
- return p;
2808
- });
2809
- }
2810
- };
2811
-
2812
- const PlaitNode = {
2813
- parent: (board, path) => {
2814
- const parentPath = Path.parent(path);
2815
- const p = PlaitNode.get(board, parentPath);
2816
- return p;
2817
- },
2818
- /**
2819
- * Return a generator of all the ancestor nodes above a specific path.
2820
- *
2821
- * By default the order is top-down, from highest to lowest ancestor in
2822
- * the tree, but you can pass the `reverse: true` option to go bottom-up.
2823
- */
2824
- *parents(root, path, options = {}) {
2825
- for (const p of Path.ancestors(path, options)) {
2826
- const n = PlaitNode.get(root, p);
2827
- yield n;
4031
+ else {
4032
+ return false;
2828
4033
  }
2829
4034
  },
2830
- get(root, path) {
2831
- let node = root;
2832
- for (let i = 0; i < path.length; i++) {
2833
- const p = path[i];
2834
- if (!node || !node.children || !node.children[p]) {
2835
- throw new Error(`Cannot find a descendant at path [${path}]`);
4035
+ getContainerG(value, options) {
4036
+ const containerG = NODE_TO_CONTAINER_G.get(value) || null;
4037
+ if (!containerG) {
4038
+ if (options.suppressThrow) {
4039
+ return null;
2836
4040
  }
2837
- node = node.children[p];
2838
- }
2839
- return node;
2840
- },
2841
- last(board, path) {
2842
- let n = PlaitNode.get(board, path);
2843
- while (n && n.children && n.children.length > 0) {
2844
- const i = n.children.length - 1;
2845
- n = n.children[i];
4041
+ throw new Error('can not resolve container g');
2846
4042
  }
2847
- return n;
4043
+ return containerG;
2848
4044
  }
2849
4045
  };
2850
4046
 
@@ -2962,12 +4158,6 @@ const Point = {
2962
4158
  }
2963
4159
  };
2964
4160
 
2965
- const Viewport = {
2966
- isViewport: (value) => {
2967
- return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
2968
- }
2969
- };
2970
-
2971
4161
  const SAVING = new WeakMap();
2972
4162
  const MERGING = new WeakMap();
2973
4163
 
@@ -3029,445 +4219,177 @@ var Direction;
3029
4219
 
3030
4220
  const PlaitGroupElement = {
3031
4221
  isGroup: (value) => {
3032
- return value.type === 'group';
3033
- }
3034
- };
3035
-
3036
- function getRectangleByElements(board, elements, recursion) {
3037
- const rectanglesCornerPoints = [];
3038
- const callback = (node) => {
3039
- const nodeRectangle = board.getRectangle(node);
3040
- if (nodeRectangle) {
3041
- const cornerPoints = RectangleClient.getCornerPoints(nodeRectangle);
3042
- const rotatedCornerPoints = rotatePoints(cornerPoints, RectangleClient.getCenterPoint(nodeRectangle), node.angle || 0);
3043
- rectanglesCornerPoints.push(rotatedCornerPoints);
3044
- }
3045
- else {
3046
- console.error(`can not get rectangle of element:`, node);
3047
- }
3048
- };
3049
- elements.forEach(element => {
3050
- if (recursion) {
3051
- depthFirstRecursion(element, node => callback(node), node => board.isRecursion(node));
3052
- }
3053
- else {
3054
- callback(element);
3055
- }
3056
- });
3057
- if (rectanglesCornerPoints.length > 0) {
3058
- if (hasSameAngle(elements)) {
3059
- const angle = getSelectionAngle(elements);
3060
- return getRotatedBoundingRectangle(rectanglesCornerPoints, angle);
3061
- }
3062
- else {
3063
- const flatCornerPoints = rectanglesCornerPoints.reduce((acc, val) => {
3064
- return acc.concat(val);
3065
- }, []);
3066
- return RectangleClient.getRectangleByPoints(flatCornerPoints);
3067
- }
3068
- }
3069
- else {
3070
- return {
3071
- x: 0,
3072
- y: 0,
3073
- width: 0,
3074
- height: 0
3075
- };
3076
- }
3077
- }
3078
- function getBoardRectangle(board) {
3079
- return getRectangleByElements(board, board.children, true);
3080
- }
3081
- function getElementById(board, id, dataSource) {
3082
- if (!dataSource) {
3083
- dataSource = findElements(board, { match: element => true, recursion: element => true });
3084
- }
3085
- let element = dataSource.find(element => element.id === id);
3086
- return element;
3087
- }
3088
- function findElements(board, options) {
3089
- let elements = [];
3090
- const isReverse = options.isReverse ?? true;
3091
- depthFirstRecursion(board, node => {
3092
- if (!PlaitBoard.isBoard(node) && options.match(node)) {
3093
- elements.push(node);
3094
- }
3095
- }, (value) => {
3096
- if (PlaitBoard.isBoard(value)) {
3097
- return true;
3098
- }
3099
- else {
3100
- return getIsRecursionFunc(board)(value) && options.recursion(value);
3101
- }
3102
- }, isReverse);
3103
- return elements;
3104
- }
3105
-
3106
- const PlaitBoard = {
3107
- isBoard(value) {
3108
- const cachedIsBoard = IS_BOARD_CACHE.get(value);
3109
- if (cachedIsBoard !== undefined) {
3110
- return cachedIsBoard;
3111
- }
3112
- const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
3113
- IS_BOARD_CACHE.set(value, isBoard);
3114
- return isBoard;
3115
- },
3116
- findPath(board, node) {
3117
- const path = [];
3118
- let child = node;
3119
- while (true) {
3120
- const parent = NODE_TO_PARENT.get(child);
3121
- if (parent == null) {
3122
- if (PlaitBoard.isBoard(child)) {
3123
- return path;
3124
- }
3125
- else {
3126
- break;
3127
- }
3128
- }
3129
- const i = NODE_TO_INDEX.get(child);
3130
- if (i == null) {
3131
- break;
3132
- }
3133
- path.unshift(i);
3134
- child = parent;
3135
- }
3136
- throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
3137
- },
3138
- getHost(board) {
3139
- return BOARD_TO_HOST.get(board);
3140
- },
3141
- getElementHost(board) {
3142
- return BOARD_TO_ELEMENT_HOST.get(board)?.host;
3143
- },
3144
- getElementUpperHost(board) {
3145
- return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
3146
- },
3147
- getElementActiveHost(board) {
3148
- return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
3149
- },
3150
- getRoughSVG(board) {
3151
- return BOARD_TO_ROUGH_SVG.get(board);
3152
- },
3153
- getComponent(board) {
3154
- return BOARD_TO_COMPONENT.get(board);
3155
- },
3156
- getBoardContainer(board) {
3157
- return BOARD_TO_ELEMENT_HOST.get(board)?.container;
3158
- },
3159
- getRectangle(board) {
3160
- return getRectangleByElements(board, board.children, true);
3161
- },
3162
- getViewportContainer(board) {
3163
- return BOARD_TO_ELEMENT_HOST.get(board)?.viewportContainer;
3164
- },
3165
- isFocus(board) {
3166
- return !!board.selection;
3167
- },
3168
- isReadonly(board) {
3169
- return board.options.readonly;
3170
- },
3171
- hasBeenTextEditing(board) {
3172
- return !!IS_TEXT_EDITABLE.get(board);
3173
- },
3174
- getPointer(board) {
3175
- return board.pointer;
3176
- },
3177
- isPointer(board, pointer) {
3178
- return board.pointer === pointer;
3179
- },
3180
- isInPointer(board, pointers) {
3181
- const point = board.pointer;
3182
- return pointers.includes(point);
3183
- },
3184
- getMovingPointInBoard(board) {
3185
- return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
3186
- },
3187
- isMovingPointInBoard(board) {
3188
- const point = BOARD_TO_MOVING_POINT.get(board);
3189
- const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
3190
- if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
3191
- return true;
3192
- }
3193
- return false;
3194
- },
3195
- getThemeColors(board) {
3196
- return (board.options.themeColors || ThemeColors);
3197
- }
3198
- };
3199
-
3200
- const applyToDraft = (board, selection, viewport, theme, op) => {
3201
- switch (op.type) {
3202
- case 'insert_node': {
3203
- const { path, node } = op;
3204
- const parent = PlaitNode.parent(board, path);
3205
- const index = path[path.length - 1];
3206
- if (!parent.children || index > parent.children.length) {
3207
- throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
3208
- }
3209
- parent.children.splice(index, 0, node);
3210
- break;
3211
- }
3212
- case 'remove_node': {
3213
- const { path } = op;
3214
- const parent = PlaitNode.parent(board, path);
3215
- const index = path[path.length - 1];
3216
- if (!parent.children || index > parent.children.length) {
3217
- throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
3218
- }
3219
- parent.children.splice(index, 1);
3220
- break;
3221
- }
3222
- case 'move_node': {
3223
- const { path, newPath } = op;
3224
- if (Path.isAncestor(path, newPath)) {
3225
- throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
3226
- }
3227
- const node = PlaitNode.get(board, path);
3228
- const parent = PlaitNode.parent(board, path);
3229
- const index = path[path.length - 1];
3230
- // This is tricky, but since the `path` and `newPath` both refer to
3231
- // the same snapshot in time, there's a mismatch. After either
3232
- // removing the original position, the second step's path can be out
3233
- // of date. So instead of using the `op.newPath` directly, we
3234
- // transform `op.path` to ascertain what the `newPath` would be after
3235
- // the operation was applied.
3236
- parent.children?.splice(index, 1);
3237
- const truePath = Path.transform(path, op);
3238
- const newParent = PlaitNode.get(board, Path.parent(truePath));
3239
- const newIndex = truePath[truePath.length - 1];
3240
- newParent.children?.splice(newIndex, 0, node);
3241
- break;
3242
- }
3243
- case 'set_node': {
3244
- const { path, properties, newProperties } = op;
3245
- if (path.length === 0) {
3246
- throw new Error(`Cannot set properties on the root node!`);
3247
- }
3248
- const node = PlaitNode.get(board, path);
3249
- for (const key in newProperties) {
3250
- const value = newProperties[key];
3251
- if (value == null) {
3252
- delete node[key];
3253
- }
3254
- else {
3255
- node[key] = value;
3256
- }
3257
- }
3258
- // properties that were previously defined, but are now missing, must be deleted
3259
- for (const key in properties) {
3260
- if (!newProperties.hasOwnProperty(key)) {
3261
- delete node[key];
3262
- }
3263
- }
3264
- break;
4222
+ return value.type === 'group';
4223
+ }
4224
+ };
4225
+
4226
+ function getRectangleByElements(board, elements, recursion) {
4227
+ const rectanglesCornerPoints = [];
4228
+ const callback = (node) => {
4229
+ const nodeRectangle = board.getRectangle(node);
4230
+ if (nodeRectangle) {
4231
+ const cornerPoints = RectangleClient.getCornerPoints(nodeRectangle);
4232
+ const rotatedCornerPoints = rotatePointsByElement(cornerPoints, node) || cornerPoints;
4233
+ rectanglesCornerPoints.push(rotatedCornerPoints);
3265
4234
  }
3266
- case 'set_viewport': {
3267
- const { newProperties } = op;
3268
- if (newProperties == null) {
3269
- viewport = newProperties;
3270
- }
3271
- else {
3272
- if (viewport == null) {
3273
- if (!Viewport.isViewport(newProperties)) {
3274
- throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
3275
- }
3276
- viewport = { ...newProperties };
3277
- }
3278
- for (const key in newProperties) {
3279
- const value = newProperties[key];
3280
- if (value == null) {
3281
- delete viewport[key];
3282
- }
3283
- else {
3284
- viewport[key] = value;
3285
- }
3286
- }
3287
- }
3288
- break;
4235
+ else {
4236
+ console.error(`can not get rectangle of element:`, node);
3289
4237
  }
3290
- case 'set_selection': {
3291
- const { newProperties } = op;
3292
- if (newProperties == null) {
3293
- selection = newProperties;
3294
- }
3295
- else {
3296
- if (selection === null) {
3297
- selection = op.newProperties;
3298
- }
3299
- else {
3300
- selection = newProperties;
3301
- }
3302
- }
3303
- break;
4238
+ };
4239
+ elements.forEach(element => {
4240
+ if (recursion) {
4241
+ depthFirstRecursion(element, node => callback(node), node => board.isRecursion(node));
3304
4242
  }
3305
- case 'set_theme': {
3306
- const { newProperties } = op;
3307
- theme = newProperties;
3308
- break;
4243
+ else {
4244
+ callback(element);
3309
4245
  }
3310
- }
3311
- return { selection, viewport, theme };
3312
- };
3313
- const GeneralTransforms = {
3314
- /**
3315
- * Transform the board by an operation.
3316
- */
3317
- transform(board, op) {
3318
- board.children = createDraft(board.children);
3319
- let viewport = board.viewport && createDraft(board.viewport);
3320
- let selection = board.selection && createDraft(board.selection);
3321
- let theme = board.theme && createDraft(board.theme);
3322
- try {
3323
- const state = applyToDraft(board, selection, viewport, theme, op);
3324
- viewport = state.viewport;
3325
- selection = state.selection;
3326
- theme = state.theme;
4246
+ });
4247
+ if (rectanglesCornerPoints.length > 0) {
4248
+ if (hasSameAngle(elements)) {
4249
+ const angle = getSelectionAngle(elements);
4250
+ return getRotatedBoundingRectangle(rectanglesCornerPoints, angle);
3327
4251
  }
3328
- finally {
3329
- board.children = finishDraft(board.children);
3330
- if (selection) {
3331
- board.selection = isDraft(selection) ? finishDraft(selection) : selection;
3332
- }
3333
- else {
3334
- board.selection = null;
3335
- }
3336
- board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
3337
- board.theme = isDraft(theme) ? finishDraft(theme) : theme;
4252
+ else {
4253
+ const flatCornerPoints = rectanglesCornerPoints.reduce((acc, val) => {
4254
+ return acc.concat(val);
4255
+ }, []);
4256
+ return RectangleClient.getRectangleByPoints(flatCornerPoints);
3338
4257
  }
3339
4258
  }
3340
- };
3341
-
3342
- function insertNode(board, node, path) {
3343
- const operation = { type: 'insert_node', node, path };
3344
- board.apply(operation);
3345
- }
3346
- function setNode(board, props, path) {
3347
- const properties = {};
3348
- const newProperties = {};
3349
- const node = PlaitNode.get(board, path);
3350
- for (const k in props) {
3351
- if (node[k] !== props[k]) {
3352
- if (node.hasOwnProperty(k)) {
3353
- properties[k] = node[k];
3354
- }
3355
- if (props[k] != null)
3356
- newProperties[k] = props[k];
3357
- }
4259
+ else {
4260
+ return {
4261
+ x: 0,
4262
+ y: 0,
4263
+ width: 0,
4264
+ height: 0
4265
+ };
3358
4266
  }
3359
- const operation = { type: 'set_node', properties, newProperties, path };
3360
- board.apply(operation);
3361
- }
3362
- function removeNode(board, path) {
3363
- const node = PlaitNode.get(board, path);
3364
- const operation = { type: 'remove_node', path, node };
3365
- board.apply(operation);
3366
- }
3367
- function moveNode(board, path, newPath) {
3368
- const operation = { type: 'move_node', path, newPath };
3369
- board.apply(operation);
3370
4267
  }
3371
- const NodeTransforms = {
3372
- insertNode,
3373
- setNode,
3374
- removeNode,
3375
- moveNode
3376
- };
3377
-
3378
- function setSelection(board, selection) {
3379
- const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
3380
- board.apply(operation);
4268
+ function getBoardRectangle(board) {
4269
+ return getRectangleByElements(board, board.children, true);
3381
4270
  }
3382
- const SelectionTransforms = {
3383
- setSelection,
3384
- addSelectionWithTemporaryElements
3385
- };
3386
- function addSelectionWithTemporaryElements(board, elements) {
3387
- const timeoutId = setTimeout(() => {
3388
- setSelection(board, { anchor: [0, 0], focus: [0, 0] });
3389
- }, 0);
3390
- let ref = getTemporaryRef(board);
3391
- if (ref) {
3392
- clearTimeout(ref.timeoutId);
3393
- const currentElements = ref.elements;
3394
- ref.elements.push(...elements.filter(element => !currentElements.includes(element)));
3395
- ref.timeoutId = timeoutId;
3396
- }
3397
- else {
3398
- BOARD_TO_TEMPORARY_ELEMENTS.set(board, { timeoutId, elements });
4271
+ function getElementById(board, id, dataSource) {
4272
+ if (!dataSource) {
4273
+ dataSource = findElements(board, { match: element => true, recursion: element => true });
3399
4274
  }
4275
+ let element = dataSource.find(element => element.id === id);
4276
+ return element;
3400
4277
  }
3401
-
3402
- const removeElements = (board, elements) => {
3403
- elements
3404
- .map(element => {
3405
- const path = PlaitBoard.findPath(board, element);
3406
- const ref = board.pathRef(path);
3407
- return () => {
3408
- removeNode(board, ref.current);
3409
- ref.unref();
3410
- removeSelectedElement(board, element, true);
3411
- };
3412
- })
3413
- .forEach(action => {
3414
- action();
3415
- });
3416
- };
3417
- const CoreTransforms = {
3418
- removeElements
3419
- };
3420
-
3421
- const addGroup = (board, elements) => {
3422
- const selectedGroups = getHighestSelectedGroups(board, elements);
3423
- const selectedIsolatedElements = getSelectedIsolatedElementsCanAddToGroup(board);
3424
- const highestSelectedElements = [...selectedGroups, ...selectedIsolatedElements];
3425
- const group = createGroup();
3426
- if (canAddGroup(board)) {
3427
- highestSelectedElements.forEach(item => {
3428
- const path = PlaitBoard.findPath(board, item);
3429
- NodeTransforms.setNode(board, { groupId: group.id }, path);
3430
- });
3431
- if (hasSelectedElementsInSameGroup(highestSelectedElements)) {
3432
- const newGroupId = selectedIsolatedElements[0].groupId;
3433
- NodeTransforms.insertNode(board, {
3434
- ...group,
3435
- groupId: newGroupId
3436
- }, [board.children.length]);
4278
+ function findElements(board, options) {
4279
+ let elements = [];
4280
+ const isReverse = options.isReverse ?? true;
4281
+ depthFirstRecursion(board, node => {
4282
+ if (!PlaitBoard.isBoard(node) && options.match(node)) {
4283
+ elements.push(node);
4284
+ }
4285
+ }, (value) => {
4286
+ if (PlaitBoard.isBoard(value)) {
4287
+ return true;
3437
4288
  }
3438
4289
  else {
3439
- NodeTransforms.insertNode(board, group, [board.children.length]);
4290
+ return getIsRecursionFunc(board)(value) && options.recursion(value);
4291
+ }
4292
+ }, isReverse);
4293
+ return elements;
4294
+ }
4295
+
4296
+ const PlaitBoard = {
4297
+ isBoard(value) {
4298
+ const cachedIsBoard = IS_BOARD_CACHE.get(value);
4299
+ if (cachedIsBoard !== undefined) {
4300
+ return cachedIsBoard;
4301
+ }
4302
+ const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
4303
+ IS_BOARD_CACHE.set(value, isBoard);
4304
+ return isBoard;
4305
+ },
4306
+ isAlive(board) {
4307
+ const isAlive = IS_BOARD_ALIVE.get(board);
4308
+ return !!isAlive;
4309
+ },
4310
+ findPath(board, node) {
4311
+ const path = [];
4312
+ let child = node;
4313
+ while (true) {
4314
+ const parent = NODE_TO_PARENT.get(child);
4315
+ if (parent == null) {
4316
+ if (PlaitBoard.isBoard(child)) {
4317
+ return path;
4318
+ }
4319
+ else {
4320
+ break;
4321
+ }
4322
+ }
4323
+ const i = NODE_TO_INDEX.get(child);
4324
+ if (i == null) {
4325
+ break;
4326
+ }
4327
+ path.unshift(i);
4328
+ child = parent;
4329
+ }
4330
+ throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
4331
+ },
4332
+ getHost(board) {
4333
+ return BOARD_TO_HOST.get(board);
4334
+ },
4335
+ getElementHost(board) {
4336
+ return BOARD_TO_ELEMENT_HOST.get(board)?.host;
4337
+ },
4338
+ getElementUpperHost(board) {
4339
+ return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
4340
+ },
4341
+ getElementActiveHost(board) {
4342
+ return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
4343
+ },
4344
+ getRoughSVG(board) {
4345
+ return BOARD_TO_ROUGH_SVG.get(board);
4346
+ },
4347
+ getComponent(board) {
4348
+ return BOARD_TO_COMPONENT.get(board);
4349
+ },
4350
+ getBoardContainer(board) {
4351
+ return BOARD_TO_ELEMENT_HOST.get(board)?.container;
4352
+ },
4353
+ getRectangle(board) {
4354
+ return getRectangleByElements(board, board.children, true);
4355
+ },
4356
+ getViewportContainer(board) {
4357
+ return BOARD_TO_ELEMENT_HOST.get(board)?.viewportContainer;
4358
+ },
4359
+ isFocus(board) {
4360
+ return !!board.selection;
4361
+ },
4362
+ isReadonly(board) {
4363
+ return board.options.readonly;
4364
+ },
4365
+ hasBeenTextEditing(board) {
4366
+ return !!IS_TEXT_EDITABLE.get(board);
4367
+ },
4368
+ getPointer(board) {
4369
+ return board.pointer;
4370
+ },
4371
+ isPointer(board, pointer) {
4372
+ return board.pointer === pointer;
4373
+ },
4374
+ isInPointer(board, pointers) {
4375
+ const point = board.pointer;
4376
+ return pointers.includes(point);
4377
+ },
4378
+ getMovingPointInBoard(board) {
4379
+ return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
4380
+ },
4381
+ isMovingPointInBoard(board) {
4382
+ const point = BOARD_TO_MOVING_POINT.get(board);
4383
+ const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
4384
+ if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
4385
+ return true;
3440
4386
  }
4387
+ return false;
4388
+ },
4389
+ getThemeColors(board) {
4390
+ return (board.options.themeColors || ThemeColors);
3441
4391
  }
3442
4392
  };
3443
- const removeGroup = (board, elements) => {
3444
- const selectedGroups = getHighestSelectedGroups(board, elements);
3445
- if (canRemoveGroup(board)) {
3446
- selectedGroups.map(group => {
3447
- const elementsInGroup = findElements(board, {
3448
- match: item => item.groupId === group.id,
3449
- recursion: () => false
3450
- });
3451
- elementsInGroup.forEach(item => {
3452
- const path = PlaitBoard.findPath(board, item);
3453
- NodeTransforms.setNode(board, { groupId: group.groupId || undefined }, path);
3454
- });
3455
- const groupPath = PlaitBoard.findPath(board, group);
3456
- NodeTransforms.removeNode(board, groupPath);
3457
- });
3458
- }
3459
- };
3460
- const GroupTransforms = {
3461
- addGroup,
3462
- removeGroup
3463
- };
3464
-
3465
- const Transforms = {
3466
- ...GeneralTransforms,
3467
- ...ViewportTransforms$1,
3468
- ...SelectionTransforms,
3469
- ...NodeTransforms
3470
- };
3471
4393
 
3472
4394
  const PathRef = {
3473
4395
  transform(ref, op) {
@@ -3547,6 +4469,9 @@ function createBoard(children, options) {
3547
4469
  },
3548
4470
  onChange: () => { },
3549
4471
  afterChange: () => { },
4472
+ drawActiveRectangle: () => {
4473
+ return drawEntireActiveRectangleG(board);
4474
+ },
3550
4475
  mousedown: (event) => { },
3551
4476
  mousemove: (event) => { },
3552
4477
  mouseleave: (event) => { },
@@ -3557,19 +4482,16 @@ function createBoard(children, options) {
3557
4482
  globalKeyDown: (event) => { },
3558
4483
  keyUp: (event) => { },
3559
4484
  dblClick: (event) => { },
3560
- setFragment: (data, clipboardContext) => {
3561
- setClipboardData(data, clipboardContext);
3562
- },
3563
- insertFragment: (data) => { },
3564
- deleteFragment: (data) => {
3565
- const elements = board.getDeletedFragment([]);
4485
+ buildFragment: (clipboardContext) => clipboardContext,
4486
+ insertFragment: () => { },
4487
+ deleteFragment: (elements) => {
3566
4488
  CoreTransforms.removeElements(board, elements);
3567
4489
  },
3568
4490
  getDeletedFragment: (data) => data,
3569
- getRelatedFragment: (data) => data,
3570
- drawElement: (context) => [],
3571
- redrawElement: (context, previousContext) => { },
3572
- destroyElement: (context) => { },
4491
+ getRelatedFragment: (data, originData) => data,
4492
+ drawElement: (context) => {
4493
+ throw new Error(`can not resolve plugin element component type: ${context.element.type}`);
4494
+ },
3573
4495
  isWithinSelection: element => false,
3574
4496
  isRectangleHit: element => false,
3575
4497
  isHit: element => false,
@@ -3588,7 +4510,9 @@ function createBoard(children, options) {
3588
4510
  globalPointerMove: pointer => { },
3589
4511
  globalPointerUp: pointer => { },
3590
4512
  isImageBindingAllowed: (element) => false,
3591
- canAddToGroup: (element) => true
4513
+ canAddToGroup: (element) => true,
4514
+ canSetZIndex: (element) => true,
4515
+ isExpanded: (element) => true
3592
4516
  };
3593
4517
  return board;
3594
4518
  }
@@ -3751,7 +4675,7 @@ function withHandPointer(board) {
3751
4675
  }
3752
4676
 
3753
4677
  function withSelection(board) {
3754
- const { pointerDown, pointerUp, pointerMove, globalPointerUp, onChange, afterChange } = board;
4678
+ const { pointerDown, pointerUp, pointerMove, globalPointerUp, onChange, afterChange, drawActiveRectangle } = board;
3755
4679
  let start = null;
3756
4680
  let end = null;
3757
4681
  let selectionMovingG;
@@ -3912,7 +4836,8 @@ function withSelection(board) {
3912
4836
  if (!isSelectionMoving(board)) {
3913
4837
  selectionRectangleG?.remove();
3914
4838
  if (newElements.length > 1) {
3915
- selectionRectangleG = createSelectionRectangleG(board);
4839
+ selectionRectangleG = board.drawActiveRectangle();
4840
+ PlaitBoard.getElementActiveHost(board).append(selectionRectangleG);
3916
4841
  }
3917
4842
  }
3918
4843
  }
@@ -3931,7 +4856,8 @@ function withSelection(board) {
3931
4856
  (currentSelectedElements.length !== previousSelectedElements.length ||
3932
4857
  currentSelectedElements.some((c, index) => c !== previousSelectedElements[index]))) {
3933
4858
  selectionRectangleG?.remove();
3934
- selectionRectangleG = createSelectionRectangleG(board);
4859
+ selectionRectangleG = board.drawActiveRectangle();
4860
+ PlaitBoard.getElementActiveHost(board).append(selectionRectangleG);
3935
4861
  previousSelectedElements = currentSelectedElements;
3936
4862
  }
3937
4863
  }
@@ -3980,354 +4906,194 @@ function withViewport(board) {
3980
4906
  return board;
3981
4907
  }
3982
4908
 
3983
- const ALIGN_TOLERANCE = 2;
3984
- class AlignReaction {
3985
- constructor(board, activeElements, activeRectangle) {
3986
- this.board = board;
3987
- this.activeElements = activeElements;
3988
- this.activeRectangle = activeRectangle;
3989
- this.alignRectangles = this.getAlignRectangle();
3990
- }
3991
- getAlignRectangle() {
3992
- const result = [];
3993
- depthFirstRecursion(this.board, node => {
3994
- if (PlaitBoard.isBoard(node) || this.activeElements.some(element => node.id === element.id) || !this.board.isAlign(node)) {
3995
- return;
3996
- }
3997
- const rectangle = this.board.getRectangle(node);
3998
- rectangle && result.push(rectangle);
3999
- }, node => {
4000
- if (node && (PlaitBoard.isBoard(node) || this.board.isRecursion(node))) {
4001
- return true;
4002
- }
4003
- else {
4004
- 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;
4005
4939
  }
4006
- }, true);
4007
- return result;
4940
+ }
4941
+ return delta;
4008
4942
  }
4009
- handleAlign() {
4010
- const alignRectangles = this.getAlignRectangle();
4011
- const g = createG();
4012
- let alignLines = [];
4013
- const offset = 12;
4014
- let deltaX = 0;
4015
- let deltaY = 0;
4016
- let isCorrectX = false;
4017
- let isCorrectY = false;
4018
- for (let alignRectangle of alignRectangles) {
4019
- const closestDistances = this.calculateClosestDistances(this.activeRectangle, alignRectangle);
4020
- let canDrawHorizontal = false;
4021
- if (!isCorrectX && closestDistances.absXDistance < ALIGN_TOLERANCE) {
4022
- deltaX = closestDistances.xDistance;
4023
- this.activeRectangle.x -= deltaX;
4024
- isCorrectX = true;
4025
- canDrawHorizontal = true;
4026
- }
4027
- if (closestDistances.absXDistance === 0) {
4028
- 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 });
4029
5004
  }
4030
- if (canDrawHorizontal) {
4031
- const verticalY = [
4032
- alignRectangle.y,
4033
- alignRectangle.y + alignRectangle.height,
4034
- this.activeRectangle.y,
4035
- this.activeRectangle.y + this.activeRectangle.height
4036
- ];
4037
- const lineTopY = Math.min(...verticalY) - offset;
4038
- const lineBottomY = Math.max(...verticalY) + offset;
4039
- const leftLine = [this.activeRectangle.x, lineTopY, this.activeRectangle.x, lineBottomY];
4040
- const middleLine = [
4041
- this.activeRectangle.x + this.activeRectangle.width / 2,
4042
- lineTopY,
4043
- this.activeRectangle.x + this.activeRectangle.width / 2,
4044
- lineBottomY
4045
- ];
4046
- const rightLine = [
4047
- this.activeRectangle.x + this.activeRectangle.width,
4048
- lineTopY,
4049
- this.activeRectangle.x + this.activeRectangle.width,
4050
- lineBottomY
4051
- ];
4052
- const shouldDrawLeftLine = closestDistances.indexX === 0 ||
4053
- closestDistances.indexX === 1 ||
4054
- (closestDistances.indexX === 2 && this.activeRectangle.width === alignRectangle.width);
4055
- if (shouldDrawLeftLine && !alignLines[0]) {
4056
- alignLines[0] = leftLine;
4057
- }
4058
- const shouldDrawRightLine = closestDistances.indexX === 2 ||
4059
- closestDistances.indexX === 3 ||
4060
- (closestDistances.indexX === 0 && this.activeRectangle.width === alignRectangle.width);
4061
- if (shouldDrawRightLine && !alignLines[2]) {
4062
- alignLines[2] = rightLine;
4063
- }
4064
- const shouldDrawMiddleLine = closestDistances.indexX === 4 || (!shouldDrawLeftLine && !shouldDrawRightLine);
4065
- if (shouldDrawMiddleLine && !alignLines[1]) {
4066
- alignLines[1] = middleLine;
4067
- }
4068
- isCorrectX = true;
5005
+ else {
5006
+ refArray[i] = { ...refArray[i], after: [{ distance, index: j }] };
4069
5007
  }
4070
- let canDrawVertical = false;
4071
- if (!isCorrectY && closestDistances.absYDistance < ALIGN_TOLERANCE) {
4072
- deltaY = closestDistances.yDistance;
4073
- this.activeRectangle.y -= deltaY;
4074
- isCorrectY = true;
4075
- canDrawVertical = true;
5008
+ if (refArray[j]?.before) {
5009
+ refArray[j].before.push({ distance, index: i });
4076
5010
  }
4077
- if (closestDistances.absYDistance === 0) {
4078
- canDrawVertical = true;
5011
+ else {
5012
+ refArray[j] = { ...refArray[j], before: [{ distance, index: i }] };
4079
5013
  }
4080
- if (canDrawVertical) {
4081
- const horizontalX = [
4082
- alignRectangle.x,
4083
- alignRectangle.x + alignRectangle.width,
4084
- this.activeRectangle.x,
4085
- this.activeRectangle.x + this.activeRectangle.width
4086
- ];
4087
- const lineLeftX = Math.min(...horizontalX) - offset;
4088
- const lineRightX = Math.max(...horizontalX) + offset;
4089
- const topLine = [lineLeftX, this.activeRectangle.y, lineRightX, this.activeRectangle.y];
4090
- const horizontalMiddleLine = [
4091
- lineLeftX,
4092
- this.activeRectangle.y + this.activeRectangle.height / 2,
4093
- lineRightX,
4094
- this.activeRectangle.y + this.activeRectangle.height / 2
4095
- ];
4096
- const bottomLine = [
4097
- lineLeftX,
4098
- this.activeRectangle.y + this.activeRectangle.height,
4099
- lineRightX,
4100
- this.activeRectangle.y + this.activeRectangle.height
4101
- ];
4102
- const shouldDrawTopLine = closestDistances.indexY === 0 ||
4103
- closestDistances.indexY === 1 ||
4104
- (closestDistances.indexY === 2 && this.activeRectangle.height === alignRectangle.height);
4105
- if (shouldDrawTopLine && !alignLines[3]) {
4106
- alignLines[3] = topLine;
4107
- }
4108
- const shouldDrawBottomLine = closestDistances.indexY === 2 ||
4109
- closestDistances.indexY === 3 ||
4110
- (closestDistances.indexY === 0 && this.activeRectangle.width === alignRectangle.width);
4111
- if (shouldDrawBottomLine && !alignLines[5]) {
4112
- alignLines[5] = bottomLine;
4113
- }
4114
- const shouldDrawMiddleLine = closestDistances.indexY === 4 || (!shouldDrawTopLine && !shouldDrawBottomLine);
4115
- if (shouldDrawMiddleLine && !alignLines[4]) {
4116
- alignLines[4] = horizontalMiddleLine;
4117
- }
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;
4118
5022
  }
4119
- }
4120
- const alignDeltaX = deltaX;
4121
- const alignDeltaY = deltaY;
4122
- this.activeRectangle.x += deltaX;
4123
- this.activeRectangle.y += deltaY;
4124
- const distributeHorizontalResult = this.alignDistribute(alignRectangles, true);
4125
- const distributeVerticalResult = this.alignDistribute(alignRectangles, false);
4126
- const distributeLines = [...distributeHorizontalResult.distributeLines, ...distributeVerticalResult.distributeLines];
4127
- if (distributeHorizontalResult.delta) {
4128
- deltaX = distributeHorizontalResult.delta;
4129
- if (alignDeltaX !== deltaX) {
4130
- alignLines[0] = [];
4131
- alignLines[1] = [];
4132
- 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;
4133
5031
  }
4134
- }
4135
- if (distributeVerticalResult.delta) {
4136
- deltaY = distributeVerticalResult.delta;
4137
- if (alignDeltaY !== deltaY) {
4138
- alignLines[3] = [];
4139
- alignLines[4] = [];
4140
- 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;
4141
5040
  }
4142
5041
  }
4143
- if (alignLines.length) {
4144
- this.drawAlignLines(alignLines, g);
4145
- }
4146
- if (distributeLines.length) {
4147
- this.drawDistributeLines(distributeLines, g);
4148
- }
4149
- return { deltaX, deltaY, g };
4150
- }
4151
- calculateClosestDistances(activeRectangle, alignRectangle) {
4152
- const activeRectangleCenter = [activeRectangle.x + activeRectangle.width / 2, activeRectangle.y + activeRectangle.height / 2];
4153
- const alignRectangleCenter = [alignRectangle.x + alignRectangle.width / 2, alignRectangle.y + alignRectangle.height / 2];
4154
- const centerXDistance = activeRectangleCenter[0] - alignRectangleCenter[0];
4155
- const centerYDistance = activeRectangleCenter[1] - alignRectangleCenter[1];
4156
- const leftToLeft = activeRectangle.x - alignRectangle.x;
4157
- const leftToRight = activeRectangle.x - (alignRectangle.x + alignRectangle.width);
4158
- const rightToRight = activeRectangle.x + activeRectangle.width - (alignRectangle.x + alignRectangle.width);
4159
- const rightToLeft = activeRectangle.x + activeRectangle.width - alignRectangle.x;
4160
- const topToTop = activeRectangle.y - alignRectangle.y;
4161
- const topToBottom = activeRectangle.y - (alignRectangle.y + alignRectangle.height);
4162
- const bottomToTop = activeRectangle.y + activeRectangle.height - alignRectangle.y;
4163
- const bottomToBottom = activeRectangle.y + activeRectangle.height - (alignRectangle.y + alignRectangle.height);
4164
- const xDistances = [leftToLeft, leftToRight, rightToRight, rightToLeft, centerXDistance];
4165
- const yDistances = [topToTop, topToBottom, bottomToBottom, bottomToTop, centerYDistance];
4166
- const xDistancesAbs = xDistances.map(distance => Math.abs(distance));
4167
- const yDistancesAbs = yDistances.map(distance => Math.abs(distance));
4168
- const indexX = xDistancesAbs.indexOf(Math.min(...xDistancesAbs));
4169
- const indexY = yDistancesAbs.indexOf(Math.min(...yDistancesAbs));
4170
- return {
4171
- absXDistance: xDistancesAbs[indexX],
4172
- xDistance: xDistances[indexX],
4173
- absYDistance: yDistancesAbs[indexY],
4174
- yDistance: yDistances[indexY],
4175
- indexX,
4176
- indexY
4177
- };
4178
5042
  }
4179
- alignDistribute(alignRectangles, isHorizontal) {
4180
- let distributeLines = [];
4181
- let delta = 0;
4182
- let rectangles = [];
4183
- const axis = isHorizontal ? 'x' : 'y';
4184
- const side = isHorizontal ? 'width' : 'height';
4185
- const activeRectangleCenter = this.activeRectangle[axis] + this.activeRectangle[side] / 2;
4186
- alignRectangles.forEach(rec => {
4187
- const isCross = isHorizontal ? isHorizontalCross(rec, this.activeRectangle) : isVerticalCross(rec, this.activeRectangle);
4188
- if (isCross && !RectangleClient.isHit(rec, this.activeRectangle)) {
4189
- rectangles.push(rec);
4190
- }
4191
- });
4192
- rectangles = [...rectangles, this.activeRectangle].sort((a, b) => a[axis] - b[axis]);
4193
- const refArray = [];
4194
- let distributeDistance = 0;
4195
- let beforeIndex = undefined;
4196
- let afterIndex = undefined;
4197
- for (let i = 0; i < rectangles.length; i++) {
4198
- for (let j = i + 1; j < rectangles.length; j++) {
4199
- const before = rectangles[i];
4200
- const after = rectangles[j];
4201
- const distance = after[axis] - (before[axis] + before[side]);
4202
- let dif = Infinity;
4203
- if (refArray[i]?.after) {
4204
- refArray[i].after.push({ distance, index: j });
4205
- }
4206
- else {
4207
- refArray[i] = { ...refArray[i], after: [{ distance, index: j }] };
4208
- }
4209
- if (refArray[j]?.before) {
4210
- refArray[j].before.push({ distance, index: i });
4211
- }
4212
- else {
4213
- refArray[j] = { ...refArray[j], before: [{ distance, index: i }] };
4214
- }
4215
- //middle
4216
- let _center = (before[axis] + before[side] + after[axis]) / 2;
4217
- dif = Math.abs(activeRectangleCenter - _center);
4218
- if (dif < ALIGN_TOLERANCE) {
4219
- distributeDistance = (after[axis] - (before[axis] + before[side]) - this.activeRectangle[side]) / 2;
4220
- delta = activeRectangleCenter - _center;
4221
- beforeIndex = i;
4222
- afterIndex = j;
4223
- }
4224
- //after
4225
- const distanceRight = after[axis] - (before[axis] + before[side]);
4226
- _center = after[axis] + after[side] + distanceRight + this.activeRectangle[side] / 2;
4227
- dif = Math.abs(activeRectangleCenter - _center);
4228
- if (!distributeDistance && dif < ALIGN_TOLERANCE) {
4229
- distributeDistance = distanceRight;
4230
- beforeIndex = j;
4231
- delta = activeRectangleCenter - _center;
4232
- }
4233
- //before
4234
- const distanceBefore = after[axis] - (before[axis] + before[side]);
4235
- _center = before[axis] - distanceBefore - this.activeRectangle[side] / 2;
4236
- dif = Math.abs(activeRectangleCenter - _center);
4237
- if (!distributeDistance && dif < ALIGN_TOLERANCE) {
4238
- distributeDistance = distanceBefore;
4239
- afterIndex = i;
4240
- delta = activeRectangleCenter - _center;
4241
- }
4242
- }
4243
- }
4244
- const activeIndex = rectangles.indexOf(this.activeRectangle);
4245
- let beforeIndexes = [];
4246
- let afterIndexes = [];
4247
- if (beforeIndex !== undefined) {
4248
- beforeIndexes.push(beforeIndex);
4249
- findRectangle(distributeDistance, refArray[beforeIndex], 'before', beforeIndexes);
4250
- }
4251
- if (afterIndex !== undefined) {
4252
- afterIndexes.push(afterIndex);
4253
- findRectangle(distributeDistance, refArray[afterIndex], 'after', afterIndexes);
4254
- }
4255
- if (beforeIndexes.length || afterIndexes.length) {
4256
- const indexArr = [...beforeIndexes.reverse(), activeIndex, ...afterIndexes];
4257
- this.activeRectangle[axis] -= delta;
4258
- for (let i = 1; i < indexArr.length; i++) {
4259
- distributeLines.push(getLinePoints(rectangles[indexArr[i - 1]], rectangles[indexArr[i]]));
4260
- }
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]]));
4261
5059
  }
4262
- function findRectangle(distance, ref, direction, rectangleIndexes) {
4263
- const arr = ref[direction];
4264
- const index = refArray.indexOf(ref);
4265
- 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);
4266
5070
  return;
4267
- for (let i = 0; i < arr.length; i++) {
4268
- if (Math.abs(arr[i].distance - distance) < 0.1) {
4269
- rectangleIndexes.push(arr[i].index);
4270
- findRectangle(distance, refArray[arr[i].index], direction, rectangleIndexes);
4271
- return;
4272
- }
4273
5071
  }
4274
5072
  }
4275
- function getLinePoints(beforeRectangle, afterRectangle) {
4276
- const oppositeAxis = axis === 'x' ? 'y' : 'x';
4277
- const oppositeSide = side === 'width' ? 'height' : 'width';
4278
- const align = [
4279
- beforeRectangle[oppositeAxis],
4280
- beforeRectangle[oppositeAxis] + beforeRectangle[oppositeSide],
4281
- afterRectangle[oppositeAxis],
4282
- afterRectangle[oppositeAxis] + afterRectangle[oppositeSide]
4283
- ];
4284
- const sortArr = align.sort((a, b) => a - b);
4285
- const average = (sortArr[1] + sortArr[2]) / 2;
4286
- const offset = 3;
4287
- return isHorizontal
4288
- ? [
4289
- [beforeRectangle.x + beforeRectangle.width + offset, average],
4290
- [afterRectangle.x - offset, average]
4291
- ]
4292
- : [
4293
- [average, beforeRectangle.y + beforeRectangle.height + offset],
4294
- [average, afterRectangle.y - offset]
4295
- ];
4296
- }
4297
- return { delta, distributeLines };
4298
- }
4299
- drawAlignLines(lines, g) {
4300
- lines.forEach(points => {
4301
- if (!points.length)
4302
- return;
4303
- const xAlign = PlaitBoard.getRoughSVG(this.board).line(points[0], points[1], points[2], points[3], {
4304
- stroke: SELECTION_BORDER_COLOR,
4305
- strokeWidth: 1,
4306
- strokeLineDash: [4, 4]
4307
- });
4308
- g.appendChild(xAlign);
4309
- });
4310
5073
  }
4311
- drawDistributeLines(lines, g) {
4312
- lines.forEach(line => {
4313
- if (!line.length)
4314
- return;
4315
- let isHorizontal = line[0][1] === line[1][1];
4316
- const yAlign = PlaitBoard.getRoughSVG(this.board).line(line[0][0], line[0][1], line[1][0], line[1][1], {
4317
- stroke: SELECTION_BORDER_COLOR,
4318
- strokeWidth: 1
4319
- });
4320
- g.appendChild(yAlign);
4321
- line.forEach(point => {
4322
- const barPoint = getBarPoint(point, isHorizontal);
4323
- const bar = PlaitBoard.getRoughSVG(this.board).line(barPoint[0][0], barPoint[0][1], barPoint[1][0], barPoint[1][1], {
4324
- stroke: SELECTION_BORDER_COLOR,
4325
- strokeWidth: 1
4326
- });
4327
- g.appendChild(bar);
4328
- });
4329
- });
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
+ ];
4330
5095
  }
5096
+ return { delta, lines };
4331
5097
  }
4332
5098
  function isHorizontalCross(rectangle, other) {
4333
5099
  return !(rectangle.y + rectangle.height < other.y || rectangle.y > other.y + other.height);
@@ -4335,17 +5101,6 @@ function isHorizontalCross(rectangle, other) {
4335
5101
  function isVerticalCross(rectangle, other) {
4336
5102
  return !(rectangle.x + rectangle.width < other.x || rectangle.x > other.x + other.width);
4337
5103
  }
4338
- function getBarPoint(point, isHorizontal) {
4339
- return isHorizontal
4340
- ? [
4341
- [point[0], point[1] - 4],
4342
- [point[0], point[1] + 4]
4343
- ]
4344
- : [
4345
- [point[0] - 4, point[1]],
4346
- [point[0] + 4, point[1]]
4347
- ];
4348
- }
4349
5104
 
4350
5105
  function withMoving(board) {
4351
5106
  const { pointerDown, pointerMove, globalPointerUp, globalPointerMove } = board;
@@ -4354,7 +5109,7 @@ function withMoving(board) {
4354
5109
  let isPreventDefault = false;
4355
5110
  let startPoint;
4356
5111
  let activeElements = [];
4357
- let alignG = null;
5112
+ let snapG = null;
4358
5113
  let activeElementsRectangle = null;
4359
5114
  let selectedTargetElements = null;
4360
5115
  let hitTargetElement = undefined;
@@ -4379,7 +5134,8 @@ function withMoving(board) {
4379
5134
  }
4380
5135
  else if (hitTargetElement) {
4381
5136
  startPoint = point;
4382
- activeElements = getElementsInGroupByElement(board, hitTargetElement);
5137
+ const relatedElements = board.getRelatedFragment([], [hitTargetElement]);
5138
+ activeElements = [...getElementsInGroupByElement(board, hitTargetElement), ...relatedElements];
4383
5139
  activeElementsRectangle = getRectangleByElements(board, activeElements, true);
4384
5140
  preventTouchMove(board, event, true);
4385
5141
  }
@@ -4402,7 +5158,7 @@ function withMoving(board) {
4402
5158
  if (!isPreventDefault) {
4403
5159
  isPreventDefault = true;
4404
5160
  }
4405
- alignG?.remove();
5161
+ snapG?.remove();
4406
5162
  const endPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4407
5163
  offsetX = endPoint[0] - startPoint[0];
4408
5164
  offsetY = endPoint[1] - startPoint[1];
@@ -4423,13 +5179,13 @@ function withMoving(board) {
4423
5179
  x: activeElementsRectangle.x + offsetX,
4424
5180
  y: activeElementsRectangle.y + offsetY
4425
5181
  };
4426
- const reactionManager = new AlignReaction(board, activeElements, newRectangle);
4427
- const ref = reactionManager.handleAlign();
4428
- offsetX -= ref.deltaX;
4429
- offsetY -= ref.deltaY;
4430
- alignG = ref.g;
4431
- alignG.classList.add(ACTIVE_MOVING_CLASS_NAME);
4432
- 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);
4433
5189
  handleTouchTarget(board);
4434
5190
  const currentElements = updatePoints(board, activeElements, offsetX, offsetY);
4435
5191
  PlaitBoard.getBoardContainer(board).classList.add('element-moving');
@@ -4464,7 +5220,7 @@ function withMoving(board) {
4464
5220
  globalPointerUp(event);
4465
5221
  };
4466
5222
  function cancelMove(board) {
4467
- alignG?.remove();
5223
+ snapG?.remove();
4468
5224
  startPoint = null;
4469
5225
  activeElementsRectangle = null;
4470
5226
  offsetX = 0;
@@ -4648,12 +5404,41 @@ const withHotkey = (board) => {
4648
5404
  Transforms.addSelectionWithTemporaryElements(board, elements);
4649
5405
  return;
4650
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
+ }
4651
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
+ }
4652
5437
  if (!PlaitBoard.isReadonly(board) &&
4653
5438
  selectedElements.length > 0 &&
4654
5439
  (hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event))) {
4655
5440
  event.preventDefault();
4656
- board.deleteFragment(null);
5441
+ deleteFragment(board);
4657
5442
  }
4658
5443
  keyDown(event);
4659
5444
  };
@@ -4708,162 +5493,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
4708
5493
  type: Injectable
4709
5494
  }] });
4710
5495
 
4711
- class PlaitElementComponent {
4712
- constructor(renderer2, viewContainerRef) {
4713
- this.renderer2 = renderer2;
4714
- this.viewContainerRef = viewContainerRef;
4715
- this.initialized = false;
4716
- }
4717
- ngOnInit() {
4718
- this.initialize();
4719
- this.drawElement();
4720
- }
4721
- initialize() {
4722
- NODE_TO_INDEX.set(this.element, this.index);
4723
- NODE_TO_PARENT.set(this.element, this.parent);
4724
- this.initialized = true;
4725
- }
4726
- drawElement() {
4727
- const context = this.getContext();
4728
- const result = this.board.drawElement(context);
4729
- if (Array.isArray(result)) {
4730
- }
4731
- else {
4732
- const componentRef = this.viewContainerRef.createComponent(result);
4733
- const instance = componentRef.instance;
4734
- instance.context = context;
4735
- this.insertG(instance.rootG ? instance.rootG : instance.g);
4736
- this.instance = instance;
4737
- }
4738
- }
4739
- insertG(g) {
4740
- if (PlaitBoard.isBoard(this.parent)) {
4741
- this.parentG.append(g);
4742
- }
4743
- else {
4744
- let siblingG = PlaitElement.getComponent(this.parent).g;
4745
- if (this.index > 0) {
4746
- const brotherElement = this.parent.children[this.index - 1];
4747
- const lastElement = PlaitNode.last(this.board, PlaitBoard.findPath(this.board, brotherElement));
4748
- let component = PlaitElement.getComponent(lastElement) || PlaitElement.getComponent(brotherElement);
4749
- siblingG = component.g;
4750
- }
4751
- this.parentG.insertBefore(g, siblingG);
4752
- }
4753
- }
4754
- ngOnChanges(simpleChanges) {
4755
- if (this.initialized) {
4756
- NODE_TO_INDEX.set(this.element, this.index);
4757
- NODE_TO_PARENT.set(this.element, this.parent);
4758
- const elementChanged = simpleChanges['element'];
4759
- const context = this.getContext();
4760
- if (elementChanged && isSelectedElement(this.board, elementChanged.previousValue)) {
4761
- context.selected = true;
4762
- removeSelectedElement(this.board, elementChanged.previousValue);
4763
- addSelectedElement(this.board, this.element);
4764
- }
4765
- if (this.instance) {
4766
- this.instance.context = context;
4767
- }
4768
- }
4769
- }
4770
- getContext() {
4771
- const isSelected = isSelectedElement(this.board, this.element);
4772
- const context = {
4773
- element: this.element,
4774
- parent: this.parent,
4775
- board: this.board,
4776
- selected: isSelected,
4777
- effect: this.effect
4778
- };
4779
- return context;
4780
- }
4781
- ngOnDestroy() {
4782
- this.board.destroyElement(this.getContext());
4783
- }
4784
- 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 }); }
4785
- 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 }); }
4786
- }
4787
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitElementComponent, decorators: [{
4788
- type: Component,
4789
- args: [{
4790
- selector: 'plait-element',
4791
- template: '',
4792
- changeDetection: ChangeDetectionStrategy.OnPush,
4793
- standalone: true
4794
- }]
4795
- }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ViewContainerRef }], propDecorators: { index: [{
4796
- type: Input
4797
- }], element: [{
4798
- type: Input
4799
- }], parent: [{
4800
- type: Input
4801
- }], board: [{
4802
- type: Input
4803
- }], effect: [{
4804
- type: Input
4805
- }], parentG: [{
4806
- type: Input
4807
- }] } });
4808
-
4809
- class PlaitChildrenElementComponent {
4810
- constructor() {
4811
- this.trackBy = (index, element) => {
4812
- return element.id;
4813
- };
4814
- }
4815
- ngOnInit() {
4816
- if (!this.parent) {
4817
- this.parent = this.board;
4818
- }
4819
- if (!this.parentG) {
4820
- this.parentG = PlaitBoard.getElementHost(this.board);
4821
- }
4822
- }
4823
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitChildrenElementComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4824
- 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: `
4825
- <plait-element
4826
- *ngFor="let item of parent.children; let index = index; trackBy: trackBy"
4827
- [index]="index"
4828
- [element]="item"
4829
- [parent]="parent"
4830
- [board]="board"
4831
- [effect]="effect"
4832
- [parentG]="parentG"
4833
- ></plait-element>
4834
- `, 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"] }] }); }
4835
- }
4836
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitChildrenElementComponent, decorators: [{
4837
- type: Component,
4838
- args: [{
4839
- selector: 'plait-children',
4840
- template: `
4841
- <plait-element
4842
- *ngFor="let item of parent.children; let index = index; trackBy: trackBy"
4843
- [index]="index"
4844
- [element]="item"
4845
- [parent]="parent"
4846
- [board]="board"
4847
- [effect]="effect"
4848
- [parentG]="parentG"
4849
- ></plait-element>
4850
- `,
4851
- standalone: true,
4852
- imports: [NgFor, PlaitElementComponent]
4853
- }]
4854
- }], ctorParameters: () => [], propDecorators: { board: [{
4855
- type: Input
4856
- }], parent: [{
4857
- type: Input
4858
- }], effect: [{
4859
- type: Input
4860
- }], parentG: [{
4861
- type: Input
4862
- }] } });
4863
-
4864
5496
  function withRelatedFragment(board) {
4865
- const { setFragment } = board;
4866
- board.setFragment = (data, clipboardContext, rectangle, type) => {
5497
+ const { buildFragment } = board;
5498
+ board.buildFragment = (clipboardContext, rectangle, type) => {
4867
5499
  const relatedFragment = board.getRelatedFragment([]);
4868
5500
  if (!clipboardContext) {
4869
5501
  clipboardContext = createClipboardContext(WritableClipboardType.elements, relatedFragment, '');
@@ -4872,10 +5504,10 @@ function withRelatedFragment(board) {
4872
5504
  clipboardContext = addClipboardContext(clipboardContext, {
4873
5505
  text: '',
4874
5506
  type: WritableClipboardType.elements,
4875
- data: relatedFragment
5507
+ elements: relatedFragment
4876
5508
  });
4877
5509
  }
4878
- setFragment(data, clipboardContext, rectangle, type);
5510
+ return buildFragment(clipboardContext, rectangle, type);
4879
5511
  };
4880
5512
  return board;
4881
5513
  }
@@ -4920,7 +5552,6 @@ class PlaitBoardComponent {
4920
5552
  this.elementRef = elementRef;
4921
5553
  this.ngZone = ngZone;
4922
5554
  this.hasInitialized = false;
4923
- this.effect = {};
4924
5555
  this.destroy$ = new Subject();
4925
5556
  this.plaitValue = [];
4926
5557
  this.plaitPlugins = [];
@@ -4953,6 +5584,7 @@ class PlaitBoardComponent {
4953
5584
  BOARD_TO_COMPONENT.set(this.board, this);
4954
5585
  BOARD_TO_ROUGH_SVG.set(this.board, roughSVG);
4955
5586
  BOARD_TO_HOST.set(this.board, this.host);
5587
+ IS_BOARD_ALIVE.set(this.board, true);
4956
5588
  BOARD_TO_ELEMENT_HOST.set(this.board, {
4957
5589
  host: elementHost,
4958
5590
  upperHost: elementUpperHost,
@@ -4962,7 +5594,7 @@ class PlaitBoardComponent {
4962
5594
  });
4963
5595
  BOARD_TO_ON_CHANGE.set(this.board, () => {
4964
5596
  this.ngZone.run(() => {
4965
- this.update();
5597
+ this.updateListRender();
4966
5598
  });
4967
5599
  });
4968
5600
  BOARD_TO_AFTER_CHANGE.set(this.board, () => {
@@ -4978,15 +5610,12 @@ class PlaitBoardComponent {
4978
5610
  this.plaitChange.emit(changeEvent);
4979
5611
  });
4980
5612
  });
5613
+ this.initializeListRender();
4981
5614
  this.hasInitialized = true;
4982
5615
  }
4983
5616
  ngAfterContentInit() {
4984
5617
  this.initializeIslands();
4985
5618
  }
4986
- update() {
4987
- this.effect = {};
4988
- this.cdr.detectChanges();
4989
- }
4990
5619
  ngOnChanges(changes) {
4991
5620
  if (this.hasInitialized) {
4992
5621
  const valueChange = changes['plaitValue'];
@@ -5104,10 +5733,8 @@ class PlaitBoardComponent {
5104
5733
  fromEvent(document, 'copy')
5105
5734
  .pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.hasBeenTextEditing(this.board)))
5106
5735
  .subscribe((event) => {
5107
- const selectedElements = getSelectedElements(this.board);
5108
5736
  event.preventDefault();
5109
- const rectangle = getRectangleByElements(this.board, selectedElements, false);
5110
- this.board.setFragment(event.clipboardData, null, rectangle, 'copy');
5737
+ setFragment(this.board, 'copy', event.clipboardData);
5111
5738
  });
5112
5739
  fromEvent(document, 'paste')
5113
5740
  .pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.isReadonly(this.board) && !PlaitBoard.hasBeenTextEditing(this.board)))
@@ -5116,19 +5743,31 @@ class PlaitBoardComponent {
5116
5743
  if (mousePoint) {
5117
5744
  const targetPoint = toViewBoxPoint(this.board, toHostPoint(this.board, mousePoint[0], mousePoint[1]));
5118
5745
  const clipboardData = await getClipboardData(clipboardEvent.clipboardData);
5119
- this.board.insertFragment(clipboardEvent.clipboardData, clipboardData, targetPoint);
5746
+ this.board.insertFragment(clipboardData, targetPoint);
5120
5747
  }
5121
5748
  });
5122
5749
  fromEvent(document, 'cut')
5123
5750
  .pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.isReadonly(this.board) && !PlaitBoard.hasBeenTextEditing(this.board)))
5124
5751
  .subscribe((event) => {
5125
- const selectedElements = getSelectedElements(this.board);
5126
5752
  event.preventDefault();
5127
- const rectangle = getRectangleByElements(this.board, selectedElements, false);
5128
- this.board.setFragment(event.clipboardData, null, rectangle, 'cut');
5129
- this.board.deleteFragment(event.clipboardData);
5753
+ setFragment(this.board, 'cut', event.clipboardData);
5754
+ deleteFragment(this.board);
5130
5755
  });
5131
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
+ }
5132
5771
  viewportScrollListener() {
5133
5772
  fromEvent(this.viewportContainer.nativeElement, 'scroll')
5134
5773
  .pipe(takeUntil(this.destroy$), filter(() => {
@@ -5208,6 +5847,7 @@ class PlaitBoardComponent {
5208
5847
  BOARD_TO_ROUGH_SVG.delete(this.board);
5209
5848
  BOARD_TO_HOST.delete(this.board);
5210
5849
  BOARD_TO_ELEMENT_HOST.delete(this.board);
5850
+ IS_BOARD_ALIVE.set(this.board, false);
5211
5851
  BOARD_TO_ON_CHANGE.delete(this.board);
5212
5852
  BOARD_TO_AFTER_CHANGE.set(this.board, () => { });
5213
5853
  }
@@ -5225,10 +5865,9 @@ class PlaitBoardComponent {
5225
5865
  <g class="element-upper-host"></g>
5226
5866
  <g class="element-active-host"></g>
5227
5867
  </svg>
5228
- <plait-children [board]="board" [effect]="effect"></plait-children>
5229
5868
  </div>
5230
5869
  <ng-content></ng-content>
5231
- `, 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 }); }
5232
5871
  }
5233
5872
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitBoardComponent, decorators: [{
5234
5873
  type: Component,
@@ -5241,14 +5880,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
5241
5880
  <g class="element-upper-host"></g>
5242
5881
  <g class="element-active-host"></g>
5243
5882
  </svg>
5244
- <plait-children [board]="board" [effect]="effect"></plait-children>
5245
5883
  </div>
5246
5884
  <ng-content></ng-content>
5247
5885
  `,
5248
5886
  changeDetection: ChangeDetectionStrategy.OnPush,
5249
5887
  providers: [PlaitContextService],
5250
- standalone: true,
5251
- imports: [PlaitChildrenElementComponent]
5888
+ standalone: true
5252
5889
  }]
5253
5890
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ViewContainerRef }, { type: i0.ElementRef }, { type: i0.NgZone }], propDecorators: { plaitValue: [{
5254
5891
  type: Input
@@ -5474,12 +6111,25 @@ class DebugGenerator {
5474
6111
  return;
5475
6112
  }
5476
6113
  const polygonG = PlaitBoard.getRoughSVG(board).polygon(points, options || { stroke: 'red' });
6114
+ polygonG.classList.add(this.debugKey);
5477
6115
  PlaitBoard.getElementActiveHost(board).append(polygonG);
5478
6116
  const gArray = getTemporaryGArray(this.debugKey);
5479
6117
  gArray.push(polygonG);
5480
6118
  setTemporaryGArray(this.debugKey, gArray);
5481
6119
  return polygonG;
5482
6120
  }
6121
+ drawLine(board, points, options) {
6122
+ if (!isDebug(this.debugKey)) {
6123
+ return;
6124
+ }
6125
+ const lineG = PlaitBoard.getRoughSVG(board).linearPath(points, options || { stroke: 'red' });
6126
+ lineG.classList.add(this.debugKey);
6127
+ PlaitBoard.getElementActiveHost(board).append(lineG);
6128
+ const gArray = getTemporaryGArray(this.debugKey);
6129
+ gArray.push(lineG);
6130
+ setTemporaryGArray(this.debugKey, gArray);
6131
+ return lineG;
6132
+ }
5483
6133
  drawRectangle(board, data, options) {
5484
6134
  if (!isDebug(this.debugKey)) {
5485
6135
  return;
@@ -5492,6 +6142,7 @@ class DebugGenerator {
5492
6142
  rectangle = data;
5493
6143
  }
5494
6144
  const rectangleG = PlaitBoard.getRoughSVG(board).rectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height, options || { stroke: 'red' });
6145
+ rectangleG.classList.add(this.debugKey);
5495
6146
  PlaitBoard.getElementActiveHost(board).append(rectangleG);
5496
6147
  const gArray = getTemporaryGArray(this.debugKey);
5497
6148
  gArray.push(rectangleG);
@@ -5505,6 +6156,7 @@ class DebugGenerator {
5505
6156
  const result = [];
5506
6157
  points.forEach((p, i) => {
5507
6158
  const circle = PlaitBoard.getRoughSVG(board).circle(p[0], p[1], isCumulativeDiameter ? diameter * (i + 1) : diameter, Object.assign({}, { stroke: 'red', fill: 'red', fillStyle: 'solid' }, options || {}));
6159
+ circle.classList.add(this.debugKey);
5508
6160
  PlaitBoard.getElementActiveHost(board).append(circle);
5509
6161
  const gArray = getTemporaryGArray(this.debugKey);
5510
6162
  gArray.push(circle);
@@ -5530,5 +6182,5 @@ const isDebug = (key) => {
5530
6182
  * Generated bundle index. Do not edit.
5531
6183
  */
5532
6184
 
5533
- 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_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, 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, createSelectionRectangleG, createTestingBoard, createText, createTouchEvent, debounce, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, 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, 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, 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, removeMovingElements, removeSelectedElement, rotate, rotateAntiPointsByElement, 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 };
5534
6186
  //# sourceMappingURL=plait-core.mjs.map