@plait/core 0.54.0 → 0.55.1

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