@plait/draw 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/constants/geometry.d.ts +25 -1
  2. package/engines/basic-shapes/circle.d.ts +8 -0
  3. package/engines/basic-shapes/cloud.d.ts +2 -0
  4. package/engines/flowchart/off-page.d.ts +4 -0
  5. package/engines/flowchart/or.d.ts +2 -0
  6. package/engines/flowchart/predefined-process.d.ts +2 -0
  7. package/engines/flowchart/summing-junction.d.ts +2 -0
  8. package/esm2022/constants/geometry.mjs +15 -3
  9. package/esm2022/engines/basic-shapes/circle.mjs +51 -0
  10. package/esm2022/engines/basic-shapes/cloud.mjs +57 -0
  11. package/esm2022/engines/basic-shapes/ellipse.mjs +3 -41
  12. package/esm2022/engines/flowchart/off-page.mjs +32 -0
  13. package/esm2022/engines/flowchart/or.mjs +25 -0
  14. package/esm2022/engines/flowchart/predefined-process.mjs +47 -0
  15. package/esm2022/engines/flowchart/summing-junction.mjs +28 -0
  16. package/esm2022/engines/index.mjs +12 -2
  17. package/esm2022/generators/line-auto-complete.generator.mjs +1 -1
  18. package/esm2022/geometry.component.mjs +7 -8
  19. package/esm2022/image.component.mjs +6 -7
  20. package/esm2022/interfaces/element.mjs +2 -1
  21. package/esm2022/interfaces/geometry.mjs +6 -1
  22. package/esm2022/interfaces/index.mjs +2 -2
  23. package/esm2022/line.component.mjs +8 -9
  24. package/esm2022/plugins/with-draw-fragment.mjs +7 -7
  25. package/esm2022/plugins/with-draw-hotkey.mjs +1 -1
  26. package/esm2022/plugins/with-draw-resize.mjs +73 -26
  27. package/esm2022/plugins/with-draw-rotate.mjs +127 -0
  28. package/esm2022/plugins/with-draw.mjs +6 -5
  29. package/esm2022/plugins/with-geometry-create.mjs +21 -5
  30. package/esm2022/plugins/with-geometry-resize.mjs +17 -23
  31. package/esm2022/plugins/with-line-auto-complete-reaction.mjs +2 -2
  32. package/esm2022/plugins/with-line-auto-complete.mjs +15 -13
  33. package/esm2022/plugins/with-line-bound-reaction.mjs +19 -21
  34. package/esm2022/plugins/with-line-create.mjs +4 -4
  35. package/esm2022/plugins/with-line-resize.mjs +11 -12
  36. package/esm2022/transforms/line.mjs +4 -4
  37. package/esm2022/utils/geometry.mjs +35 -23
  38. package/esm2022/utils/hit.mjs +26 -26
  39. package/esm2022/utils/line/elbow.mjs +11 -6
  40. package/esm2022/utils/line/line-basic.mjs +25 -30
  41. package/esm2022/utils/line/line-common.mjs +3 -3
  42. package/esm2022/utils/line/line-resize.mjs +2 -2
  43. package/esm2022/utils/position/geometry.mjs +59 -21
  44. package/esm2022/utils/shape.mjs +2 -2
  45. package/esm2022/utils/snap-resizing.mjs +185 -0
  46. package/esm2022/utils/style/stroke.mjs +9 -2
  47. package/fesm2022/plait-draw.mjs +1393 -1121
  48. package/fesm2022/plait-draw.mjs.map +1 -1
  49. package/generators/line-auto-complete.generator.d.ts +3 -3
  50. package/geometry.component.d.ts +2 -3
  51. package/image.component.d.ts +2 -3
  52. package/interfaces/element.d.ts +2 -1
  53. package/interfaces/geometry.d.ts +7 -2
  54. package/interfaces/index.d.ts +2 -2
  55. package/line.component.d.ts +2 -3
  56. package/package.json +1 -1
  57. package/plugins/with-draw-fragment.d.ts +2 -2
  58. package/plugins/with-draw-resize.d.ts +9 -5
  59. package/plugins/with-draw-rotate.d.ts +2 -0
  60. package/utils/geometry.d.ts +7 -4
  61. package/utils/hit.d.ts +3 -1
  62. package/utils/line/elbow.d.ts +3 -0
  63. package/utils/line/line-basic.d.ts +4 -4
  64. package/utils/position/geometry.d.ts +16 -2
  65. package/utils/shape.d.ts +2 -2
  66. package/utils/snap-resizing.d.ts +25 -0
  67. package/esm2022/utils/resize-align-reaction.mjs +0 -316
  68. package/esm2022/utils/resize-align.mjs +0 -37
  69. package/utils/resize-align-reaction.d.ts +0 -42
  70. package/utils/resize-align.d.ts +0 -8
@@ -1,5 +1,5 @@
1
- import { ACTIVE_STROKE_WIDTH, ThemeColorMode, createDebugGenerator, Point, RectangleClient, getElementById, rotatePointsByElement, createG, arrowPoints, createPath, distanceBetweenPointAndPoint, drawLinearPath, rotate, rotatePoints, depthFirstRecursion, rotateAntiPointsByElement, getIsRecursionFunc, idCreator, catmullRomFitting, PlaitBoard, findElements, createMask, createRect, getSelectedElements, distanceBetweenPointAndSegments, HIT_DISTANCE_BUFFER, isPolylineHitRectangle, isPointInPolygon, setStrokeLinecap, getNearestPointBetweenPointAndSegments, isPointInEllipse, getEllipseTangentSlope, getVectorFromPointAndSlope, drawRectangle, drawRoundRectangle, isPointInRoundRectangle, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, drawCircle, Transforms, clearSelectedElement, addSelectedElement, BoardTransforms, PlaitPointerType, Direction, hasValidAngle, Path, PlaitNode, toViewBoxPoint, toHostPoint, isSelectionMoving, RgbaToHEX, PlaitElement, getHitElementByPoint, preventTouchMove, createClipboardContext, WritableClipboardType, addClipboardContext, getRectangleByElements, getSelectionAngle, rotatedDataPoints, setAngleForG, CursorClass, temporaryDisableSelection, PRESS_AND_MOVE_BUFFER } from '@plait/core';
2
- import { removeDuplicatePoints, generateElbowLineRoute, simplifyOrthogonalPoints, isSourceAndTargetIntersect, getPoints, getPointByVectorComponent, getExtendPoint, getUnitVectorByPointAndPoint, Generator, getRectangleResizeHandleRefs, RESIZE_HANDLE_DIAMETER, getMemorizedLatest, memorizeLatest, getPointOnPolyline, TRANSPARENT, getCrossingPointsBetweenPointAndSegment, isPointOnSegment, getDirectionByVector, getOppositeDirection, getDirectionFactor, rotateVector, getDirectionByPointOfRectangle, rotateVectorAnti90, getSourceAndTargetOuterRectangle, getNextPoint, normalizeShapePoints, getFirstTextEditor, PRIMARY_COLOR, CommonPluginElement, ActiveGenerator, WithTextPluginKey, drawPrimaryHandle, drawFillPrimaryHandle, isVirtualKey, isDelete, isSpaceHotkey, isDndMode, isDrawingMode, getElementsText, acceptImageTypes, getElementOfFocusedImage, buildImage, getDirectionFactorByDirectionComponent, isCornerHandle, resetPointsAfterResize, getFirstTextManage, withResize, drawHandle, getIndexByResizeHandle, getSymmetricHandleIndex, getResizeHandlePointByIndex, isResizingByCondition, getRatioByPoint, ImageGenerator, ResizeHandle } from '@plait/common';
1
+ import { ACTIVE_STROKE_WIDTH, ThemeColorMode, createDebugGenerator, Point, RectangleClient, getElementById, rotatePointsByElement, createG, arrowPoints, createPath, distanceBetweenPointAndPoint, drawLinearPath, rotate, distanceBetweenPointAndSegments, HIT_DISTANCE_BUFFER, isPolylineHitRectangle, rotateAntiPointsByElement, rotatePoints, depthFirstRecursion, PlaitBoard, getIsRecursionFunc, idCreator, catmullRomFitting, setStrokeLinecap, findElements, createMask, createRect, getSelectedElements, isPointInPolygon, getNearestPointBetweenPointAndSegments, isPointInEllipse, getEllipseTangentSlope, getVectorFromPointAndSlope, drawRectangle, drawRoundRectangle, isPointInRoundRectangle, setPathStrokeLinecap, getCrossingPointsBetweenEllipseAndSegment, SNAPPING_STROKE_WIDTH, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, drawCircle, Transforms, clearSelectedElement, addSelectedElement, BoardTransforms, PlaitPointerType, Direction, hasValidAngle, Path, PlaitNode, toViewBoxPoint, toHostPoint, isSelectionMoving, RgbaToHEX, PlaitElement, getHitElementByPoint, getRectangleByElements, getSelectionAngle, rotatedDataPoints, isAxisChangedByAngle, getRectangleByAngle, getSnapRectangles, getTripleAxis, getMinPointDelta, SNAP_TOLERANCE, drawPointSnapLines, drawSolidLines, preventTouchMove, createClipboardContext, WritableClipboardType, addClipboardContext, setAngleForG, CursorClass, temporaryDisableSelection, PRESS_AND_MOVE_BUFFER, isMainPointer, throttleRAF, getAngleBetweenPoints, degreesToRadians, normalizeAngle, rotateElements, MERGING, ROTATE_HANDLE_CLASS_NAME, SELECTION_RECTANGLE_CLASS_NAME } from '@plait/core';
2
+ import { removeDuplicatePoints, generateElbowLineRoute, simplifyOrthogonalPoints, isSourceAndTargetIntersect, getPoints, DEFAULT_ROUTE_MARGIN, getPointByVectorComponent, getExtendPoint, getUnitVectorByPointAndPoint, Generator, RESIZE_HANDLE_DIAMETER, getPointOnPolyline, TRANSPARENT, getRectangleResizeHandleRefs, getRotatedResizeCursorClassByAngle, ROTATE_HANDLE_DISTANCE_TO_ELEMENT, ROTATE_HANDLE_SIZE, getMemorizedLatest, memorizeLatest, getCrossingPointsBetweenPointAndSegment, isPointOnSegment, getDirectionByVector, getOppositeDirection, getDirectionFactor, rotateVector, getDirectionByPointOfRectangle, rotateVectorAnti90, getSourceAndTargetOuterRectangle, getNextPoint, normalizeShapePoints, getFirstTextEditor, PRIMARY_COLOR, CommonPluginElement, ActiveGenerator, WithTextPluginKey, drawPrimaryHandle, drawFillPrimaryHandle, isVirtualKey, isDelete, isSpaceHotkey, isCornerHandle, getIndexByResizeHandle, resetPointsAfterResize, getFirstTextManage, withResize, drawHandle, getSymmetricHandleIndex, getResizeHandlePointByIndex, getDirectionFactorByDirectionComponent, isDndMode, isDrawingMode, getElementsText, acceptImageTypes, getElementOfFocusedImage, buildImage, isResizingByCondition, getRatioByPoint, ImageGenerator, ResizeHandle, addRotating, removeRotating, drawRotateHandle } from '@plait/common';
3
3
  import { Alignment, buildText, DEFAULT_FONT_SIZE, getTextSize, AlignEditor, TextManage } from '@plait/text';
4
4
  import { pointsOnBezierCurves } from 'points-on-curve';
5
5
  import * as i0 from '@angular/core';
@@ -30,6 +30,7 @@ var BasicShapes;
30
30
  BasicShapes["twoWayArrow"] = "twoWayArrow";
31
31
  BasicShapes["comment"] = "comment";
32
32
  BasicShapes["roundComment"] = "roundComment";
33
+ BasicShapes["cloud"] = "cloud";
33
34
  })(BasicShapes || (BasicShapes = {}));
34
35
  var FlowchartSymbols;
35
36
  (function (FlowchartSymbols) {
@@ -44,6 +45,10 @@ var FlowchartSymbols;
44
45
  FlowchartSymbols["merge"] = "merge";
45
46
  FlowchartSymbols["delay"] = "delay";
46
47
  FlowchartSymbols["storedData"] = "storedData";
48
+ FlowchartSymbols["or"] = "or";
49
+ FlowchartSymbols["summingJunction"] = "summingJunction";
50
+ FlowchartSymbols["predefinedProcess"] = "predefinedProcess";
51
+ FlowchartSymbols["offPage"] = "offPage";
47
52
  })(FlowchartSymbols || (FlowchartSymbols = {}));
48
53
  const PlaitGeometry = {};
49
54
 
@@ -66,6 +71,12 @@ const DefaultBasicShapeProperty = {
66
71
  strokeColor: '#333',
67
72
  strokeWidth: 2
68
73
  };
74
+ const DefaultCloudShapeProperty = {
75
+ width: 120,
76
+ height: 100,
77
+ strokeColor: '#333',
78
+ strokeWidth: 2
79
+ };
69
80
  const DefaultTextProperty = {
70
81
  width: 36,
71
82
  height: 20,
@@ -109,9 +120,15 @@ const DefaultFlowchartPropertyMap = {
109
120
  [FlowchartSymbols.manualLoop]: DefaultFlowchartProperty,
110
121
  [FlowchartSymbols.merge]: DefaultMergeProperty,
111
122
  [FlowchartSymbols.delay]: DefaultFlowchartProperty,
112
- [FlowchartSymbols.storedData]: DefaultFlowchartProperty
113
- };
114
- const REACTION_MARGIN = -4;
123
+ [FlowchartSymbols.storedData]: DefaultFlowchartProperty,
124
+ [FlowchartSymbols.or]: DefaultConnectorProperty,
125
+ [FlowchartSymbols.summingJunction]: DefaultConnectorProperty,
126
+ [FlowchartSymbols.predefinedProcess]: DefaultFlowchartProperty,
127
+ [FlowchartSymbols.offPage]: DefaultFlowchartProperty
128
+ };
129
+ const LINE_HIT_GEOMETRY_BUFFER = 10;
130
+ const LINE_SNAPPING_BUFFER = 6;
131
+ const LINE_SNAPPING_CONNECTOR_BUFFER = 8;
115
132
 
116
133
  const getGeometryPointers = () => {
117
134
  return [...Object.keys(BasicShapes), ...Object.keys(FlowchartSymbols)];
@@ -166,8 +183,8 @@ const LINE_AUTO_COMPLETE_HOVERED_OPACITY = 0.8;
166
183
  const LINE_AUTO_COMPLETE_HOVERED_DIAMETER = 10;
167
184
  const LINE_ALIGN_TOLERANCE = 3;
168
185
 
169
- const debugKey$2 = 'debug:plait:line-mirror';
170
- const debugGenerator$2 = createDebugGenerator(debugKey$2);
186
+ const debugKey$3 = 'debug:plait:line-mirror';
187
+ const debugGenerator$3 = createDebugGenerator(debugKey$3);
171
188
  const alignPoints = (basePoint, movingPoint) => {
172
189
  const newPoint = [...movingPoint];
173
190
  if (Point.isVertical(newPoint, basePoint, LINE_ALIGN_TOLERANCE)) {
@@ -304,7 +321,7 @@ function getIndexAndDeleteCountByKeyPoint(board, element, dataPoints, nextRender
304
321
  if (midDataPoints.length > 0) {
305
322
  const handleRefPair = getLineHandleRefPair(board, element);
306
323
  const params = getElbowLineRouteOptions(board, element, handleRefPair);
307
- const keyPoints = removeDuplicatePoints(generateElbowLineRoute(params));
324
+ const keyPoints = removeDuplicatePoints(generateElbowLineRoute(params, board));
308
325
  const nextKeyPoints = simplifyOrthogonalPoints(keyPoints.slice(1, keyPoints.length - 1));
309
326
  const nextDataPoints = [nextRenderPoints[0], ...midDataPoints, nextRenderPoints[nextRenderPoints.length - 1]];
310
327
  const mirrorDataPoints = getMirrorDataPoints(board, nextDataPoints, nextKeyPoints, params);
@@ -431,7 +448,7 @@ function findOrthogonalParallelSegments(segment, keyPoints) {
431
448
  return parallelSegments;
432
449
  }
433
450
  function findMirrorSegments(board, segment, parallelSegments, sourceRectangle, targetRectangle) {
434
- debugGenerator$2.isDebug() && debugGenerator$2.clear();
451
+ debugGenerator$3.isDebug() && debugGenerator$3.clear();
435
452
  const mirrorSegments = [];
436
453
  for (let index = 0; index < parallelSegments.length; index++) {
437
454
  const parallelPath = parallelSegments[index];
@@ -445,7 +462,7 @@ function findMirrorSegments(board, segment, parallelSegments, sourceRectangle, t
445
462
  const isValid = !RectangleClient.isHit(fakeRectangle, sourceRectangle) && !RectangleClient.isHit(fakeRectangle, targetRectangle);
446
463
  if (isValid) {
447
464
  mirrorSegments.push([startPoint, endPoint]);
448
- debugGenerator$2.isDebug() && debugGenerator$2.drawPolygon(board, RectangleClient.getCornerPoints(fakeRectangle));
465
+ debugGenerator$3.isDebug() && debugGenerator$3.drawPolygon(board, RectangleClient.getCornerPoints(fakeRectangle));
449
466
  }
450
467
  }
451
468
  return mirrorSegments;
@@ -471,15 +488,20 @@ const hasIllegalElbowPoint = (midDataPoints) => {
471
488
  });
472
489
  };
473
490
 
491
+ const isSelfLoop = (element) => {
492
+ return element.source.boundId && element.source.boundId === element.target.boundId;
493
+ };
494
+ const isUseDefaultOrthogonalRoute = (element, options) => {
495
+ return isSourceAndTargetIntersect(options) && !isSelfLoop(element);
496
+ };
474
497
  const getElbowPoints = (board, element) => {
475
498
  const handleRefPair = getLineHandleRefPair(board, element);
476
499
  const params = getElbowLineRouteOptions(board, element, handleRefPair);
477
500
  // console.log(params, 'params');
478
- const isIntersect = isSourceAndTargetIntersect(params);
479
- if (isIntersect) {
480
- return simplifyOrthogonalPoints(getPoints(handleRefPair.source.point, handleRefPair.source.direction, handleRefPair.target.point, handleRefPair.target.direction, 0));
501
+ if (isUseDefaultOrthogonalRoute(element, params)) {
502
+ return simplifyOrthogonalPoints(getPoints(handleRefPair.source.point, handleRefPair.source.direction, handleRefPair.target.point, handleRefPair.target.direction, DEFAULT_ROUTE_MARGIN));
481
503
  }
482
- const keyPoints = removeDuplicatePoints(generateElbowLineRoute(params));
504
+ const keyPoints = removeDuplicatePoints(generateElbowLineRoute(params, board));
483
505
  const nextKeyPoints = keyPoints.slice(1, keyPoints.length - 1);
484
506
  if (element.points.length === 2) {
485
507
  return simplifyOrthogonalPoints(keyPoints);
@@ -692,7 +714,7 @@ const drawHollowTriangleArrow = (source, target, options) => {
692
714
  return drawLinearPath([pointLeft, pointRight, target], { ...options, fill: 'white' }, true);
693
715
  };
694
716
 
695
- const getShape = (value) => {
717
+ const getElementShape = (value) => {
696
718
  if (PlaitDrawElement.isImage(value)) {
697
719
  return BasicShapes.rectangle;
698
720
  }
@@ -710,29 +732,246 @@ class LineShapeGenerator extends Generator {
710
732
  }
711
733
  }
712
734
 
735
+ var LineResizeHandle;
736
+ (function (LineResizeHandle) {
737
+ LineResizeHandle["source"] = "source";
738
+ LineResizeHandle["target"] = "target";
739
+ LineResizeHandle["addHandle"] = "addHandle";
740
+ })(LineResizeHandle || (LineResizeHandle = {}));
741
+ const getHitLineResizeHandleRef = (board, element, point) => {
742
+ let dataPoints = PlaitLine.getPoints(board, element);
743
+ const index = getHitPointIndex(dataPoints, point);
744
+ if (index !== -1) {
745
+ const handleIndex = index;
746
+ if (index === 0) {
747
+ return { handle: LineResizeHandle.source, handleIndex };
748
+ }
749
+ if (index === dataPoints.length - 1) {
750
+ return { handle: LineResizeHandle.target, handleIndex };
751
+ }
752
+ // elbow line, data points only verify source connection point and target connection point
753
+ if (element.shape !== LineShape.elbow) {
754
+ return { handleIndex };
755
+ }
756
+ }
757
+ const middlePoints = getMiddlePoints(board, element);
758
+ const indexOfMiddlePoints = getHitPointIndex(middlePoints, point);
759
+ if (indexOfMiddlePoints !== -1) {
760
+ return {
761
+ handle: LineResizeHandle.addHandle,
762
+ handleIndex: indexOfMiddlePoints
763
+ };
764
+ }
765
+ return undefined;
766
+ };
767
+ function getHitPointIndex(points, movingPoint) {
768
+ const rectangles = points.map(point => {
769
+ return {
770
+ x: point[0] - RESIZE_HANDLE_DIAMETER / 2,
771
+ y: point[1] - RESIZE_HANDLE_DIAMETER / 2,
772
+ width: RESIZE_HANDLE_DIAMETER,
773
+ height: RESIZE_HANDLE_DIAMETER
774
+ };
775
+ });
776
+ const rectangle = rectangles.find(rectangle => {
777
+ return RectangleClient.isHit(RectangleClient.getRectangleByPoints([movingPoint, movingPoint]), rectangle);
778
+ });
779
+ return rectangle ? rectangles.indexOf(rectangle) : -1;
780
+ }
781
+ const getHitLineTextIndex = (board, element, point) => {
782
+ const texts = element.texts;
783
+ if (!texts.length)
784
+ return -1;
785
+ const points = getLinePoints(board, element);
786
+ return texts.findIndex(text => {
787
+ const center = getPointOnPolyline(points, text.position);
788
+ const rectangle = {
789
+ x: center[0] - text.width / 2,
790
+ y: center[1] - text.height / 2,
791
+ width: text.width,
792
+ height: text.height
793
+ };
794
+ return RectangleClient.isHit(rectangle, RectangleClient.getRectangleByPoints([point, point]));
795
+ });
796
+ };
797
+
798
+ const isTextExceedingBounds = (geometry) => {
799
+ const client = RectangleClient.getRectangleByPoints(geometry.points);
800
+ if (geometry.textHeight > client.height) {
801
+ return true;
802
+ }
803
+ return false;
804
+ };
805
+ const isHitLineText = (board, element, point) => {
806
+ return getHitLineTextIndex(board, element, point) !== -1;
807
+ };
808
+ const isHitPolyLine = (pathPoints, point) => {
809
+ const distance = distanceBetweenPointAndSegments(pathPoints, point);
810
+ return distance <= HIT_DISTANCE_BUFFER;
811
+ };
812
+ const isHitLine = (board, element, point) => {
813
+ const points = getLinePoints(board, element);
814
+ const isHitText = isHitLineText(board, element, point);
815
+ return isHitText || isHitPolyLine(points, point);
816
+ };
817
+ const isRectangleHitDrawElement = (board, element, selection) => {
818
+ const rangeRectangle = RectangleClient.getRectangleByPoints([selection.anchor, selection.focus]);
819
+ if (PlaitDrawElement.isGeometry(element)) {
820
+ const client = RectangleClient.getRectangleByPoints(element.points);
821
+ let rotatedCornerPoints = rotatePointsByElement(RectangleClient.getCornerPoints(client), element) || RectangleClient.getCornerPoints(client);
822
+ if (isTextExceedingBounds(element)) {
823
+ const textClient = getTextRectangle(element);
824
+ rotatedCornerPoints =
825
+ rotatePointsByElement(RectangleClient.getCornerPoints(textClient), element) || RectangleClient.getCornerPoints(textClient);
826
+ }
827
+ return isPolylineHitRectangle(rotatedCornerPoints, rangeRectangle);
828
+ }
829
+ if (PlaitDrawElement.isImage(element)) {
830
+ const client = RectangleClient.getRectangleByPoints(element.points);
831
+ const rotatedCornerPoints = rotatePointsByElement(RectangleClient.getCornerPoints(client), element) || RectangleClient.getCornerPoints(client);
832
+ return isPolylineHitRectangle(rotatedCornerPoints, rangeRectangle);
833
+ }
834
+ if (PlaitDrawElement.isLine(element)) {
835
+ const points = getLinePoints(board, element);
836
+ return isPolylineHitRectangle(points, rangeRectangle);
837
+ }
838
+ return null;
839
+ };
840
+ const isHitDrawElement = (board, element, point) => {
841
+ const rectangle = board.getRectangle(element);
842
+ point = rotateAntiPointsByElement(point, element) || point;
843
+ if (PlaitDrawElement.isGeometry(element)) {
844
+ const fill = getFillByElement(board, element);
845
+ if (isHitEdgeOfShape(board, element, point, HIT_DISTANCE_BUFFER)) {
846
+ return true;
847
+ }
848
+ const engine = getEngine(getElementShape(element));
849
+ // when shape equals text, fill is not allowed
850
+ if (fill !== DefaultGeometryStyle.fill && fill !== TRANSPARENT && !PlaitDrawElement.isText(element)) {
851
+ const isHitInside = engine.isInsidePoint(rectangle, point);
852
+ if (isHitInside) {
853
+ return isHitInside;
854
+ }
855
+ }
856
+ else {
857
+ // if shape equals text, only check text rectangle
858
+ if (PlaitDrawElement.isText(element)) {
859
+ const textClient = getTextRectangle(element);
860
+ let isHitText = RectangleClient.isPointInRectangle(textClient, point);
861
+ return isHitText;
862
+ }
863
+ // check textRectangle of element
864
+ const textClient = engine.getTextRectangle ? engine.getTextRectangle(element) : getTextRectangle(element);
865
+ const isHitTextRectangle = RectangleClient.isPointInRectangle(textClient, point);
866
+ if (isHitTextRectangle) {
867
+ return isHitTextRectangle;
868
+ }
869
+ }
870
+ }
871
+ if (PlaitDrawElement.isImage(element)) {
872
+ const client = RectangleClient.getRectangleByPoints(element.points);
873
+ return RectangleClient.isPointInRectangle(client, point);
874
+ }
875
+ if (PlaitDrawElement.isLine(element)) {
876
+ return isHitLine(board, element, point);
877
+ }
878
+ return null;
879
+ };
880
+ const isHitEdgeOfShape = (board, element, point, hitDistanceBuffer) => {
881
+ const nearestPoint = getNearestPoint(element, point);
882
+ const distance = distanceBetweenPointAndPoint(nearestPoint[0], nearestPoint[1], point[0], point[1]);
883
+ return distance <= hitDistanceBuffer;
884
+ };
885
+ const isInsideOfShape = (board, element, point, hitDistanceBuffer) => {
886
+ const client = RectangleClient.inflate(RectangleClient.getRectangleByPoints(element.points), hitDistanceBuffer);
887
+ return getEngine(getElementShape(element)).isInsidePoint(client, point);
888
+ };
889
+ const isHitElementInside = (board, element, point) => {
890
+ const rectangle = board.getRectangle(element);
891
+ point = rotateAntiPointsByElement(point, element) || point;
892
+ if (PlaitDrawElement.isGeometry(element)) {
893
+ const engine = getEngine(getElementShape(element));
894
+ const isHitInside = engine.isInsidePoint(rectangle, point);
895
+ if (isHitInside) {
896
+ return isHitInside;
897
+ }
898
+ if (engine.getTextRectangle) {
899
+ const textClient = engine.getTextRectangle(element);
900
+ const isHitTextRectangle = RectangleClient.isPointInRectangle(textClient, point);
901
+ if (isHitTextRectangle) {
902
+ return isHitTextRectangle;
903
+ }
904
+ }
905
+ }
906
+ if (PlaitDrawElement.isImage(element)) {
907
+ const client = RectangleClient.getRectangleByPoints(element.points);
908
+ return RectangleClient.isPointInRectangle(client, point);
909
+ }
910
+ if (PlaitDrawElement.isLine(element)) {
911
+ return isHitLine(board, element, point);
912
+ }
913
+ return null;
914
+ };
915
+
713
916
  const getHitRectangleResizeHandleRef = (board, rectangle, point, angle = 0) => {
714
917
  const centerPoint = RectangleClient.getCenterPoint(rectangle);
715
- const rotatedPoint = rotatePoints([point], centerPoint, -angle)[0];
716
- const resizeHandleRefs = getRectangleResizeHandleRefs(rectangle, RESIZE_HANDLE_DIAMETER, angle);
717
- const result = resizeHandleRefs.find(resizeHandleRef => {
718
- return RectangleClient.isHit(RectangleClient.getRectangleByPoints([rotatedPoint, rotatedPoint]), resizeHandleRef.rectangle);
918
+ const resizeHandleRefs = getRectangleResizeHandleRefs(rectangle, RESIZE_HANDLE_DIAMETER);
919
+ if (angle) {
920
+ const rotatedPoint = rotatePoints([point], centerPoint, -angle)[0];
921
+ let result = resizeHandleRefs.find(resizeHandleRef => {
922
+ return RectangleClient.isHit(RectangleClient.getRectangleByPoints([rotatedPoint, rotatedPoint]), resizeHandleRef.rectangle);
923
+ });
924
+ if (result) {
925
+ result.cursorClass = getRotatedResizeCursorClassByAngle(result.cursorClass, angle);
926
+ }
927
+ return result;
928
+ }
929
+ else {
930
+ return resizeHandleRefs.find(resizeHandleRef => {
931
+ return RectangleClient.isHit(RectangleClient.getRectangleByPoints([point, point]), resizeHandleRef.rectangle);
932
+ });
933
+ }
934
+ };
935
+ const getSnappingGeometry = (board, point) => {
936
+ let hitElement = getHitGeometry(board, point);
937
+ if (hitElement) {
938
+ const ref = getSnappingRef(board, hitElement, point);
939
+ if (ref.isHitConnector || ref.isHitEdge) {
940
+ return hitElement;
941
+ }
942
+ }
943
+ return null;
944
+ };
945
+ const getSnappingRef = (board, hitElement, point) => {
946
+ const rotatedPoint = rotateAntiPointsByElement(point, hitElement) || point;
947
+ const connectorPoint = getHitConnectorPoint(rotatedPoint, hitElement);
948
+ const edgePoint = getNearestPoint(hitElement, rotatedPoint);
949
+ const isHitEdge = isHitEdgeOfShape(board, hitElement, rotatedPoint, LINE_SNAPPING_BUFFER);
950
+ return { isHitEdge, isHitConnector: !!connectorPoint, connectorPoint, edgePoint };
951
+ };
952
+ const getHitGeometry = (board, point, offset = LINE_HIT_GEOMETRY_BUFFER) => {
953
+ let hitShape = null;
954
+ traverseDrawShapes(board, (element) => {
955
+ if (hitShape === null && isInsideOfShape(board, element, rotateAntiPointsByElement(point, element) || point, offset * 2)) {
956
+ hitShape = element;
957
+ }
719
958
  });
720
- return result;
959
+ return hitShape;
721
960
  };
722
- const getHitOutlineGeometry = (board, point, offset = 0) => {
723
- let geometry = null;
961
+ const traverseDrawShapes = (board, callback) => {
724
962
  depthFirstRecursion(board, node => {
725
- if (PlaitDrawElement.isGeometry(node) || PlaitDrawElement.isImage(node)) {
726
- let client = RectangleClient.getRectangleByPoints(node.points);
727
- client = RectangleClient.getOutlineRectangle(client, offset);
728
- const shape = getShape(node);
729
- const isHit = getEngine(shape).isInsidePoint(client, rotateAntiPointsByElement(point, node) || point);
730
- if (isHit) {
731
- geometry = node;
732
- }
963
+ if (!PlaitBoard.isBoard(node) && PlaitDrawElement.isShapeElement(node)) {
964
+ callback(node);
733
965
  }
734
966
  }, getIsRecursionFunc(board), true);
735
- return geometry;
967
+ };
968
+ const getRotateHandleRectangle = (rectangle) => {
969
+ return {
970
+ x: rectangle.x - ROTATE_HANDLE_DISTANCE_TO_ELEMENT - ROTATE_HANDLE_SIZE,
971
+ y: rectangle.y + rectangle.height + ROTATE_HANDLE_DISTANCE_TO_ELEMENT,
972
+ width: ROTATE_HANDLE_SIZE,
973
+ height: ROTATE_HANDLE_SIZE
974
+ };
736
975
  };
737
976
 
738
977
  const SHAPE_MAX_LENGTH = 6;
@@ -910,8 +1149,7 @@ function getMiddlePoints(board, element) {
910
1149
  if (shape === LineShape.elbow) {
911
1150
  const renderPoints = getElbowPoints(board, element);
912
1151
  const options = getElbowLineRouteOptions(board, element);
913
- const isIntersect = isSourceAndTargetIntersect(options);
914
- if (!isIntersect) {
1152
+ if (!isUseDefaultOrthogonalRoute(element, options)) {
915
1153
  const [nextSourcePoint, nextTargetPoint] = getNextSourceAndTargetPoints(board, element);
916
1154
  for (let i = 0; i < renderPoints.length - 1; i++) {
917
1155
  if ((i == 0 && Point.isEquals(renderPoints[i + 1], nextSourcePoint)) ||
@@ -943,6 +1181,9 @@ const drawLine = (board, element) => {
943
1181
  }
944
1182
  const id = idCreator();
945
1183
  line.setAttribute('mask', `url(#${id})`);
1184
+ if (element.strokeStyle === StrokeStyle.dotted) {
1185
+ setStrokeLinecap(line, 'round');
1186
+ }
946
1187
  lineG.appendChild(line);
947
1188
  const { mask, maskTargetFillRect } = drawMask(board, element, id);
948
1189
  lineG.appendChild(mask);
@@ -951,20 +1192,18 @@ const drawLine = (board, element) => {
951
1192
  arrow && lineG.appendChild(arrow);
952
1193
  return lineG;
953
1194
  };
954
- const getConnectionByNearestPoint = (board, point, hitElement) => {
1195
+ const getHitConnection = (board, point, hitElement) => {
955
1196
  let rectangle = RectangleClient.getRectangleByPoints(hitElement.points);
956
- let nearestPoint = getNearestPoint(hitElement, point);
957
- const hitConnector = getHitConnectorPoint(nearestPoint, hitElement, rectangle);
958
- nearestPoint = hitConnector ? hitConnector : nearestPoint;
959
- return [(nearestPoint[0] - rectangle.x) / rectangle.width, (nearestPoint[1] - rectangle.y) / rectangle.height];
960
- };
961
- const getHitConnectorPoint = (point, hitElement, rectangle) => {
962
- const shape = getShape(hitElement);
963
- const connector = getEngine(shape).getConnectorPoints(rectangle);
964
- const points = RectangleClient.getPoints(RectangleClient.getRectangleByCenterPoint(point, 10, 10));
965
- const pointRectangle = RectangleClient.getRectangleByPoints(points);
966
- return connector.find(point => {
967
- return RectangleClient.isHit(pointRectangle, RectangleClient.getRectangleByPoints([point, point]));
1197
+ const ref = getSnappingRef(board, hitElement, point);
1198
+ const connectionPoint = ref.connectorPoint || ref.edgePoint;
1199
+ return [(connectionPoint[0] - rectangle.x) / rectangle.width, (connectionPoint[1] - rectangle.y) / rectangle.height];
1200
+ };
1201
+ const getHitConnectorPoint = (point, hitElement) => {
1202
+ const rectangle = RectangleClient.getRectangleByPoints(hitElement.points);
1203
+ const shape = getElementShape(hitElement);
1204
+ const connectorPoints = getEngine(shape).getConnectorPoints(rectangle);
1205
+ return connectorPoints.find(connectorPoint => {
1206
+ return distanceBetweenPointAndPoint(...connectorPoint, ...point) <= LINE_SNAPPING_CONNECTOR_BUFFER;
968
1207
  });
969
1208
  };
970
1209
  const getLineTextRectangle = (board, element, index) => {
@@ -1001,13 +1240,9 @@ const Q2C = (points) => {
1001
1240
  return result;
1002
1241
  };
1003
1242
  const handleLineCreating = (board, lineShape, sourcePoint, movingPoint, sourceElement, lineShapeG) => {
1004
- const hitElement = getHitOutlineGeometry(board, movingPoint, REACTION_MARGIN);
1005
- const targetConnection = hitElement
1006
- ? getConnectionByNearestPoint(board, rotateAntiPointsByElement(movingPoint, hitElement) || movingPoint, hitElement)
1007
- : undefined;
1008
- const sourceConnection = sourceElement
1009
- ? getConnectionByNearestPoint(board, rotateAntiPointsByElement(sourcePoint, sourceElement) || sourcePoint, sourceElement)
1010
- : undefined;
1243
+ const hitElement = getSnappingGeometry(board, movingPoint);
1244
+ const targetConnection = hitElement ? getHitConnection(board, movingPoint, hitElement) : undefined;
1245
+ const sourceConnection = sourceElement ? getHitConnection(board, sourcePoint, sourceElement) : undefined;
1011
1246
  const targetBoundId = hitElement ? hitElement.id : undefined;
1012
1247
  const lineGenerator = new LineShapeGenerator(board);
1013
1248
  const memorizedLatest = getLineMemorizedLatest();
@@ -1070,204 +1305,23 @@ const getSelectedImageElements = (board) => {
1070
1305
  return selectedElements;
1071
1306
  };
1072
1307
 
1073
- var LineResizeHandle;
1074
- (function (LineResizeHandle) {
1075
- LineResizeHandle["source"] = "source";
1076
- LineResizeHandle["target"] = "target";
1077
- LineResizeHandle["addHandle"] = "addHandle";
1078
- })(LineResizeHandle || (LineResizeHandle = {}));
1079
- const getHitLineResizeHandleRef = (board, element, point) => {
1080
- let dataPoints = PlaitLine.getPoints(board, element);
1081
- const index = getHitPointIndex(dataPoints, point);
1082
- if (index !== -1) {
1083
- const handleIndex = index;
1084
- if (index === 0) {
1085
- return { handle: LineResizeHandle.source, handleIndex };
1086
- }
1087
- if (index === dataPoints.length - 1) {
1088
- return { handle: LineResizeHandle.target, handleIndex };
1089
- }
1090
- // elbow line, data points only verify source connection point and target connection point
1091
- if (element.shape !== LineShape.elbow) {
1092
- return { handleIndex };
1093
- }
1308
+ const getCenterPointsOnPolygon$1 = (points) => {
1309
+ const centerPoints = [];
1310
+ for (let i = 0; i < points.length; i++) {
1311
+ let j = i == points.length - 1 ? 0 : i + 1;
1312
+ centerPoints.push([(points[i][0] + points[j][0]) / 2, (points[i][1] + points[j][1]) / 2]);
1094
1313
  }
1095
- const middlePoints = getMiddlePoints(board, element);
1096
- const indexOfMiddlePoints = getHitPointIndex(middlePoints, point);
1097
- if (indexOfMiddlePoints !== -1) {
1098
- return {
1099
- handle: LineResizeHandle.addHandle,
1100
- handleIndex: indexOfMiddlePoints
1101
- };
1314
+ return centerPoints;
1315
+ };
1316
+ const getCrossingPointBetweenPointAndPolygon = (corners, point) => {
1317
+ const result = [];
1318
+ for (let index = 1; index <= corners.length; index++) {
1319
+ let start = corners[index - 1];
1320
+ let end = index === corners.length ? corners[0] : corners[index];
1321
+ const crossingPoint = getCrossingPointsBetweenPointAndSegment(point, start, end);
1322
+ result.push(...crossingPoint);
1102
1323
  }
1103
- return undefined;
1104
- };
1105
- function getHitPointIndex(points, movingPoint) {
1106
- const rectangles = points.map(point => {
1107
- return {
1108
- x: point[0] - RESIZE_HANDLE_DIAMETER / 2,
1109
- y: point[1] - RESIZE_HANDLE_DIAMETER / 2,
1110
- width: RESIZE_HANDLE_DIAMETER,
1111
- height: RESIZE_HANDLE_DIAMETER
1112
- };
1113
- });
1114
- const rectangle = rectangles.find(rectangle => {
1115
- return RectangleClient.isHit(RectangleClient.getRectangleByPoints([movingPoint, movingPoint]), rectangle);
1116
- });
1117
- return rectangle ? rectangles.indexOf(rectangle) : -1;
1118
- }
1119
- const getHitLineTextIndex = (board, element, point) => {
1120
- const texts = element.texts;
1121
- if (!texts.length)
1122
- return -1;
1123
- const points = getLinePoints(board, element);
1124
- return texts.findIndex(text => {
1125
- const center = getPointOnPolyline(points, text.position);
1126
- const rectangle = {
1127
- x: center[0] - text.width / 2,
1128
- y: center[1] - text.height / 2,
1129
- width: text.width,
1130
- height: text.height
1131
- };
1132
- return RectangleClient.isHit(rectangle, RectangleClient.getRectangleByPoints([point, point]));
1133
- });
1134
- };
1135
-
1136
- const isTextExceedingBounds = (geometry) => {
1137
- const client = RectangleClient.getRectangleByPoints(geometry.points);
1138
- if (geometry.textHeight > client.height) {
1139
- return true;
1140
- }
1141
- return false;
1142
- };
1143
- const isHitLineText = (board, element, point) => {
1144
- return getHitLineTextIndex(board, element, point) !== -1;
1145
- };
1146
- const isHitPolyLine = (pathPoints, point) => {
1147
- const distance = distanceBetweenPointAndSegments(pathPoints, point);
1148
- return distance <= HIT_DISTANCE_BUFFER;
1149
- };
1150
- const isHitLine = (board, element, point) => {
1151
- const points = getLinePoints(board, element);
1152
- const isHitText = isHitLineText(board, element, point);
1153
- return isHitText || isHitPolyLine(points, point);
1154
- };
1155
- const isRectangleHitDrawElement = (board, element, selection) => {
1156
- const rangeRectangle = RectangleClient.getRectangleByPoints([selection.anchor, selection.focus]);
1157
- if (PlaitDrawElement.isGeometry(element)) {
1158
- const client = RectangleClient.getRectangleByPoints(element.points);
1159
- const centerPoint = RectangleClient.getCenterPoint(client);
1160
- let rotatedCornerPoints = rotatePoints(RectangleClient.getCornerPoints(client), centerPoint, element.angle);
1161
- if (isTextExceedingBounds(element)) {
1162
- const textClient = getTextRectangle(element);
1163
- rotatedCornerPoints = rotatePoints(RectangleClient.getCornerPoints(textClient), centerPoint, element.angle);
1164
- }
1165
- return isPolylineHitRectangle(rotatedCornerPoints, rangeRectangle);
1166
- }
1167
- if (PlaitDrawElement.isImage(element)) {
1168
- const client = RectangleClient.getRectangleByPoints(element.points);
1169
- const rotatedCornerPoints = rotatePoints(RectangleClient.getCornerPoints(client), RectangleClient.getCenterPoint(client), element.angle);
1170
- return isPolylineHitRectangle(rotatedCornerPoints, rangeRectangle);
1171
- }
1172
- if (PlaitDrawElement.isLine(element)) {
1173
- const points = getLinePoints(board, element);
1174
- return isPolylineHitRectangle(points, rangeRectangle);
1175
- }
1176
- return null;
1177
- };
1178
- const isHitDrawElement = (board, element, point) => {
1179
- const rectangle = board.getRectangle(element);
1180
- const centerPoint = RectangleClient.getCenterPoint(rectangle);
1181
- if (element.angle) {
1182
- point = rotate(point[0], point[1], centerPoint[0], centerPoint[1], -element.angle);
1183
- }
1184
- if (PlaitDrawElement.isGeometry(element)) {
1185
- const fill = getFillByElement(board, element);
1186
- const engine = getEngine(getShape(element));
1187
- const nearestPoint = engine.getNearestPoint(rectangle, point);
1188
- const distance = distanceBetweenPointAndPoint(nearestPoint[0], nearestPoint[1], point[0], point[1]);
1189
- const isHitEdge = distance <= HIT_DISTANCE_BUFFER;
1190
- if (isHitEdge) {
1191
- return isHitEdge;
1192
- }
1193
- // when shape equals text, fill is not allowed
1194
- if (fill !== DefaultGeometryStyle.fill && fill !== TRANSPARENT && !PlaitDrawElement.isText(element)) {
1195
- const isHitInside = engine.isInsidePoint(rectangle, point);
1196
- if (isHitInside) {
1197
- return isHitInside;
1198
- }
1199
- }
1200
- else {
1201
- // if shape equals text, only check text rectangle
1202
- if (PlaitDrawElement.isText(element)) {
1203
- const textClient = getTextRectangle(element);
1204
- let isHitText = RectangleClient.isPointInRectangle(textClient, point);
1205
- return isHitText;
1206
- }
1207
- // check textRectangle of element
1208
- const textClient = engine.getTextRectangle ? engine.getTextRectangle(element) : getTextRectangle(element);
1209
- const isHitTextRectangle = RectangleClient.isPointInRectangle(textClient, point);
1210
- if (isHitTextRectangle) {
1211
- return isHitTextRectangle;
1212
- }
1213
- }
1214
- }
1215
- if (PlaitDrawElement.isImage(element)) {
1216
- const client = RectangleClient.getRectangleByPoints(element.points);
1217
- const rotatedCornerPoints = rotatePoints(RectangleClient.getCornerPoints(client), RectangleClient.getCenterPoint(client), element.angle);
1218
- return isPointInPolygon(point, rotatedCornerPoints);
1219
- }
1220
- if (PlaitDrawElement.isLine(element)) {
1221
- return isHitLine(board, element, point);
1222
- }
1223
- return null;
1224
- };
1225
- const isHitElementInside = (board, element, point) => {
1226
- const rectangle = board.getRectangle(element);
1227
- const centerPoint = RectangleClient.getCenterPoint(rectangle);
1228
- if (element.angle) {
1229
- point = rotate(point[0], point[1], centerPoint[0], centerPoint[1], -element.angle);
1230
- }
1231
- if (PlaitDrawElement.isGeometry(element)) {
1232
- const engine = getEngine(getShape(element));
1233
- const isHitInside = engine.isInsidePoint(rectangle, point);
1234
- if (isHitInside) {
1235
- return isHitInside;
1236
- }
1237
- if (engine.getTextRectangle) {
1238
- const textClient = engine.getTextRectangle(element);
1239
- const isHitTextRectangle = RectangleClient.isPointInRectangle(textClient, point);
1240
- if (isHitTextRectangle) {
1241
- return isHitTextRectangle;
1242
- }
1243
- }
1244
- }
1245
- if (PlaitDrawElement.isImage(element)) {
1246
- return isRectangleHitDrawElement(board, element, { anchor: point, focus: point });
1247
- }
1248
- if (PlaitDrawElement.isLine(element)) {
1249
- return isHitLine(board, element, point);
1250
- }
1251
- return null;
1252
- };
1253
-
1254
- const getCenterPointsOnPolygon$1 = (points) => {
1255
- const centerPoints = [];
1256
- for (let i = 0; i < points.length; i++) {
1257
- let j = i == points.length - 1 ? 0 : i + 1;
1258
- centerPoints.push([(points[i][0] + points[j][0]) / 2, (points[i][1] + points[j][1]) / 2]);
1259
- }
1260
- return centerPoints;
1261
- };
1262
- const getCrossingPointBetweenPointAndPolygon = (corners, point) => {
1263
- const result = [];
1264
- for (let index = 1; index <= corners.length; index++) {
1265
- let start = corners[index - 1];
1266
- let end = index === corners.length ? corners[0] : corners[index];
1267
- const crossingPoint = getCrossingPointsBetweenPointAndSegment(point, start, end);
1268
- result.push(...crossingPoint);
1269
- }
1270
- return result;
1324
+ return result;
1271
1325
  };
1272
1326
  const getPolygonEdgeByConnectionPoint = (corners, point) => {
1273
1327
  for (let index = 1; index <= corners.length; index++) {
@@ -1429,44 +1483,55 @@ const DiamondEngine = createPolygonEngine({
1429
1483
  }
1430
1484
  });
1431
1485
 
1432
- const EllipseEngine = {
1433
- draw(board, rectangle, options) {
1434
- const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
1435
- const rs = PlaitBoard.getRoughSVG(board);
1436
- return rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, { ...options, fillStyle: 'solid' });
1437
- },
1438
- isInsidePoint(rectangle, point) {
1439
- const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
1440
- return isPointInEllipse(point, centerPoint, rectangle.width / 2, rectangle.height / 2);
1441
- },
1442
- getCornerPoints(rectangle) {
1443
- return RectangleClient.getEdgeCenterPoints(rectangle);
1444
- },
1445
- getNearestPoint(rectangle, point) {
1446
- const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
1447
- return getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width / 2, rectangle.height / 2);
1448
- },
1449
- getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) {
1450
- const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
1451
- const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
1452
- const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])];
1453
- const a = rectangle.width / 2;
1454
- const b = rectangle.height / 2;
1455
- const slope = getEllipseTangentSlope(point[0], point[1], a, b);
1456
- const vector = getVectorFromPointAndSlope(point[0], point[1], slope);
1457
- return vector;
1458
- },
1459
- getConnectorPoints(rectangle) {
1460
- return RectangleClient.getEdgeCenterPoints(rectangle);
1461
- },
1462
- getTextRectangle(element) {
1463
- const rectangle = getTextRectangle(element);
1464
- const width = rectangle.width;
1465
- rectangle.width = (rectangle.width * 3) / 4;
1466
- rectangle.x += width / 8;
1467
- return rectangle;
1486
+ function createEllipseEngine(createOptions) {
1487
+ const engine = {
1488
+ draw(board, rectangle, options) {
1489
+ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
1490
+ const rs = PlaitBoard.getRoughSVG(board);
1491
+ return rs.ellipse(centerPoint[0], centerPoint[1], rectangle.width, rectangle.height, { ...options, fillStyle: 'solid' });
1492
+ },
1493
+ isInsidePoint(rectangle, point) {
1494
+ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
1495
+ return isPointInEllipse(point, centerPoint, rectangle.width / 2, rectangle.height / 2);
1496
+ },
1497
+ getCornerPoints(rectangle) {
1498
+ return RectangleClient.getEdgeCenterPoints(rectangle);
1499
+ },
1500
+ getNearestPoint(rectangle, point) {
1501
+ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
1502
+ return getNearestPointBetweenPointAndEllipse(point, centerPoint, rectangle.width / 2, rectangle.height / 2);
1503
+ },
1504
+ getTangentVectorByConnectionPoint(rectangle, pointOfRectangle) {
1505
+ const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
1506
+ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
1507
+ const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])];
1508
+ const a = rectangle.width / 2;
1509
+ const b = rectangle.height / 2;
1510
+ const slope = getEllipseTangentSlope(point[0], point[1], a, b);
1511
+ const vector = getVectorFromPointAndSlope(point[0], point[1], slope);
1512
+ return vector;
1513
+ },
1514
+ getConnectorPoints(rectangle) {
1515
+ return RectangleClient.getEdgeCenterPoints(rectangle);
1516
+ },
1517
+ getTextRectangle(element) {
1518
+ const rectangle = getTextRectangle(element);
1519
+ const width = rectangle.width;
1520
+ rectangle.width = (rectangle.width * 3) / 4;
1521
+ rectangle.x += width / 8;
1522
+ return rectangle;
1523
+ }
1524
+ };
1525
+ if (createOptions?.draw) {
1526
+ engine.draw = createOptions.draw;
1468
1527
  }
1469
- };
1528
+ if (createOptions?.getTextRectangle) {
1529
+ engine.getTextRectangle = createOptions.getTextRectangle;
1530
+ }
1531
+ return engine;
1532
+ }
1533
+
1534
+ const EllipseEngine = createEllipseEngine();
1470
1535
  function getNearestPointBetweenPointAndEllipse(point, center, rx, ry, rotation = 0) {
1471
1536
  const rectangleClient = {
1472
1537
  x: center[0] - rx,
@@ -2254,54 +2319,229 @@ const StoredDataEngine = {
2254
2319
  }
2255
2320
  };
2256
2321
 
2257
- const ShapeEngineMap = {
2258
- [BasicShapes.rectangle]: RectangleEngine,
2259
- [BasicShapes.diamond]: DiamondEngine,
2260
- [BasicShapes.ellipse]: EllipseEngine,
2261
- [BasicShapes.parallelogram]: ParallelogramEngine,
2262
- [BasicShapes.roundRectangle]: RoundRectangleEngine,
2263
- [BasicShapes.text]: RectangleEngine,
2264
- [BasicShapes.triangle]: TriangleEngine,
2265
- [BasicShapes.leftArrow]: LeftArrowEngine,
2266
- [BasicShapes.trapezoid]: TrapezoidEngine,
2267
- [BasicShapes.rightArrow]: RightArrowEngine,
2268
- [BasicShapes.cross]: CrossEngine,
2269
- [BasicShapes.star]: StarEngine,
2270
- [BasicShapes.pentagon]: PentagonEngine,
2271
- [BasicShapes.hexagon]: HexagonEngine,
2272
- [BasicShapes.octagon]: OctagonEngine,
2273
- [BasicShapes.pentagonArrow]: PentagonArrowEngine,
2274
- [BasicShapes.processArrow]: ProcessArrowEngine,
2275
- [BasicShapes.twoWayArrow]: TwoWayArrowEngine,
2276
- [BasicShapes.comment]: CommentEngine,
2277
- [BasicShapes.roundComment]: RoundCommentEngine,
2278
- [FlowchartSymbols.process]: RectangleEngine,
2279
- [FlowchartSymbols.decision]: DiamondEngine,
2280
- [FlowchartSymbols.connector]: EllipseEngine,
2281
- [FlowchartSymbols.data]: ParallelogramEngine,
2282
- [FlowchartSymbols.terminal]: TerminalEngine,
2283
- [FlowchartSymbols.manualInput]: ManualInputEngine,
2284
- [FlowchartSymbols.preparation]: PreparationEngine,
2285
- [FlowchartSymbols.manualLoop]: ManualLoopEngine,
2286
- [FlowchartSymbols.merge]: MergeEngine,
2287
- [FlowchartSymbols.delay]: DelayEngine,
2288
- [FlowchartSymbols.storedData]: StoredDataEngine
2289
- };
2290
- const getEngine = (shape) => {
2291
- return ShapeEngineMap[shape];
2322
+ const PredefinedProcessEngine = {
2323
+ draw(board, rectangle, options) {
2324
+ const rs = PlaitBoard.getRoughSVG(board);
2325
+ const shape = rs.path(`M${rectangle.x} ${rectangle.y} H${rectangle.x + rectangle.width} V${rectangle.y + rectangle.height} H${rectangle.x} Z M${rectangle.x + rectangle.width * 0.06} ${rectangle.y} L${rectangle.x + rectangle.width * 0.06} ${rectangle.y +
2326
+ rectangle.height} M${rectangle.x + rectangle.width - rectangle.width * 0.06} ${rectangle.y} L${rectangle.x +
2327
+ rectangle.width -
2328
+ rectangle.width * 0.06} ${rectangle.y + rectangle.height}`, { ...options, fillStyle: 'solid' });
2329
+ setStrokeLinecap(shape, 'round');
2330
+ return shape;
2331
+ },
2332
+ isInsidePoint(rectangle, point) {
2333
+ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]);
2334
+ return RectangleClient.isHit(rectangle, rangeRectangle);
2335
+ },
2336
+ getCornerPoints(rectangle) {
2337
+ return RectangleClient.getCornerPoints(rectangle);
2338
+ },
2339
+ getNearestPoint(rectangle, point) {
2340
+ return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle));
2341
+ },
2342
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
2343
+ const corners = RectangleEngine.getCornerPoints(rectangle);
2344
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
2345
+ return getPolygonEdgeByConnectionPoint(corners, point);
2346
+ },
2347
+ getConnectorPoints(rectangle) {
2348
+ return RectangleClient.getEdgeCenterPoints(rectangle);
2349
+ },
2350
+ getTextRectangle: (element) => {
2351
+ const elementRectangle = RectangleClient.getRectangleByPoints(element.points);
2352
+ const strokeWidth = getStrokeWidthByElement(element);
2353
+ const height = element.textHeight;
2354
+ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2 - elementRectangle.width * 0.06 * 2;
2355
+ return {
2356
+ height,
2357
+ width: width > 0 ? width : 0,
2358
+ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth + elementRectangle.width * 0.06,
2359
+ y: elementRectangle.y + (elementRectangle.height - height) / 2
2360
+ };
2361
+ }
2292
2362
  };
2293
2363
 
2294
- const createGeometryElement = (shape, points, text, options = {}, textProperties = {}) => {
2295
- let textOptions = {};
2296
- let alignment = Alignment.center;
2297
- let textHeight = DefaultTextProperty.height;
2298
- if (shape === BasicShapes.text) {
2299
- textOptions = { autoSize: true };
2300
- alignment = undefined;
2301
- }
2302
- textProperties = { ...textProperties };
2303
- textProperties?.align && (alignment = textProperties?.align);
2304
- textProperties?.textHeight && (textHeight = textProperties?.textHeight);
2364
+ const getOffPagePoints = (rectangle) => {
2365
+ return [
2366
+ [rectangle.x, rectangle.y],
2367
+ [rectangle.x + rectangle.width, rectangle.y],
2368
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
2369
+ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
2370
+ [rectangle.x, rectangle.y + rectangle.height / 2]
2371
+ ];
2372
+ };
2373
+ const OffPageEngine = createPolygonEngine({
2374
+ getPolygonPoints: getOffPagePoints,
2375
+ getConnectorPoints: (rectangle) => {
2376
+ return RectangleClient.getEdgeCenterPoints(rectangle);
2377
+ },
2378
+ getTextRectangle: (element) => {
2379
+ const elementRectangle = RectangleClient.getRectangleByPoints(element.points);
2380
+ const strokeWidth = getStrokeWidthByElement(element);
2381
+ const height = element.textHeight;
2382
+ const width = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2;
2383
+ return {
2384
+ width: width > 0 ? width : 0,
2385
+ height: height,
2386
+ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth,
2387
+ y: elementRectangle.y + (elementRectangle.height - elementRectangle.height / 2 - height) / 2
2388
+ };
2389
+ }
2390
+ });
2391
+
2392
+ const CloudEngine = {
2393
+ draw(board, rectangle, options) {
2394
+ const rs = PlaitBoard.getRoughSVG(board);
2395
+ const divisionWidth = rectangle.width / 7;
2396
+ const divisionHeight = rectangle.height / 3.2;
2397
+ const xRadius = divisionWidth / 8.5;
2398
+ const yRadius = divisionHeight / 20;
2399
+ const svgElement = rs.path(`M ${rectangle.x + divisionWidth} ${rectangle.y + divisionHeight}
2400
+ A ${xRadius} ${yRadius * 1.2} 0 1 1 ${rectangle.x + divisionWidth * 2} ${rectangle.y + divisionHeight / 2}
2401
+ A ${xRadius} ${yRadius} 0 1 1 ${rectangle.x + divisionWidth * 4.2} ${rectangle.y + divisionHeight / 2.2}
2402
+ A ${xRadius} ${yRadius} 0 1 1 ${rectangle.x + divisionWidth * 5.8} ${rectangle.y + divisionHeight}
2403
+ A ${xRadius} ${yRadius * 1.3} 0 1 1 ${rectangle.x + divisionWidth * 6} ${rectangle.y + divisionHeight * 2.2}
2404
+ A ${xRadius} ${yRadius * 1.2} 0 1 1 ${rectangle.x + divisionWidth * 5} ${rectangle.y + divisionHeight * 2.8}
2405
+ A ${xRadius} ${yRadius / 1.2} 0 1 1 ${rectangle.x + divisionWidth * 2.8} ${rectangle.y + divisionHeight * 2.8}
2406
+ A ${xRadius} ${yRadius} 0 1 1 ${rectangle.x + divisionWidth} ${rectangle.y + divisionHeight * 2.2}
2407
+ A ${xRadius} ${yRadius * 1.42} 0 1 1 ${rectangle.x + divisionWidth} ${rectangle.y + divisionHeight}
2408
+ Z`, { ...options, fillStyle: 'solid' });
2409
+ setPathStrokeLinecap(svgElement, 'round');
2410
+ return svgElement;
2411
+ },
2412
+ isInsidePoint(rectangle, point) {
2413
+ const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]);
2414
+ return RectangleClient.isHit(rectangle, rangeRectangle);
2415
+ },
2416
+ getCornerPoints(rectangle) {
2417
+ return RectangleClient.getCornerPoints(rectangle);
2418
+ },
2419
+ getNearestPoint(rectangle, point) {
2420
+ return getNearestPointBetweenPointAndSegments(point, CloudEngine.getCornerPoints(rectangle));
2421
+ },
2422
+ getEdgeByConnectionPoint(rectangle, pointOfRectangle) {
2423
+ const corners = CloudEngine.getCornerPoints(rectangle);
2424
+ const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle);
2425
+ return getPolygonEdgeByConnectionPoint(corners, point);
2426
+ },
2427
+ getConnectorPoints(rectangle) {
2428
+ return RectangleClient.getEdgeCenterPoints(rectangle);
2429
+ },
2430
+ getTextRectangle(element) {
2431
+ const elementRectangle = RectangleClient.getRectangleByPoints(element.points);
2432
+ const strokeWidth = getStrokeWidthByElement(element);
2433
+ const height = element.textHeight;
2434
+ const originWidth = elementRectangle.width - ShapeDefaultSpace.rectangleAndText * 2 - strokeWidth * 2;
2435
+ const width = originWidth / 1.5;
2436
+ return {
2437
+ height,
2438
+ width: width > 0 ? width : 0,
2439
+ x: elementRectangle.x + ShapeDefaultSpace.rectangleAndText + strokeWidth + originWidth / 6,
2440
+ y: elementRectangle.y + elementRectangle.height / 6 + ((elementRectangle.height * 4) / 6 - height) / 2
2441
+ };
2442
+ }
2443
+ };
2444
+
2445
+ const OrEngine = createEllipseEngine({
2446
+ draw(board, rectangle, options) {
2447
+ const rs = PlaitBoard.getRoughSVG(board);
2448
+ const rx = rectangle.width / 2;
2449
+ const ry = rectangle.height / 2;
2450
+ const startPoint = [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2];
2451
+ return rs.path(`M${startPoint[0]} ${startPoint[1]}
2452
+ A${rx},${ry} 0 1,1 ${startPoint[0]} ${startPoint[1] - 0.01}
2453
+ M${rectangle.x} ${rectangle.y + rectangle.height / 2}
2454
+ L${rectangle.x + rectangle.width} ${rectangle.y + rectangle.height / 2}
2455
+ M${rectangle.x + rectangle.width / 2} ${rectangle.y}
2456
+ L${rectangle.x + rectangle.width / 2} ${rectangle.y + rectangle.height}
2457
+ `, { ...options, fillStyle: 'solid' });
2458
+ },
2459
+ getTextRectangle(element) {
2460
+ const rectangle = getTextRectangle(element);
2461
+ rectangle.width = 0;
2462
+ rectangle.height = 0;
2463
+ return rectangle;
2464
+ }
2465
+ });
2466
+
2467
+ const SummingJunctionEngine = createEllipseEngine({
2468
+ draw(board, rectangle, options) {
2469
+ const rs = PlaitBoard.getRoughSVG(board);
2470
+ const rx = rectangle.width / 2;
2471
+ const ry = rectangle.height / 2;
2472
+ const startPoint = [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2];
2473
+ const centerPoint = [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
2474
+ const line1Points = getCrossingPointsBetweenEllipseAndSegment([rectangle.x, rectangle.y], [rectangle.x + rectangle.width, rectangle.y + rectangle.height], centerPoint[0], centerPoint[1], rx, ry);
2475
+ const line2Points = getCrossingPointsBetweenEllipseAndSegment([rectangle.x + rectangle.width, rectangle.y], [rectangle.x, rectangle.y + rectangle.height], centerPoint[0], centerPoint[1], rx, ry);
2476
+ return rs.path(`M${startPoint[0]} ${startPoint[1]}
2477
+ A${rx},${ry} 0 1,1 ${startPoint[0]} ${startPoint[1] - 0.01}
2478
+ M${line1Points[0][0]} ${line1Points[0][1]}
2479
+ L${line1Points[1][0]} ${line1Points[1][1]}
2480
+ M${line2Points[0][0]} ${line2Points[0][1]}
2481
+ L${line2Points[1][0]} ${line2Points[1][1]}
2482
+ `, { ...options, fillStyle: 'solid' });
2483
+ },
2484
+ getTextRectangle(element) {
2485
+ const rectangle = getTextRectangle(element);
2486
+ rectangle.width = 0;
2487
+ rectangle.height = 0;
2488
+ return rectangle;
2489
+ }
2490
+ });
2491
+
2492
+ const ShapeEngineMap = {
2493
+ [BasicShapes.rectangle]: RectangleEngine,
2494
+ [BasicShapes.diamond]: DiamondEngine,
2495
+ [BasicShapes.ellipse]: EllipseEngine,
2496
+ [BasicShapes.parallelogram]: ParallelogramEngine,
2497
+ [BasicShapes.roundRectangle]: RoundRectangleEngine,
2498
+ [BasicShapes.text]: RectangleEngine,
2499
+ [BasicShapes.triangle]: TriangleEngine,
2500
+ [BasicShapes.leftArrow]: LeftArrowEngine,
2501
+ [BasicShapes.trapezoid]: TrapezoidEngine,
2502
+ [BasicShapes.rightArrow]: RightArrowEngine,
2503
+ [BasicShapes.cross]: CrossEngine,
2504
+ [BasicShapes.star]: StarEngine,
2505
+ [BasicShapes.pentagon]: PentagonEngine,
2506
+ [BasicShapes.hexagon]: HexagonEngine,
2507
+ [BasicShapes.octagon]: OctagonEngine,
2508
+ [BasicShapes.pentagonArrow]: PentagonArrowEngine,
2509
+ [BasicShapes.processArrow]: ProcessArrowEngine,
2510
+ [BasicShapes.twoWayArrow]: TwoWayArrowEngine,
2511
+ [BasicShapes.comment]: CommentEngine,
2512
+ [BasicShapes.roundComment]: RoundCommentEngine,
2513
+ [BasicShapes.cloud]: CloudEngine,
2514
+ [FlowchartSymbols.process]: RectangleEngine,
2515
+ [FlowchartSymbols.decision]: DiamondEngine,
2516
+ [FlowchartSymbols.connector]: EllipseEngine,
2517
+ [FlowchartSymbols.data]: ParallelogramEngine,
2518
+ [FlowchartSymbols.terminal]: TerminalEngine,
2519
+ [FlowchartSymbols.manualInput]: ManualInputEngine,
2520
+ [FlowchartSymbols.preparation]: PreparationEngine,
2521
+ [FlowchartSymbols.manualLoop]: ManualLoopEngine,
2522
+ [FlowchartSymbols.merge]: MergeEngine,
2523
+ [FlowchartSymbols.delay]: DelayEngine,
2524
+ [FlowchartSymbols.storedData]: StoredDataEngine,
2525
+ [FlowchartSymbols.or]: OrEngine,
2526
+ [FlowchartSymbols.summingJunction]: SummingJunctionEngine,
2527
+ [FlowchartSymbols.predefinedProcess]: PredefinedProcessEngine,
2528
+ [FlowchartSymbols.offPage]: OffPageEngine
2529
+ };
2530
+ const getEngine = (shape) => {
2531
+ return ShapeEngineMap[shape];
2532
+ };
2533
+
2534
+ const createGeometryElement = (shape, points, text, options = {}, textProperties = {}) => {
2535
+ let textOptions = {};
2536
+ let alignment = Alignment.center;
2537
+ let textHeight = DefaultTextProperty.height;
2538
+ if (shape === BasicShapes.text) {
2539
+ textOptions = { autoSize: true };
2540
+ alignment = undefined;
2541
+ }
2542
+ textProperties = { ...textProperties };
2543
+ textProperties?.align && (alignment = textProperties?.align);
2544
+ textProperties?.textHeight && (textHeight = textProperties?.textHeight);
2305
2545
  delete textProperties?.align;
2306
2546
  delete textProperties?.textHeight;
2307
2547
  return {
@@ -2329,36 +2569,45 @@ const getTextRectangle = (element) => {
2329
2569
  y: elementRectangle.y + (elementRectangle.height - height) / 2
2330
2570
  };
2331
2571
  };
2332
- const drawBoundMask = (board, element) => {
2333
- const G = createG();
2572
+ const drawBoundReaction = (board, element, options = { hasMask: true, hasConnector: true }) => {
2573
+ const g = createG();
2334
2574
  const rectangle = RectangleClient.getRectangleByPoints(element.points);
2335
- const activeRectangle = RectangleClient.inflate(rectangle, ACTIVE_STROKE_WIDTH);
2336
- const shape = getShape(element);
2337
- const maskG = drawGeometry(board, activeRectangle, shape, {
2575
+ const activeRectangle = RectangleClient.inflate(rectangle, SNAPPING_STROKE_WIDTH);
2576
+ const shape = getElementShape(element);
2577
+ const strokeG = drawGeometry(board, activeRectangle, shape, {
2338
2578
  stroke: SELECTION_BORDER_COLOR,
2339
- strokeWidth: 1,
2340
- fill: SELECTION_FILL_COLOR,
2341
- fillStyle: 'solid'
2579
+ strokeWidth: SNAPPING_STROKE_WIDTH
2342
2580
  });
2343
- G.appendChild(maskG);
2344
- const connectorPoints = getEngine(shape).getConnectorPoints(activeRectangle);
2345
- connectorPoints.forEach(point => {
2346
- const circleG = drawCircle(PlaitBoard.getRoughSVG(board), point, 6, {
2347
- stroke: '#999999',
2348
- strokeWidth: 1,
2349
- fill: '#FFF',
2581
+ g.appendChild(strokeG);
2582
+ if (options.hasMask) {
2583
+ const maskG = drawGeometry(board, activeRectangle, shape, {
2584
+ stroke: SELECTION_BORDER_COLOR,
2585
+ strokeWidth: 0,
2586
+ fill: SELECTION_FILL_COLOR,
2350
2587
  fillStyle: 'solid'
2351
2588
  });
2352
- G.appendChild(circleG);
2353
- });
2354
- return G;
2589
+ g.appendChild(maskG);
2590
+ }
2591
+ if (options.hasConnector) {
2592
+ const connectorPoints = getEngine(shape).getConnectorPoints(rectangle);
2593
+ connectorPoints.forEach(point => {
2594
+ const circleG = drawCircle(PlaitBoard.getRoughSVG(board), point, 8, {
2595
+ stroke: SELECTION_BORDER_COLOR,
2596
+ strokeWidth: ACTIVE_STROKE_WIDTH,
2597
+ fill: '#FFF',
2598
+ fillStyle: 'solid'
2599
+ });
2600
+ g.appendChild(circleG);
2601
+ });
2602
+ }
2603
+ return g;
2355
2604
  };
2356
2605
  const drawGeometry = (board, outerRectangle, shape, options) => {
2357
2606
  return getEngine(shape).draw(board, outerRectangle, options);
2358
2607
  };
2359
2608
  const getNearestPoint = (element, point) => {
2360
2609
  const rectangle = RectangleClient.getRectangleByPoints(element.points);
2361
- const shape = getShape(element);
2610
+ const shape = getElementShape(element);
2362
2611
  return getEngine(shape).getNearestPoint(rectangle, point);
2363
2612
  };
2364
2613
  const getCenterPointsOnPolygon = (points) => {
@@ -2465,6 +2714,9 @@ const getDefaultGeometryProperty = (pointer) => {
2465
2714
  return getDefaultFlowchartProperty(pointer);
2466
2715
  }
2467
2716
  else {
2717
+ if (pointer === BasicShapes.cloud) {
2718
+ return DefaultCloudShapeProperty;
2719
+ }
2468
2720
  return DefaultBasicShapeProperty;
2469
2721
  }
2470
2722
  };
@@ -2516,7 +2768,14 @@ const getFillByElement = (board, element) => {
2516
2768
  return fill;
2517
2769
  };
2518
2770
  const getLineDashByElement = (element) => {
2519
- return element.strokeStyle === 'dashed' ? [8, 8 + getStrokeWidthByElement(element)] : undefined;
2771
+ switch (element.strokeStyle) {
2772
+ case StrokeStyle.dashed:
2773
+ return [8, 8 + getStrokeWidthByElement(element)];
2774
+ case StrokeStyle.dotted:
2775
+ return [0, 4 + getStrokeWidthByElement(element)];
2776
+ default:
2777
+ return undefined;
2778
+ }
2520
2779
  };
2521
2780
  const getStrokeStyleByElement = (element) => {
2522
2781
  return element.strokeStyle || StrokeStyle.solid;
@@ -2595,7 +2854,7 @@ const getConnectionPoint = (geometry, connection, direction, delta) => {
2595
2854
  };
2596
2855
  const getVectorByConnection = (boundElement, connection) => {
2597
2856
  const rectangle = RectangleClient.getRectangleByPoints(boundElement.points);
2598
- const shape = getShape(boundElement);
2857
+ const shape = getElementShape(boundElement);
2599
2858
  const engine = getEngine(shape);
2600
2859
  let vector = [0, 0];
2601
2860
  const direction = getDirectionByPointOfRectangle(connection);
@@ -2710,6 +2969,7 @@ var StrokeStyle;
2710
2969
  (function (StrokeStyle) {
2711
2970
  StrokeStyle["solid"] = "solid";
2712
2971
  StrokeStyle["dashed"] = "dashed";
2972
+ StrokeStyle["dotted"] = "dotted";
2713
2973
  })(StrokeStyle || (StrokeStyle = {}));
2714
2974
  var MemorizeKey;
2715
2975
  (function (MemorizeKey) {
@@ -2740,7 +3000,7 @@ const PlaitDrawElement = {
2740
3000
  return false;
2741
3001
  }
2742
3002
  },
2743
- isShape: (value) => {
3003
+ isShapeElement: (value) => {
2744
3004
  return PlaitDrawElement.isImage(value) || PlaitDrawElement.isGeometry(value);
2745
3005
  },
2746
3006
  isBasicShape: (value) => {
@@ -2826,7 +3086,7 @@ const collectLineUpdatedRefsByGeometry = (board, geometry, refs) => {
2826
3086
  const object = { ...line[handle] };
2827
3087
  const linePoints = getLinePoints(board, line);
2828
3088
  const point = isSourceBound ? linePoints[0] : linePoints[linePoints.length - 1];
2829
- object.connection = getConnectionByNearestPoint(board, point, geometry);
3089
+ object.connection = getHitConnection(board, point, geometry);
2830
3090
  const path = PlaitBoard.findPath(board, line);
2831
3091
  const index = refs.findIndex(obj => Path.equals(obj.path, path));
2832
3092
  if (index === -1) {
@@ -2846,7 +3106,7 @@ const collectLineUpdatedRefsByGeometry = (board, geometry, refs) => {
2846
3106
  const connectLineToGeometry = (board, lineElement, handle, geometryElement) => {
2847
3107
  const linePoints = PlaitLine.getPoints(board, lineElement);
2848
3108
  const point = handle === LineHandleKey.source ? linePoints[0] : linePoints[linePoints.length - 1];
2849
- const connection = getConnectionByNearestPoint(board, point, geometryElement);
3109
+ const connection = getHitConnection(board, point, geometryElement);
2850
3110
  if (connection) {
2851
3111
  let source = lineElement.source;
2852
3112
  let target = lineElement.target;
@@ -3058,9 +3318,8 @@ class GeometryComponent extends CommonPluginElement {
3058
3318
  get textManage() {
3059
3319
  return this.getTextManages()[0];
3060
3320
  }
3061
- constructor(viewContainerRef, cdr) {
3321
+ constructor(cdr) {
3062
3322
  super(cdr);
3063
- this.viewContainerRef = viewContainerRef;
3064
3323
  this.cdr = cdr;
3065
3324
  this.destroy$ = new Subject();
3066
3325
  }
@@ -3102,7 +3361,7 @@ class GeometryComponent extends CommonPluginElement {
3102
3361
  ngOnInit() {
3103
3362
  super.ngOnInit();
3104
3363
  this.initializeGenerator();
3105
- this.shapeGenerator.processDrawing(this.element, this.g);
3364
+ this.shapeGenerator.processDrawing(this.element, this.getElementG());
3106
3365
  this.activeGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), { selected: this.selected });
3107
3366
  this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), {
3108
3367
  selected: this.selected
@@ -3112,7 +3371,7 @@ class GeometryComponent extends CommonPluginElement {
3112
3371
  onContextChanged(value, previous) {
3113
3372
  const isChangeTheme = this.board.operations.find(op => op.type === 'set_theme');
3114
3373
  if (value.element !== previous.element || isChangeTheme) {
3115
- this.shapeGenerator.processDrawing(this.element, this.g);
3374
+ this.shapeGenerator.processDrawing(this.element, this.getElementG());
3116
3375
  this.activeGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), { selected: this.selected });
3117
3376
  this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), {
3118
3377
  selected: this.selected
@@ -3136,7 +3395,7 @@ class GeometryComponent extends CommonPluginElement {
3136
3395
  }
3137
3396
  drawText() {
3138
3397
  this.textManage.draw(this.element.text);
3139
- this.g.append(this.textManage.g);
3398
+ this.getElementG().append(this.textManage.g);
3140
3399
  const centerPoint = RectangleClient.getCenterPoint(this.board.getRectangle(this.element));
3141
3400
  this.textManage.updateAngle(centerPoint, this.element.angle);
3142
3401
  }
@@ -3187,7 +3446,7 @@ class GeometryComponent extends CommonPluginElement {
3187
3446
  this.activeGenerator.destroy();
3188
3447
  this.lineAutoCompleteGenerator.destroy();
3189
3448
  }
3190
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: GeometryComponent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
3449
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: GeometryComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
3191
3450
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.4", type: GeometryComponent, isStandalone: true, selector: "plait-draw-geometry", usesInheritance: true, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3192
3451
  }
3193
3452
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: GeometryComponent, decorators: [{
@@ -3198,7 +3457,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
3198
3457
  changeDetection: ChangeDetectionStrategy.OnPush,
3199
3458
  standalone: true
3200
3459
  }]
3201
- }], ctorParameters: () => [{ type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }] });
3460
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }] });
3202
3461
 
3203
3462
  class LineActiveGenerator extends Generator {
3204
3463
  constructor() {
@@ -3271,12 +3530,11 @@ class LineActiveGenerator extends Generator {
3271
3530
  }
3272
3531
  }
3273
3532
 
3274
- const debugKey$1 = 'debug:plait:line-turning';
3275
- const debugGenerator$1 = createDebugGenerator(debugKey$1);
3533
+ const debugKey$2 = 'debug:plait:line-turning';
3534
+ const debugGenerator$2 = createDebugGenerator(debugKey$2);
3276
3535
  class LineComponent extends CommonPluginElement {
3277
- constructor(viewContainerRef, cdr) {
3536
+ constructor(cdr) {
3278
3537
  super(cdr);
3279
- this.viewContainerRef = viewContainerRef;
3280
3538
  this.cdr = cdr;
3281
3539
  this.destroy$ = new Subject();
3282
3540
  this.boundedElements = {};
@@ -3288,7 +3546,7 @@ class LineComponent extends CommonPluginElement {
3288
3546
  }
3289
3547
  ngOnInit() {
3290
3548
  this.initializeGenerator();
3291
- this.shapeGenerator.processDrawing(this.element, this.g);
3549
+ this.shapeGenerator.processDrawing(this.element, this.getElementG());
3292
3550
  const linePoints = getLinePoints(this.board, this.element);
3293
3551
  this.activeGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), {
3294
3552
  selected: this.selected,
@@ -3297,7 +3555,7 @@ class LineComponent extends CommonPluginElement {
3297
3555
  super.ngOnInit();
3298
3556
  this.boundedElements = this.getBoundedElements();
3299
3557
  this.drawText();
3300
- debugGenerator$1.isDebug() && debugGenerator$1.drawCircles(this.board, this.element.points.slice(1, -1), 4, true);
3558
+ debugGenerator$2.isDebug() && debugGenerator$2.drawCircles(this.board, this.element.points.slice(1, -1), 4, true);
3301
3559
  }
3302
3560
  getBoundedElements() {
3303
3561
  const boundedElements = {};
@@ -3322,7 +3580,7 @@ class LineComponent extends CommonPluginElement {
3322
3580
  const isChangeTheme = this.board.operations.find(op => op.type === 'set_theme');
3323
3581
  const linePoints = getLinePoints(this.board, this.element);
3324
3582
  if (value.element !== previous.element || isChangeTheme) {
3325
- this.shapeGenerator.processDrawing(this.element, this.g);
3583
+ this.shapeGenerator.processDrawing(this.element, this.getElementG());
3326
3584
  this.activeGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), {
3327
3585
  selected: this.selected,
3328
3586
  linePoints
@@ -3340,7 +3598,7 @@ class LineComponent extends CommonPluginElement {
3340
3598
  }
3341
3599
  }
3342
3600
  if (isBoundedElementsChanged) {
3343
- this.shapeGenerator.processDrawing(this.element, this.g);
3601
+ this.shapeGenerator.processDrawing(this.element, this.getElementG());
3344
3602
  this.activeGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), {
3345
3603
  selected: this.selected,
3346
3604
  linePoints
@@ -3363,7 +3621,7 @@ class LineComponent extends CommonPluginElement {
3363
3621
  if (this.element.texts?.length) {
3364
3622
  this.getTextManages().forEach((manage, index) => {
3365
3623
  manage.draw(this.element.texts[index].text);
3366
- this.g.append(manage.g);
3624
+ this.getElementG().append(manage.g);
3367
3625
  });
3368
3626
  }
3369
3627
  }
@@ -3420,7 +3678,7 @@ class LineComponent extends CommonPluginElement {
3420
3678
  this.destroy$.complete();
3421
3679
  this.destroyTextManages();
3422
3680
  }
3423
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: LineComponent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
3681
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: LineComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
3424
3682
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.4", type: LineComponent, isStandalone: true, selector: "plait-draw-line", usesInheritance: true, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3425
3683
  }
3426
3684
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: LineComponent, decorators: [{
@@ -3431,7 +3689,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
3431
3689
  changeDetection: ChangeDetectionStrategy.OnPush,
3432
3690
  standalone: true
3433
3691
  }]
3434
- }], ctorParameters: () => [{ type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }] });
3692
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }] });
3435
3693
 
3436
3694
  const withDrawHotkey = (board) => {
3437
3695
  const { keyDown, dblClick } = board;
@@ -3466,57 +3724,483 @@ const withDrawHotkey = (board) => {
3466
3724
  return board;
3467
3725
  };
3468
3726
 
3469
- const withGeometryCreateByDrag = (board) => {
3470
- const { pointerMove, globalPointerUp } = board;
3471
- let geometryShapeG = null;
3472
- let temporaryElement = null;
3473
- let fakeCreateTextRef = null;
3474
- board.pointerMove = (event) => {
3475
- geometryShapeG?.remove();
3476
- geometryShapeG = createG();
3477
- const geometryGenerator = new GeometryShapeGenerator(board);
3478
- const geometryPointers = getGeometryPointers();
3479
- const isGeometryPointer = PlaitBoard.isInPointer(board, geometryPointers);
3480
- const dragMode = isGeometryPointer && isDndMode(board);
3481
- const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
3482
- const pointer = PlaitBoard.getPointer(board);
3483
- if (dragMode) {
3484
- const memorizedLatest = getMemorizedLatestByPointer(pointer);
3485
- if (pointer === BasicShapes.text) {
3486
- const property = getTextShapeProperty(board, DefaultTextProperty.text, memorizedLatest.textProperties['font-size']);
3487
- const points = RectangleClient.getPoints(RectangleClient.getRectangleByCenterPoint(movingPoint, property.width, property.height));
3488
- temporaryElement = createTextElement(board, points);
3489
- if (!fakeCreateTextRef) {
3490
- const textManage = new TextManage(board, PlaitBoard.getComponent(board).viewContainerRef, {
3491
- getRectangle: () => {
3492
- return getTextRectangle(temporaryElement);
3493
- }
3494
- });
3495
- PlaitBoard.getComponent(board)
3496
- .viewContainerRef.injector.get(NgZone)
3497
- .run(() => {
3498
- textManage.draw(temporaryElement.text);
3727
+ const debugKey$1 = 'debug:plait:resize-for-rotation';
3728
+ const debugGenerator$1 = createDebugGenerator(debugKey$1);
3729
+ function withDrawResize(board) {
3730
+ const { afterChange, drawActiveRectangle } = board;
3731
+ let snapG;
3732
+ let handleG;
3733
+ let needCustomActiveRectangle = false;
3734
+ let resizeActivePoints = null;
3735
+ const canResize = () => {
3736
+ const elements = getSelectedElements(board);
3737
+ return elements.length > 1 && elements.every(el => PlaitDrawElement.isDrawElement(el));
3738
+ };
3739
+ const options = {
3740
+ key: 'draw-elements',
3741
+ canResize,
3742
+ hitTest: (point) => {
3743
+ const elements = getSelectedElements(board);
3744
+ const boundingRectangle = getRectangleByElements(board, elements, false);
3745
+ const angle = getSelectionAngle(elements);
3746
+ const handleRef = getHitRectangleResizeHandleRef(board, boundingRectangle, point, angle);
3747
+ if (handleRef) {
3748
+ return {
3749
+ element: elements,
3750
+ rectangle: boundingRectangle,
3751
+ handle: handleRef.handle,
3752
+ cursorClass: handleRef.cursorClass
3753
+ };
3754
+ }
3755
+ return null;
3756
+ },
3757
+ onResize: (resizeRef, resizeState) => {
3758
+ snapG?.remove();
3759
+ debugGenerator$1.isDebug() && debugGenerator$1.clear();
3760
+ const isFromCorner = isCornerHandle(board, resizeRef.handle);
3761
+ const isAspectRatio = resizeState.isShift || isFromCorner;
3762
+ const centerPoint = RectangleClient.getCenterPoint(resizeRef.rectangle);
3763
+ const handleIndex = getIndexByResizeHandle(resizeRef.handle);
3764
+ const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, handleIndex, resizeRef.rectangle);
3765
+ const angle = getSelectionAngle(resizeRef.element);
3766
+ let bulkRotationRef;
3767
+ if (angle) {
3768
+ bulkRotationRef = {
3769
+ angle: angle,
3770
+ offsetX: 0,
3771
+ offsetY: 0,
3772
+ newCenterPoint: [0, 0]
3773
+ };
3774
+ const [rotatedStartPoint, rotateEndPoint] = rotatePoints([resizeState.startPoint, resizeState.endPoint], centerPoint, -bulkRotationRef.angle);
3775
+ resizeState.startPoint = rotatedStartPoint;
3776
+ resizeState.endPoint = rotateEndPoint;
3777
+ }
3778
+ const resizeSnapRefOptions = getSnapResizingRefOptions(board, resizeRef, resizeState, {
3779
+ originPoint,
3780
+ handlePoint
3781
+ }, isAspectRatio, isFromCorner);
3782
+ const resizeSnapRef = getSnapResizingRef(board, resizeRef.element, resizeSnapRefOptions);
3783
+ resizeActivePoints = resizeSnapRef.activePoints;
3784
+ snapG = resizeSnapRef.snapG;
3785
+ PlaitBoard.getElementActiveHost(board).append(snapG);
3786
+ if (bulkRotationRef) {
3787
+ const boundingBoxCornerPoints = RectangleClient.getPoints(resizeRef.rectangle);
3788
+ const resizedBoundingBoxCornerPoints = boundingBoxCornerPoints.map(p => {
3789
+ return movePointByZoomAndOriginPoint(p, originPoint, resizeSnapRef.xZoom, resizeSnapRef.yZoom);
3790
+ });
3791
+ const newBoundingBox = RectangleClient.getRectangleByPoints(resizedBoundingBoxCornerPoints);
3792
+ debugGenerator$1.isDebug() && debugGenerator$1.drawRectangle(board, newBoundingBox, { stroke: 'blue' });
3793
+ const newBoundingBoxCenter = RectangleClient.getCenterPoint(newBoundingBox);
3794
+ const adjustedNewBoundingBoxPoints = resetPointsAfterResize(RectangleClient.getRectangleByPoints(boundingBoxCornerPoints), RectangleClient.getRectangleByPoints(resizedBoundingBoxCornerPoints), centerPoint, newBoundingBoxCenter, bulkRotationRef.angle);
3795
+ const newCenter = RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(adjustedNewBoundingBoxPoints));
3796
+ bulkRotationRef = Object.assign(bulkRotationRef, {
3797
+ offsetX: newCenter[0] - newBoundingBoxCenter[0],
3798
+ offsetY: newCenter[1] - newBoundingBoxCenter[1],
3799
+ newCenterPoint: newCenter
3800
+ });
3801
+ debugGenerator$1.isDebug() && debugGenerator$1.drawRectangle(board, adjustedNewBoundingBoxPoints);
3802
+ }
3803
+ resizeRef.element.forEach(target => {
3804
+ const path = PlaitBoard.findPath(board, target);
3805
+ let points;
3806
+ if (bulkRotationRef) {
3807
+ const reversedPoints = rotatedDataPoints(target.points, centerPoint, -bulkRotationRef.angle);
3808
+ points = reversedPoints.map((p) => {
3809
+ return movePointByZoomAndOriginPoint(p, originPoint, resizeSnapRef.xZoom, resizeSnapRef.yZoom);
3499
3810
  });
3500
- fakeCreateTextRef = {
3501
- g: createG(),
3502
- textManage
3503
- };
3504
- PlaitBoard.getHost(board).append(fakeCreateTextRef.g);
3505
- fakeCreateTextRef.g.append(textManage.g);
3811
+ const adjustTargetPoints = points.map(p => [
3812
+ p[0] + bulkRotationRef.offsetX,
3813
+ p[1] + bulkRotationRef.offsetY
3814
+ ]);
3815
+ points = rotatedDataPoints(adjustTargetPoints, bulkRotationRef.newCenterPoint, bulkRotationRef.angle);
3506
3816
  }
3507
3817
  else {
3508
- fakeCreateTextRef.textManage.updateRectangle();
3509
- fakeCreateTextRef.g.append(fakeCreateTextRef.textManage.g);
3818
+ if (hasValidAngle(target)) {
3819
+ needCustomActiveRectangle = true;
3820
+ }
3821
+ if (hasValidAngle(target) && isAxisChangedByAngle(target.angle)) {
3822
+ points = getResizePointsByOtherwiseAxis(board, target.points, originPoint, resizeSnapRef.xZoom, resizeSnapRef.yZoom);
3823
+ }
3824
+ else {
3825
+ points = target.points.map(p => {
3826
+ return movePointByZoomAndOriginPoint(p, originPoint, resizeSnapRef.xZoom, resizeSnapRef.yZoom);
3827
+ });
3828
+ }
3510
3829
  }
3511
- }
3512
- else {
3513
- const points = getDefaultGeometryPoints(pointer, movingPoint);
3514
- temporaryElement = createDefaultGeometry(board, points, pointer);
3515
- geometryGenerator.processDrawing(temporaryElement, geometryShapeG);
3516
- PlaitBoard.getElementActiveHost(board).append(geometryShapeG);
3517
- }
3518
- }
3519
- pointerMove(event);
3830
+ if (PlaitDrawElement.isGeometry(target)) {
3831
+ const { height: textHeight } = getFirstTextManage(target).getSize();
3832
+ DrawTransforms.resizeGeometry(board, points, textHeight, path);
3833
+ }
3834
+ else if (PlaitDrawElement.isLine(target)) {
3835
+ Transforms.setNode(board, { points }, path);
3836
+ }
3837
+ else if (PlaitDrawElement.isImage(target)) {
3838
+ if (isAspectRatio) {
3839
+ Transforms.setNode(board, { points }, path);
3840
+ }
3841
+ else {
3842
+ // The image element does not follow the resize, but moves based on the center point.
3843
+ const targetRectangle = RectangleClient.getRectangleByPoints(target.points);
3844
+ const centerPoint = RectangleClient.getCenterPoint(targetRectangle);
3845
+ const newCenterPoint = movePointByZoomAndOriginPoint(centerPoint, originPoint, resizeSnapRef.xZoom, resizeSnapRef.yZoom);
3846
+ const newTargetRectangle = RectangleClient.getRectangleByCenterPoint(newCenterPoint, targetRectangle.width, targetRectangle.height);
3847
+ Transforms.setNode(board, { points: RectangleClient.getPoints(newTargetRectangle) }, path);
3848
+ }
3849
+ }
3850
+ });
3851
+ },
3852
+ afterResize: (resizeRef) => {
3853
+ snapG?.remove();
3854
+ snapG = null;
3855
+ if (needCustomActiveRectangle) {
3856
+ needCustomActiveRectangle = false;
3857
+ resizeActivePoints = null;
3858
+ const selectedElements = getSelectedElements(board);
3859
+ Transforms.addSelectionWithTemporaryElements(board, selectedElements);
3860
+ }
3861
+ }
3862
+ };
3863
+ withResize(board, options);
3864
+ board.afterChange = () => {
3865
+ afterChange();
3866
+ if (handleG) {
3867
+ handleG.remove();
3868
+ handleG = null;
3869
+ }
3870
+ if (canResize() && !isSelectionMoving(board)) {
3871
+ handleG = createG();
3872
+ const elements = getSelectedElements(board);
3873
+ const boundingRectangle = needCustomActiveRectangle
3874
+ ? RectangleClient.getRectangleByPoints(resizeActivePoints)
3875
+ : getRectangleByElements(board, elements, false);
3876
+ let corners = RectangleClient.getCornerPoints(boundingRectangle);
3877
+ const angle = getSelectionAngle(elements);
3878
+ if (angle) {
3879
+ const centerPoint = RectangleClient.getCenterPoint(boundingRectangle);
3880
+ corners = rotatePoints(corners, centerPoint, angle);
3881
+ }
3882
+ corners.forEach(corner => {
3883
+ const g = drawHandle(board, corner);
3884
+ handleG && handleG.append(g);
3885
+ });
3886
+ PlaitBoard.getElementActiveHost(board).append(handleG);
3887
+ }
3888
+ };
3889
+ board.drawActiveRectangle = () => {
3890
+ if (needCustomActiveRectangle) {
3891
+ const rectangle = RectangleClient.getRectangleByPoints(resizeActivePoints);
3892
+ return drawRectangle(board, RectangleClient.inflate(rectangle, ACTIVE_STROKE_WIDTH), {
3893
+ stroke: SELECTION_BORDER_COLOR,
3894
+ strokeWidth: ACTIVE_STROKE_WIDTH
3895
+ });
3896
+ }
3897
+ return drawActiveRectangle();
3898
+ };
3899
+ return board;
3900
+ }
3901
+ const getResizeOriginPointAndHandlePoint = (board, handleIndex, rectangle) => {
3902
+ const symmetricHandleIndex = getSymmetricHandleIndex(board, handleIndex);
3903
+ const originPoint = getResizeHandlePointByIndex(rectangle, symmetricHandleIndex);
3904
+ const handlePoint = getResizeHandlePointByIndex(rectangle, handleIndex);
3905
+ return {
3906
+ originPoint,
3907
+ handlePoint
3908
+ };
3909
+ };
3910
+ const getResizeZoom = (resizeStartAndEnd, resizeOriginPoint, resizeHandlePoint, isFromCorner, isAspectRatio) => {
3911
+ const [startPoint, endPoint] = resizeStartAndEnd;
3912
+ let xZoom = 0;
3913
+ let yZoom = 0;
3914
+ if (isFromCorner) {
3915
+ if (isAspectRatio) {
3916
+ let normalizedOffsetX = Point.getOffsetX(startPoint, endPoint);
3917
+ xZoom = normalizedOffsetX / (resizeHandlePoint[0] - resizeOriginPoint[0]);
3918
+ yZoom = xZoom;
3919
+ }
3920
+ else {
3921
+ let normalizedOffsetX = Point.getOffsetX(startPoint, endPoint);
3922
+ let normalizedOffsetY = Point.getOffsetY(startPoint, endPoint);
3923
+ xZoom = normalizedOffsetX / (resizeHandlePoint[0] - resizeOriginPoint[0]);
3924
+ yZoom = normalizedOffsetY / (resizeHandlePoint[1] - resizeOriginPoint[1]);
3925
+ }
3926
+ }
3927
+ else {
3928
+ const isHorizontal = Point.isHorizontal(resizeOriginPoint, resizeHandlePoint, 0.1) || false;
3929
+ let normalizedOffset = isHorizontal ? Point.getOffsetX(startPoint, endPoint) : Point.getOffsetY(startPoint, endPoint);
3930
+ let benchmarkOffset = isHorizontal ? resizeHandlePoint[0] - resizeOriginPoint[0] : resizeHandlePoint[1] - resizeOriginPoint[1];
3931
+ const zoom = normalizedOffset / benchmarkOffset;
3932
+ if (isAspectRatio) {
3933
+ xZoom = zoom;
3934
+ yZoom = zoom;
3935
+ }
3936
+ else {
3937
+ if (isHorizontal) {
3938
+ xZoom = zoom;
3939
+ }
3940
+ else {
3941
+ yZoom = zoom;
3942
+ }
3943
+ }
3944
+ }
3945
+ return { xZoom, yZoom };
3946
+ };
3947
+ const movePointByZoomAndOriginPoint = (p, resizeOriginPoint, xZoom, yZoom) => {
3948
+ const offsetX = (p[0] - resizeOriginPoint[0]) * xZoom;
3949
+ const offsetY = (p[1] - resizeOriginPoint[1]) * yZoom;
3950
+ return [p[0] + offsetX, p[1] + offsetY];
3951
+ };
3952
+ /**
3953
+ * 1. Rotate 90°
3954
+ * 2. Scale based on the rotated points
3955
+ * 3. Reverse rotate the scaled points by 90°
3956
+ */
3957
+ const getResizePointsByOtherwiseAxis = (board, points, resizeOriginPoint, xZoom, yZoom) => {
3958
+ const currentRectangle = RectangleClient.getRectangleByPoints(points);
3959
+ debugGenerator$1.isDebug() && debugGenerator$1.drawRectangle(board, currentRectangle, { stroke: 'black' });
3960
+ let resultPoints = points;
3961
+ resultPoints = rotatePoints(resultPoints, RectangleClient.getCenterPoint(currentRectangle), (1 / 2) * Math.PI);
3962
+ debugGenerator$1.isDebug() && debugGenerator$1.drawRectangle(board, resultPoints, { stroke: 'blue' });
3963
+ resultPoints = resultPoints.map(p => {
3964
+ return movePointByZoomAndOriginPoint(p, resizeOriginPoint, xZoom, yZoom);
3965
+ });
3966
+ debugGenerator$1.isDebug() && debugGenerator$1.drawRectangle(board, resultPoints);
3967
+ const newRectangle = RectangleClient.getRectangleByPoints(resultPoints);
3968
+ return rotatePoints(resultPoints, RectangleClient.getCenterPoint(newRectangle), -(1 / 2) * Math.PI);
3969
+ };
3970
+
3971
+ const debugKey = 'debug:plait:point-for-geometry';
3972
+ const debugGenerator = createDebugGenerator(debugKey);
3973
+ const EQUAL_SPACING = 10;
3974
+ function getSnapResizingRefOptions(board, resizeRef, resizeState, resizeOriginPointAndHandlePoint, isAspectRatio, isFromCorner) {
3975
+ const { originPoint, handlePoint } = resizeOriginPointAndHandlePoint;
3976
+ const resizePoints = [resizeState.startPoint, resizeState.endPoint];
3977
+ const { xZoom, yZoom } = getResizeZoom(resizePoints, originPoint, handlePoint, isFromCorner, isAspectRatio);
3978
+ let activeElements;
3979
+ let resizeOriginPoint = [];
3980
+ if (Array.isArray(resizeRef.element)) {
3981
+ activeElements = resizeRef.element;
3982
+ const rectangle = getRectangleByElements(board, resizeRef.element, false);
3983
+ resizeOriginPoint = RectangleClient.getPoints(rectangle);
3984
+ }
3985
+ else {
3986
+ activeElements = [resizeRef.element];
3987
+ resizeOriginPoint = resizeRef.element.points;
3988
+ }
3989
+ const points = resizeOriginPoint.map(p => {
3990
+ return movePointByZoomAndOriginPoint(p, originPoint, xZoom, yZoom);
3991
+ });
3992
+ const activeRectangle = getRectangleByAngle(RectangleClient.getRectangleByPoints(points), getSelectionAngle(activeElements)) ||
3993
+ RectangleClient.getRectangleByPoints(points);
3994
+ const resizeHandlePoint = movePointByZoomAndOriginPoint(handlePoint, originPoint, xZoom, yZoom);
3995
+ const [x, y] = getUnitVectorByPointAndPoint(originPoint, resizeHandlePoint);
3996
+ return {
3997
+ resizePoints,
3998
+ resizeOriginPoint,
3999
+ activeRectangle,
4000
+ originPoint,
4001
+ handlePoint,
4002
+ directionFactors: [getDirectionFactorByDirectionComponent(x), getDirectionFactorByDirectionComponent(y)],
4003
+ isAspectRatio,
4004
+ isFromCorner
4005
+ };
4006
+ }
4007
+ function getSnapResizingRef(board, activeElements, resizeSnapOptions) {
4008
+ const snapG = createG();
4009
+ const snapRectangles = getSnapRectangles(board, activeElements);
4010
+ let snapLineDelta = getIsometricLineDelta(snapRectangles, resizeSnapOptions);
4011
+ if (snapLineDelta.deltaX === 0 && snapLineDelta.deltaY === 0) {
4012
+ snapLineDelta = getSnapPointDelta(snapRectangles, resizeSnapOptions);
4013
+ }
4014
+ const angle = getSelectionAngle(activeElements);
4015
+ const activePointAndZoom = getActivePointAndZoom(snapLineDelta, resizeSnapOptions, angle);
4016
+ const isometricLinesG = drawIsometricSnapLines(board, activePointAndZoom.activePoints, snapRectangles, resizeSnapOptions, angle);
4017
+ const pointLinesG = drawResizingPointSnapLines(board, activePointAndZoom.activePoints, snapRectangles, resizeSnapOptions, angle);
4018
+ snapG.append(isometricLinesG, pointLinesG);
4019
+ return { ...activePointAndZoom, ...snapLineDelta, snapG };
4020
+ }
4021
+ function getSnapPointDelta(snapRectangles, resizeSnapOptions) {
4022
+ let pointLineDelta = {
4023
+ deltaX: 0,
4024
+ deltaY: 0
4025
+ };
4026
+ const { isAspectRatio, activeRectangle, directionFactors } = resizeSnapOptions;
4027
+ const drawHorizontal = directionFactors[0] !== 0 || isAspectRatio;
4028
+ const drawVertical = directionFactors[1] !== 0 || isAspectRatio;
4029
+ if (drawHorizontal) {
4030
+ const xSnapAxis = getTripleAxis(activeRectangle, true);
4031
+ const pointX = directionFactors[0] === -1 ? xSnapAxis[0] : xSnapAxis[2];
4032
+ const deltaX = getMinPointDelta(snapRectangles, pointX, true);
4033
+ if (Math.abs(deltaX) < SNAP_TOLERANCE) {
4034
+ pointLineDelta.deltaX = deltaX;
4035
+ if (pointLineDelta.deltaX !== 0 && isAspectRatio) {
4036
+ pointLineDelta.deltaY = pointLineDelta.deltaX / (activeRectangle.width / activeRectangle.height);
4037
+ return pointLineDelta;
4038
+ }
4039
+ }
4040
+ }
4041
+ if (drawVertical) {
4042
+ const ySnapAxis = getTripleAxis(activeRectangle, false);
4043
+ const pointY = directionFactors[1] === -1 ? ySnapAxis[0] : ySnapAxis[2];
4044
+ const deltaY = getMinPointDelta(snapRectangles, pointY, false);
4045
+ if (Math.abs(deltaY) < SNAP_TOLERANCE) {
4046
+ pointLineDelta.deltaY = deltaY;
4047
+ if (pointLineDelta.deltaY !== 0 && isAspectRatio) {
4048
+ pointLineDelta.deltaX = pointLineDelta.deltaY * (activeRectangle.width / activeRectangle.height);
4049
+ return pointLineDelta;
4050
+ }
4051
+ }
4052
+ }
4053
+ return pointLineDelta;
4054
+ }
4055
+ function getActivePointAndZoom(resizeSnapDelta, resizeSnapOptions, angle) {
4056
+ const { deltaX, deltaY } = resizeSnapDelta;
4057
+ const { resizePoints, isCreate } = resizeSnapOptions;
4058
+ const newResizePoints = [resizePoints[0], [resizePoints[1][0] + deltaX, resizePoints[1][1] + deltaY]];
4059
+ let activePoints = newResizePoints;
4060
+ let xZoom = 0;
4061
+ let yZoom = 0;
4062
+ if (!isCreate) {
4063
+ const { originPoint, handlePoint, isFromCorner, isAspectRatio, resizeOriginPoint } = resizeSnapOptions;
4064
+ const resizeZoom = getResizeZoom(newResizePoints, originPoint, handlePoint, isFromCorner, isAspectRatio);
4065
+ xZoom = resizeZoom.xZoom;
4066
+ yZoom = resizeZoom.yZoom;
4067
+ activePoints = resizeOriginPoint.map(p => {
4068
+ return movePointByZoomAndOriginPoint(p, originPoint, xZoom, yZoom);
4069
+ });
4070
+ if (angle) {
4071
+ activePoints = resetPointsAfterResize(RectangleClient.getRectangleByPoints(resizeOriginPoint), RectangleClient.getRectangleByPoints(activePoints), RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(resizeOriginPoint)), RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(activePoints)), angle);
4072
+ }
4073
+ }
4074
+ return {
4075
+ xZoom,
4076
+ yZoom,
4077
+ activePoints
4078
+ };
4079
+ }
4080
+ function getIsometricLineDelta(snapRectangles, resizeSnapOptions) {
4081
+ let isometricLineDelta = {
4082
+ deltaX: 0,
4083
+ deltaY: 0
4084
+ };
4085
+ const { isAspectRatio, activeRectangle } = resizeSnapOptions;
4086
+ const widthSnapRectangle = snapRectangles.find(item => Math.abs(item.width - activeRectangle.width) < SNAP_TOLERANCE);
4087
+ if (widthSnapRectangle) {
4088
+ const deltaWidth = widthSnapRectangle.width - activeRectangle.width;
4089
+ isometricLineDelta.deltaX = deltaWidth * resizeSnapOptions.directionFactors[0];
4090
+ if (isAspectRatio) {
4091
+ const deltaHeight = deltaWidth / (activeRectangle.width / activeRectangle.height);
4092
+ isometricLineDelta.deltaY = deltaHeight * resizeSnapOptions.directionFactors[1];
4093
+ return isometricLineDelta;
4094
+ }
4095
+ }
4096
+ const heightSnapRectangle = snapRectangles.find(item => Math.abs(item.height - activeRectangle.height) < SNAP_TOLERANCE);
4097
+ if (heightSnapRectangle) {
4098
+ const deltaHeight = heightSnapRectangle.height - activeRectangle.height;
4099
+ isometricLineDelta.deltaY = deltaHeight * resizeSnapOptions.directionFactors[1];
4100
+ if (isAspectRatio) {
4101
+ const deltaWidth = deltaHeight * (activeRectangle.width / activeRectangle.height);
4102
+ isometricLineDelta.deltaX = deltaWidth * resizeSnapOptions.directionFactors[0];
4103
+ return isometricLineDelta;
4104
+ }
4105
+ }
4106
+ return isometricLineDelta;
4107
+ }
4108
+ function getIsometricLinePoints(rectangle, isHorizontal) {
4109
+ return isHorizontal
4110
+ ? [
4111
+ [rectangle.x, rectangle.y - EQUAL_SPACING],
4112
+ [rectangle.x + rectangle.width, rectangle.y - EQUAL_SPACING]
4113
+ ]
4114
+ : [
4115
+ [rectangle.x - EQUAL_SPACING, rectangle.y],
4116
+ [rectangle.x - EQUAL_SPACING, rectangle.y + rectangle.height]
4117
+ ];
4118
+ }
4119
+ function drawResizingPointSnapLines(board, activePoints, snapRectangles, resizeSnapOptions, angle) {
4120
+ debugGenerator.isDebug() && debugGenerator.clear();
4121
+ const activeRectangle = getRectangleByAngle(RectangleClient.getRectangleByPoints(activePoints), angle) ||
4122
+ RectangleClient.getRectangleByPoints(activePoints);
4123
+ const { isAspectRatio, directionFactors } = resizeSnapOptions;
4124
+ const drawHorizontal = directionFactors[0] !== 0 || isAspectRatio;
4125
+ const drawVertical = directionFactors[1] !== 0 || isAspectRatio;
4126
+ return drawPointSnapLines(board, activeRectangle, snapRectangles, drawHorizontal, drawVertical);
4127
+ }
4128
+ function drawIsometricSnapLines(board, activePoints, snapRectangles, resizeSnapOptions, angle) {
4129
+ let widthIsometricPoints = [];
4130
+ let heightIsometricPoints = [];
4131
+ const drawHorizontalLine = resizeSnapOptions.directionFactors[0] !== 0 || resizeSnapOptions.isAspectRatio;
4132
+ const drawVerticalLine = resizeSnapOptions.directionFactors[1] !== 0 || resizeSnapOptions.isAspectRatio;
4133
+ const activeRectangle = getRectangleByAngle(RectangleClient.getRectangleByPoints(activePoints), angle) ||
4134
+ RectangleClient.getRectangleByPoints(activePoints);
4135
+ for (let snapRectangle of snapRectangles) {
4136
+ if (activeRectangle.width === snapRectangle.width && drawHorizontalLine) {
4137
+ widthIsometricPoints.push(getIsometricLinePoints(snapRectangle, true));
4138
+ }
4139
+ if (activeRectangle.height === snapRectangle.height && drawVerticalLine) {
4140
+ heightIsometricPoints.push(getIsometricLinePoints(snapRectangle, false));
4141
+ }
4142
+ }
4143
+ if (widthIsometricPoints.length && drawHorizontalLine) {
4144
+ widthIsometricPoints.push(getIsometricLinePoints(activeRectangle, true));
4145
+ }
4146
+ if (heightIsometricPoints.length && drawVerticalLine) {
4147
+ heightIsometricPoints.push(getIsometricLinePoints(activeRectangle, false));
4148
+ }
4149
+ const isometricLines = [...widthIsometricPoints, ...heightIsometricPoints];
4150
+ return drawSolidLines(board, isometricLines);
4151
+ }
4152
+
4153
+ const withGeometryCreateByDrag = (board) => {
4154
+ const { pointerMove, globalPointerUp } = board;
4155
+ let geometryShapeG = null;
4156
+ let temporaryElement = null;
4157
+ let fakeCreateTextRef = null;
4158
+ board.pointerMove = (event) => {
4159
+ geometryShapeG?.remove();
4160
+ geometryShapeG = createG();
4161
+ const geometryGenerator = new GeometryShapeGenerator(board);
4162
+ const geometryPointers = getGeometryPointers();
4163
+ const isGeometryPointer = PlaitBoard.isInPointer(board, geometryPointers);
4164
+ const dragMode = isGeometryPointer && isDndMode(board);
4165
+ const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4166
+ const pointer = PlaitBoard.getPointer(board);
4167
+ if (dragMode) {
4168
+ const memorizedLatest = getMemorizedLatestByPointer(pointer);
4169
+ if (pointer === BasicShapes.text) {
4170
+ const property = getTextShapeProperty(board, DefaultTextProperty.text, memorizedLatest.textProperties['font-size']);
4171
+ const points = RectangleClient.getPoints(RectangleClient.getRectangleByCenterPoint(movingPoint, property.width, property.height));
4172
+ temporaryElement = createTextElement(board, points);
4173
+ if (!fakeCreateTextRef) {
4174
+ const textManage = new TextManage(board, PlaitBoard.getComponent(board).viewContainerRef, {
4175
+ getRectangle: () => {
4176
+ return getTextRectangle(temporaryElement);
4177
+ }
4178
+ });
4179
+ PlaitBoard.getComponent(board)
4180
+ .viewContainerRef.injector.get(NgZone)
4181
+ .run(() => {
4182
+ textManage.draw(temporaryElement.text);
4183
+ });
4184
+ fakeCreateTextRef = {
4185
+ g: createG(),
4186
+ textManage
4187
+ };
4188
+ PlaitBoard.getHost(board).append(fakeCreateTextRef.g);
4189
+ fakeCreateTextRef.g.append(textManage.g);
4190
+ }
4191
+ else {
4192
+ fakeCreateTextRef.textManage.updateRectangle();
4193
+ fakeCreateTextRef.g.append(fakeCreateTextRef.textManage.g);
4194
+ }
4195
+ }
4196
+ else {
4197
+ const points = getDefaultGeometryPoints(pointer, movingPoint);
4198
+ temporaryElement = createDefaultGeometry(board, points, pointer);
4199
+ geometryGenerator.processDrawing(temporaryElement, geometryShapeG);
4200
+ PlaitBoard.getElementActiveHost(board).append(geometryShapeG);
4201
+ }
4202
+ }
4203
+ pointerMove(event);
3520
4204
  };
3521
4205
  board.globalPointerUp = (event) => {
3522
4206
  const geometryPointers = getGeometryPointers();
@@ -3541,6 +4225,7 @@ const withGeometryCreateByDrawing = (board) => {
3541
4225
  let geometryShapeG = null;
3542
4226
  let temporaryElement = null;
3543
4227
  let isShift = false;
4228
+ let snapG;
3544
4229
  board.keyDown = (event) => {
3545
4230
  isShift = isKeyHotkey('shift', event);
3546
4231
  keyDown(event);
@@ -3572,11 +4257,24 @@ const withGeometryCreateByDrawing = (board) => {
3572
4257
  geometryShapeG?.remove();
3573
4258
  geometryShapeG = createG();
3574
4259
  const geometryGenerator = new GeometryShapeGenerator(board);
3575
- const drawMode = !!start;
3576
4260
  const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
3577
4261
  const pointer = PlaitBoard.getPointer(board);
3578
- if (drawMode && pointer !== BasicShapes.text) {
3579
- const points = normalizeShapePoints([start, movingPoint], isShift);
4262
+ snapG?.remove();
4263
+ if (start && pointer !== BasicShapes.text) {
4264
+ let points = normalizeShapePoints([start, movingPoint], isShift);
4265
+ const activeRectangle = RectangleClient.getRectangleByPoints(points);
4266
+ const [x, y] = getUnitVectorByPointAndPoint(start, movingPoint);
4267
+ const resizeSnapRef = getSnapResizingRef(board, [], {
4268
+ resizePoints: points,
4269
+ activeRectangle,
4270
+ directionFactors: [getDirectionFactorByDirectionComponent(x), getDirectionFactorByDirectionComponent(y)],
4271
+ isAspectRatio: isShift,
4272
+ isFromCorner: true,
4273
+ isCreate: true
4274
+ });
4275
+ snapG = resizeSnapRef.snapG;
4276
+ PlaitBoard.getElementActiveHost(board).append(snapG);
4277
+ points = normalizeShapePoints(resizeSnapRef.activePoints, isShift);
3580
4278
  temporaryElement = createDefaultGeometry(board, points, pointer);
3581
4279
  geometryGenerator.processDrawing(temporaryElement, geometryShapeG);
3582
4280
  PlaitBoard.getElementActiveHost(board).append(geometryShapeG);
@@ -3599,6 +4297,7 @@ const withGeometryCreateByDrawing = (board) => {
3599
4297
  if (temporaryElement) {
3600
4298
  insertElement(board, temporaryElement);
3601
4299
  }
4300
+ snapG?.remove();
3602
4301
  geometryShapeG?.remove();
3603
4302
  geometryShapeG = null;
3604
4303
  start = null;
@@ -3670,7 +4369,7 @@ const insertClipboardData = (board, elements, startPoint) => {
3670
4369
 
3671
4370
  const withDrawFragment = (baseBoard) => {
3672
4371
  const board = baseBoard;
3673
- const { getDeletedFragment, setFragment, insertFragment } = board;
4372
+ const { getDeletedFragment, buildFragment, insertFragment } = board;
3674
4373
  board.getDeletedFragment = (data) => {
3675
4374
  const drawElements = getSelectedDrawElements(board);
3676
4375
  if (drawElements.length) {
@@ -3690,7 +4389,7 @@ const withDrawFragment = (baseBoard) => {
3690
4389
  }
3691
4390
  return getDeletedFragment(data);
3692
4391
  };
3693
- board.setFragment = (data, clipboardContext, rectangle, type) => {
4392
+ board.buildFragment = (clipboardContext, rectangle, type) => {
3694
4393
  const targetDrawElements = getSelectedDrawElements(board);
3695
4394
  let boundLineElements = [];
3696
4395
  if (targetDrawElements.length) {
@@ -3699,655 +4398,111 @@ const withDrawFragment = (baseBoard) => {
3699
4398
  const lineElements = targetDrawElements.filter(value => PlaitDrawElement.isLine(value));
3700
4399
  boundLineElements = getBoundedLineElements(board, geometryElements).filter(line => !lineElements.includes(line));
3701
4400
  }
3702
- const selectedElements = [...targetDrawElements, ...boundLineElements];
3703
- const elements = buildClipboardData(board, selectedElements, rectangle ? [rectangle.x, rectangle.y] : [0, 0]);
3704
- const text = getElementsText(selectedElements);
3705
- if (!clipboardContext) {
3706
- clipboardContext = createClipboardContext(WritableClipboardType.elements, elements, text);
3707
- }
3708
- else {
3709
- clipboardContext = addClipboardContext(clipboardContext, {
3710
- text,
3711
- type: WritableClipboardType.elements,
3712
- data: elements
3713
- });
3714
- }
3715
- }
3716
- setFragment(data, clipboardContext, rectangle, type);
3717
- };
3718
- board.insertFragment = (data, clipboardData, targetPoint) => {
3719
- const selectedElements = getSelectedElements(board);
3720
- if (clipboardData?.files?.length) {
3721
- const acceptImageArray = acceptImageTypes.map(type => 'image/' + type);
3722
- const canInsertionImage = !getElementOfFocusedImage(board) && !(selectedElements.length === 1 && board.isImageBindingAllowed(selectedElements[0]));
3723
- if (acceptImageArray.includes(clipboardData.files[0].type) && canInsertionImage) {
3724
- const imageFile = clipboardData.files[0];
3725
- buildImage(board, imageFile, DEFAULT_IMAGE_WIDTH, imageItem => {
3726
- DrawTransforms.insertImage(board, imageItem, targetPoint);
3727
- });
3728
- return;
3729
- }
3730
- }
3731
- if (clipboardData?.elements?.length) {
3732
- const drawElements = clipboardData.elements?.filter(value => PlaitDrawElement.isDrawElement(value));
3733
- if (clipboardData.elements && clipboardData.elements.length > 0 && drawElements.length > 0) {
3734
- insertClipboardData(board, drawElements, targetPoint);
3735
- }
3736
- }
3737
- if (clipboardData?.text) {
3738
- if (!clipboardData.elements || clipboardData.elements.length === 0) {
3739
- // (* ̄︶ ̄)
3740
- const insertAsChildren = selectedElements.length === 1 && selectedElements[0].children;
3741
- const insertAsFreeText = !insertAsChildren;
3742
- if (insertAsFreeText) {
3743
- DrawTransforms.insertText(board, targetPoint, clipboardData.text);
3744
- return;
3745
- }
3746
- }
3747
- }
3748
- insertFragment(data, clipboardData, targetPoint);
3749
- };
3750
- return board;
3751
- };
3752
- const getBoundedLineElements = (board, plaitShapes) => {
3753
- const lines = getLines(board);
3754
- return lines.filter(line => plaitShapes.find(shape => PlaitLine.isBoundElementOfSource(line, shape) || PlaitLine.isBoundElementOfTarget(line, shape)));
3755
- };
3756
-
3757
- const withLineCreateByDraw = (board) => {
3758
- const { pointerDown, pointerMove, globalPointerUp } = board;
3759
- let start = null;
3760
- let sourceElement;
3761
- let lineShapeG = null;
3762
- let temporaryElement = null;
3763
- board.pointerDown = (event) => {
3764
- const linePointers = getLinePointers();
3765
- const isLinePointer = PlaitBoard.isInPointer(board, linePointers);
3766
- if (!PlaitBoard.isReadonly(board) && isLinePointer && isDrawingMode(board)) {
3767
- const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
3768
- start = point;
3769
- const hitElement = getHitOutlineGeometry(board, point, REACTION_MARGIN);
3770
- if (hitElement) {
3771
- sourceElement = hitElement;
3772
- }
3773
- preventTouchMove(board, event, true);
3774
- }
3775
- pointerDown(event);
3776
- };
3777
- board.pointerMove = (event) => {
3778
- lineShapeG?.remove();
3779
- lineShapeG = createG();
3780
- let movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
3781
- if (start) {
3782
- const lineShape = PlaitBoard.getPointer(board);
3783
- temporaryElement = handleLineCreating(board, lineShape, start, movingPoint, sourceElement, lineShapeG);
3784
- }
3785
- pointerMove(event);
3786
- };
3787
- board.globalPointerUp = (event) => {
3788
- if (temporaryElement) {
3789
- Transforms.insertNode(board, temporaryElement, [board.children.length]);
3790
- clearSelectedElement(board);
3791
- addSelectedElement(board, temporaryElement);
3792
- BoardTransforms.updatePointerType(board, PlaitPointerType.selection);
3793
- }
3794
- lineShapeG?.remove();
3795
- lineShapeG = null;
3796
- sourceElement = null;
3797
- start = null;
3798
- temporaryElement = null;
3799
- preventTouchMove(board, event, false);
3800
- globalPointerUp(event);
3801
- };
3802
- return board;
3803
- };
3804
-
3805
- const ALIGN_TOLERANCE = 2;
3806
- const EQUAL_SPACING = 10;
3807
- const ALIGN_SPACING = 24;
3808
- class ResizeAlignReaction {
3809
- constructor(board, activeElements) {
3810
- this.board = board;
3811
- this.activeElements = activeElements;
3812
- this.alignRectangles = this.getAlignRectangle();
3813
- }
3814
- getAlignRectangle() {
3815
- const elements = findElements(this.board, {
3816
- match: element => this.board.isAlign(element) && !this.activeElements.some(item => item.id === element.id),
3817
- recursion: () => false,
3818
- isReverse: false
3819
- });
3820
- return elements.map(item => this.board.getRectangle(item));
3821
- }
3822
- getAlignLineRef(resizeAlignDelta, resizeAlignOptions) {
3823
- const { deltaX, deltaY } = resizeAlignDelta;
3824
- const { resizeState, originPoint, handlePoint, isFromCorner, isAspectRatio, resizeOriginPoints } = resizeAlignOptions;
3825
- const newResizeState = {
3826
- ...resizeState,
3827
- endPoint: [resizeState.endPoint[0] + deltaX, resizeState.endPoint[1] + deltaY]
3828
- };
3829
- const { xZoom, yZoom } = getResizeZoom(newResizeState, originPoint, handlePoint, isFromCorner, isAspectRatio);
3830
- const activePoints = resizeOriginPoints.map(p => {
3831
- return movePointByZoomAndOriginPoint(p, originPoint, xZoom, yZoom);
3832
- });
3833
- return {
3834
- deltaX,
3835
- deltaY,
3836
- xZoom,
3837
- yZoom,
3838
- activePoints
3839
- };
3840
- }
3841
- getEqualLineDelta(resizeAlignOptions) {
3842
- let equalLineDelta = {
3843
- deltaX: 0,
3844
- deltaY: 0
3845
- };
3846
- const { isAspectRatio, activeRectangle } = resizeAlignOptions;
3847
- const widthAlignRectangle = this.alignRectangles.find(item => Math.abs(item.width - activeRectangle.width) < ALIGN_TOLERANCE);
3848
- if (widthAlignRectangle) {
3849
- const deltaWidth = widthAlignRectangle.width - activeRectangle.width;
3850
- equalLineDelta.deltaX = deltaWidth * resizeAlignOptions.directionFactors[0];
3851
- if (isAspectRatio) {
3852
- const deltaHeight = deltaWidth / (activeRectangle.width / activeRectangle.height);
3853
- equalLineDelta.deltaY = deltaHeight * resizeAlignOptions.directionFactors[1];
3854
- return equalLineDelta;
3855
- }
3856
- }
3857
- const heightAlignRectangle = this.alignRectangles.find(item => Math.abs(item.height - activeRectangle.height) < ALIGN_TOLERANCE);
3858
- if (heightAlignRectangle) {
3859
- const deltaHeight = heightAlignRectangle.height - activeRectangle.height;
3860
- equalLineDelta.deltaY = deltaHeight * resizeAlignOptions.directionFactors[1];
3861
- if (isAspectRatio) {
3862
- const deltaWidth = deltaHeight * (activeRectangle.width / activeRectangle.height);
3863
- equalLineDelta.deltaX = deltaWidth * resizeAlignOptions.directionFactors[0];
3864
- return equalLineDelta;
3865
- }
3866
- }
3867
- return equalLineDelta;
3868
- }
3869
- drawEqualLines(activePoints, resizeAlignOptions) {
3870
- let widthEqualPoints = [];
3871
- let heightEqualPoints = [];
3872
- const drawHorizontalLine = resizeAlignOptions.directionFactors[0] !== 0 || resizeAlignOptions.isAspectRatio;
3873
- const drawVerticalLine = resizeAlignOptions.directionFactors[1] !== 0 || resizeAlignOptions.isAspectRatio;
3874
- const activeRectangle = RectangleClient.getRectangleByPoints(activePoints);
3875
- for (let alignRectangle of this.alignRectangles) {
3876
- if (activeRectangle.width === alignRectangle.width && drawHorizontalLine) {
3877
- widthEqualPoints.push(getEqualLinePoints(alignRectangle, true));
3878
- }
3879
- if (activeRectangle.height === alignRectangle.height && drawVerticalLine) {
3880
- heightEqualPoints.push(getEqualLinePoints(alignRectangle, false));
3881
- }
3882
- }
3883
- if (widthEqualPoints.length && drawHorizontalLine) {
3884
- widthEqualPoints.push(getEqualLinePoints(activeRectangle, true));
3885
- }
3886
- if (heightEqualPoints.length && drawVerticalLine) {
3887
- heightEqualPoints.push(getEqualLinePoints(activeRectangle, false));
3888
- }
3889
- const equalLinePoints = [...widthEqualPoints, ...heightEqualPoints];
3890
- return drawEqualLines(this.board, equalLinePoints);
3891
- }
3892
- getAlignLineDelta(resizeAlignOptions) {
3893
- let alignLineDelta = {
3894
- deltaX: 0,
3895
- deltaY: 0
3896
- };
3897
- const { isAspectRatio, activeRectangle, directionFactors } = resizeAlignOptions;
3898
- const drawHorizontal = directionFactors[0] !== 0 || isAspectRatio;
3899
- const drawVertical = directionFactors[1] !== 0 || isAspectRatio;
3900
- if (drawHorizontal) {
3901
- const xAlignAxis = getTripleAlignAxis(activeRectangle, true);
3902
- const alignX = directionFactors[0] === -1 ? xAlignAxis[0] : xAlignAxis[2];
3903
- const deltaX = getMinAlignDelta(this.alignRectangles, alignX, true);
3904
- if (Math.abs(deltaX) < ALIGN_TOLERANCE) {
3905
- alignLineDelta.deltaX = deltaX;
3906
- if (alignLineDelta.deltaX !== 0 && isAspectRatio) {
3907
- alignLineDelta.deltaY = alignLineDelta.deltaX / (activeRectangle.width / activeRectangle.height);
3908
- return alignLineDelta;
3909
- }
3910
- }
3911
- }
3912
- if (drawVertical) {
3913
- const yAlignAxis = getTripleAlignAxis(activeRectangle, false);
3914
- const alignY = directionFactors[1] === -1 ? yAlignAxis[0] : yAlignAxis[2];
3915
- const deltaY = getMinAlignDelta(this.alignRectangles, alignY, false);
3916
- if (Math.abs(deltaY) < ALIGN_TOLERANCE) {
3917
- alignLineDelta.deltaY = deltaY;
3918
- if (alignLineDelta.deltaY !== 0 && isAspectRatio) {
3919
- alignLineDelta.deltaX = alignLineDelta.deltaY * (activeRectangle.width / activeRectangle.height);
3920
- return alignLineDelta;
3921
- }
3922
- }
3923
- }
3924
- return alignLineDelta;
3925
- }
3926
- drawAlignLines(activePoints, resizeAlignOptions) {
3927
- let alignLinePoints = [];
3928
- const activeRectangle = RectangleClient.getRectangleByPoints(activePoints);
3929
- const alignAxisX = getTripleAlignAxis(activeRectangle, true);
3930
- const alignAxisY = getTripleAlignAxis(activeRectangle, false);
3931
- const alignLineRefs = [
3932
- {
3933
- axis: alignAxisX[0],
3934
- isHorizontal: true,
3935
- alignRectangles: []
3936
- },
3937
- {
3938
- axis: alignAxisX[2],
3939
- isHorizontal: true,
3940
- alignRectangles: []
3941
- },
3942
- {
3943
- axis: alignAxisY[0],
3944
- isHorizontal: false,
3945
- alignRectangles: []
3946
- },
3947
- {
3948
- axis: alignAxisY[2],
3949
- isHorizontal: false,
3950
- alignRectangles: []
3951
- }
3952
- ];
3953
- const setAlignLine = (axis, alignRectangle, isHorizontal) => {
3954
- const boundingRectangle = RectangleClient.inflate(RectangleClient.getBoundingRectangle([activeRectangle, alignRectangle]), ALIGN_SPACING);
3955
- if (isHorizontal) {
3956
- const pointStart = [axis, boundingRectangle.y];
3957
- const pointEnd = [axis, boundingRectangle.y + boundingRectangle.height];
3958
- alignLinePoints.push([pointStart, pointEnd]);
3959
- }
3960
- else {
3961
- const pointStart = [boundingRectangle.x, axis];
3962
- const pointEnd = [boundingRectangle.x + boundingRectangle.width, axis];
3963
- alignLinePoints.push([pointStart, pointEnd]);
3964
- }
3965
- };
3966
- const { isAspectRatio, directionFactors } = resizeAlignOptions;
3967
- const drawHorizontal = directionFactors[0] !== 0 || isAspectRatio;
3968
- const drawVertical = directionFactors[1] !== 0 || isAspectRatio;
3969
- for (let index = 0; index < this.alignRectangles.length; index++) {
3970
- const element = this.alignRectangles[index];
3971
- if (isAlign(alignLineRefs[0].axis, element, alignLineRefs[0].isHorizontal)) {
3972
- alignLineRefs[0].alignRectangles.push(element);
3973
- }
3974
- if (isAlign(alignLineRefs[1].axis, element, alignLineRefs[1].isHorizontal)) {
3975
- alignLineRefs[1].alignRectangles.push(element);
3976
- }
3977
- if (isAlign(alignLineRefs[2].axis, element, alignLineRefs[2].isHorizontal)) {
3978
- alignLineRefs[2].alignRectangles.push(element);
3979
- }
3980
- if (isAlign(alignLineRefs[3].axis, element, alignLineRefs[3].isHorizontal)) {
3981
- alignLineRefs[3].alignRectangles.push(element);
3982
- }
3983
- }
3984
- if (drawHorizontal && alignLineRefs[0].alignRectangles.length) {
3985
- const leftRectangle = alignLineRefs[0].alignRectangles.length === 1
3986
- ? alignLineRefs[0].alignRectangles[0]
3987
- : getNearestAlignRectangle(alignLineRefs[0].alignRectangles, activeRectangle);
3988
- setAlignLine(alignLineRefs[0].axis, leftRectangle, alignLineRefs[0].isHorizontal);
3989
- }
3990
- if (drawHorizontal && alignLineRefs[1].alignRectangles.length) {
3991
- const rightRectangle = alignLineRefs[1].alignRectangles.length === 1
3992
- ? alignLineRefs[1].alignRectangles[0]
3993
- : getNearestAlignRectangle(alignLineRefs[1].alignRectangles, activeRectangle);
3994
- setAlignLine(alignLineRefs[1].axis, rightRectangle, alignLineRefs[1].isHorizontal);
3995
- }
3996
- if (drawVertical && alignLineRefs[2].alignRectangles.length) {
3997
- const topRectangle = alignLineRefs[2].alignRectangles.length === 1
3998
- ? alignLineRefs[2].alignRectangles[0]
3999
- : getNearestAlignRectangle(alignLineRefs[2].alignRectangles, activeRectangle);
4000
- setAlignLine(alignLineRefs[2].axis, topRectangle, alignLineRefs[2].isHorizontal);
4001
- }
4002
- if (drawVertical && alignLineRefs[3].alignRectangles.length) {
4003
- const bottomRectangle = alignLineRefs[3].alignRectangles.length === 1
4004
- ? alignLineRefs[3].alignRectangles[0]
4005
- : getNearestAlignRectangle(alignLineRefs[3].alignRectangles, activeRectangle);
4006
- setAlignLine(alignLineRefs[3].axis, bottomRectangle, alignLineRefs[3].isHorizontal);
4007
- }
4008
- return drawAlignLines(this.board, alignLinePoints);
4009
- }
4010
- handleResizeAlign(resizeAlignOptions) {
4011
- const alignG = createG();
4012
- let alignLineDelta = this.getEqualLineDelta(resizeAlignOptions);
4013
- if (alignLineDelta.deltaX === 0 && alignLineDelta.deltaY === 0) {
4014
- alignLineDelta = this.getAlignLineDelta(resizeAlignOptions);
4015
- }
4016
- const equalLineRef = this.getAlignLineRef(alignLineDelta, resizeAlignOptions);
4017
- const equalLinesG = this.drawEqualLines(equalLineRef.activePoints, resizeAlignOptions);
4018
- const alignLinesG = this.drawAlignLines(equalLineRef.activePoints, resizeAlignOptions);
4019
- alignG.append(equalLinesG, alignLinesG);
4020
- return { ...equalLineRef, alignG };
4021
- }
4022
- }
4023
- function getBarPoint(point, isHorizontal) {
4024
- return isHorizontal
4025
- ? [
4026
- [point[0], point[1] - 4],
4027
- [point[0], point[1] + 4]
4028
- ]
4029
- : [
4030
- [point[0] - 4, point[1]],
4031
- [point[0] + 4, point[1]]
4032
- ];
4033
- }
4034
- function getEqualLinePoints(rectangle, isHorizontal) {
4035
- return isHorizontal
4036
- ? [
4037
- [rectangle.x, rectangle.y - EQUAL_SPACING],
4038
- [rectangle.x + rectangle.width, rectangle.y - EQUAL_SPACING]
4039
- ]
4040
- : [
4041
- [rectangle.x - EQUAL_SPACING, rectangle.y],
4042
- [rectangle.x - EQUAL_SPACING, rectangle.y + rectangle.height]
4043
- ];
4044
- }
4045
- function drawEqualLines(board, lines) {
4046
- const g = createG();
4047
- lines.forEach(line => {
4048
- if (!line.length)
4049
- return;
4050
- const yAlign = PlaitBoard.getRoughSVG(board).line(line[0][0], line[0][1], line[1][0], line[1][1], {
4051
- stroke: SELECTION_BORDER_COLOR,
4052
- strokeWidth: 1
4053
- });
4054
- g.appendChild(yAlign);
4055
- line.forEach(point => {
4056
- const barPoint = getBarPoint(point, !!Point.isHorizontal(line[0], line[1]));
4057
- const bar = PlaitBoard.getRoughSVG(board).line(barPoint[0][0], barPoint[0][1], barPoint[1][0], barPoint[1][1], {
4058
- stroke: SELECTION_BORDER_COLOR,
4059
- strokeWidth: 1
4060
- });
4061
- g.appendChild(bar);
4062
- });
4063
- });
4064
- return g;
4065
- }
4066
- function drawAlignLines(board, lines) {
4067
- const g = createG();
4068
- lines.forEach(points => {
4069
- if (!points.length)
4070
- return;
4071
- const xAlign = PlaitBoard.getRoughSVG(board).line(points[0][0], points[0][1], points[1][0], points[1][1], {
4072
- stroke: SELECTION_BORDER_COLOR,
4073
- strokeWidth: 1,
4074
- strokeLineDash: [4, 4]
4075
- });
4076
- g.appendChild(xAlign);
4077
- });
4078
- return g;
4079
- }
4080
- const getTripleAlignAxis = (rectangle, isHorizontal) => {
4081
- const axis = isHorizontal ? 'x' : 'y';
4082
- const side = isHorizontal ? 'width' : 'height';
4083
- return [rectangle[axis], rectangle[axis] + rectangle[side] / 2, rectangle[axis] + rectangle[side]];
4084
- };
4085
- const isAlign = (axis, rectangle, isHorizontal) => {
4086
- const alignAxis = getTripleAlignAxis(rectangle, isHorizontal);
4087
- return alignAxis.includes(axis);
4088
- };
4089
- const getClosestDelta = (axis, rectangle, isHorizontal) => {
4090
- const alignAxis = getTripleAlignAxis(rectangle, isHorizontal);
4091
- const deltas = alignAxis.map(item => item - axis);
4092
- const absDeltas = deltas.map(item => Math.abs(item));
4093
- const index = absDeltas.indexOf(Math.min(...absDeltas));
4094
- return deltas[index];
4095
- };
4096
- function getMinAlignDelta(alignRectangles, axis, isHorizontal) {
4097
- let delta = ALIGN_TOLERANCE;
4098
- alignRectangles.forEach(item => {
4099
- const distance = getClosestDelta(axis, item, isHorizontal);
4100
- if (Math.abs(distance) < Math.abs(delta)) {
4101
- delta = distance;
4102
- }
4103
- });
4104
- return delta;
4105
- }
4106
- function getNearestAlignRectangle(alignRectangles, activeRectangle) {
4107
- let minDistance = Infinity;
4108
- let nearestRectangle = alignRectangles[0];
4109
- alignRectangles.forEach(item => {
4110
- const distance = Math.sqrt(Math.pow(activeRectangle.x - item.x, 2) + Math.pow(activeRectangle.y - item.y, 2));
4111
- if (distance < minDistance) {
4112
- minDistance = distance;
4113
- nearestRectangle = item;
4114
- }
4115
- });
4116
- return nearestRectangle;
4117
- }
4118
-
4119
- function getResizeAlignRef(board, resizeRef, resizeState, resizeOriginPointAndHandlePoint, isAspectRatio, isFromCorner) {
4120
- const { originPoint, handlePoint } = resizeOriginPointAndHandlePoint;
4121
- const { xZoom, yZoom } = getResizeZoom(resizeState, originPoint, handlePoint, isFromCorner, isAspectRatio);
4122
- let activeElements;
4123
- let resizeOriginPoints = [];
4124
- if (Array.isArray(resizeRef.element)) {
4125
- activeElements = resizeRef.element;
4126
- const rectangle = getRectangleByElements(board, resizeRef.element, false);
4127
- resizeOriginPoints = RectangleClient.getPoints(rectangle);
4128
- }
4129
- else {
4130
- activeElements = [resizeRef.element];
4131
- resizeOriginPoints = resizeRef.element.points;
4132
- }
4133
- const points = resizeOriginPoints.map(p => {
4134
- return movePointByZoomAndOriginPoint(p, originPoint, xZoom, yZoom);
4135
- });
4136
- const activeRectangle = RectangleClient.getRectangleByPoints(points);
4137
- const resizeAlignReaction = new ResizeAlignReaction(board, activeElements);
4138
- const resizeHandlePoint = movePointByZoomAndOriginPoint(handlePoint, originPoint, xZoom, yZoom);
4139
- const [x, y] = getUnitVectorByPointAndPoint(originPoint, resizeHandlePoint);
4140
- return resizeAlignReaction.handleResizeAlign({
4141
- resizeState,
4142
- resizeOriginPoints,
4143
- activeRectangle,
4144
- originPoint,
4145
- handlePoint,
4146
- directionFactors: [getDirectionFactorByDirectionComponent(x), getDirectionFactorByDirectionComponent(y)],
4147
- isAspectRatio,
4148
- isFromCorner
4149
- });
4150
- }
4151
-
4152
- const debugKey = 'debug:plait:resize-for-rotation';
4153
- const debugGenerator = createDebugGenerator(debugKey);
4154
- function withDrawResize(board) {
4155
- const { afterChange } = board;
4156
- let alignG;
4157
- let handleG;
4158
- const canResize = () => {
4159
- const elements = getSelectedElements(board);
4160
- return elements.length > 1 && elements.every(el => PlaitDrawElement.isDrawElement(el));
4161
- };
4162
- const options = {
4163
- key: 'draw-elements',
4164
- canResize,
4165
- hitTest: (point) => {
4166
- const elements = getSelectedElements(board);
4167
- const boundingRectangle = getRectangleByElements(board, elements, false);
4168
- const angle = getSelectionAngle(elements);
4169
- const handleRef = getHitRectangleResizeHandleRef(board, boundingRectangle, point, angle);
4170
- if (handleRef) {
4171
- return {
4172
- element: elements,
4173
- rectangle: boundingRectangle,
4174
- handle: handleRef.handle,
4175
- cursorClass: handleRef.cursorClass
4176
- };
4177
- }
4178
- return null;
4179
- },
4180
- onResize: (resizeRef, resizeState) => {
4181
- alignG?.remove();
4182
- debugGenerator.isDebug() && debugGenerator.clear();
4183
- const isFromCorner = isCornerHandle(board, resizeRef.handle);
4184
- const isAspectRatio = resizeState.isShift || isFromCorner;
4185
- const centerPoint = RectangleClient.getCenterPoint(resizeRef.rectangle);
4186
- const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, resizeRef);
4187
- const angle = getSelectionAngle(resizeRef.element);
4188
- let bulkRotationRef;
4189
- if (angle) {
4190
- bulkRotationRef = {
4191
- angle: angle,
4192
- offsetX: 0,
4193
- offsetY: 0,
4194
- newCenterPoint: [0, 0]
4195
- };
4196
- const [rotatedStartPoint, rotateEndPoint] = rotatePoints([resizeState.startPoint, resizeState.endPoint], centerPoint, -bulkRotationRef.angle);
4197
- resizeState.startPoint = rotatedStartPoint;
4198
- resizeState.endPoint = rotateEndPoint;
4199
- }
4200
- const resizeAlignRef = getResizeAlignRef(board, resizeRef, resizeState, {
4201
- originPoint,
4202
- handlePoint
4203
- }, isAspectRatio, isFromCorner);
4204
- alignG = resizeAlignRef.alignG;
4205
- PlaitBoard.getElementActiveHost(board).append(alignG);
4206
- if (bulkRotationRef) {
4207
- const boundingBoxCornerPoints = RectangleClient.getPoints(resizeRef.rectangle);
4208
- const resizedBoundingBoxCornerPoints = boundingBoxCornerPoints.map(p => {
4209
- return movePointByZoomAndOriginPoint(p, originPoint, resizeAlignRef.xZoom, resizeAlignRef.yZoom);
4210
- });
4211
- const newBoundingBox = RectangleClient.getRectangleByPoints(resizedBoundingBoxCornerPoints);
4212
- debugGenerator.isDebug() && debugGenerator.drawRectangle(board, newBoundingBox, { stroke: 'blue' });
4213
- const newBoundingBoxCenter = RectangleClient.getCenterPoint(newBoundingBox);
4214
- const adjustedNewBoundingBoxPoints = resetPointsAfterResize(RectangleClient.getRectangleByPoints(boundingBoxCornerPoints), RectangleClient.getRectangleByPoints(resizedBoundingBoxCornerPoints), centerPoint, newBoundingBoxCenter, bulkRotationRef.angle);
4215
- const newCenter = RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(adjustedNewBoundingBoxPoints));
4216
- bulkRotationRef = Object.assign(bulkRotationRef, {
4217
- offsetX: newCenter[0] - newBoundingBoxCenter[0],
4218
- offsetY: newCenter[1] - newBoundingBoxCenter[1],
4219
- newCenterPoint: newCenter
4401
+ const selectedElements = [...targetDrawElements, ...boundLineElements];
4402
+ const elements = buildClipboardData(board, selectedElements, rectangle ? [rectangle.x, rectangle.y] : [0, 0]);
4403
+ const text = getElementsText(selectedElements);
4404
+ if (!clipboardContext) {
4405
+ clipboardContext = createClipboardContext(WritableClipboardType.elements, elements, text);
4406
+ }
4407
+ else {
4408
+ clipboardContext = addClipboardContext(clipboardContext, {
4409
+ text,
4410
+ type: WritableClipboardType.elements,
4411
+ elements
4220
4412
  });
4221
- debugGenerator.isDebug() && debugGenerator.drawRectangle(board, adjustedNewBoundingBoxPoints);
4222
4413
  }
4223
- resizeRef.element.forEach(target => {
4224
- const path = PlaitBoard.findPath(board, target);
4225
- let points;
4226
- if (bulkRotationRef) {
4227
- const reversedPoints = rotatedDataPoints(target.points, centerPoint, -bulkRotationRef.angle);
4228
- points = reversedPoints.map((p) => {
4229
- return movePointByZoomAndOriginPoint(p, originPoint, resizeAlignRef.xZoom, resizeAlignRef.yZoom);
4230
- });
4231
- const adjustTargetPoints = points.map(p => [
4232
- p[0] + bulkRotationRef.offsetX,
4233
- p[1] + bulkRotationRef.offsetY
4234
- ]);
4235
- points = rotatedDataPoints(adjustTargetPoints, bulkRotationRef.newCenterPoint, bulkRotationRef.angle);
4236
- }
4237
- else {
4238
- points = target.points.map(p => {
4239
- return movePointByZoomAndOriginPoint(p, originPoint, resizeAlignRef.xZoom, resizeAlignRef.yZoom);
4240
- });
4241
- }
4242
- if (PlaitDrawElement.isGeometry(target)) {
4243
- const { height: textHeight } = getFirstTextManage(target).getSize();
4244
- DrawTransforms.resizeGeometry(board, points, textHeight, path);
4245
- }
4246
- else if (PlaitDrawElement.isLine(target)) {
4247
- Transforms.setNode(board, { points }, path);
4248
- }
4249
- else if (PlaitDrawElement.isImage(target)) {
4250
- if (isAspectRatio) {
4251
- Transforms.setNode(board, { points }, path);
4252
- }
4253
- else {
4254
- // The image element does not follow the resize, but moves based on the center point.
4255
- const targetRectangle = RectangleClient.getRectangleByPoints(target.points);
4256
- const centerPoint = RectangleClient.getCenterPoint(targetRectangle);
4257
- const newCenterPoint = movePointByZoomAndOriginPoint(centerPoint, originPoint, resizeAlignRef.xZoom, resizeAlignRef.yZoom);
4258
- const newTargetRectangle = RectangleClient.getRectangleByCenterPoint(newCenterPoint, targetRectangle.width, targetRectangle.height);
4259
- Transforms.setNode(board, { points: RectangleClient.getPoints(newTargetRectangle) }, path);
4260
- }
4261
- }
4262
- });
4263
- },
4264
- afterResize: (resizeRef) => {
4265
- alignG?.remove();
4266
- alignG = null;
4267
4414
  }
4415
+ return buildFragment(clipboardContext, rectangle, type);
4268
4416
  };
4269
- withResize(board, options);
4270
- board.afterChange = () => {
4271
- afterChange();
4272
- if (handleG) {
4273
- handleG.remove();
4274
- handleG = null;
4417
+ board.insertFragment = (clipboardData, targetPoint) => {
4418
+ const selectedElements = getSelectedElements(board);
4419
+ if (clipboardData?.files?.length) {
4420
+ const acceptImageArray = acceptImageTypes.map(type => 'image/' + type);
4421
+ const canInsertionImage = !getElementOfFocusedImage(board) && !(selectedElements.length === 1 && board.isImageBindingAllowed(selectedElements[0]));
4422
+ if (acceptImageArray.includes(clipboardData.files[0].type) && canInsertionImage) {
4423
+ const imageFile = clipboardData.files[0];
4424
+ buildImage(board, imageFile, DEFAULT_IMAGE_WIDTH, imageItem => {
4425
+ DrawTransforms.insertImage(board, imageItem, targetPoint);
4426
+ });
4427
+ return;
4428
+ }
4275
4429
  }
4276
- if (canResize() && !isSelectionMoving(board)) {
4277
- handleG = createG();
4278
- const elements = getSelectedElements(board);
4279
- const boundingRectangle = getRectangleByElements(board, elements, false);
4280
- let corners = RectangleClient.getCornerPoints(boundingRectangle);
4281
- const angle = getSelectionAngle(elements);
4282
- if (angle) {
4283
- const centerPoint = RectangleClient.getCenterPoint(boundingRectangle);
4284
- corners = rotatePoints(corners, centerPoint, angle);
4430
+ if (clipboardData?.elements?.length) {
4431
+ const drawElements = clipboardData.elements?.filter(value => PlaitDrawElement.isDrawElement(value));
4432
+ if (clipboardData.elements && clipboardData.elements.length > 0 && drawElements.length > 0) {
4433
+ insertClipboardData(board, drawElements, targetPoint);
4434
+ }
4435
+ }
4436
+ if (clipboardData?.text) {
4437
+ if (!clipboardData.elements || clipboardData.elements.length === 0) {
4438
+ // (* ̄︶ ̄)
4439
+ const insertAsChildren = selectedElements.length === 1 && selectedElements[0].children;
4440
+ const insertAsFreeText = !insertAsChildren;
4441
+ if (insertAsFreeText) {
4442
+ DrawTransforms.insertText(board, targetPoint, clipboardData.text);
4443
+ return;
4444
+ }
4285
4445
  }
4286
- corners.forEach(corner => {
4287
- const g = drawHandle(board, corner);
4288
- handleG && handleG.append(g);
4289
- });
4290
- PlaitBoard.getElementActiveHost(board).append(handleG);
4291
4446
  }
4447
+ insertFragment(clipboardData, targetPoint);
4292
4448
  };
4293
4449
  return board;
4294
- }
4295
- const getResizeOriginPointAndHandlePoint = (board, resizeRef) => {
4296
- const handleIndex = getIndexByResizeHandle(resizeRef.handle);
4297
- const symmetricHandleIndex = getSymmetricHandleIndex(board, handleIndex);
4298
- const originPoint = getResizeHandlePointByIndex(resizeRef.rectangle, symmetricHandleIndex);
4299
- const handlePoint = getResizeHandlePointByIndex(resizeRef.rectangle, handleIndex);
4300
- return {
4301
- originPoint,
4302
- handlePoint
4303
- };
4304
4450
  };
4305
- const getResizeZoom = (resizeState, resizeOriginPoint, resizeHandlePoint, isFromCorner, isAspectRatio) => {
4306
- const startPoint = resizeState.startPoint;
4307
- const endPoint = resizeState.endPoint;
4308
- let xZoom = 0;
4309
- let yZoom = 0;
4310
- if (isFromCorner) {
4311
- if (isAspectRatio) {
4312
- let normalizedOffsetX = Point.getOffsetX(startPoint, endPoint);
4313
- xZoom = normalizedOffsetX / (resizeHandlePoint[0] - resizeOriginPoint[0]);
4314
- yZoom = xZoom;
4315
- }
4316
- else {
4317
- let normalizedOffsetX = Point.getOffsetX(startPoint, endPoint);
4318
- let normalizedOffsetY = Point.getOffsetY(startPoint, endPoint);
4319
- xZoom = normalizedOffsetX / (resizeHandlePoint[0] - resizeOriginPoint[0]);
4320
- yZoom = normalizedOffsetY / (resizeHandlePoint[1] - resizeOriginPoint[1]);
4451
+ const getBoundedLineElements = (board, plaitShapes) => {
4452
+ const lines = getLines(board);
4453
+ return lines.filter(line => plaitShapes.find(shape => PlaitLine.isBoundElementOfSource(line, shape) || PlaitLine.isBoundElementOfTarget(line, shape)));
4454
+ };
4455
+
4456
+ const withLineCreateByDraw = (board) => {
4457
+ const { pointerDown, pointerMove, globalPointerUp } = board;
4458
+ let start = null;
4459
+ let sourceElement;
4460
+ let lineShapeG = null;
4461
+ let temporaryElement = null;
4462
+ board.pointerDown = (event) => {
4463
+ const linePointers = getLinePointers();
4464
+ const isLinePointer = PlaitBoard.isInPointer(board, linePointers);
4465
+ if (!PlaitBoard.isReadonly(board) && isLinePointer && isDrawingMode(board)) {
4466
+ const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4467
+ start = point;
4468
+ const hitElement = getSnappingGeometry(board, point);
4469
+ if (hitElement) {
4470
+ sourceElement = hitElement;
4471
+ }
4472
+ preventTouchMove(board, event, true);
4321
4473
  }
4322
- }
4323
- else {
4324
- const isHorizontal = Point.isHorizontal(resizeOriginPoint, resizeHandlePoint, 0.1) || false;
4325
- let normalizedOffset = isHorizontal ? Point.getOffsetX(startPoint, endPoint) : Point.getOffsetY(startPoint, endPoint);
4326
- let benchmarkOffset = isHorizontal ? resizeHandlePoint[0] - resizeOriginPoint[0] : resizeHandlePoint[1] - resizeOriginPoint[1];
4327
- const zoom = normalizedOffset / benchmarkOffset;
4328
- if (isAspectRatio) {
4329
- xZoom = zoom;
4330
- yZoom = zoom;
4474
+ pointerDown(event);
4475
+ };
4476
+ board.pointerMove = (event) => {
4477
+ lineShapeG?.remove();
4478
+ lineShapeG = createG();
4479
+ let movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4480
+ if (start) {
4481
+ const lineShape = PlaitBoard.getPointer(board);
4482
+ temporaryElement = handleLineCreating(board, lineShape, start, movingPoint, sourceElement, lineShapeG);
4331
4483
  }
4332
- else {
4333
- if (isHorizontal) {
4334
- xZoom = zoom;
4335
- }
4336
- else {
4337
- yZoom = zoom;
4338
- }
4484
+ pointerMove(event);
4485
+ };
4486
+ board.globalPointerUp = (event) => {
4487
+ if (temporaryElement) {
4488
+ Transforms.insertNode(board, temporaryElement, [board.children.length]);
4489
+ clearSelectedElement(board);
4490
+ addSelectedElement(board, temporaryElement);
4491
+ BoardTransforms.updatePointerType(board, PlaitPointerType.selection);
4339
4492
  }
4340
- }
4341
- return { xZoom, yZoom };
4342
- };
4343
- const movePointByZoomAndOriginPoint = (p, resizeOriginPoint, xZoom, yZoom) => {
4344
- const offsetX = (p[0] - resizeOriginPoint[0]) * xZoom;
4345
- const offsetY = (p[1] - resizeOriginPoint[1]) * yZoom;
4346
- return [p[0] + offsetX, p[1] + offsetY];
4493
+ lineShapeG?.remove();
4494
+ lineShapeG = null;
4495
+ sourceElement = null;
4496
+ start = null;
4497
+ temporaryElement = null;
4498
+ preventTouchMove(board, event, false);
4499
+ globalPointerUp(event);
4500
+ };
4501
+ return board;
4347
4502
  };
4348
4503
 
4349
4504
  const withGeometryResize = (board) => {
4350
- let alignG;
4505
+ let snapG;
4351
4506
  const options = {
4352
4507
  key: 'draw-geometry',
4353
4508
  canResize: () => {
@@ -4375,27 +4530,21 @@ const withGeometryResize = (board) => {
4375
4530
  return null;
4376
4531
  },
4377
4532
  onResize: (resizeRef, resizeState) => {
4378
- const centerPoint = RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(resizeRef.element.points));
4379
- const angle = resizeRef.element.angle;
4380
- if (angle) {
4381
- const [rotatedStartPoint, rotateEndPoint] = rotatePoints([resizeState.startPoint, resizeState.endPoint], centerPoint, -resizeRef.element.angle);
4382
- resizeState.startPoint = rotatedStartPoint;
4383
- resizeState.endPoint = rotateEndPoint;
4384
- }
4385
- alignG?.remove();
4533
+ resizeState.startPoint = rotateAntiPointsByElement(resizeState.startPoint, resizeRef.element) || resizeState.startPoint;
4534
+ resizeState.endPoint = rotateAntiPointsByElement(resizeState.endPoint, resizeRef.element) || resizeState.endPoint;
4535
+ snapG?.remove();
4386
4536
  const isFromCorner = isCornerHandle(board, resizeRef.handle);
4387
4537
  const isAspectRatio = resizeState.isShift || PlaitDrawElement.isImage(resizeRef.element);
4388
- const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, resizeRef);
4389
- const resizeAlignRef = getResizeAlignRef(board, resizeRef, resizeState, {
4538
+ const handleIndex = getIndexByResizeHandle(resizeRef.handle);
4539
+ const { originPoint, handlePoint } = getResizeOriginPointAndHandlePoint(board, handleIndex, resizeRef.rectangle);
4540
+ const resizeSnapRefOptions = getSnapResizingRefOptions(board, resizeRef, resizeState, {
4390
4541
  originPoint,
4391
4542
  handlePoint
4392
4543
  }, isAspectRatio, isFromCorner);
4393
- alignG = resizeAlignRef.alignG;
4394
- PlaitBoard.getElementActiveHost(board).append(alignG);
4395
- let points = resizeAlignRef.activePoints;
4396
- if (angle) {
4397
- points = resetPointsAfterResize(resizeRef.rectangle, RectangleClient.getRectangleByPoints(points), centerPoint, RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(points)), angle);
4398
- }
4544
+ const resizeSnapRef = getSnapResizingRef(board, [resizeRef.element], resizeSnapRefOptions);
4545
+ snapG = resizeSnapRef.snapG;
4546
+ PlaitBoard.getElementActiveHost(board).append(snapG);
4547
+ let points = resizeSnapRef.activePoints;
4399
4548
  if (PlaitDrawElement.isGeometry(resizeRef.element)) {
4400
4549
  const { height: textHeight } = getFirstTextManage(resizeRef.element).getSize();
4401
4550
  DrawTransforms.resizeGeometry(board, points, textHeight, resizeRef.path);
@@ -4406,8 +4555,8 @@ const withGeometryResize = (board) => {
4406
4555
  }
4407
4556
  },
4408
4557
  afterResize: (resizeRef) => {
4409
- alignG?.remove();
4410
- alignG = null;
4558
+ snapG?.remove();
4559
+ snapG = null;
4411
4560
  }
4412
4561
  };
4413
4562
  withResize(board, options);
@@ -4448,8 +4597,7 @@ const withLineResize = (board) => {
4448
4597
  resizeRef.handle !== LineResizeHandle.source &&
4449
4598
  resizeRef.handle !== LineResizeHandle.target) {
4450
4599
  const params = getElbowLineRouteOptions(board, resizeRef.element);
4451
- const isIntersect = isSourceAndTargetIntersect(params);
4452
- if (isIntersect) {
4600
+ if (isUseDefaultOrthogonalRoute(resizeRef.element, params)) {
4453
4601
  return;
4454
4602
  }
4455
4603
  const points = [...resizeRef.element.points];
@@ -4468,12 +4616,12 @@ const withLineResize = (board) => {
4468
4616
  let source = { ...resizeRef.element.source };
4469
4617
  let target = { ...resizeRef.element.target };
4470
4618
  let handleIndex = resizeRef.handleIndex;
4471
- const hitElement = getHitOutlineGeometry(board, resizeState.endPoint, REACTION_MARGIN);
4619
+ const hitElement = getSnappingGeometry(board, resizeState.endPoint);
4472
4620
  if (resizeRef.handle === LineResizeHandle.source || resizeRef.handle === LineResizeHandle.target) {
4473
4621
  const object = resizeRef.handle === LineResizeHandle.source ? source : target;
4474
4622
  points[handleIndex] = resizeState.endPoint;
4475
4623
  if (hitElement) {
4476
- object.connection = getConnectionByNearestPoint(board, rotateAntiPointsByElement(resizeState.endPoint, hitElement) || resizeState.endPoint, hitElement);
4624
+ object.connection = getHitConnection(board, resizeState.endPoint, hitElement);
4477
4625
  object.boundId = hitElement.id;
4478
4626
  }
4479
4627
  else {
@@ -4515,7 +4663,8 @@ const withLineResize = (board) => {
4515
4663
  const newPoints = [...points];
4516
4664
  newPoints[0] = drawPoints[0];
4517
4665
  newPoints[newPoints.length - 1] = drawPoints[drawPoints.length - 1];
4518
- if (resizeRef.element.shape !== LineShape.elbow) {
4666
+ if (resizeRef.element.shape !== LineShape.elbow ||
4667
+ (resizeRef.element.shape === LineShape.elbow && newPoints.length === 2)) {
4519
4668
  newPoints.forEach((point, index) => {
4520
4669
  if (index === handleIndex)
4521
4670
  return;
@@ -4579,25 +4728,24 @@ const withLineBoundReaction = (board) => {
4579
4728
  return PlaitDrawElement.isLine(element) && isSourceOrTarget;
4580
4729
  });
4581
4730
  if (isLinePointer || isLineResizing) {
4582
- const hitElement = getHitOutlineGeometry(board, movingPoint, -4);
4731
+ const hitElement = getHitGeometry(board, movingPoint);
4583
4732
  if (hitElement) {
4584
- const rectangle = RectangleClient.getRectangleByPoints(hitElement.points);
4585
- boundShapeG = drawBoundMask(board, hitElement);
4586
- let nearestPoint = getNearestPoint(hitElement, rotateAntiPointsByElement(movingPoint, hitElement) || movingPoint);
4587
- const activeRectangle = RectangleClient.inflate(rectangle, ACTIVE_STROKE_WIDTH);
4588
- const hitConnector = getHitConnectorPoint(nearestPoint, hitElement, activeRectangle);
4589
- nearestPoint = hitConnector ? hitConnector : nearestPoint;
4590
- const circleG = drawCircle(PlaitBoard.getRoughSVG(board), nearestPoint, 6, {
4591
- stroke: SELECTION_BORDER_COLOR,
4592
- strokeWidth: ACTIVE_STROKE_WIDTH,
4593
- fill: SELECTION_BORDER_COLOR,
4594
- fillStyle: 'solid'
4595
- });
4596
- boundShapeG.appendChild(circleG);
4597
- PlaitBoard.getElementActiveHost(board).append(boundShapeG);
4733
+ const ref = getSnappingRef(board, hitElement, movingPoint);
4734
+ const isSnapping = ref.isHitEdge || ref.isHitConnector;
4735
+ boundShapeG = drawBoundReaction(board, hitElement, { hasMask: isSnapping, hasConnector: true });
4736
+ if (isSnapping) {
4737
+ const circleG = drawCircle(PlaitBoard.getRoughSVG(board), ref.connectorPoint || ref.edgePoint, 6, {
4738
+ stroke: SELECTION_BORDER_COLOR,
4739
+ strokeWidth: SNAPPING_STROKE_WIDTH,
4740
+ fill: SELECTION_BORDER_COLOR,
4741
+ fillStyle: 'solid'
4742
+ });
4743
+ boundShapeG.appendChild(circleG);
4744
+ }
4598
4745
  if (hasValidAngle(hitElement)) {
4599
- setAngleForG(boundShapeG, RectangleClient.getCenterPoint(rectangle), hitElement.angle);
4746
+ setAngleForG(boundShapeG, RectangleClient.getCenterPointByPoints(hitElement.points), hitElement.angle);
4600
4747
  }
4748
+ PlaitBoard.getElementActiveHost(board).append(boundShapeG);
4601
4749
  }
4602
4750
  }
4603
4751
  pointerMove(event);
@@ -4664,9 +4812,8 @@ class ImageComponent extends CommonPluginElement {
4664
4812
  get activeGenerator() {
4665
4813
  return this.imageGenerator.componentRef.instance.activeGenerator;
4666
4814
  }
4667
- constructor(viewContainerRef, cdr) {
4815
+ constructor(cdr) {
4668
4816
  super(cdr);
4669
- this.viewContainerRef = viewContainerRef;
4670
4817
  this.cdr = cdr;
4671
4818
  this.destroy$ = new Subject();
4672
4819
  }
@@ -4693,14 +4840,14 @@ class ImageComponent extends CommonPluginElement {
4693
4840
  ngOnInit() {
4694
4841
  super.ngOnInit();
4695
4842
  this.initializeGenerator();
4696
- this.imageGenerator.processDrawing(this.element, this.g, this.viewContainerRef);
4843
+ this.imageGenerator.processDrawing(this.element, this.getElementG(), this.viewContainerRef);
4697
4844
  this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), {
4698
4845
  selected: this.selected
4699
4846
  });
4700
4847
  }
4701
4848
  onContextChanged(value, previous) {
4702
4849
  if (value.element !== previous.element) {
4703
- this.imageGenerator.updateImage(this.g, previous.element, value.element);
4850
+ this.imageGenerator.updateImage(this.getElementG(), previous.element, value.element);
4704
4851
  this.imageGenerator.componentRef.instance.isFocus = this.selected;
4705
4852
  this.lineAutoCompleteGenerator.processDrawing(this.element, PlaitBoard.getElementActiveHost(this.board), {
4706
4853
  selected: this.selected
@@ -4724,7 +4871,7 @@ class ImageComponent extends CommonPluginElement {
4724
4871
  this.imageGenerator.destroy();
4725
4872
  this.lineAutoCompleteGenerator.destroy();
4726
4873
  }
4727
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: ImageComponent, deps: [{ token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
4874
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: ImageComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
4728
4875
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.4", type: ImageComponent, isStandalone: true, selector: "plait-draw-geometry", usesInheritance: true, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4729
4876
  }
4730
4877
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: ImageComponent, decorators: [{
@@ -4735,7 +4882,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
4735
4882
  changeDetection: ChangeDetectionStrategy.OnPush,
4736
4883
  standalone: true
4737
4884
  }]
4738
- }], ctorParameters: () => [{ type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }] });
4885
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }] });
4739
4886
 
4740
4887
  const withLineAutoCompleteReaction = (board) => {
4741
4888
  const { pointerMove } = board;
@@ -4746,7 +4893,7 @@ const withLineAutoCompleteReaction = (board) => {
4746
4893
  const selectedElements = getSelectedDrawElements(board);
4747
4894
  const targetElement = selectedElements.length === 1 && selectedElements[0];
4748
4895
  const movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4749
- if (!PlaitBoard.isReadonly(board) && !isSelectionMoving(board) && targetElement && PlaitDrawElement.isShape(targetElement)) {
4896
+ if (!PlaitBoard.isReadonly(board) && !isSelectionMoving(board) && targetElement && PlaitDrawElement.isShapeElement(targetElement)) {
4750
4897
  const points = getAutoCompletePoints(targetElement);
4751
4898
  const hitIndex = getHitIndexOfAutoCompletePoint(rotateAntiPointsByElement(movingPoint, targetElement) || movingPoint, points);
4752
4899
  const hitPoint = points[hitIndex];
@@ -4774,7 +4921,7 @@ const withLineAutoCompleteReaction = (board) => {
4774
4921
  const WithLineAutoCompletePluginKey = 'plait-line-auto-complete-plugin-key';
4775
4922
  const withLineAutoComplete = (board) => {
4776
4923
  const { pointerDown, pointerMove, globalPointerUp } = board;
4777
- let startPoint = null;
4924
+ let autoCompletePoint = null;
4778
4925
  let lineShapeG = null;
4779
4926
  let sourceElement;
4780
4927
  let temporaryElement;
@@ -4782,13 +4929,13 @@ const withLineAutoComplete = (board) => {
4782
4929
  const selectedElements = getSelectedDrawElements(board);
4783
4930
  const targetElement = selectedElements.length === 1 && selectedElements[0];
4784
4931
  const clickPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4785
- if (!PlaitBoard.isReadonly(board) && targetElement && PlaitDrawElement.isShape(targetElement)) {
4932
+ if (!PlaitBoard.isReadonly(board) && targetElement && PlaitDrawElement.isShapeElement(targetElement)) {
4786
4933
  const points = getAutoCompletePoints(targetElement);
4787
4934
  const index = getHitIndexOfAutoCompletePoint(rotateAntiPointsByElement(clickPoint, targetElement) || clickPoint, points);
4788
4935
  const hitPoint = points[index];
4789
4936
  if (hitPoint) {
4790
4937
  temporaryDisableSelection(board);
4791
- startPoint = hitPoint;
4938
+ autoCompletePoint = hitPoint;
4792
4939
  sourceElement = targetElement;
4793
4940
  BoardTransforms.updatePointerType(board, LineShape.elbow);
4794
4941
  }
@@ -4799,18 +4946,20 @@ const withLineAutoComplete = (board) => {
4799
4946
  lineShapeG?.remove();
4800
4947
  lineShapeG = createG();
4801
4948
  let movingPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4802
- if (startPoint && sourceElement) {
4803
- const distance = distanceBetweenPointAndPoint(...movingPoint, ...(rotatePointsByElement(startPoint, sourceElement) || startPoint));
4949
+ if (autoCompletePoint && sourceElement) {
4950
+ const distance = distanceBetweenPointAndPoint(...(rotateAntiPointsByElement(movingPoint, sourceElement) || movingPoint), ...autoCompletePoint);
4804
4951
  if (distance > PRESS_AND_MOVE_BUFFER) {
4805
4952
  const rectangle = RectangleClient.getRectangleByPoints(sourceElement.points);
4806
- const shape = getShape(sourceElement);
4953
+ const shape = getElementShape(sourceElement);
4807
4954
  const engine = getEngine(shape);
4808
- let sourcePoint = startPoint;
4955
+ let sourcePoint = autoCompletePoint;
4809
4956
  if (engine.getNearestCrossingPoint) {
4810
- const crossingPoint = engine.getNearestCrossingPoint(rectangle, startPoint);
4957
+ const crossingPoint = engine.getNearestCrossingPoint(rectangle, autoCompletePoint);
4811
4958
  sourcePoint = crossingPoint;
4812
4959
  }
4813
- temporaryElement = handleLineCreating(board, LineShape.elbow, rotatePointsByElement(sourcePoint, sourceElement) || sourcePoint, movingPoint, sourceElement, lineShapeG);
4960
+ // source point must be click point
4961
+ const rotatedSourcePoint = rotatePointsByElement(sourcePoint, sourceElement) || sourcePoint;
4962
+ temporaryElement = handleLineCreating(board, LineShape.elbow, rotatedSourcePoint, movingPoint, sourceElement, lineShapeG);
4814
4963
  }
4815
4964
  }
4816
4965
  pointerMove(event);
@@ -4824,9 +4973,9 @@ const withLineAutoComplete = (board) => {
4824
4973
  ?.afterComplete;
4825
4974
  afterComplete && afterComplete(temporaryElement);
4826
4975
  }
4827
- if (startPoint) {
4976
+ if (autoCompletePoint) {
4828
4977
  BoardTransforms.updatePointerType(board, PlaitPointerType.selection);
4829
- startPoint = null;
4978
+ autoCompletePoint = null;
4830
4979
  }
4831
4980
  lineShapeG?.remove();
4832
4981
  lineShapeG = null;
@@ -4884,6 +5033,129 @@ const withLineTextMove = (board) => {
4884
5033
  return board;
4885
5034
  };
4886
5035
 
5036
+ const withDrawRotate = (board) => {
5037
+ const { pointerDown, pointerMove, globalPointerUp, afterChange, drawActiveRectangle } = board;
5038
+ let rotateRef = null;
5039
+ let rotateHandleG;
5040
+ let needCustomActiveRectangle = false;
5041
+ const canRotate = () => {
5042
+ const elements = getSelectedElements(board);
5043
+ return elements.length > 0 && elements.every(el => PlaitDrawElement.isGeometry(el) || PlaitDrawElement.isImage(el));
5044
+ };
5045
+ board.pointerDown = (event) => {
5046
+ if (!canRotate() || PlaitBoard.isReadonly(board) || PlaitBoard.hasBeenTextEditing(board) || !isMainPointer(event)) {
5047
+ pointerDown(event);
5048
+ return;
5049
+ }
5050
+ const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
5051
+ const elements = getSelectedElements(board);
5052
+ const boundingRectangle = getRectangleByElements(board, elements, false);
5053
+ const handleRectangle = getRotateHandleRectangle(boundingRectangle);
5054
+ const angle = getSelectionAngle(elements);
5055
+ const rotatedPoint = angle ? rotatePoints(point, RectangleClient.getCenterPoint(boundingRectangle), -angle) : point;
5056
+ if (handleRectangle && RectangleClient.isHit(RectangleClient.getRectangleByPoints([rotatedPoint, rotatedPoint]), handleRectangle)) {
5057
+ rotateRef = {
5058
+ elements: elements,
5059
+ startPoint: point
5060
+ };
5061
+ }
5062
+ pointerDown(event);
5063
+ };
5064
+ board.pointerMove = (event) => {
5065
+ if (rotateRef) {
5066
+ event.preventDefault();
5067
+ const isShift = !!event.shiftKey;
5068
+ addRotating(board, rotateRef);
5069
+ const endPoint = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
5070
+ const selectionRectangle = getRectangleByElements(board, rotateRef.elements, false);
5071
+ const selectionCenterPoint = RectangleClient.getCenterPoint(selectionRectangle);
5072
+ if (!getSelectionAngle(rotateRef.elements) && rotateRef.elements.length > 1) {
5073
+ needCustomActiveRectangle = true;
5074
+ }
5075
+ throttleRAF(board, 'with-common-rotate', () => {
5076
+ if (rotateRef && rotateRef.startPoint) {
5077
+ let angle = getAngleBetweenPoints(rotateRef.startPoint, endPoint, selectionCenterPoint);
5078
+ if (isShift) {
5079
+ angle += Math.PI / 12 / 2;
5080
+ angle -= angle % (Math.PI / 12);
5081
+ }
5082
+ const selectionAngle = getSelectionAngle(rotateRef.elements);
5083
+ let remainder = (selectionAngle + angle) % (Math.PI / 2);
5084
+ if (Math.PI / 2 - remainder <= degreesToRadians(5)) {
5085
+ const snapAngle = Math.PI / 2 - remainder;
5086
+ angle += snapAngle;
5087
+ }
5088
+ if (remainder <= degreesToRadians(5)) {
5089
+ const snapAngle = -remainder;
5090
+ angle += snapAngle;
5091
+ }
5092
+ rotateRef.angle = normalizeAngle(angle);
5093
+ if (rotateRef.angle) {
5094
+ rotateElements(board, rotateRef.elements, rotateRef.angle);
5095
+ }
5096
+ PlaitBoard.getBoardContainer(board).classList.add('element-rotating');
5097
+ }
5098
+ });
5099
+ return;
5100
+ }
5101
+ pointerMove(event);
5102
+ };
5103
+ board.globalPointerUp = (event) => {
5104
+ globalPointerUp(event);
5105
+ if (needCustomActiveRectangle) {
5106
+ needCustomActiveRectangle = false;
5107
+ const selectedElements = getSelectedElements(board);
5108
+ Transforms.addSelectionWithTemporaryElements(board, selectedElements);
5109
+ }
5110
+ PlaitBoard.getBoardContainer(board).classList.remove('element-rotating');
5111
+ removeRotating(board);
5112
+ rotateRef = null;
5113
+ MERGING.set(board, false);
5114
+ preventTouchMove(board, event, false);
5115
+ };
5116
+ board.afterChange = () => {
5117
+ afterChange();
5118
+ if (rotateHandleG) {
5119
+ rotateHandleG.remove();
5120
+ rotateHandleG = null;
5121
+ }
5122
+ if (canRotate() && !isSelectionMoving(board)) {
5123
+ if (needCustomActiveRectangle && rotateRef) {
5124
+ const boundingRectangle = getRectangleByElements(board, rotateRef.elements, false);
5125
+ rotateHandleG = drawRotateHandle(board, boundingRectangle);
5126
+ rotateHandleG.classList.add(ROTATE_HANDLE_CLASS_NAME);
5127
+ if (rotateRef.angle) {
5128
+ setAngleForG(rotateHandleG, RectangleClient.getCenterPoint(boundingRectangle), rotateRef.angle);
5129
+ }
5130
+ }
5131
+ else {
5132
+ const elements = getSelectedElements(board);
5133
+ const boundingRectangle = getRectangleByElements(board, elements, false);
5134
+ rotateHandleG = drawRotateHandle(board, boundingRectangle);
5135
+ rotateHandleG.classList.add(ROTATE_HANDLE_CLASS_NAME);
5136
+ setAngleForG(rotateHandleG, RectangleClient.getCenterPoint(boundingRectangle), getSelectionAngle(elements));
5137
+ }
5138
+ PlaitBoard.getElementActiveHost(board).append(rotateHandleG);
5139
+ }
5140
+ };
5141
+ board.drawActiveRectangle = () => {
5142
+ if (needCustomActiveRectangle && rotateRef) {
5143
+ const rectangle = getRectangleByElements(board, rotateRef.elements, false);
5144
+ const rectangleG = drawRectangle(board, RectangleClient.inflate(rectangle, ACTIVE_STROKE_WIDTH), {
5145
+ stroke: SELECTION_BORDER_COLOR,
5146
+ strokeWidth: ACTIVE_STROKE_WIDTH
5147
+ });
5148
+ rectangleG.classList.add(SELECTION_RECTANGLE_CLASS_NAME);
5149
+ if (rotateRef.angle) {
5150
+ setAngleForG(rectangleG, RectangleClient.getCenterPoint(rectangle), rotateRef.angle);
5151
+ }
5152
+ return rectangleG;
5153
+ }
5154
+ return drawActiveRectangle();
5155
+ };
5156
+ return board;
5157
+ };
5158
+
4887
5159
  const withDraw = (board) => {
4888
5160
  const { drawElement, getRectangle, isRectangleHit, isHit, isInsidePoint, isMovable, isAlign, getRelatedFragment } = board;
4889
5161
  board.drawElement = (context) => {
@@ -4968,8 +5240,8 @@ const withDraw = (board) => {
4968
5240
  }
4969
5241
  return isAlign(element);
4970
5242
  };
4971
- board.getRelatedFragment = (elements) => {
4972
- const selectedElements = getSelectedElements(board);
5243
+ board.getRelatedFragment = (elements, originData) => {
5244
+ const selectedElements = originData || getSelectedElements(board);
4973
5245
  const lineElements = board.children.filter(element => PlaitDrawElement.isLine(element));
4974
5246
  const activeLines = lineElements.filter(line => {
4975
5247
  const source = selectedElements.find(element => element.id === line.source.boundId);
@@ -4977,14 +5249,14 @@ const withDraw = (board) => {
4977
5249
  const isSelected = selectedElements.includes(line);
4978
5250
  return source && target && !isSelected;
4979
5251
  });
4980
- return getRelatedFragment([...elements, ...activeLines]);
5252
+ return getRelatedFragment([...elements, ...activeLines], originData);
4981
5253
  };
4982
- return withDrawResize(withLineTextMove(withLineAutoCompleteReaction(withLineText(withLineBoundReaction(withLineResize(withGeometryResize(withLineCreateByDraw(withLineAutoComplete(withGeometryCreateByDrag(withGeometryCreateByDrawing(withDrawFragment(withDrawHotkey(board)))))))))))));
5254
+ return withDrawResize(withLineTextMove(withLineAutoCompleteReaction(withLineText(withLineBoundReaction(withLineResize(withGeometryResize(withDrawRotate(withLineCreateByDraw(withLineAutoComplete(withGeometryCreateByDrag(withGeometryCreateByDrawing(withDrawFragment(withDrawHotkey(board))))))))))))));
4983
5255
  };
4984
5256
 
4985
5257
  /**
4986
5258
  * Generated bundle index. Do not edit.
4987
5259
  */
4988
5260
 
4989
- export { BasicShapes, DEFAULT_IMAGE_WIDTH, DefaultBasicShapeProperty, DefaultConnectorProperty, DefaultDataProperty, DefaultDecisionProperty, DefaultFlowchartProperty, DefaultFlowchartPropertyMap, DefaultGeometryActiveStyle, DefaultGeometryStyle, DefaultManualInputProperty, DefaultMergeProperty, DefaultTextProperty, DrawThemeColors, DrawTransforms, FlowchartSymbols, GeometryComponent, GeometryThreshold, LineComponent, LineHandleKey, LineMarkerType, LineShape, MemorizeKey, PlaitDrawElement, PlaitGeometry, PlaitLine, Q2C, REACTION_MARGIN, ShapeDefaultSpace, StrokeStyle, WithLineAutoCompletePluginKey, alignElbowSegment, alignPoints, createDefaultFlowchart, createDefaultGeometry, createGeometryElement, createLineElement, createTextElement, drawBoundMask, drawGeometry, drawLine, drawLineArrow, getAutoCompletePoints, getBasicPointers, getCenterPointsOnPolygon, getConnectionByNearestPoint, getConnectionPoint, getCurvePoints, getDefaultFlowchartProperty, getDefaultGeometryPoints, getDefaultGeometryProperty, getDefaultTextPoints, getDrawDefaultStrokeColor, getElbowLineRouteOptions, getElbowPoints, getFillByElement, getFlowchartDefaultFill, getFlowchartPointers, getGeometryPointers, getHitConnectorPoint, getHitIndexOfAutoCompletePoint, getIndexAndDeleteCountByKeyPoint, getLineDashByElement, getLineHandleRefPair, getLineMemorizedLatest, getLinePointers, getLinePoints, getLineTextRectangle, getLines, getMemorizeKey, getMemorizedLatestByPointer, getMemorizedLatestShape, getMidKeyPoints, getMiddlePoints, getMirrorDataPoints, getNearestPoint, getNextRenderPoints, getNextSourceAndTargetPoints, getResizedPreviousAndNextPoint, getSelectedDrawElements, getSelectedGeometryElements, getSelectedImageElements, getSelectedLineElements, getSourceAndTargetRectangle, getStrokeColorByElement, getStrokeStyleByElement, getStrokeWidthByElement, getTextRectangle, getTextShapeProperty, getVectorByConnection, handleLineCreating, hasIllegalElbowPoint, insertElement, isHitDrawElement, isHitElementInside, isHitLine, isHitLineText, isHitPolyLine, isRectangleHitDrawElement, isTextExceedingBounds, isUpdatedHandleIndex, memorizeLatestShape, memorizeLatestText, withDraw, withLineAutoComplete };
5261
+ export { BasicShapes, DEFAULT_IMAGE_WIDTH, DefaultBasicShapeProperty, DefaultCloudShapeProperty, DefaultConnectorProperty, DefaultDataProperty, DefaultDecisionProperty, DefaultFlowchartProperty, DefaultFlowchartPropertyMap, DefaultGeometryActiveStyle, DefaultGeometryStyle, DefaultManualInputProperty, DefaultMergeProperty, DefaultTextProperty, DrawThemeColors, DrawTransforms, FlowchartSymbols, GeometryComponent, GeometryThreshold, LINE_HIT_GEOMETRY_BUFFER, LINE_SNAPPING_BUFFER, LINE_SNAPPING_CONNECTOR_BUFFER, LineComponent, LineHandleKey, LineMarkerType, LineShape, MemorizeKey, PlaitDrawElement, PlaitGeometry, PlaitLine, Q2C, ShapeDefaultSpace, StrokeStyle, WithLineAutoCompletePluginKey, alignElbowSegment, alignPoints, createDefaultFlowchart, createDefaultGeometry, createGeometryElement, createLineElement, createTextElement, drawBoundReaction, drawGeometry, drawLine, drawLineArrow, getAutoCompletePoints, getBasicPointers, getCenterPointsOnPolygon, getConnectionPoint, getCurvePoints, getDefaultFlowchartProperty, getDefaultGeometryPoints, getDefaultGeometryProperty, getDefaultTextPoints, getDrawDefaultStrokeColor, getElbowLineRouteOptions, getElbowPoints, getFillByElement, getFlowchartDefaultFill, getFlowchartPointers, getGeometryPointers, getHitConnection, getHitConnectorPoint, getHitIndexOfAutoCompletePoint, getIndexAndDeleteCountByKeyPoint, getLineDashByElement, getLineHandleRefPair, getLineMemorizedLatest, getLinePointers, getLinePoints, getLineTextRectangle, getLines, getMemorizeKey, getMemorizedLatestByPointer, getMemorizedLatestShape, getMidKeyPoints, getMiddlePoints, getMirrorDataPoints, getNearestPoint, getNextRenderPoints, getNextSourceAndTargetPoints, getResizedPreviousAndNextPoint, getSelectedDrawElements, getSelectedGeometryElements, getSelectedImageElements, getSelectedLineElements, getSourceAndTargetRectangle, getStrokeColorByElement, getStrokeStyleByElement, getStrokeWidthByElement, getTextRectangle, getTextShapeProperty, getVectorByConnection, handleLineCreating, hasIllegalElbowPoint, insertElement, isHitDrawElement, isHitEdgeOfShape, isHitElementInside, isHitLine, isHitLineText, isHitPolyLine, isInsideOfShape, isRectangleHitDrawElement, isSelfLoop, isTextExceedingBounds, isUpdatedHandleIndex, isUseDefaultOrthogonalRoute, memorizeLatestShape, memorizeLatestText, withDraw, withLineAutoComplete };
4990
5262
  //# sourceMappingURL=plait-draw.mjs.map