@plait/core 0.50.0 → 0.51.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 (59) hide show
  1. package/README.md +30 -28
  2. package/board/board.component.interface.d.ts +0 -5
  3. package/esm2022/board/board.component.interface.mjs +1 -1
  4. package/esm2022/board/board.component.mjs +9 -6
  5. package/esm2022/interfaces/board.mjs +3 -3
  6. package/esm2022/interfaces/direction.mjs +1 -1
  7. package/esm2022/interfaces/point.mjs +20 -5
  8. package/esm2022/interfaces/rectangle-client.mjs +57 -2
  9. package/esm2022/plugins/create-board.mjs +5 -5
  10. package/esm2022/plugins/with-hand.mjs +6 -6
  11. package/esm2022/plugins/with-history.mjs +4 -4
  12. package/esm2022/plugins/with-hotkey.mjs +10 -56
  13. package/esm2022/plugins/with-moving.mjs +97 -37
  14. package/esm2022/plugins/with-selection.mjs +36 -19
  15. package/esm2022/utils/clipboard/clipboard.mjs +9 -5
  16. package/esm2022/utils/clipboard/common.mjs +4 -1
  17. package/esm2022/utils/clipboard/data-transfer.mjs +14 -2
  18. package/esm2022/utils/clipboard/navigator-clipboard.mjs +12 -14
  19. package/esm2022/utils/common.mjs +18 -6
  20. package/esm2022/utils/dom/common.mjs +17 -1
  21. package/esm2022/utils/drawing/arrow.mjs +23 -0
  22. package/esm2022/utils/drawing/circle.mjs +4 -0
  23. package/esm2022/utils/drawing/line.mjs +47 -0
  24. package/esm2022/utils/drawing/rectangle.mjs +34 -0
  25. package/esm2022/utils/element.mjs +11 -22
  26. package/esm2022/utils/helper.mjs +5 -2
  27. package/esm2022/utils/id-creator.mjs +2 -2
  28. package/esm2022/utils/index.mjs +5 -5
  29. package/esm2022/utils/math.mjs +37 -4
  30. package/esm2022/utils/moving-element.mjs +2 -7
  31. package/esm2022/utils/selected-element.mjs +15 -2
  32. package/esm2022/utils/weak-maps.mjs +1 -1
  33. package/fesm2022/plait-core.mjs +462 -283
  34. package/fesm2022/plait-core.mjs.map +1 -1
  35. package/interfaces/board.d.ts +4 -4
  36. package/interfaces/direction.d.ts +2 -0
  37. package/interfaces/point.d.ts +7 -2
  38. package/interfaces/rectangle-client.d.ts +7 -6
  39. package/package.json +1 -1
  40. package/plugins/with-moving.d.ts +4 -0
  41. package/styles/styles.scss +4 -0
  42. package/utils/clipboard/common.d.ts +1 -0
  43. package/utils/clipboard/data-transfer.d.ts +1 -1
  44. package/utils/common.d.ts +2 -1
  45. package/utils/dom/common.d.ts +1 -0
  46. package/utils/helper.d.ts +2 -1
  47. package/utils/index.d.ts +4 -4
  48. package/utils/math.d.ts +14 -1
  49. package/utils/moving-element.d.ts +0 -1
  50. package/utils/selected-element.d.ts +2 -1
  51. package/utils/weak-maps.d.ts +8 -2
  52. package/esm2022/utils/draw/arrow.mjs +0 -23
  53. package/esm2022/utils/draw/circle.mjs +0 -4
  54. package/esm2022/utils/draw/line.mjs +0 -47
  55. package/esm2022/utils/draw/rectangle.mjs +0 -34
  56. /package/utils/{draw → drawing}/arrow.d.ts +0 -0
  57. /package/utils/{draw → drawing}/circle.d.ts +0 -0
  58. /package/utils/{draw → drawing}/line.d.ts +0 -0
  59. /package/utils/{draw → drawing}/rectangle.d.ts +0 -0
@@ -84,6 +84,165 @@ const sortElements = (board, elements) => {
84
84
  });
85
85
  };
86
86
 
87
+ const RectangleClient = {
88
+ isHit: (origin, target) => {
89
+ return RectangleClient.isHitX(origin, target) && RectangleClient.isHitY(origin, target);
90
+ },
91
+ isHitX: (origin, target) => {
92
+ const minX = origin.x < target.x ? origin.x : target.x;
93
+ const maxX = origin.x + origin.width > target.x + target.width ? origin.x + origin.width : target.x + target.width;
94
+ // float calculate error( eg: 1.4210854715202004e-14 > 0)
95
+ if (Math.floor(maxX - minX - origin.width - target.width) <= 0) {
96
+ return true;
97
+ }
98
+ else {
99
+ return false;
100
+ }
101
+ },
102
+ isHitY: (origin, target) => {
103
+ const minY = origin.y < target.y ? origin.y : target.y;
104
+ const maxY = origin.y + origin.height > target.y + target.height ? origin.y + origin.height : target.y + target.height;
105
+ // float calculate error( eg: 1.4210854715202004e-14 > 0)
106
+ if (Math.floor(maxY - minY - origin.height - target.height) <= 0) {
107
+ return true;
108
+ }
109
+ else {
110
+ return false;
111
+ }
112
+ },
113
+ getPoints(rectangle) {
114
+ return [
115
+ [rectangle.x, rectangle.y],
116
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height]
117
+ ];
118
+ },
119
+ getRectangleByCenterPoint(point, width, height) {
120
+ return RectangleClient.getRectangleByPoint([point[0] - width / 2, point[1] - height / 2], width, height);
121
+ },
122
+ getRectangleByPoint(point, width, height) {
123
+ return {
124
+ x: point[0],
125
+ y: point[1],
126
+ width,
127
+ height
128
+ };
129
+ },
130
+ getRectangleByPoints(points) {
131
+ const xArray = points.map(ele => ele[0]);
132
+ const yArray = points.map(ele => ele[1]);
133
+ const xMin = Math.min(...xArray);
134
+ const xMax = Math.max(...xArray);
135
+ const yMin = Math.min(...yArray);
136
+ const yMax = Math.max(...yArray);
137
+ const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
138
+ return rect;
139
+ },
140
+ getCornerPointsByPoints(points) {
141
+ const xArray = points.map(ele => ele[0]);
142
+ const yArray = points.map(ele => ele[1]);
143
+ const xMin = Math.min(...xArray);
144
+ const xMax = Math.max(...xArray);
145
+ const yMin = Math.min(...yArray);
146
+ const yMax = Math.max(...yArray);
147
+ return [
148
+ [xMin, yMin],
149
+ [xMax, yMin],
150
+ [xMax, yMax],
151
+ [xMin, yMax]
152
+ ];
153
+ },
154
+ getOutlineRectangle: (rectangle, offset) => {
155
+ return {
156
+ x: rectangle.x + offset,
157
+ y: rectangle.y + offset,
158
+ width: rectangle.width - offset * 2,
159
+ height: rectangle.height - offset * 2
160
+ };
161
+ },
162
+ inflate: (rectangle, delta) => {
163
+ const half = delta / 2;
164
+ return {
165
+ x: rectangle.x - half,
166
+ y: rectangle.y - half,
167
+ width: rectangle.width + half * 2,
168
+ height: rectangle.height + half * 2
169
+ };
170
+ },
171
+ isEqual: (rectangle, otherRectangle) => {
172
+ return (rectangle.x === otherRectangle.x &&
173
+ rectangle.y === otherRectangle.y &&
174
+ rectangle.width === otherRectangle.width &&
175
+ rectangle.height === otherRectangle.height);
176
+ },
177
+ getCornerPoints: (rectangle) => {
178
+ return [
179
+ [rectangle.x, rectangle.y],
180
+ [rectangle.x + rectangle.width, rectangle.y],
181
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height],
182
+ [rectangle.x, rectangle.y + rectangle.height]
183
+ ];
184
+ },
185
+ getCenterPoint: (rectangle) => {
186
+ return [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height / 2];
187
+ },
188
+ getEdgeCenterPoints: (rectangle) => {
189
+ return [
190
+ [rectangle.x + rectangle.width / 2, rectangle.y],
191
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
192
+ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
193
+ [rectangle.x, rectangle.y + rectangle.height / 2]
194
+ ];
195
+ },
196
+ getConnectionPoint: (rectangle, point) => {
197
+ return [rectangle.x + rectangle.width * point[0], rectangle.y + rectangle.height * point[1]];
198
+ },
199
+ expand(rectangle, left, top = left, right = left, bottom = top) {
200
+ return {
201
+ x: rectangle.x - left,
202
+ y: rectangle.y - top,
203
+ width: rectangle.width + left + right,
204
+ height: rectangle.height + top + bottom
205
+ };
206
+ },
207
+ getGapCenter(rectangle1, rectangle2, isHorizontal) {
208
+ const axis = isHorizontal ? 'x' : 'y';
209
+ const side = isHorizontal ? 'width' : 'height';
210
+ const align = [rectangle1[axis], rectangle1[axis] + rectangle1[side], rectangle2[axis], rectangle2[axis] + rectangle2[side]];
211
+ const sortArr = align.sort((a, b) => a - b);
212
+ return (sortArr[1] + sortArr[2]) / 2;
213
+ },
214
+ isPointInRectangle(rectangle, point) {
215
+ const x = point[0], y = point[1];
216
+ return x > rectangle.x && x < rectangle.x + rectangle.width && y > rectangle.y && y < rectangle.y + rectangle.height;
217
+ },
218
+ getBoundingRectangle(rectangles) {
219
+ if (rectangles.length === 0) {
220
+ throw new Error('rectangles can not be empty array');
221
+ }
222
+ let minX = Number.MAX_VALUE;
223
+ let minY = Number.MAX_VALUE;
224
+ let maxX = Number.NEGATIVE_INFINITY;
225
+ let maxY = Number.NEGATIVE_INFINITY;
226
+ rectangles.forEach(rect => {
227
+ minX = Math.min(minX, rect.x);
228
+ minY = Math.min(minY, rect.y);
229
+ maxX = Math.max(maxX, rect.x + rect.width);
230
+ maxY = Math.max(maxY, rect.y + rect.height);
231
+ });
232
+ return {
233
+ x: minX,
234
+ y: minY,
235
+ width: maxX - minX,
236
+ height: maxY - minY
237
+ };
238
+ }
239
+ };
240
+
241
+ var PlaitPluginKey;
242
+ (function (PlaitPluginKey) {
243
+ PlaitPluginKey["withSelection"] = "withSelection";
244
+ })(PlaitPluginKey || (PlaitPluginKey = {}));
245
+
87
246
  const getHitElementsBySelection = (board, selection, match = () => true) => {
88
247
  const newSelection = selection || board.selection;
89
248
  const rectangleHitElements = [];
@@ -127,6 +286,17 @@ const getHitElementByPoint = (board, point, match = () => true) => {
127
286
  }, getIsRecursionFunc(board), true);
128
287
  return hitElement || rectangleHitElement;
129
288
  };
289
+ const getHitSelectedElements = (board, point) => {
290
+ const selectedElements = getSelectedElements(board);
291
+ const targetRectangle = selectedElements.length > 0 && getRectangleByElements(board, selectedElements, false);
292
+ const isInTargetRectangle = targetRectangle && RectangleClient.isPointInRectangle(targetRectangle, point);
293
+ if (isInTargetRectangle) {
294
+ return selectedElements;
295
+ }
296
+ else {
297
+ return [];
298
+ }
299
+ };
130
300
  const cacheSelectedElements = (board, selectedElements) => {
131
301
  const sortedElements = sortElements(board, selectedElements);
132
302
  BOARD_TO_SELECTED_ELEMENT.set(board, sortedElements);
@@ -357,6 +527,22 @@ function createRect(rectangle, options) {
357
527
  const setStrokeLinecap = (g, value) => {
358
528
  g.setAttribute('stroke-linecap', value);
359
529
  };
530
+ const setTransformRotate = (g, rectangle, angle) => {
531
+ var centerX = rectangle.x + rectangle.width / 2;
532
+ var centerY = rectangle.y + rectangle.height / 2;
533
+ let cosTheta = Math.cos(angle);
534
+ let sinTheta = Math.sin(angle);
535
+ let transformMatrix = [
536
+ cosTheta,
537
+ sinTheta,
538
+ -sinTheta,
539
+ cosTheta,
540
+ centerX * (1 - cosTheta) + centerY * sinTheta,
541
+ centerY * (1 - cosTheta) - centerX * sinTheta
542
+ ];
543
+ let matrix = 'matrix(' + transformMatrix.join(',') + ')';
544
+ g.setAttribute('transform', `${matrix}`);
545
+ };
360
546
  const setPathStrokeLinecap = (g, value) => {
361
547
  g.querySelectorAll('path').forEach(path => {
362
548
  path.setAttribute('stroke-linecap', value);
@@ -486,105 +672,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
486
672
  }] } });
487
673
  const ELEMENT_TO_COMPONENT = new WeakMap();
488
674
 
489
- const RectangleClient = {
490
- isHit: (origin, target) => {
491
- return RectangleClient.isHitX(origin, target) && RectangleClient.isHitY(origin, target);
492
- },
493
- isHitX: (origin, target) => {
494
- const minX = origin.x < target.x ? origin.x : target.x;
495
- const maxX = origin.x + origin.width > target.x + target.width ? origin.x + origin.width : target.x + target.width;
496
- // float calculate error( eg: 1.4210854715202004e-14 > 0)
497
- if (Math.floor(maxX - minX - origin.width - target.width) <= 0) {
498
- return true;
499
- }
500
- else {
501
- return false;
502
- }
503
- },
504
- isHitY: (origin, target) => {
505
- const minY = origin.y < target.y ? origin.y : target.y;
506
- const maxY = origin.y + origin.height > target.y + target.height ? origin.y + origin.height : target.y + target.height;
507
- // float calculate error( eg: 1.4210854715202004e-14 > 0)
508
- if (Math.floor(maxY - minY - origin.height - target.height) <= 0) {
509
- return true;
510
- }
511
- else {
512
- return false;
513
- }
514
- },
515
- toRectangleClient: (points) => {
516
- const xArray = points.map(ele => ele[0]);
517
- const yArray = points.map(ele => ele[1]);
518
- const xMin = Math.min(...xArray);
519
- const xMax = Math.max(...xArray);
520
- const yMin = Math.min(...yArray);
521
- const yMax = Math.max(...yArray);
522
- const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
523
- return rect;
524
- },
525
- getOutlineRectangle: (rectangle, offset) => {
526
- return {
527
- x: rectangle.x + offset,
528
- y: rectangle.y + offset,
529
- width: rectangle.width - offset * 2,
530
- height: rectangle.height - offset * 2
531
- };
532
- },
533
- inflate: (rectangle, delta) => {
534
- const half = delta / 2;
535
- return {
536
- x: rectangle.x - half,
537
- y: rectangle.y - half,
538
- width: rectangle.width + half * 2,
539
- height: rectangle.height + half * 2
540
- };
541
- },
542
- isEqual: (rectangle, otherRectangle) => {
543
- return (rectangle.x === otherRectangle.x &&
544
- rectangle.y === otherRectangle.y &&
545
- rectangle.width === otherRectangle.width &&
546
- rectangle.height === otherRectangle.height);
547
- },
548
- getCornerPoints: (rectangle) => {
549
- return [
550
- [rectangle.x, rectangle.y],
551
- [rectangle.x + rectangle.width, rectangle.y],
552
- [rectangle.x + rectangle.width, rectangle.y + rectangle.height],
553
- [rectangle.x, rectangle.y + rectangle.height]
554
- ];
555
- },
556
- getEdgeCenterPoints: (rectangle) => {
557
- return [
558
- [rectangle.x + rectangle.width / 2, rectangle.y],
559
- [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
560
- [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
561
- [rectangle.x, rectangle.y + rectangle.height / 2]
562
- ];
563
- },
564
- getConnectionPoint: (rectangle, point) => {
565
- return [rectangle.x + rectangle.width * point[0], rectangle.y + rectangle.height * point[1]];
566
- },
567
- expand(rectangle, left, top = left, right = left, bottom = top) {
568
- return {
569
- x: rectangle.x - left,
570
- y: rectangle.y - top,
571
- width: rectangle.width + left + right,
572
- height: rectangle.height + top + bottom
573
- };
574
- },
575
- getGapCenter(rectangle1, rectangle2, isHorizontal) {
576
- const axis = isHorizontal ? 'x' : 'y';
577
- const side = isHorizontal ? 'width' : 'height';
578
- const align = [rectangle1[axis], rectangle1[axis] + rectangle1[side], rectangle2[axis], rectangle2[axis] + rectangle2[side]];
579
- const sortArr = align.sort((a, b) => a - b);
580
- return (sortArr[1] + sortArr[2]) / 2;
581
- },
582
- isPointInRectangle(rectangle, point) {
583
- const x = point[0], y = point[1];
584
- return x > rectangle.x && x < rectangle.x + rectangle.width && y > rectangle.y && y < rectangle.y + rectangle.height;
585
- }
586
- };
587
-
588
675
  // https://stackoverflow.com/a/6853926/232122
589
676
  function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
590
677
  const A = x - x1;
@@ -761,9 +848,6 @@ const isPointInRoundRectangle = (point, rectangle, radius) => {
761
848
  const isInCorner = handleLeftTop || handleLeftBottom || handleRightTop || handleRightBottom;
762
849
  return isInRectangle && !isInCorner;
763
850
  };
764
- const downScale = (number) => {
765
- return Number(number.toFixed(2));
766
- };
767
851
  // https://gist.github.com/nicholaswmin/c2661eb11cad5671d816
768
852
  const catmullRomFitting = function (points) {
769
853
  const alpha = 0.5;
@@ -815,6 +899,42 @@ const catmullRomFitting = function (points) {
815
899
  }
816
900
  return result;
817
901
  };
902
+ /**
903
+ * the result of slope is based on Cartesian coordinate system
904
+ * x, y are based on the position in the Cartesian coordinate system
905
+ */
906
+ function getEllipseTangentSlope(x, y, a, b) {
907
+ if (Math.abs(y) === 0) {
908
+ return x > 0 ? -Infinity : Infinity;
909
+ }
910
+ const k = (-b * b * x) / (a * a * y);
911
+ return k;
912
+ }
913
+ /**
914
+ * x, y are based on the position in the Cartesian coordinate system
915
+ */
916
+ function getVectorFromPointAndSlope(x, y, slope) {
917
+ if (slope === Infinity) {
918
+ return [0, -1];
919
+ }
920
+ else if (slope === -Infinity) {
921
+ return [0, 1];
922
+ }
923
+ let vector = [1, -slope];
924
+ if (y < 0) {
925
+ vector = [-vector[0], -vector[1]];
926
+ }
927
+ return vector;
928
+ }
929
+ /**
930
+ * The DOM likes values to be fixed to 3 decimal places
931
+ */
932
+ function toDomPrecision(v) {
933
+ return +v.toFixed(4);
934
+ }
935
+ function toFixed(v) {
936
+ return +v.toFixed(2);
937
+ }
818
938
 
819
939
  function isInPlaitBoard(board, x, y) {
820
940
  const plaitBoardElement = PlaitBoard.getBoardContainer(board);
@@ -875,7 +995,7 @@ function isNullOrUndefined(value) {
875
995
  return value === null || value === undefined;
876
996
  }
877
997
  /**
878
- * 规范 point
998
+ * get {x,y} point
879
999
  * @param point
880
1000
  * @returns point
881
1001
  */
@@ -890,6 +1010,9 @@ function normalizePoint(point) {
890
1010
  const RgbaToHEX = (Rgb, opacity) => {
891
1011
  return Rgb + Math.floor(opacity * 255).toString(16);
892
1012
  };
1013
+ function isContextmenu(event) {
1014
+ return event.button === 2;
1015
+ }
893
1016
 
894
1017
  /**
895
1018
  * Check whether to merge an operation into the previous operation.
@@ -1060,7 +1183,7 @@ const hotkeys = {
1060
1183
 
1061
1184
  function idCreator(length = 5) {
1062
1185
  // remove numeral
1063
- const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
1186
+ const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /**** Easily confusing characters are removed by default oOLl,9gq,Vv,Uu,I1****/
1064
1187
  const maxPosition = $chars.length;
1065
1188
  let key = '';
1066
1189
  for (let i = 0; i < length; i++) {
@@ -1529,17 +1652,29 @@ const setIsFromViewportChange = (board, state) => {
1529
1652
  };
1530
1653
  function scrollToRectangle(board, client) { }
1531
1654
 
1532
- let timerId = null;
1533
- const throttleRAF = (fn) => {
1655
+ const BOARD_TO_RAF = new WeakMap();
1656
+ const getTimerId = (board, key) => {
1657
+ const state = getRAFState(board);
1658
+ return state[key] || null;
1659
+ };
1660
+ const getRAFState = (board) => {
1661
+ return BOARD_TO_RAF.get(board) || {};
1662
+ };
1663
+ const throttleRAF = (board, key, fn) => {
1534
1664
  const scheduleFunc = () => {
1535
- timerId = requestAnimationFrame(() => {
1536
- timerId = null;
1665
+ let timerId = requestAnimationFrame(() => {
1666
+ const value = BOARD_TO_RAF.get(board) || {};
1667
+ value[key] = null;
1668
+ BOARD_TO_RAF.set(board, value);
1537
1669
  fn();
1538
1670
  });
1671
+ const state = getRAFState(board);
1672
+ state[key] = timerId;
1673
+ BOARD_TO_RAF.set(board, state);
1539
1674
  };
1675
+ let timerId = getTimerId(board, key);
1540
1676
  if (timerId !== null) {
1541
1677
  cancelAnimationFrame(timerId);
1542
- timerId = null;
1543
1678
  }
1544
1679
  scheduleFunc();
1545
1680
  };
@@ -1577,18 +1712,13 @@ const getMovingElements = (board) => {
1577
1712
  const isMovingElements = (board) => {
1578
1713
  return (BOARD_TO_MOVING_ELEMENT.get(board) || []).length > 0;
1579
1714
  };
1580
- const addMovingElements = (board, elements) => {
1581
- const movingElements = getMovingElements(board);
1582
- const newElements = elements.filter(item => !movingElements.find(movingElement => movingElement.key === item.key));
1583
- cacheMovingElements(board, [...movingElements, ...newElements]);
1584
- setDragging(board, true);
1585
- };
1586
1715
  const removeMovingElements = (board) => {
1587
1716
  BOARD_TO_MOVING_ELEMENT.delete(board);
1588
1717
  setDragging(board, false);
1589
1718
  };
1590
1719
  const cacheMovingElements = (board, elements) => {
1591
1720
  BOARD_TO_MOVING_ELEMENT.set(board, elements);
1721
+ setDragging(board, true);
1592
1722
  };
1593
1723
 
1594
1724
  const IMAGE_CONTAINER = 'plait-image-container';
@@ -1835,6 +1965,9 @@ const stripHtml = (html) => {
1835
1965
  const getProbablySupportsClipboardWrite = () => {
1836
1966
  return 'clipboard' in navigator && 'write' in navigator.clipboard;
1837
1967
  };
1968
+ const getProbablySupportsClipboardWriteText = () => {
1969
+ return 'clipboard' in navigator && 'writeText' in navigator.clipboard;
1970
+ };
1838
1971
  const getProbablySupportsClipboardRead = () => {
1839
1972
  return 'clipboard' in navigator && 'read' in navigator.clipboard;
1840
1973
  };
@@ -1874,26 +2007,36 @@ const getDataTransferClipboard = (data) => {
1874
2007
  return {};
1875
2008
  };
1876
2009
  const getDataTransferClipboardText = (data) => {
1877
- return (data ? data.getData(`text/plain`) : '');
2010
+ if (!data) {
2011
+ return {};
2012
+ }
2013
+ const text = data?.getData(`text/plain`);
2014
+ if (text) {
2015
+ const htmlClipboardData = getClipboardFromHtml(text);
2016
+ if (htmlClipboardData) {
2017
+ return htmlClipboardData;
2018
+ }
2019
+ }
2020
+ return {
2021
+ text
2022
+ };
1878
2023
  };
1879
2024
 
1880
2025
  const setNavigatorClipboard = async (type, data, text = '') => {
1881
2026
  let textClipboard = text;
1882
- if ('clipboard' in navigator && 'write' in navigator.clipboard) {
1883
- if (navigator.clipboard && typeof navigator.clipboard.write === 'function') {
1884
- await navigator.clipboard.write([
1885
- new ClipboardItem({
1886
- 'text/html': new Blob([buildPlaitHtml(type, data)], {
1887
- type: 'text/html'
1888
- }),
1889
- 'text/plain': new Blob([JSON.stringify(textClipboard ?? data)], { type: 'text/plain' })
1890
- })
1891
- ]);
1892
- }
2027
+ if (getProbablySupportsClipboardWrite()) {
2028
+ await navigator.clipboard.write([
2029
+ new ClipboardItem({
2030
+ 'text/html': new Blob([buildPlaitHtml(type, data)], {
2031
+ type: 'text/html'
2032
+ }),
2033
+ 'text/plain': new Blob([JSON.stringify(textClipboard ?? data)], { type: 'text/plain' })
2034
+ })
2035
+ ]);
1893
2036
  }
1894
2037
  };
1895
2038
  const getNavigatorClipboard = async () => {
1896
- if (!('clipboard' in navigator && 'read' in navigator.clipboard)) {
2039
+ if (!getProbablySupportsClipboardRead()) {
1897
2040
  return {};
1898
2041
  }
1899
2042
  const clipboardItems = await navigator.clipboard.read();
@@ -1957,9 +2100,7 @@ const getClipboardData = async (dataTransfer) => {
1957
2100
  }
1958
2101
  clipboardData = getDataTransferClipboard(dataTransfer);
1959
2102
  if (Object.keys(clipboardData).length === 0) {
1960
- clipboardData = {
1961
- text: getDataTransferClipboardText(dataTransfer)
1962
- };
2103
+ clipboardData = getDataTransferClipboardText(dataTransfer);
1963
2104
  }
1964
2105
  return clipboardData;
1965
2106
  }
@@ -1979,6 +2120,12 @@ const setClipboardData = async (dataTransfer, clipboardContext) => {
1979
2120
  if (dataTransfer) {
1980
2121
  setDataTransferClipboard(dataTransfer, type, data);
1981
2122
  setDataTransferClipboardText(dataTransfer, text);
2123
+ return;
2124
+ }
2125
+ // Compatible with situations where navigator.clipboard.write is not supported and dataTransfer is empty
2126
+ // Such as contextmenu copy in Firefox.
2127
+ if (getProbablySupportsClipboardWriteText()) {
2128
+ return await navigator.clipboard.writeText(buildPlaitHtml(type, data));
1982
2129
  }
1983
2130
  };
1984
2131
 
@@ -2344,11 +2491,26 @@ const Point = {
2344
2491
  isEquals(point, otherPoint) {
2345
2492
  return point && otherPoint && point[0] === otherPoint[0] && point[1] === otherPoint[1];
2346
2493
  },
2347
- isHorizontalAlign(point, otherPoint, tolerance = 0) {
2348
- return point && otherPoint && Math.abs(point[1] - otherPoint[1]) <= tolerance;
2494
+ isHorizontal(point, otherPoint, tolerance = 0) {
2495
+ return point && otherPoint && Point.isOverHorizontal([point, otherPoint], tolerance);
2349
2496
  },
2350
- isVerticalAlign(point, otherPoint, tolerance = 0) {
2351
- return point && otherPoint && Math.abs(point[0] - otherPoint[0]) <= tolerance;
2497
+ isOverHorizontal(points, tolerance = 0) {
2498
+ return points.every(point => Math.abs(point[1] - points[0][1]) <= tolerance);
2499
+ },
2500
+ isVertical(point, otherPoint, tolerance = 0) {
2501
+ return point && otherPoint && Point.isOverVertical([point, otherPoint], tolerance);
2502
+ },
2503
+ isOverVertical(points, tolerance = 0) {
2504
+ return points.every(point => Math.abs(point[0] - points[0][0]) <= tolerance);
2505
+ },
2506
+ isAlign(points, tolerance = 0) {
2507
+ return Point.isOverHorizontal(points, tolerance) || Point.isOverVertical(points, tolerance);
2508
+ },
2509
+ getOffsetX(point1, point2) {
2510
+ return point2[0] - point1[0];
2511
+ },
2512
+ getOffsetY(point1, point2) {
2513
+ return point2[1] - point1[1];
2352
2514
  }
2353
2515
  };
2354
2516
 
@@ -2361,11 +2523,6 @@ const Viewport = {
2361
2523
  const SAVING = new WeakMap();
2362
2524
  const MERGING = new WeakMap();
2363
2525
 
2364
- var PlaitPluginKey;
2365
- (function (PlaitPluginKey) {
2366
- PlaitPluginKey["withSelection"] = "withSelection";
2367
- })(PlaitPluginKey || (PlaitPluginKey = {}));
2368
-
2369
2526
  var ThemeColorMode;
2370
2527
  (function (ThemeColorMode) {
2371
2528
  ThemeColorMode["default"] = "default";
@@ -2423,19 +2580,11 @@ var Direction;
2423
2580
  })(Direction || (Direction = {}));
2424
2581
 
2425
2582
  function getRectangleByElements(board, elements, recursion) {
2426
- const boundaryBox = {
2427
- left: Number.MAX_VALUE,
2428
- top: Number.MAX_VALUE,
2429
- right: Number.NEGATIVE_INFINITY,
2430
- bottom: Number.NEGATIVE_INFINITY
2431
- };
2432
- const calcRectangleClient = (node) => {
2583
+ const rectangles = [];
2584
+ const callback = (node) => {
2433
2585
  const nodeRectangle = board.getRectangle(node);
2434
2586
  if (nodeRectangle) {
2435
- boundaryBox.left = Math.min(boundaryBox.left, nodeRectangle.x);
2436
- boundaryBox.top = Math.min(boundaryBox.top, nodeRectangle.y);
2437
- boundaryBox.right = Math.max(boundaryBox.right, nodeRectangle.x + nodeRectangle.width);
2438
- boundaryBox.bottom = Math.max(boundaryBox.bottom, nodeRectangle.y + nodeRectangle.height);
2587
+ rectangles.push(nodeRectangle);
2439
2588
  }
2440
2589
  else {
2441
2590
  console.error(`can not get rectangle of element:`, node);
@@ -2443,13 +2592,16 @@ function getRectangleByElements(board, elements, recursion) {
2443
2592
  };
2444
2593
  elements.forEach(element => {
2445
2594
  if (recursion) {
2446
- depthFirstRecursion(element, node => calcRectangleClient(node), node => board.isRecursion(node));
2595
+ depthFirstRecursion(element, node => callback(node), node => board.isRecursion(node));
2447
2596
  }
2448
2597
  else {
2449
- calcRectangleClient(element);
2598
+ callback(element);
2450
2599
  }
2451
2600
  });
2452
- if (boundaryBox.left === Number.MAX_VALUE) {
2601
+ if (rectangles.length > 0) {
2602
+ return RectangleClient.getBoundingRectangle(rectangles);
2603
+ }
2604
+ else {
2453
2605
  return {
2454
2606
  x: 0,
2455
2607
  y: 0,
@@ -2457,12 +2609,6 @@ function getRectangleByElements(board, elements, recursion) {
2457
2609
  height: 0
2458
2610
  };
2459
2611
  }
2460
- return {
2461
- x: boundaryBox.left,
2462
- y: boundaryBox.top,
2463
- width: boundaryBox.right - boundaryBox.left,
2464
- height: boundaryBox.bottom - boundaryBox.top
2465
- };
2466
2612
  }
2467
2613
  function getBoardRectangle(board) {
2468
2614
  return getRectangleByElements(board, board.children, true);
@@ -2543,13 +2689,13 @@ const PlaitBoard = {
2543
2689
  return BOARD_TO_COMPONENT.get(board);
2544
2690
  },
2545
2691
  getBoardContainer(board) {
2546
- return PlaitBoard.getComponent(board).nativeElement;
2692
+ return BOARD_TO_ELEMENT_HOST.get(board)?.container;
2547
2693
  },
2548
2694
  getRectangle(board) {
2549
2695
  return getRectangleByElements(board, board.children, true);
2550
2696
  },
2551
2697
  getViewportContainer(board) {
2552
- return PlaitBoard.getHost(board).parentElement;
2698
+ return BOARD_TO_ELEMENT_HOST.get(board)?.viewportContainer;
2553
2699
  },
2554
2700
  isFocus(board) {
2555
2701
  return !!board.selection;
@@ -2765,7 +2911,7 @@ const NodeTransforms = {
2765
2911
  };
2766
2912
 
2767
2913
  function withSelection(board) {
2768
- const { pointerDown, pointerUp, pointerMove, globalPointerUp, keydown, keyup, onChange, afterChange } = board;
2914
+ const { pointerDown, pointerUp, pointerMove, globalPointerUp, onChange, afterChange } = board;
2769
2915
  let start = null;
2770
2916
  let end = null;
2771
2917
  let selectionMovingG;
@@ -2787,9 +2933,12 @@ function withSelection(board) {
2787
2933
  event.preventDefault();
2788
2934
  }
2789
2935
  const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
2936
+ const selectedElements = getSelectedElements(board);
2790
2937
  const hitElement = getHitElementByPoint(board, point);
2938
+ const hitSelectedElements = selectedElements.length > 1 ? getHitSelectedElements(board, point) : [];
2939
+ const isHitTarget = hitElement || hitSelectedElements.length > 0;
2791
2940
  const options = board.getPluginOptions(PlaitPluginKey.withSelection);
2792
- if (PlaitBoard.isPointer(board, PlaitPointerType.selection) && !hitElement && options.isMultiple && !options.isDisabledSelect) {
2941
+ if (PlaitBoard.isPointer(board, PlaitPointerType.selection) && !isHitTarget && options.isMultiple && !options.isDisabledSelect) {
2793
2942
  preventTouchMove(board, event, true);
2794
2943
  // start rectangle selection
2795
2944
  start = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
@@ -2803,11 +2952,11 @@ function withSelection(board) {
2803
2952
  }
2804
2953
  if (start && PlaitBoard.isPointer(board, PlaitPointerType.selection)) {
2805
2954
  const movedTarget = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
2806
- const rectangle = RectangleClient.toRectangleClient([start, movedTarget]);
2955
+ const rectangle = RectangleClient.getRectangleByPoints([start, movedTarget]);
2807
2956
  selectionMovingG?.remove();
2808
2957
  if (Math.hypot(rectangle.width, rectangle.height) > PRESS_AND_MOVE_BUFFER || isSelectionMoving(board)) {
2809
2958
  end = movedTarget;
2810
- throttleRAF(() => {
2959
+ throttleRAF(board, 'with-selection', () => {
2811
2960
  if (start && end) {
2812
2961
  Transforms.setSelection(board, { anchor: start, focus: end });
2813
2962
  }
@@ -2829,7 +2978,7 @@ function withSelection(board) {
2829
2978
  const isSetSelectionPointer = PlaitBoard.isPointer(board, PlaitPointerType.selection) || PlaitBoard.isPointer(board, PlaitPointerType.hand);
2830
2979
  const isSkip = !isMainPointer(event) || isDragging(board) || !isSetSelectionPointer;
2831
2980
  if (isSkip) {
2832
- pointerDown(event);
2981
+ pointerUp(event);
2833
2982
  return;
2834
2983
  }
2835
2984
  const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
@@ -2872,23 +3021,36 @@ function withSelection(board) {
2872
3021
  });
2873
3022
  if (isHandleSelection(board) && isSetSelectionOperation(board)) {
2874
3023
  try {
2875
- selectionRectangleG?.remove();
3024
+ if (!isShift) {
3025
+ selectionRectangleG?.remove();
3026
+ }
2876
3027
  const temporaryElements = getTemporaryElements(board);
2877
3028
  let elements = temporaryElements ? temporaryElements : getHitElementsBySelection(board);
2878
3029
  if (!options.isMultiple && elements.length > 1) {
2879
3030
  elements = [elements[0]];
2880
3031
  }
2881
- if (isShift && board.selection && Selection.isCollapsed(board.selection)) {
2882
- const newSelectedElements = [...getSelectedElements(board)];
2883
- elements.forEach(element => {
2884
- if (newSelectedElements.includes(element)) {
2885
- newSelectedElements.splice(newSelectedElements.indexOf(element), 1);
2886
- }
2887
- else {
2888
- newSelectedElements.push(element);
2889
- }
2890
- });
2891
- cacheSelectedElements(board, newSelectedElements);
3032
+ if (isShift) {
3033
+ if (board.selection && Selection.isCollapsed(board.selection)) {
3034
+ const newSelectedElements = [...getSelectedElements(board)];
3035
+ elements.forEach(element => {
3036
+ if (newSelectedElements.includes(element)) {
3037
+ newSelectedElements.splice(newSelectedElements.indexOf(element), 1);
3038
+ }
3039
+ else {
3040
+ newSelectedElements.push(element);
3041
+ }
3042
+ });
3043
+ cacheSelectedElements(board, newSelectedElements);
3044
+ }
3045
+ else {
3046
+ const newSelectedElements = [...getSelectedElements(board)];
3047
+ elements.forEach(element => {
3048
+ if (!newSelectedElements.includes(element)) {
3049
+ newSelectedElements.push(element);
3050
+ }
3051
+ });
3052
+ cacheSelectedElements(board, newSelectedElements);
3053
+ }
2892
3054
  }
2893
3055
  else {
2894
3056
  const newSelectedElements = [...elements];
@@ -2898,6 +3060,7 @@ function withSelection(board) {
2898
3060
  previousSelectedElements = newElements;
2899
3061
  deleteTemporaryElements(board);
2900
3062
  if (!isSelectionMoving(board) && newElements.length > 1) {
3063
+ selectionRectangleG?.remove();
2901
3064
  selectionRectangleG = createSelectionRectangleG(board);
2902
3065
  }
2903
3066
  }
@@ -3120,10 +3283,10 @@ function createBoard(children, options) {
3120
3283
  globalMousemove: (event) => { },
3121
3284
  mouseup: (event) => { },
3122
3285
  globalMouseup: (event) => { },
3123
- keydown: (event) => { },
3124
- globalKeydown: (event) => { },
3125
- keyup: (event) => { },
3126
- dblclick: (event) => { },
3286
+ keyDown: (event) => { },
3287
+ globalKeyDown: (event) => { },
3288
+ keyUp: (event) => { },
3289
+ dblClick: (event) => { },
3127
3290
  setFragment: (data, clipboardContext) => {
3128
3291
  setClipboardData(data, clipboardContext);
3129
3292
  },
@@ -3178,7 +3341,7 @@ function withBoard(board) {
3178
3341
  }
3179
3342
 
3180
3343
  function withHistory(board) {
3181
- const { apply, keydown } = board;
3344
+ const { apply, keyDown } = board;
3182
3345
  board.history = { undos: [], redos: [] };
3183
3346
  board.redo = () => {
3184
3347
  const { history } = board;
@@ -3247,7 +3410,7 @@ function withHistory(board) {
3247
3410
  }
3248
3411
  apply(op);
3249
3412
  };
3250
- board.keydown = (event) => {
3413
+ board.keyDown = (event) => {
3251
3414
  if (isHotkey('mod+z', event)) {
3252
3415
  board.undo();
3253
3416
  return;
@@ -3256,13 +3419,13 @@ function withHistory(board) {
3256
3419
  board.redo();
3257
3420
  return;
3258
3421
  }
3259
- keydown(event);
3422
+ keyDown(event);
3260
3423
  };
3261
3424
  return board;
3262
3425
  }
3263
3426
 
3264
3427
  function withHandPointer(board) {
3265
- const { pointerDown, pointerMove, globalPointerUp, keydown, keyup } = board;
3428
+ const { pointerDown, pointerMove, globalPointerUp, keyDown, keyUp } = board;
3266
3429
  let isMoving = false;
3267
3430
  const plaitBoardMove = {
3268
3431
  x: 0,
@@ -3297,20 +3460,20 @@ function withHandPointer(board) {
3297
3460
  }
3298
3461
  globalPointerUp(event);
3299
3462
  };
3300
- board.keydown = (event) => {
3463
+ board.keyDown = (event) => {
3301
3464
  if (event.code === 'Space') {
3302
3465
  if (!PlaitBoard.isPointer(board, PlaitPointerType.hand)) {
3303
3466
  BoardTransforms.updatePointerType(board, PlaitPointerType.hand);
3304
3467
  }
3305
3468
  event.preventDefault();
3306
3469
  }
3307
- keydown(event);
3470
+ keyDown(event);
3308
3471
  };
3309
- board.keyup = (event) => {
3472
+ board.keyUp = (event) => {
3310
3473
  if (!board.options.readonly && event.code === 'Space') {
3311
3474
  BoardTransforms.updatePointerType(board, PlaitPointerType.selection);
3312
3475
  }
3313
- keyup(event);
3476
+ keyUp(event);
3314
3477
  };
3315
3478
  return board;
3316
3479
  }
@@ -3720,29 +3883,32 @@ function withMoving(board) {
3720
3883
  let alignG = null;
3721
3884
  let activeElementsRectangle = null;
3722
3885
  board.pointerDown = (event) => {
3886
+ if (PlaitBoard.isReadonly(board) ||
3887
+ !PlaitBoard.isPointer(board, PlaitPointerType.selection) ||
3888
+ isPreventTouchMove(board) ||
3889
+ !isMainPointer(event)) {
3890
+ pointerDown(event);
3891
+ }
3723
3892
  const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
3724
- let movableElements = board.children.filter(item => board.isMovable(item));
3725
- if (!PlaitBoard.isReadonly(board) &&
3726
- PlaitBoard.isPointer(board, PlaitPointerType.selection) &&
3727
- movableElements.length &&
3728
- !isPreventTouchMove(board) &&
3729
- isMainPointer(event)) {
3893
+ const targetElements = getTargetElements(board);
3894
+ const targetRectangle = targetElements.length > 0 && getRectangleByElements(board, targetElements, false);
3895
+ const isInTargetRectangle = targetRectangle && RectangleClient.isPointInRectangle(targetRectangle, point);
3896
+ if (isInTargetRectangle) {
3730
3897
  startPoint = point;
3731
- const selectedMovableElements = getSelectedElements(board).filter(item => movableElements.includes(item));
3732
- const hitElement = getHitElementByPoint(board, point);
3733
- if (hitElement && movableElements.includes(hitElement)) {
3734
- if (selectedMovableElements.includes(hitElement)) {
3735
- const relatedElements = board.getRelatedFragment([]);
3736
- activeElements = [...selectedMovableElements, ...relatedElements];
3737
- }
3738
- else {
3739
- activeElements = [hitElement];
3898
+ activeElements = targetElements;
3899
+ preventTouchMove(board, event, true);
3900
+ activeElementsRectangle = getRectangleByElements(board, activeElements, true);
3901
+ }
3902
+ else {
3903
+ const targetElement = getHitElementByPoint(board, point, el => board.isMovable(el));
3904
+ if (targetElement) {
3905
+ startPoint = point;
3906
+ activeElements = [targetElement];
3907
+ if (targetElements.length > 0) {
3908
+ addSelectionWithTemporaryElements(board, []);
3740
3909
  }
3910
+ activeElementsRectangle = getRectangleByElements(board, activeElements, true);
3741
3911
  }
3742
- if (activeElements.length > 0) {
3743
- preventTouchMove(board, event, true);
3744
- }
3745
- activeElementsRectangle = getRectangleByElements(board, activeElements, true);
3746
3912
  }
3747
3913
  pointerDown(event);
3748
3914
  };
@@ -3757,7 +3923,7 @@ function withMoving(board) {
3757
3923
  offsetY = endPoint[1] - startPoint[1];
3758
3924
  const distance = distanceBetweenPointAndPoint(...endPoint, ...startPoint);
3759
3925
  if (distance > PRESS_AND_MOVE_BUFFER || getMovingElements(board).length > 0) {
3760
- throttleRAF(() => {
3926
+ throttleRAF(board, 'with-moving', () => {
3761
3927
  if (!activeElementsRectangle) {
3762
3928
  return;
3763
3929
  }
@@ -3774,19 +3940,9 @@ function withMoving(board) {
3774
3940
  alignG.classList.add(ACTIVE_MOVING_CLASS_NAME);
3775
3941
  PlaitBoard.getElementActiveHost(board).append(alignG);
3776
3942
  handleTouchTarget(board);
3777
- const currentElements = activeElements.map(activeElement => {
3778
- const points = activeElement.points || [];
3779
- const [x, y] = activeElement.points[0];
3780
- const newPoints = points.map(p => [p[0] + offsetX, p[1] + offsetY]);
3781
- const index = board.children.findIndex(item => item.id === activeElement.id);
3782
- Transforms.setNode(board, {
3783
- points: newPoints
3784
- }, [index]);
3785
- MERGING.set(board, true);
3786
- return PlaitNode.get(board, [index]);
3787
- });
3943
+ const currentElements = updatePoints(board, activeElements, offsetX, offsetY);
3788
3944
  PlaitBoard.getBoardContainer(board).classList.add('element-moving');
3789
- addMovingElements(board, currentElements);
3945
+ cacheMovingElements(board, currentElements);
3790
3946
  });
3791
3947
  }
3792
3948
  }
@@ -3820,12 +3976,78 @@ function withMoving(board) {
3820
3976
  offsetX = 0;
3821
3977
  offsetY = 0;
3822
3978
  activeElements = [];
3823
- removeMovingElements(board);
3979
+ if (isMovingElements(board)) {
3980
+ removeMovingElements(board);
3981
+ }
3824
3982
  MERGING.set(board, false);
3825
3983
  PlaitBoard.getBoardContainer(board).classList.remove('element-moving');
3826
3984
  }
3985
+ return withArrowMoving(board);
3986
+ }
3987
+ function withArrowMoving(board) {
3988
+ const { keyDown, keyUp } = board;
3989
+ board.keyDown = (event) => {
3990
+ const selectedElements = getSelectedElements(board);
3991
+ if (!PlaitBoard.isReadonly(board) && selectedElements.length > 0 && (hotkeys.isArrow(event) || hotkeys.isExtendArrow(event))) {
3992
+ event.preventDefault();
3993
+ const isShift = event.shiftKey ? true : false;
3994
+ const offset = [0, 0];
3995
+ const buffer = isShift ? 10 : 1;
3996
+ switch (true) {
3997
+ case hotkeys.isMoveUp(event) || hotkeys.isExtendUp(event): {
3998
+ offset[1] = -buffer;
3999
+ break;
4000
+ }
4001
+ case hotkeys.isMoveDown(event) || hotkeys.isExtendDown(event): {
4002
+ offset[1] = buffer;
4003
+ break;
4004
+ }
4005
+ case hotkeys.isMoveBackward(event) || hotkeys.isExtendBackward(event): {
4006
+ offset[0] = -buffer;
4007
+ break;
4008
+ }
4009
+ case hotkeys.isMoveForward(event) || hotkeys.isExtendForward(event): {
4010
+ offset[0] = buffer;
4011
+ break;
4012
+ }
4013
+ }
4014
+ const targetElements = getTargetElements(board);
4015
+ throttleRAF(board, 'with-arrow-moving', () => {
4016
+ updatePoints(board, targetElements, offset[0], offset[1]);
4017
+ });
4018
+ }
4019
+ keyDown(event);
4020
+ };
4021
+ board.keyUp = (event) => {
4022
+ MERGING.set(board, false);
4023
+ keyUp(event);
4024
+ };
3827
4025
  return board;
3828
4026
  }
4027
+ function getTargetElements(board) {
4028
+ const selectedElements = getSelectedElements(board);
4029
+ const movableElements = board.children.filter(item => board.isMovable(item));
4030
+ const targetElements = selectedElements.filter(element => {
4031
+ return movableElements.includes(element);
4032
+ });
4033
+ const relatedElements = board.getRelatedFragment([]);
4034
+ targetElements.push(...relatedElements);
4035
+ return targetElements;
4036
+ }
4037
+ function updatePoints(board, targetElements, offsetX, offsetY) {
4038
+ const validElements = targetElements.filter(element => board.children.findIndex(item => item.id === element.id) > -1);
4039
+ const currentElements = validElements.map(element => {
4040
+ const points = element.points || [];
4041
+ const newPoints = points.map(p => [p[0] + offsetX, p[1] + offsetY]);
4042
+ const index = board.children.findIndex(item => item.id === element.id);
4043
+ Transforms.setNode(board, {
4044
+ points: newPoints
4045
+ }, [index]);
4046
+ MERGING.set(board, true);
4047
+ return PlaitNode.get(board, [index]);
4048
+ });
4049
+ return currentElements;
4050
+ }
3829
4051
 
3830
4052
  const withOptions = (board) => {
3831
4053
  const pluginOptions = new Map();
@@ -3910,13 +4132,9 @@ const hasOnBoardChange = (value) => {
3910
4132
  };
3911
4133
 
3912
4134
  const withHotkey = (board) => {
3913
- const { keydown, keyup, globalKeydown } = board;
3914
- let isShift = false;
3915
- board.keydown = (event) => {
4135
+ const { keyDown, keyUp, globalKeyDown } = board;
4136
+ board.keyDown = (event) => {
3916
4137
  const options = board.getPluginOptions(PlaitPluginKey.withSelection);
3917
- if (hotkeys.isShift(event)) {
3918
- isShift = true;
3919
- }
3920
4138
  if (!PlaitBoard.isReadonly(board) && options.isMultiple && isHotkey('mod+a', event)) {
3921
4139
  event.preventDefault();
3922
4140
  let elements = [];
@@ -3943,54 +4161,12 @@ const withHotkey = (board) => {
3943
4161
  event.preventDefault();
3944
4162
  board.deleteFragment(null);
3945
4163
  }
3946
- if (!PlaitBoard.isReadonly(board) && selectedElements.length > 0 && (hotkeys.isArrow(event) || hotkeys.isExtendArrow(event))) {
3947
- event.preventDefault();
3948
- const offset = [0, 0];
3949
- const buffer = isShift ? 10 : 1;
3950
- switch (true) {
3951
- case hotkeys.isMoveUp(event) || hotkeys.isExtendUp(event): {
3952
- offset[1] = -buffer;
3953
- break;
3954
- }
3955
- case hotkeys.isMoveDown(event) || hotkeys.isExtendDown(event): {
3956
- offset[1] = buffer;
3957
- break;
3958
- }
3959
- case hotkeys.isMoveBackward(event) || hotkeys.isExtendBackward(event): {
3960
- offset[0] = -buffer;
3961
- break;
3962
- }
3963
- case hotkeys.isMoveForward(event) || hotkeys.isExtendForward(event): {
3964
- offset[0] = buffer;
3965
- break;
3966
- }
3967
- }
3968
- const selectedElements = getSelectedElements(board);
3969
- const relatedElements = board.getRelatedFragment([]);
3970
- const movableElements = board.children.filter(item => board.isMovable(item));
3971
- throttleRAF(() => {
3972
- [...selectedElements, ...relatedElements]
3973
- .filter(element => movableElements.includes(element))
3974
- .forEach(element => {
3975
- const points = element.points || [];
3976
- const newPoints = points.map(p => [p[0] + offset[0], p[1] + offset[1]]);
3977
- Transforms.setNode(board, {
3978
- points: newPoints
3979
- }, PlaitBoard.findPath(board, element));
3980
- MERGING.set(board, true);
3981
- });
3982
- });
3983
- }
3984
- keydown(event);
4164
+ keyDown(event);
3985
4165
  };
3986
- board.keyup = (event) => {
3987
- if (event.key === 'Shift') {
3988
- isShift = false;
3989
- }
3990
- MERGING.set(board, false);
3991
- keyup(event);
4166
+ board.keyUp = (event) => {
4167
+ keyUp(event);
3992
4168
  };
3993
- board.globalKeydown = (event) => {
4169
+ board.globalKeyDown = (event) => {
3994
4170
  if (PlaitBoard.getMovingPointInBoard(board) || PlaitBoard.isMovingPointInBoard(board)) {
3995
4171
  if (isHotkey(['mod+=', 'mod++'], { byKey: true })(event)) {
3996
4172
  event.preventDefault();
@@ -4013,7 +4189,7 @@ const withHotkey = (board) => {
4013
4189
  return;
4014
4190
  }
4015
4191
  }
4016
- globalKeydown(event);
4192
+ globalKeyDown(event);
4017
4193
  };
4018
4194
  return board;
4019
4195
  };
@@ -4267,7 +4443,9 @@ class PlaitBoardComponent {
4267
4443
  BOARD_TO_ELEMENT_HOST.set(this.board, {
4268
4444
  host: elementHost,
4269
4445
  upperHost: elementUpperHost,
4270
- activeHost: elementActiveHost
4446
+ activeHost: elementActiveHost,
4447
+ container: this.elementRef.nativeElement,
4448
+ viewportContainer: this.viewportContainer.nativeElement
4271
4449
  });
4272
4450
  BOARD_TO_ON_CHANGE.set(this.board, () => {
4273
4451
  this.ngZone.run(() => {
@@ -4396,19 +4574,19 @@ class PlaitBoardComponent {
4396
4574
  fromEvent(this.host, 'dblclick')
4397
4575
  .pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.hasBeenTextEditing(this.board)))
4398
4576
  .subscribe((event) => {
4399
- this.board.dblclick(event);
4577
+ this.board.dblClick(event);
4400
4578
  });
4401
4579
  fromEvent(document, 'keydown')
4402
4580
  .pipe(takeUntil(this.destroy$), tap(event => {
4403
- this.board.globalKeydown(event);
4581
+ this.board.globalKeyDown(event);
4404
4582
  }), filter(event => this.isFocused && !PlaitBoard.hasBeenTextEditing(this.board) && !hasInputOrTextareaTarget(event.target)))
4405
4583
  .subscribe((event) => {
4406
- this.board.keydown(event);
4584
+ this.board.keyDown(event);
4407
4585
  });
4408
4586
  fromEvent(document, 'keyup')
4409
4587
  .pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.hasBeenTextEditing(this.board)))
4410
4588
  .subscribe((event) => {
4411
- this.board?.keyup(event);
4589
+ this.board?.keyUp(event);
4412
4590
  });
4413
4591
  fromEvent(document, 'copy')
4414
4592
  .pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.hasBeenTextEditing(this.board)))
@@ -4518,6 +4696,7 @@ class PlaitBoardComponent {
4518
4696
  BOARD_TO_HOST.delete(this.board);
4519
4697
  BOARD_TO_ELEMENT_HOST.delete(this.board);
4520
4698
  BOARD_TO_ON_CHANGE.delete(this.board);
4699
+ BOARD_TO_AFTER_CHANGE.set(this.board, () => { });
4521
4700
  }
4522
4701
  markForCheck() {
4523
4702
  this.cdr.markForCheck();
@@ -4763,5 +4942,5 @@ function createModModifierKeys() {
4763
4942
  * Generated bundle index. Do not edit.
4764
4943
  */
4765
4944
 
4766
- export { A, ACTIVE_MOVING_CLASS_NAME, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, BOARD_TO_AFTER_CHANGE, BOARD_TO_COMPONENT, BOARD_TO_ELEMENT_HOST, BOARD_TO_HOST, BOARD_TO_IS_SELECTION_MOVING, BOARD_TO_MOVING_ELEMENT, BOARD_TO_MOVING_POINT, BOARD_TO_MOVING_POINT_IN_BOARD, BOARD_TO_ON_CHANGE, BOARD_TO_ROUGH_SVG, BOARD_TO_SELECTED_ELEMENT, BOARD_TO_TEMPORARY_ELEMENTS, BOARD_TO_TOUCH_REF, BOARD_TO_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, CursorClass, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DefaultThemeColor, Direction, E, EIGHT, ELEMENT_TO_COMPONENT, END, ENTER, EQUALS, ESCAPE, F, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, FF_EQUALS, FF_MINUS, FF_MUTE, FF_SEMICOLON, FF_VOLUME_DOWN, FF_VOLUME_UP, FIRST_MEDIA, FIVE, FLUSHING, FOUR, G, H, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_DRAGGING, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_SAFARI, IS_TEXT_EDITABLE, J, K, L, LAST_MEDIA, LEFT_ARROW, M, MAC_ENTER, MAC_META, MAC_WK_CMD_LEFT, MAC_WK_CMD_RIGHT, MAX_RADIUS, MERGING, META, MUTE, N, NINE, NODE_TO_INDEX, NODE_TO_PARENT, NS, NUMPAD_DIVIDE, NUMPAD_EIGHT, NUMPAD_FIVE, NUMPAD_FOUR, NUMPAD_MINUS, NUMPAD_MULTIPLY, NUMPAD_NINE, NUMPAD_ONE, NUMPAD_PERIOD, NUMPAD_PLUS, NUMPAD_SEVEN, NUMPAD_SIX, NUMPAD_THREE, NUMPAD_TWO, NUMPAD_ZERO, NUM_CENTER, NUM_LOCK, O, ONE, OPEN_SQUARE_BRACKET, P, PAGE_DOWN, PAGE_UP, PATH_REFS, PAUSE, PERIOD, PLUS_SIGN, POINTER_BUTTON, PRESS_AND_MOVE_BUFFER, PRINT_SCREEN, Path, PlaitBoard, PlaitBoardComponent, PlaitChildrenElementComponent, PlaitContextService, PlaitElement, PlaitElementComponent, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RIGHT_ARROW, RectangleClient, ResizeCursorClass, RetroThemeColor, RgbaToHEX, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SELECTION_RECTANGLE_CLASS_NAME, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, WritableClipboardType, X, Y, Z, ZERO, addClipboardContext, addMovingElements, addSelectedElement, arrowPoints, buildPlaitHtml, cacheMovingElements, cacheSelectedElements, calcNewViewBox, catmullRomFitting, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createClipboardContext, createFakeEvent, createForeignObject, createG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createSelectionRectangleG, createTestingBoard, createText, createTouchEvent, debounce, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downScale, downloadImage, drawArrow, drawBezierPath, drawCircle, drawLine, drawLinearPath, drawRectangle, drawRoundRectangle, fakeNodeWeakMap, findElements, getBoardRectangle, getClipboardData, getClipboardFromHtml, getDataTransferClipboard, getDataTransferClipboardText, getElementById, getElementHostBBox, getHitElementByPoint, getHitElementsBySelection, getIsRecursionFunc, getMovingElements, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getProbablySupportsClipboardRead, getProbablySupportsClipboardWrite, getRealScrollBarWidth, getRectangleByElements, getSelectedElements, getTemporaryElements, getTemporaryRef, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, handleTouchTarget, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isDOMElement, isDOMNode, isDragging, isFromScrolling, isFromViewportChange, isHandleSelection, isInPlaitBoard, isLineHitLine, isMainPointer, isMovingElements, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedElement, isSelectionMoving, isSetSelectionOperation, isSetViewportOperation, normalizePoint, preventTouchMove, removeMovingElements, removeSelectedElement, rotate, scrollToRectangle, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setDragging, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectionMoving, setStrokeLinecap, shouldClear, shouldMerge, shouldSave, stripHtml, temporaryDisableSelection, throttleRAF, toHostPoint, toHostPointFromViewBoxPoint, toImage, toScreenPointFromHostPoint, toViewBoxPoint, toViewBoxPoints, updateForeignObject, updateForeignObjectWidth, updateViewportByScrolling, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withMoving, withOptions, withSelection };
4945
+ export { A, ACTIVE_MOVING_CLASS_NAME, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, BOARD_TO_AFTER_CHANGE, BOARD_TO_COMPONENT, BOARD_TO_ELEMENT_HOST, BOARD_TO_HOST, BOARD_TO_IS_SELECTION_MOVING, BOARD_TO_MOVING_ELEMENT, BOARD_TO_MOVING_POINT, BOARD_TO_MOVING_POINT_IN_BOARD, BOARD_TO_ON_CHANGE, BOARD_TO_ROUGH_SVG, BOARD_TO_SELECTED_ELEMENT, BOARD_TO_TEMPORARY_ELEMENTS, BOARD_TO_TOUCH_REF, BOARD_TO_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, CursorClass, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DefaultThemeColor, Direction, E, EIGHT, ELEMENT_TO_COMPONENT, END, ENTER, EQUALS, ESCAPE, F, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, FF_EQUALS, FF_MINUS, FF_MUTE, FF_SEMICOLON, FF_VOLUME_DOWN, FF_VOLUME_UP, FIRST_MEDIA, FIVE, FLUSHING, FOUR, G, H, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_DRAGGING, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_SAFARI, IS_TEXT_EDITABLE, J, K, L, LAST_MEDIA, LEFT_ARROW, M, MAC_ENTER, MAC_META, MAC_WK_CMD_LEFT, MAC_WK_CMD_RIGHT, MAX_RADIUS, MERGING, META, MUTE, N, NINE, NODE_TO_INDEX, NODE_TO_PARENT, NS, NUMPAD_DIVIDE, NUMPAD_EIGHT, NUMPAD_FIVE, NUMPAD_FOUR, NUMPAD_MINUS, NUMPAD_MULTIPLY, NUMPAD_NINE, NUMPAD_ONE, NUMPAD_PERIOD, NUMPAD_PLUS, NUMPAD_SEVEN, NUMPAD_SIX, NUMPAD_THREE, NUMPAD_TWO, NUMPAD_ZERO, NUM_CENTER, NUM_LOCK, O, ONE, OPEN_SQUARE_BRACKET, P, PAGE_DOWN, PAGE_UP, PATH_REFS, PAUSE, PERIOD, PLUS_SIGN, POINTER_BUTTON, PRESS_AND_MOVE_BUFFER, PRINT_SCREEN, Path, PlaitBoard, PlaitBoardComponent, PlaitChildrenElementComponent, PlaitContextService, PlaitElement, PlaitElementComponent, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RIGHT_ARROW, RectangleClient, ResizeCursorClass, RetroThemeColor, RgbaToHEX, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SELECTION_RECTANGLE_CLASS_NAME, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, WritableClipboardType, X, Y, Z, ZERO, addClipboardContext, addSelectedElement, arrowPoints, buildPlaitHtml, cacheMovingElements, cacheSelectedElements, calcNewViewBox, catmullRomFitting, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createClipboardContext, createFakeEvent, createForeignObject, createG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createSelectionRectangleG, createTestingBoard, createText, createTouchEvent, debounce, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawLine, drawLinearPath, drawRectangle, drawRoundRectangle, fakeNodeWeakMap, findElements, getBoardRectangle, getClipboardData, getClipboardFromHtml, getDataTransferClipboard, getDataTransferClipboardText, getElementById, getElementHostBBox, getEllipseTangentSlope, getHitElementByPoint, getHitElementsBySelection, getHitSelectedElements, getIsRecursionFunc, getMovingElements, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getProbablySupportsClipboardRead, getProbablySupportsClipboardWrite, getProbablySupportsClipboardWriteText, getRealScrollBarWidth, getRectangleByElements, getSelectedElements, getTargetElements, getTemporaryElements, getTemporaryRef, getVectorFromPointAndSlope, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, handleTouchTarget, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isContextmenu, isDOMElement, isDOMNode, isDragging, isFromScrolling, isFromViewportChange, isHandleSelection, isInPlaitBoard, isLineHitLine, isMainPointer, isMovingElements, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedElement, isSelectionMoving, isSetSelectionOperation, isSetViewportOperation, normalizePoint, preventTouchMove, removeMovingElements, removeSelectedElement, rotate, scrollToRectangle, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setDragging, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectionMoving, setStrokeLinecap, setTransformRotate, shouldClear, shouldMerge, shouldSave, stripHtml, temporaryDisableSelection, throttleRAF, toDomPrecision, toFixed, toHostPoint, toHostPointFromViewBoxPoint, toImage, toScreenPointFromHostPoint, toViewBoxPoint, toViewBoxPoints, updateForeignObject, updateForeignObjectWidth, updatePoints, updateViewportByScrolling, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withArrowMoving, withMoving, withOptions, withSelection };
4767
4946
  //# sourceMappingURL=plait-core.mjs.map