@plait/core 0.24.0-next.1 → 0.24.0-next.10

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 (133) hide show
  1. package/board/board.component.d.ts +1 -1
  2. package/board/board.component.interface.d.ts +3 -1
  3. package/constants/selection.d.ts +2 -0
  4. package/core/children/children.component.d.ts +1 -1
  5. package/core/element/element.component.d.ts +1 -1
  6. package/core/element/plugin-element.d.ts +1 -1
  7. package/core/island/island-base.component.d.ts +4 -2
  8. package/esm2022/board/board.component.interface.mjs +2 -0
  9. package/esm2022/board/board.component.mjs +405 -0
  10. package/esm2022/constants/selection.mjs +4 -0
  11. package/{esm2020 → esm2022}/core/children/children.component.mjs +6 -6
  12. package/{esm2020 → esm2022}/core/element/element.component.mjs +4 -4
  13. package/{esm2020 → esm2022}/core/element/plugin-element.mjs +4 -4
  14. package/esm2022/core/island/island-base.component.mjs +72 -0
  15. package/{esm2020 → esm2022}/interfaces/board.mjs +1 -1
  16. package/esm2022/interfaces/rectangle-client.mjs +65 -0
  17. package/{esm2020 → esm2022}/plait.module.mjs +5 -5
  18. package/esm2022/plugins/create-board.mjs +106 -0
  19. package/esm2022/plugins/with-hotkey.mjs +62 -0
  20. package/esm2022/plugins/with-moving.mjs +116 -0
  21. package/esm2022/plugins/with-selection.mjs +193 -0
  22. package/{esm2020 → esm2022}/services/image-context.service.mjs +4 -4
  23. package/esm2022/transforms/element.mjs +22 -0
  24. package/esm2022/transforms/index.mjs +13 -0
  25. package/esm2022/transforms/selection.mjs +26 -0
  26. package/esm2022/utils/dom/common.mjs +76 -0
  27. package/esm2022/utils/dom/foreign.mjs +25 -0
  28. package/esm2022/utils/draw/line.mjs +47 -0
  29. package/esm2022/utils/draw/rectangle.mjs +34 -0
  30. package/esm2022/utils/element.mjs +71 -0
  31. package/esm2022/utils/math.mjs +176 -0
  32. package/esm2022/utils/reaction-manager.mjs +185 -0
  33. package/esm2022/utils/touch.mjs +35 -0
  34. package/esm2022/utils/weak-maps.mjs +21 -0
  35. package/{fesm2020 → fesm2022}/plait-core.mjs +2427 -1938
  36. package/fesm2022/plait-core.mjs.map +1 -0
  37. package/interfaces/board.d.ts +2 -1
  38. package/interfaces/rectangle-client.d.ts +8 -0
  39. package/package.json +14 -14
  40. package/plugins/with-selection.d.ts +5 -1
  41. package/styles/styles.scss +1 -1
  42. package/transforms/element.d.ts +6 -0
  43. package/transforms/index.d.ts +1 -0
  44. package/transforms/selection.d.ts +2 -2
  45. package/utils/dom/common.d.ts +6 -0
  46. package/utils/dom/foreign.d.ts +2 -1
  47. package/utils/draw/line.d.ts +1 -1
  48. package/utils/draw/rectangle.d.ts +3 -0
  49. package/utils/element.d.ts +5 -0
  50. package/utils/math.d.ts +9 -0
  51. package/utils/reaction-manager.d.ts +25 -0
  52. package/utils/touch.d.ts +14 -1
  53. package/utils/weak-maps.d.ts +4 -2
  54. package/esm2020/board/board.component.interface.mjs +0 -2
  55. package/esm2020/board/board.component.mjs +0 -407
  56. package/esm2020/constants/selection.mjs +0 -2
  57. package/esm2020/core/island/island-base.component.mjs +0 -69
  58. package/esm2020/interfaces/rectangle-client.mjs +0 -40
  59. package/esm2020/plugins/create-board.mjs +0 -101
  60. package/esm2020/plugins/with-hotkey.mjs +0 -57
  61. package/esm2020/plugins/with-moving.mjs +0 -97
  62. package/esm2020/plugins/with-selection.mjs +0 -183
  63. package/esm2020/transforms/index.mjs +0 -12
  64. package/esm2020/transforms/selection.mjs +0 -16
  65. package/esm2020/utils/dom/common.mjs +0 -53
  66. package/esm2020/utils/dom/foreign.mjs +0 -19
  67. package/esm2020/utils/draw/line.mjs +0 -43
  68. package/esm2020/utils/draw/rectangle.mjs +0 -26
  69. package/esm2020/utils/element.mjs +0 -44
  70. package/esm2020/utils/math.mjs +0 -48
  71. package/esm2020/utils/touch.mjs +0 -8
  72. package/esm2020/utils/weak-maps.mjs +0 -22
  73. package/fesm2015/plait-core.mjs +0 -3454
  74. package/fesm2015/plait-core.mjs.map +0 -1
  75. package/fesm2020/plait-core.mjs.map +0 -1
  76. /package/{esm2020 → esm2022}/constants/index.mjs +0 -0
  77. /package/{esm2020 → esm2022}/constants/keycodes.mjs +0 -0
  78. /package/{esm2020 → esm2022}/constants/resize.mjs +0 -0
  79. /package/{esm2020 → esm2022}/core/children/effect.mjs +0 -0
  80. /package/{esm2020 → esm2022}/core/element/context-change.mjs +0 -0
  81. /package/{esm2020 → esm2022}/core/element/context.mjs +0 -0
  82. /package/{esm2020 → esm2022}/interfaces/custom-types.mjs +0 -0
  83. /package/{esm2020 → esm2022}/interfaces/element.mjs +0 -0
  84. /package/{esm2020 → esm2022}/interfaces/history.mjs +0 -0
  85. /package/{esm2020 → esm2022}/interfaces/index.mjs +0 -0
  86. /package/{esm2020 → esm2022}/interfaces/node.mjs +0 -0
  87. /package/{esm2020 → esm2022}/interfaces/operation.mjs +0 -0
  88. /package/{esm2020 → esm2022}/interfaces/path-ref.mjs +0 -0
  89. /package/{esm2020 → esm2022}/interfaces/path.mjs +0 -0
  90. /package/{esm2020 → esm2022}/interfaces/plugin-key.mjs +0 -0
  91. /package/{esm2020 → esm2022}/interfaces/plugin.mjs +0 -0
  92. /package/{esm2020 → esm2022}/interfaces/point.mjs +0 -0
  93. /package/{esm2020 → esm2022}/interfaces/pointer.mjs +0 -0
  94. /package/{esm2020 → esm2022}/interfaces/selection.mjs +0 -0
  95. /package/{esm2020 → esm2022}/interfaces/theme.mjs +0 -0
  96. /package/{esm2020 → esm2022}/interfaces/viewport.mjs +0 -0
  97. /package/{esm2020 → esm2022}/plait-core.mjs +0 -0
  98. /package/{esm2020 → esm2022}/plugins/with-board.mjs +0 -0
  99. /package/{esm2020 → esm2022}/plugins/with-hand.mjs +0 -0
  100. /package/{esm2020 → esm2022}/plugins/with-history.mjs +0 -0
  101. /package/{esm2020 → esm2022}/plugins/with-options.mjs +0 -0
  102. /package/{esm2020 → esm2022}/plugins/with-viewport.mjs +0 -0
  103. /package/{esm2020 → esm2022}/public-api.mjs +0 -0
  104. /package/{esm2020 → esm2022}/testing/core/create-board.mjs +0 -0
  105. /package/{esm2020 → esm2022}/testing/core/fake-weak-map.mjs +0 -0
  106. /package/{esm2020 → esm2022}/testing/core/index.mjs +0 -0
  107. /package/{esm2020 → esm2022}/testing/fake-events/event-objects.mjs +0 -0
  108. /package/{esm2020 → esm2022}/testing/fake-events/index.mjs +0 -0
  109. /package/{esm2020 → esm2022}/testing/index.mjs +0 -0
  110. /package/{esm2020 → esm2022}/testing/test-element.mjs +0 -0
  111. /package/{esm2020 → esm2022}/transforms/board.mjs +0 -0
  112. /package/{esm2020 → esm2022}/transforms/general.mjs +0 -0
  113. /package/{esm2020 → esm2022}/transforms/node.mjs +0 -0
  114. /package/{esm2020 → esm2022}/transforms/theme.mjs +0 -0
  115. /package/{esm2020 → esm2022}/transforms/viewport.mjs +0 -0
  116. /package/{esm2020 → esm2022}/utils/board.mjs +0 -0
  117. /package/{esm2020 → esm2022}/utils/clipboard.mjs +0 -0
  118. /package/{esm2020 → esm2022}/utils/common.mjs +0 -0
  119. /package/{esm2020 → esm2022}/utils/dom/environment.mjs +0 -0
  120. /package/{esm2020 → esm2022}/utils/dom/index.mjs +0 -0
  121. /package/{esm2020 → esm2022}/utils/draw/arrow.mjs +0 -0
  122. /package/{esm2020 → esm2022}/utils/draw/circle.mjs +0 -0
  123. /package/{esm2020 → esm2022}/utils/environment.mjs +0 -0
  124. /package/{esm2020 → esm2022}/utils/helper.mjs +0 -0
  125. /package/{esm2020 → esm2022}/utils/history.mjs +0 -0
  126. /package/{esm2020 → esm2022}/utils/hotkeys.mjs +0 -0
  127. /package/{esm2020 → esm2022}/utils/id-creator.mjs +0 -0
  128. /package/{esm2020 → esm2022}/utils/index.mjs +0 -0
  129. /package/{esm2020 → esm2022}/utils/moving-element.mjs +0 -0
  130. /package/{esm2020 → esm2022}/utils/selected-element.mjs +0 -0
  131. /package/{esm2020 → esm2022}/utils/to-image.mjs +0 -0
  132. /package/{esm2020 → esm2022}/utils/tree.mjs +0 -0
  133. /package/{esm2020 → esm2022}/utils/viewport.mjs +0 -0
@@ -3,8 +3,8 @@ import { Directive, Input, Injectable, Component, ChangeDetectionStrategy, Event
3
3
  import rough from 'roughjs/bin/rough';
4
4
  import { timer, Subject, fromEvent } from 'rxjs';
5
5
  import { takeUntil, filter, tap } from 'rxjs/operators';
6
- import produce, { createDraft, finishDraft, isDraft } from 'immer';
7
6
  import { isKeyHotkey, isHotkey } from 'is-hotkey';
7
+ import produce, { createDraft, finishDraft, isDraft } from 'immer';
8
8
  import * as i1 from '@angular/common';
9
9
  import { CommonModule } from '@angular/common';
10
10
 
@@ -27,9 +27,18 @@ const BOARD_TO_IS_SELECTION_MOVING = new WeakMap();
27
27
  // save no standard selected elements
28
28
  const BOARD_TO_TEMPORARY_ELEMENTS = new WeakMap();
29
29
  const BOARD_TO_MOVING_ELEMENT = new WeakMap();
30
- const IS_PREVENT_TOUCH_MOVE = new WeakMap();
31
30
  const PATH_REFS = new WeakMap();
32
31
 
32
+ var PlaitPointerType;
33
+ (function (PlaitPointerType) {
34
+ PlaitPointerType["hand"] = "hand";
35
+ PlaitPointerType["selection"] = "selection";
36
+ })(PlaitPointerType || (PlaitPointerType = {}));
37
+
38
+ /**
39
+ * Extendable Custom Types Interface
40
+ */
41
+
33
42
  function depthFirstRecursion(node, callback, recursion, isReverse) {
34
43
  if (!recursion || recursion(node)) {
35
44
  let children = [];
@@ -54,1886 +63,2311 @@ const getIsRecursionFunc = (board) => {
54
63
  };
55
64
  };
56
65
 
57
- function getRectangleByElements(board, elements, recursion) {
58
- const boundaryBox = {
59
- left: Number.MAX_VALUE,
60
- top: Number.MAX_VALUE,
61
- right: Number.NEGATIVE_INFINITY,
62
- bottom: Number.NEGATIVE_INFINITY
63
- };
64
- const calcRectangleClient = (node) => {
65
- const nodeRectangle = board.getRectangle(node);
66
- if (nodeRectangle) {
67
- boundaryBox.left = Math.min(boundaryBox.left, nodeRectangle.x);
68
- boundaryBox.top = Math.min(boundaryBox.top, nodeRectangle.y);
69
- boundaryBox.right = Math.max(boundaryBox.right, nodeRectangle.x + nodeRectangle.width);
70
- boundaryBox.bottom = Math.max(boundaryBox.bottom, nodeRectangle.y + nodeRectangle.height);
71
- }
72
- };
73
- elements.forEach(element => {
74
- if (recursion) {
75
- depthFirstRecursion(element, node => calcRectangleClient(node), node => board.isRecursion(node));
66
+ const SELECTION_BORDER_COLOR = '#6698FF';
67
+ const SELECTION_FILL_COLOR = '#6698FF19'; // 主色 0.1 透明度
68
+ const Selection = {
69
+ isCollapsed(selection) {
70
+ if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
71
+ return true;
76
72
  }
77
73
  else {
78
- calcRectangleClient(element);
74
+ return false;
79
75
  }
80
- });
81
- if (boundaryBox.left === Number.MAX_VALUE) {
82
- return {
83
- x: 0,
84
- y: 0,
85
- width: 0,
86
- height: 0
87
- };
88
76
  }
89
- return {
90
- x: boundaryBox.left,
91
- y: boundaryBox.top,
92
- width: boundaryBox.right - boundaryBox.left,
93
- height: boundaryBox.bottom - boundaryBox.top
94
- };
95
- }
96
- function getBoardRectangle(board) {
97
- return getRectangleByElements(board, board.children, true);
98
- }
77
+ };
99
78
 
100
- var ThemeColorMode;
101
- (function (ThemeColorMode) {
102
- ThemeColorMode["default"] = "default";
103
- ThemeColorMode["colorful"] = "colorful";
104
- ThemeColorMode["soft"] = "soft";
105
- ThemeColorMode["retro"] = "retro";
106
- ThemeColorMode["dark"] = "dark";
107
- ThemeColorMode["starry"] = "starry";
108
- })(ThemeColorMode || (ThemeColorMode = {}));
109
- const DefaultThemeColor = {
110
- mode: ThemeColorMode.default,
111
- boardBackground: '#ffffff',
112
- textColor: '#333333'
79
+ const getHitElements = (board, selection, match = () => true) => {
80
+ const realSelection = selection || board.selection;
81
+ const selectedElements = [];
82
+ const isCollapsed = realSelection && realSelection.ranges.length === 1 && Selection.isCollapsed(realSelection.ranges[0]);
83
+ depthFirstRecursion(board, node => {
84
+ if (selectedElements.length > 0 && isCollapsed) {
85
+ return;
86
+ }
87
+ if (!PlaitBoard.isBoard(node) &&
88
+ match(node) &&
89
+ realSelection &&
90
+ realSelection.ranges.some(range => {
91
+ return board.isHitSelection(node, range);
92
+ })) {
93
+ selectedElements.push(node);
94
+ }
95
+ }, getIsRecursionFunc(board), true);
96
+ return selectedElements;
113
97
  };
114
- const ColorfulThemeColor = {
115
- mode: ThemeColorMode.colorful,
116
- boardBackground: '#ffffff',
117
- textColor: '#333333'
98
+ const getHitElementOfRoot = (board, rootElements, range) => {
99
+ const newRootElements = [...rootElements].reverse();
100
+ return newRootElements.find(item => {
101
+ return board.isHitSelection(item, range);
102
+ });
118
103
  };
119
- const SoftThemeColor = {
120
- mode: ThemeColorMode.soft,
121
- boardBackground: '#f5f5f5',
122
- textColor: '#333333'
104
+ const isHitElements = (board, elements, ranges) => {
105
+ let isIntersectionElements = false;
106
+ if (elements.length) {
107
+ elements.map(item => {
108
+ if (!isIntersectionElements) {
109
+ isIntersectionElements = ranges.some(range => {
110
+ return board.isHitSelection(item, range);
111
+ });
112
+ }
113
+ });
114
+ }
115
+ return isIntersectionElements;
123
116
  };
124
- const RetroThemeColor = {
125
- mode: ThemeColorMode.retro,
126
- boardBackground: '#f9f8ed',
127
- textColor: '#333333'
117
+ const cacheSelectedElements = (board, selectedElements) => {
118
+ BOARD_TO_SELECTED_ELEMENT.set(board, selectedElements);
128
119
  };
129
- const DarkThemeColor = {
130
- mode: ThemeColorMode.dark,
131
- boardBackground: '#141414',
132
- textColor: '#FFFFFF'
120
+ const getSelectedElements = (board) => {
121
+ return BOARD_TO_SELECTED_ELEMENT.get(board) || [];
133
122
  };
134
- const StarryThemeColor = {
135
- mode: ThemeColorMode.starry,
136
- boardBackground: '#0d2537',
137
- textColor: '#FFFFFF'
123
+ const addSelectedElement = (board, element) => {
124
+ const selectedElements = getSelectedElements(board);
125
+ cacheSelectedElements(board, [...selectedElements, element]);
126
+ };
127
+ const removeSelectedElement = (board, element) => {
128
+ const selectedElements = getSelectedElements(board);
129
+ const newSelectedElements = selectedElements.filter(value => value !== element);
130
+ cacheSelectedElements(board, newSelectedElements);
131
+ };
132
+ const clearSelectedElement = (board) => {
133
+ cacheSelectedElements(board, []);
134
+ };
135
+ const isSelectedElement = (board, element) => {
136
+ const selectedElements = getSelectedElements(board);
137
+ return !!selectedElements.find(value => value === element);
138
138
  };
139
- const ThemeColors = [
140
- DefaultThemeColor,
141
- ColorfulThemeColor,
142
- SoftThemeColor,
143
- RetroThemeColor,
144
- DarkThemeColor,
145
- StarryThemeColor
146
- ];
147
-
148
- // https://stackoverflow.com/a/6853926/232122
149
- function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
150
- const A = x - x1;
151
- const B = y - y1;
152
- const C = x2 - x1;
153
- const D = y2 - y1;
154
- const dot = A * C + B * D;
155
- const lenSquare = C * C + D * D;
156
- let param = -1;
157
- if (lenSquare !== 0) {
158
- // in case of 0 length line
159
- param = dot / lenSquare;
160
- }
161
- let xx, yy;
162
- if (param < 0) {
163
- xx = x1;
164
- yy = y1;
165
- }
166
- else if (param > 1) {
167
- xx = x2;
168
- yy = y2;
169
- }
170
- else {
171
- xx = x1 + param * C;
172
- yy = y1 + param * D;
173
- }
174
- const dx = x - xx;
175
- const dy = y - yy;
176
- return Math.hypot(dx, dy);
177
- }
178
- function rotate(x1, y1, x2, y2, angle) {
179
- // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
180
- // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
181
- // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
182
- return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
183
- }
184
- function distanceBetweenPointAndPoint(x1, y1, x2, y2) {
185
- const dx = x1 - x2;
186
- const dy = y1 - y2;
187
- return Math.hypot(dx, dy);
188
- }
189
- // https://stackoverflow.com/questions/5254838/calculating-distance-between-a-point-and-a-rectangular-box-nearest-point
190
- function distanceBetweenPointAndRectangle(x, y, rect) {
191
- var dx = Math.max(rect.x - x, 0, x - (rect.x + rect.width));
192
- var dy = Math.max(rect.y - y, 0, y - (rect.y + rect.height));
193
- return Math.sqrt(dx * dx + dy * dy);
194
- }
195
139
 
196
- const PlaitBoard = {
197
- isBoard(value) {
198
- const cachedIsBoard = IS_BOARD_CACHE.get(value);
199
- if (cachedIsBoard !== undefined) {
200
- return cachedIsBoard;
201
- }
202
- const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
203
- IS_BOARD_CACHE.set(value, isBoard);
204
- return isBoard;
205
- },
206
- findPath(board, node) {
207
- const path = [];
208
- let child = node;
209
- while (true) {
210
- const parent = NODE_TO_PARENT.get(child);
211
- if (parent == null) {
212
- if (PlaitBoard.isBoard(child)) {
213
- return path;
214
- }
215
- else {
216
- break;
217
- }
218
- }
219
- const i = NODE_TO_INDEX.get(child);
220
- if (i == null) {
221
- break;
222
- }
223
- path.unshift(i);
224
- child = parent;
225
- }
226
- throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
227
- },
228
- getHost(board) {
229
- return BOARD_TO_HOST.get(board);
230
- },
231
- getElementHost(board) {
232
- return BOARD_TO_ELEMENT_HOST.get(board)?.host;
233
- },
234
- getElementUpperHost(board) {
235
- return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
236
- },
237
- getElementActiveHost(board) {
238
- return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
239
- },
240
- getRoughSVG(board) {
241
- return BOARD_TO_ROUGH_SVG.get(board);
242
- },
243
- getComponent(board) {
244
- return BOARD_TO_COMPONENT.get(board);
245
- },
246
- getBoardContainer(board) {
247
- return PlaitBoard.getComponent(board).nativeElement;
248
- },
249
- getRectangle(board) {
250
- return getRectangleByElements(board, board.children, true);
251
- },
252
- getViewportContainer(board) {
253
- return PlaitBoard.getHost(board).parentElement;
254
- },
255
- isFocus(board) {
256
- return !!board.selection;
257
- },
258
- isReadonly(board) {
259
- return board.options.readonly;
260
- },
261
- hasBeenTextEditing(board) {
262
- return !!IS_TEXT_EDITABLE.get(board);
263
- },
264
- getPointer(board) {
265
- return board.pointer;
266
- },
267
- isPointer(board, pointer) {
268
- return board.pointer === pointer;
269
- },
270
- isInPointer(board, pointers) {
271
- const point = board.pointer;
272
- return pointers.includes(point);
273
- },
274
- getMovingPointInBoard(board) {
275
- return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
276
- },
277
- isMovingPointInBoard(board) {
278
- const point = BOARD_TO_MOVING_POINT.get(board);
279
- const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
280
- if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
281
- return true;
282
- }
283
- return false;
284
- },
285
- getThemeColors(board) {
286
- return (board.options.themeColors || ThemeColors);
287
- }
288
- };
140
+ /**
141
+ * @license
142
+ * Copyright Google LLC All Rights Reserved.
143
+ *
144
+ * Use of this source code is governed by an MIT-style license that can be
145
+ * found in the LICENSE file at https://angular.io/license
146
+ */
147
+ const MAC_ENTER = 3;
148
+ const BACKSPACE = 8;
149
+ const TAB = 9;
150
+ const NUM_CENTER = 12;
151
+ const ENTER = 13;
152
+ const SHIFT = 16;
153
+ const CONTROL = 17;
154
+ const ALT = 18;
155
+ const PAUSE = 19;
156
+ const CAPS_LOCK = 20;
157
+ const ESCAPE = 27;
158
+ const SPACE = 32;
159
+ const PAGE_UP = 33;
160
+ const PAGE_DOWN = 34;
161
+ const END = 35;
162
+ const HOME = 36;
163
+ const LEFT_ARROW = 37;
164
+ const UP_ARROW = 38;
165
+ const RIGHT_ARROW = 39;
166
+ const DOWN_ARROW = 40;
167
+ const PLUS_SIGN = 43;
168
+ const PRINT_SCREEN = 44;
169
+ const INSERT = 45;
170
+ const DELETE = 46;
171
+ const ZERO = 48;
172
+ const ONE = 49;
173
+ const TWO = 50;
174
+ const THREE = 51;
175
+ const FOUR = 52;
176
+ const FIVE = 53;
177
+ const SIX = 54;
178
+ const SEVEN = 55;
179
+ const EIGHT = 56;
180
+ const NINE = 57;
181
+ const FF_SEMICOLON = 59; // Firefox (Gecko) fires this for semicolon instead of 186
182
+ const FF_EQUALS = 61; // Firefox (Gecko) fires this for equals instead of 187
183
+ const QUESTION_MARK = 63;
184
+ const AT_SIGN = 64;
185
+ const A = 65;
186
+ const B = 66;
187
+ const C = 67;
188
+ const D = 68;
189
+ const E = 69;
190
+ const F = 70;
191
+ const G = 71;
192
+ const H = 72;
193
+ const I = 73;
194
+ const J = 74;
195
+ const K = 75;
196
+ const L = 76;
197
+ const M = 77;
198
+ const N = 78;
199
+ const O = 79;
200
+ const P = 80;
201
+ const Q = 81;
202
+ const R = 82;
203
+ const S = 83;
204
+ const T = 84;
205
+ const U = 85;
206
+ const V = 86;
207
+ const W = 87;
208
+ const X = 88;
209
+ const Y = 89;
210
+ const Z = 90;
211
+ const META = 91; // WIN_KEY_LEFT
212
+ const MAC_WK_CMD_LEFT = 91;
213
+ const MAC_WK_CMD_RIGHT = 93;
214
+ const CONTEXT_MENU = 93;
215
+ const NUMPAD_ZERO = 96;
216
+ const NUMPAD_ONE = 97;
217
+ const NUMPAD_TWO = 98;
218
+ const NUMPAD_THREE = 99;
219
+ const NUMPAD_FOUR = 100;
220
+ const NUMPAD_FIVE = 101;
221
+ const NUMPAD_SIX = 102;
222
+ const NUMPAD_SEVEN = 103;
223
+ const NUMPAD_EIGHT = 104;
224
+ const NUMPAD_NINE = 105;
225
+ const NUMPAD_MULTIPLY = 106;
226
+ const NUMPAD_PLUS = 107;
227
+ const NUMPAD_MINUS = 109;
228
+ const NUMPAD_PERIOD = 110;
229
+ const NUMPAD_DIVIDE = 111;
230
+ const F1 = 112;
231
+ const F2 = 113;
232
+ const F3 = 114;
233
+ const F4 = 115;
234
+ const F5 = 116;
235
+ const F6 = 117;
236
+ const F7 = 118;
237
+ const F8 = 119;
238
+ const F9 = 120;
239
+ const F10 = 121;
240
+ const F11 = 122;
241
+ const F12 = 123;
242
+ const NUM_LOCK = 144;
243
+ const SCROLL_LOCK = 145;
244
+ const FIRST_MEDIA = 166;
245
+ const FF_MINUS = 173;
246
+ const MUTE = 173; // Firefox (Gecko) fires 181 for MUTE
247
+ const VOLUME_DOWN = 174; // Firefox (Gecko) fires 182 for VOLUME_DOWN
248
+ const VOLUME_UP = 175; // Firefox (Gecko) fires 183 for VOLUME_UP
249
+ const FF_MUTE = 181;
250
+ const FF_VOLUME_DOWN = 182;
251
+ const LAST_MEDIA = 183;
252
+ const FF_VOLUME_UP = 183;
253
+ const SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON
254
+ const EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS
255
+ const COMMA = 188;
256
+ const DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS
257
+ const PERIOD = 190;
258
+ const SLASH = 191;
259
+ const APOSTROPHE = 192;
260
+ const TILDE = 192;
261
+ const OPEN_SQUARE_BRACKET = 219;
262
+ const BACKSLASH = 220;
263
+ const CLOSE_SQUARE_BRACKET = 221;
264
+ const SINGLE_QUOTE = 222;
265
+ const MAC_META = 224;
289
266
 
290
- var PlaitPointerType;
291
- (function (PlaitPointerType) {
292
- PlaitPointerType["hand"] = "hand";
293
- PlaitPointerType["selection"] = "selection";
294
- })(PlaitPointerType || (PlaitPointerType = {}));
267
+ var ResizeCursorClass;
268
+ (function (ResizeCursorClass) {
269
+ ResizeCursorClass["ew-resize"] = "ew-resize";
270
+ })(ResizeCursorClass || (ResizeCursorClass = {}));
295
271
 
296
- function isNullOrUndefined(value) {
297
- return value === null || value === undefined;
272
+ const ATTACHED_ELEMENT_CLASS_NAME = 'plait-board-attached';
273
+ const ACTIVE_STROKE_WIDTH = 1;
274
+ const SELECTION_RECTANGLE_CLASS_NAME = 'selection-rectangle';
275
+
276
+ const CLIP_BOARD_FORMAT_KEY = 'x-plait-fragment';
277
+ const HOST_CLASS_NAME = 'plait-board-container';
278
+ const SCROLL_BAR_WIDTH = 20;
279
+ const MAX_RADIUS = 16;
280
+ const POINTER_BUTTON = {
281
+ MAIN: 0,
282
+ WHEEL: 1,
283
+ SECONDARY: 2,
284
+ TOUCH: -1
285
+ };
286
+ const PRESS_AND_MOVE_BUFFER = 5;
287
+
288
+ const NS = 'http://www.w3.org/2000/svg';
289
+ function toPoint(x, y, container) {
290
+ const rect = container.getBoundingClientRect();
291
+ return [x - rect.x, y - rect.y];
292
+ }
293
+ function createG() {
294
+ const newG = document.createElementNS(NS, 'g');
295
+ return newG;
296
+ }
297
+ function createPath() {
298
+ const newG = document.createElementNS(NS, 'path');
299
+ return newG;
300
+ }
301
+ function createRect(rectangle, options) {
302
+ const rect = document.createElementNS(NS, 'rect');
303
+ rect.setAttribute('x', `${rectangle.x}`);
304
+ rect.setAttribute('y', `${rectangle.y}`);
305
+ rect.setAttribute('width', `${rectangle.width}`);
306
+ rect.setAttribute('height', `${rectangle.height}`);
307
+ for (let key in options) {
308
+ const optionKey = key;
309
+ rect.setAttribute(key, `${options[optionKey]}`);
310
+ }
311
+ return rect;
312
+ }
313
+ const setStrokeLinecap = (g, value) => {
314
+ g.setAttribute('stroke-linecap', value);
315
+ };
316
+ const setPathStrokeLinecap = (g, value) => {
317
+ g.querySelectorAll('path').forEach(path => {
318
+ path.setAttribute('stroke-linecap', value);
319
+ });
320
+ };
321
+ function createMask() {
322
+ return document.createElementNS(NS, 'mask');
323
+ }
324
+ function createSVG() {
325
+ const svg = document.createElementNS(NS, 'svg');
326
+ return svg;
327
+ }
328
+ function createText(x, y, fill, textContent) {
329
+ var text = document.createElementNS(NS, 'text');
330
+ text.setAttribute('x', `${x}`);
331
+ text.setAttribute('y', `${y}`);
332
+ text.setAttribute('fill', fill);
333
+ text.textContent = textContent;
334
+ return text;
298
335
  }
299
336
  /**
300
- * 规范 point
301
- * @param point
302
- * @returns point
337
+ * Check if a DOM node is an element node.
303
338
  */
304
- function normalizePoint(point) {
305
- return Array.isArray(point)
306
- ? {
307
- x: point[0],
308
- y: point[1]
339
+ const isDOMElement = (value) => {
340
+ return isDOMNode(value) && value.nodeType === 1;
341
+ };
342
+ /**
343
+ * Check if a value is a DOM node.
344
+ */
345
+ const isDOMNode = (value) => {
346
+ return value instanceof window.Node;
347
+ };
348
+ const hasInputOrTextareaTarget = (target) => {
349
+ if (isDOMElement(target)) {
350
+ if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
351
+ return true;
309
352
  }
310
- : point;
311
- }
312
-
313
- const Viewport = {
314
- isViewport: (value) => {
315
- return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
316
- },
353
+ }
354
+ return false;
355
+ };
356
+ const isSecondaryPointer = (event) => {
357
+ return event.button === POINTER_BUTTON.SECONDARY;
358
+ };
359
+ const isMainPointer = (event) => {
360
+ return event.button === POINTER_BUTTON.MAIN;
317
361
  };
318
362
 
319
- const Path = {
320
- /**
321
- * Get a list of ancestor paths for a given path.
322
- *
323
- * The paths are sorted from shallowest to deepest ancestor. However, if the
324
- * `reverse: true` option is passed, they are reversed.
325
- */
326
- ancestors(path, options = {}) {
327
- const { reverse = false } = options;
328
- let paths = Path.levels(path, options);
329
- if (reverse) {
330
- paths = paths.slice(1);
363
+ function hasBeforeContextChange(value) {
364
+ if (value.beforeContextChange) {
365
+ return true;
366
+ }
367
+ return false;
368
+ }
369
+ function hasOnContextChanged(value) {
370
+ if (value.onContextChanged) {
371
+ return true;
372
+ }
373
+ return false;
374
+ }
375
+
376
+ class PlaitPluginElementComponent {
377
+ set context(value) {
378
+ if (hasBeforeContextChange(this)) {
379
+ this.beforeContextChange(value);
331
380
  }
332
- else {
333
- paths = paths.slice(0, -1);
381
+ const previousContext = this._context;
382
+ this._context = value;
383
+ if (this.element) {
384
+ ELEMENT_TO_COMPONENT.set(this.element, this);
334
385
  }
335
- return paths;
336
- },
337
- /**
338
- * Get a list of paths at every level down to a path. Note: this is the same
339
- * as `Path.ancestors`, but including the path itself.
340
- *
341
- * The paths are sorted from shallowest to deepest. However, if the `reverse:
342
- * true` option is passed, they are reversed.
343
- */
344
- levels(path, options = {}) {
345
- const { reverse = false } = options;
346
- const list = [];
347
- for (let i = 0; i <= path.length; i++) {
348
- list.push(path.slice(0, i));
386
+ if (this.initialized) {
387
+ this.cdr.markForCheck();
388
+ if (hasOnContextChanged(this)) {
389
+ this.onContextChanged(value, previousContext);
390
+ }
349
391
  }
350
- if (reverse) {
351
- list.reverse();
392
+ else {
393
+ if (PlaitElement.isRootElement(this.element) && this.element.children) {
394
+ this.g = createG();
395
+ this.rootG = createG();
396
+ this.rootG.append(this.g);
397
+ }
398
+ else {
399
+ this.g = createG();
400
+ }
352
401
  }
353
- return list;
354
- },
355
- parent(path) {
356
- if (path.length === 0) {
357
- throw new Error(`Cannot get the parent path of the root path [${path}].`);
402
+ }
403
+ get context() {
404
+ return this._context;
405
+ }
406
+ get element() {
407
+ return this.context && this.context.element;
408
+ }
409
+ get board() {
410
+ return this.context && this.context.board;
411
+ }
412
+ get selected() {
413
+ return this.context && this.context.selected;
414
+ }
415
+ get effect() {
416
+ return this.context && this.context.effect;
417
+ }
418
+ constructor(cdr) {
419
+ this.cdr = cdr;
420
+ this.initialized = false;
421
+ }
422
+ ngOnInit() {
423
+ if (this.element.type) {
424
+ (this.rootG || this.g).setAttribute(`plait-${this.element.type}`, 'true');
358
425
  }
359
- return path.slice(0, -1);
360
- },
361
- next(path) {
362
- if (path.length === 0) {
363
- throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
426
+ this.initialized = true;
427
+ }
428
+ ngOnDestroy() {
429
+ if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
430
+ ELEMENT_TO_COMPONENT.delete(this.element);
364
431
  }
365
- const last = path[path.length - 1];
366
- return path.slice(0, -1).concat(last + 1);
367
- },
368
- hasPrevious(path) {
369
- return path[path.length - 1] > 0;
370
- },
371
- previous(path) {
372
- if (path.length === 0) {
373
- throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
432
+ removeSelectedElement(this.board, this.element);
433
+ (this.rootG || this.g).remove();
434
+ }
435
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitPluginElementComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
436
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.3", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
437
+ }
438
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitPluginElementComponent, decorators: [{
439
+ type: Directive
440
+ }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { context: [{
441
+ type: Input
442
+ }] } });
443
+ const ELEMENT_TO_COMPONENT = new WeakMap();
444
+
445
+ const RectangleClient = {
446
+ isHit: (origin, target) => {
447
+ const minX = origin.x < target.x ? origin.x : target.x;
448
+ const maxX = origin.x + origin.width > target.x + target.width ? origin.x + origin.width : target.x + target.width;
449
+ const minY = origin.y < target.y ? origin.y : target.y;
450
+ const maxY = origin.y + origin.height > target.y + target.height ? origin.y + origin.height : target.y + target.height;
451
+ // float calculate error( eg: 1.4210854715202004e-14 > 0)
452
+ if (Math.floor(maxX - minX - origin.width - target.width) <= 0 && Math.floor(maxY - minY - origin.height - target.height) <= 0) {
453
+ return true;
454
+ }
455
+ else {
456
+ return false;
374
457
  }
375
- const last = path[path.length - 1];
376
- return path.slice(0, -1).concat(last - 1);
377
458
  },
378
- /**
379
- * Check if a path is an ancestor of another.
380
- */
381
- isAncestor(path, another) {
382
- return path.length < another.length && Path.compare(path, another) === 0;
459
+ toRectangleClient: (points) => {
460
+ const xArray = points.map(ele => ele[0]);
461
+ const yArray = points.map(ele => ele[1]);
462
+ const xMin = Math.min(...xArray);
463
+ const xMax = Math.max(...xArray);
464
+ const yMin = Math.min(...yArray);
465
+ const yMax = Math.max(...yArray);
466
+ const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
467
+ return rect;
383
468
  },
384
- /**
385
- * Compare a path to another, returning an integer indicating whether the path
386
- * was before, at, or after the other.
387
- *
388
- * Note: Two paths of unequal length can still receive a `0` result if one is
389
- * directly above or below the other. If you want exact matching, use
390
- * [[Path.equals]] instead.
391
- */
392
- compare(path, another) {
393
- const min = Math.min(path.length, another.length);
394
- for (let i = 0; i < min; i++) {
395
- if (path[i] < another[i])
396
- return -1;
397
- if (path[i] > another[i])
398
- return 1;
399
- }
400
- return 0;
469
+ getOutlineRectangle: (rectangle, offset) => {
470
+ return {
471
+ x: rectangle.x + offset,
472
+ y: rectangle.y + offset,
473
+ width: rectangle.width - offset * 2,
474
+ height: rectangle.height - offset * 2
475
+ };
401
476
  },
402
- /**
403
- * Check if a path is exactly equal to another.
404
- */
405
- equals(path, another) {
406
- return path.length === another.length && path.every((n, i) => n === another[i]);
477
+ inflate: (rectangle, delta) => {
478
+ const half = delta / 2;
479
+ return {
480
+ x: rectangle.x - half,
481
+ y: rectangle.y - half,
482
+ width: rectangle.width + half * 2,
483
+ height: rectangle.height + half * 2
484
+ };
407
485
  },
408
- /**
409
- * Check if a path ends before one of the indexes in another.
410
- */
411
- endsBefore(path, another) {
412
- const i = path.length - 1;
413
- const as = path.slice(0, i);
414
- const bs = another.slice(0, i);
415
- const av = path[i];
416
- const bv = another[i];
417
- return Path.equals(as, bs) && av < bv;
486
+ isEqual: (rectangle, otherRectangle) => {
487
+ return (rectangle.x === otherRectangle.x &&
488
+ rectangle.y === otherRectangle.y &&
489
+ rectangle.width === otherRectangle.width &&
490
+ rectangle.height === otherRectangle.height);
418
491
  },
419
- /**
420
- * Check if a path is a sibling of another.
421
- */
422
- isSibling(path, another) {
423
- if (path.length !== another.length) {
424
- return false;
425
- }
426
- const as = path.slice(0, -1);
427
- const bs = another.slice(0, -1);
428
- const al = path[path.length - 1];
429
- const bl = another[another.length - 1];
430
- return al !== bl && Path.equals(as, bs);
492
+ getCornerPoints: (rectangle) => {
493
+ return [
494
+ [rectangle.x, rectangle.y],
495
+ [rectangle.x + rectangle.width, rectangle.y],
496
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height],
497
+ [rectangle.x, rectangle.y + rectangle.height]
498
+ ];
431
499
  },
432
- transform(path, operation) {
433
- return produce(path, p => {
434
- // PERF: Exit early if the operation is guaranteed not to have an effect.
435
- if (!path || path?.length === 0) {
436
- return;
437
- }
438
- if (p === null) {
439
- return null;
440
- }
441
- switch (operation.type) {
442
- case 'insert_node': {
443
- const { path: op } = operation;
444
- if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
445
- p[op.length - 1] += 1;
446
- }
447
- break;
448
- }
449
- case 'remove_node': {
450
- const { path: op } = operation;
451
- if (Path.equals(op, p) || Path.isAncestor(op, p)) {
452
- return null;
453
- }
454
- else if (Path.endsBefore(op, p)) {
455
- p[op.length - 1] -= 1;
456
- }
457
- break;
458
- }
459
- case 'move_node': {
460
- const { path: op, newPath: onp } = operation;
461
- // If the old and new path are the same, it's a no-op.
462
- if (Path.equals(op, onp)) {
463
- return;
464
- }
465
- if (Path.isAncestor(op, p) || Path.equals(op, p)) {
466
- const copy = onp.slice();
467
- // op.length <= onp.length is different for slate
468
- // resolve drag from [0, 0] to [0, 3] issue
469
- if (Path.endsBefore(op, onp) && op.length <= onp.length) {
470
- copy[op.length - 1] -= 1;
471
- }
472
- return copy.concat(p.slice(op.length));
473
- }
474
- else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
475
- if (Path.endsBefore(op, p)) {
476
- p[op.length - 1] -= 1;
477
- }
478
- else {
479
- p[op.length - 1] += 1;
480
- }
481
- }
482
- else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
483
- if (Path.endsBefore(op, p)) {
484
- p[op.length - 1] -= 1;
485
- }
486
- p[onp.length - 1] += 1;
487
- }
488
- else if (Path.endsBefore(op, p)) {
489
- if (Path.equals(onp, p)) {
490
- p[onp.length - 1] += 1;
491
- }
492
- p[op.length - 1] -= 1;
493
- }
494
- break;
495
- }
496
- }
497
- return p;
498
- });
500
+ getEdgeCenterPoints: (rectangle) => {
501
+ return [
502
+ [rectangle.x + rectangle.width / 2, rectangle.y],
503
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
504
+ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
505
+ [rectangle.x, rectangle.y + rectangle.height / 2]
506
+ ];
499
507
  }
500
508
  };
501
509
 
502
- const PlaitNode = {
503
- parent: (board, path) => {
504
- const parentPath = Path.parent(path);
505
- const p = PlaitNode.get(board, parentPath);
506
- return p;
507
- },
508
- /**
509
- * Return a generator of all the ancestor nodes above a specific path.
510
- *
511
- * By default the order is top-down, from highest to lowest ancestor in
512
- * the tree, but you can pass the `reverse: true` option to go bottom-up.
513
- */
514
- *parents(root, path, options = {}) {
515
- for (const p of Path.ancestors(path, options)) {
516
- const n = PlaitNode.get(root, p);
517
- yield n;
510
+ // https://stackoverflow.com/a/6853926/232122
511
+ function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
512
+ const A = x - x1;
513
+ const B = y - y1;
514
+ const C = x2 - x1;
515
+ const D = y2 - y1;
516
+ const dot = A * C + B * D;
517
+ const lenSquare = C * C + D * D;
518
+ let param = -1;
519
+ if (lenSquare !== 0) {
520
+ // in case of 0 length line
521
+ param = dot / lenSquare;
522
+ }
523
+ let xx, yy;
524
+ if (param < 0) {
525
+ xx = x1;
526
+ yy = y1;
527
+ }
528
+ else if (param > 1) {
529
+ xx = x2;
530
+ yy = y2;
531
+ }
532
+ else {
533
+ xx = x1 + param * C;
534
+ yy = y1 + param * D;
535
+ }
536
+ const dx = x - xx;
537
+ const dy = y - yy;
538
+ return Math.hypot(dx, dy);
539
+ }
540
+ function getNearestPointBetweenPointAndSegment(point, linePoints) {
541
+ const x = point[0], y = point[1], x1 = linePoints[0][0], y1 = linePoints[0][1], x2 = linePoints[1][0], y2 = linePoints[1][1];
542
+ const A = x - x1;
543
+ const B = y - y1;
544
+ const C = x2 - x1;
545
+ const D = y2 - y1;
546
+ const dot = A * C + B * D;
547
+ const lenSquare = C * C + D * D;
548
+ let param = -1;
549
+ if (lenSquare !== 0) {
550
+ // in case of 0 length line
551
+ param = dot / lenSquare;
552
+ }
553
+ let xx, yy;
554
+ if (param < 0) {
555
+ xx = x1;
556
+ yy = y1;
557
+ }
558
+ else if (param > 1) {
559
+ xx = x2;
560
+ yy = y2;
561
+ }
562
+ else {
563
+ xx = x1 + param * C;
564
+ yy = y1 + param * D;
565
+ }
566
+ return [xx, yy];
567
+ }
568
+ function distanceBetweenPointAndSegments(points, point) {
569
+ const len = points.length;
570
+ let distance = Infinity;
571
+ for (let i = 0; i < len - 1; i++) {
572
+ const p = points[i];
573
+ const p2 = points[i + 1];
574
+ const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
575
+ if (currentDistance < distance) {
576
+ distance = currentDistance;
518
577
  }
519
- },
520
- get(root, path) {
521
- let node = root;
522
- for (let i = 0; i < path.length; i++) {
523
- const p = path[i];
524
- if (!node || !node.children || !node.children[p]) {
525
- throw new Error(`Cannot find a descendant at path [${path}]`);
526
- }
527
- node = node.children[p];
578
+ }
579
+ return distance;
580
+ }
581
+ function getNearestPointBetweenPointAndSegments(point, points) {
582
+ const len = points.length;
583
+ let distance = Infinity;
584
+ let result = point;
585
+ for (let i = 0; i < len; i++) {
586
+ const p = points[i];
587
+ const p2 = i === len - 1 ? points[0] : points[i + 1];
588
+ const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
589
+ if (currentDistance < distance) {
590
+ distance = currentDistance;
591
+ result = getNearestPointBetweenPointAndSegment(point, [p, p2]);
528
592
  }
529
- return node;
530
- },
531
- last(board, path) {
532
- let n = PlaitNode.get(board, path);
533
- while (n && n.children && n.children.length > 0) {
534
- const i = n.children.length - 1;
535
- n = n.children[i];
593
+ }
594
+ return result;
595
+ }
596
+ function rotate(x1, y1, x2, y2, angle) {
597
+ // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
598
+ // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
599
+ // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
600
+ return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
601
+ }
602
+ function distanceBetweenPointAndPoint(x1, y1, x2, y2) {
603
+ const dx = x1 - x2;
604
+ const dy = y1 - y2;
605
+ return Math.hypot(dx, dy);
606
+ }
607
+ // https://stackoverflow.com/questions/5254838/calculating-distance-between-a-point-and-a-rectangular-box-nearest-point
608
+ function distanceBetweenPointAndRectangle(x, y, rect) {
609
+ var dx = Math.max(rect.x - x, 0, x - (rect.x + rect.width));
610
+ var dy = Math.max(rect.y - y, 0, y - (rect.y + rect.height));
611
+ return Math.sqrt(dx * dx + dy * dy);
612
+ }
613
+ const isLineHitLine = (a, b, c, d) => {
614
+ const crossProduct = (v1, v2) => v1[0] * v2[1] - v1[1] * v2[0];
615
+ const ab = [b[0] - a[0], b[1] - a[1]];
616
+ const ac = [c[0] - a[0], c[1] - a[1]];
617
+ const ad = [d[0] - a[0], d[1] - a[1]];
618
+ const ca = [a[0] - c[0], a[1] - c[1]];
619
+ const cb = [b[0] - c[0], b[1] - c[1]];
620
+ const cd = [d[0] - c[0], d[1] - c[1]];
621
+ return crossProduct(ab, ac) * crossProduct(ab, ad) <= 0 && crossProduct(cd, ca) * crossProduct(cd, cb) <= 0;
622
+ };
623
+ const isPolylineHitRectangle = (points, rectangle) => {
624
+ const rectanglePoints = RectangleClient.getCornerPoints(rectangle);
625
+ for (let i = 1; i < points.length; i++) {
626
+ const isIntersect = isLineHitLine(points[i], points[i - 1], rectanglePoints[0], rectanglePoints[1]) ||
627
+ isLineHitLine(points[i], points[i - 1], rectanglePoints[1], rectanglePoints[2]) ||
628
+ isLineHitLine(points[i], points[i - 1], rectanglePoints[2], rectanglePoints[3]) ||
629
+ isLineHitLine(points[i], points[i - 1], rectanglePoints[3], rectanglePoints[0]);
630
+ if (isIntersect) {
631
+ return true;
536
632
  }
537
- return n;
538
633
  }
634
+ return false;
635
+ };
636
+ //https://stackoverflow.com/questions/22521982/check-if-point-is-inside-a-polygon
637
+ const isPointInPolygon = (point, points) => {
638
+ // ray-casting algorithm based on
639
+ // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
640
+ const x = point[0], y = point[1];
641
+ let inside = false;
642
+ for (var i = 0, j = points.length - 1; i < points.length; j = i++) {
643
+ let xi = points[i][0], yi = points[i][1];
644
+ let xj = points[j][0], yj = points[j][1];
645
+ let intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
646
+ if (intersect)
647
+ inside = !inside;
648
+ }
649
+ return inside;
650
+ };
651
+ const isPointInEllipse = (point, center, rx, ry, rotation = 0) => {
652
+ const cosAngle = Math.cos(rotation);
653
+ const sinAngle = Math.sin(rotation);
654
+ const x1 = (point[0] - center[0]) * cosAngle + (point[1] - center[1]) * sinAngle;
655
+ const y1 = (point[1] - center[1]) * cosAngle - (point[0] - center[0]) * sinAngle;
656
+ return (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry) <= 1;
657
+ };
658
+ const isPointInRoundRectangle = (point, rectangle, radius) => {
659
+ const { x: rectX, y: rectY, width, height } = rectangle;
660
+ const isInRectangle = point[0] >= rectX && point[0] <= rectX + width && point[1] >= rectY && point[1] <= rectY + height;
661
+ const handleLeftTop = point[0] >= rectX &&
662
+ point[0] <= rectX + radius &&
663
+ point[1] >= rectY &&
664
+ point[1] <= rectY + radius &&
665
+ Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + radius)) > radius;
666
+ const handleLeftBottom = point[0] >= rectX &&
667
+ point[0] <= rectX + radius &&
668
+ point[1] >= rectY + height &&
669
+ point[1] <= rectY + height - radius &&
670
+ Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + height - radius)) > radius;
671
+ const handleRightTop = point[0] >= rectX + width - radius &&
672
+ point[0] <= rectX + width &&
673
+ point[1] >= rectY &&
674
+ point[1] <= rectY + radius &&
675
+ Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + radius)) > radius;
676
+ const handleRightBottom = point[0] >= rectX + width - radius &&
677
+ point[0] <= rectX + width &&
678
+ point[1] >= rectY + height - radius &&
679
+ point[1] <= rectY + height &&
680
+ Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + height - radius)) > radius;
681
+ const isInCorner = handleLeftTop || handleLeftBottom || handleRightTop || handleRightBottom;
682
+ return isInRectangle && !isInCorner;
539
683
  };
540
684
 
541
- const applyToDraft = (board, selection, viewport, theme, op) => {
542
- switch (op.type) {
543
- case 'insert_node': {
544
- const { path, node } = op;
545
- const parent = PlaitNode.parent(board, path);
546
- const index = path[path.length - 1];
547
- if (!parent.children || index > parent.children.length) {
548
- throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
549
- }
550
- parent.children.splice(index, 0, node);
551
- break;
552
- }
553
- case 'remove_node': {
554
- const { path } = op;
555
- const parent = PlaitNode.parent(board, path);
556
- const index = path[path.length - 1];
557
- if (!parent.children || index > parent.children.length) {
558
- throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
559
- }
560
- parent.children.splice(index, 1);
561
- break;
562
- }
563
- case 'move_node': {
564
- const { path, newPath } = op;
565
- if (Path.isAncestor(path, newPath)) {
566
- throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
567
- }
568
- const node = PlaitNode.get(board, path);
569
- const parent = PlaitNode.parent(board, path);
570
- const index = path[path.length - 1];
571
- // This is tricky, but since the `path` and `newPath` both refer to
572
- // the same snapshot in time, there's a mismatch. After either
573
- // removing the original position, the second step's path can be out
574
- // of date. So instead of using the `op.newPath` directly, we
575
- // transform `op.path` to ascertain what the `newPath` would be after
576
- // the operation was applied.
577
- parent.children?.splice(index, 1);
578
- const truePath = Path.transform(path, op);
579
- const newParent = PlaitNode.get(board, Path.parent(truePath));
580
- const newIndex = truePath[truePath.length - 1];
581
- newParent.children?.splice(newIndex, 0, node);
582
- break;
583
- }
584
- case 'set_node': {
585
- const { path, properties, newProperties } = op;
586
- if (path.length === 0) {
587
- throw new Error(`Cannot set properties on the root node!`);
588
- }
589
- const node = PlaitNode.get(board, path);
590
- for (const key in newProperties) {
591
- const value = newProperties[key];
592
- if (value == null) {
593
- delete node[key];
594
- }
595
- else {
596
- node[key] = value;
597
- }
598
- }
599
- // properties that were previously defined, but are now missing, must be deleted
600
- for (const key in properties) {
601
- if (!newProperties.hasOwnProperty(key)) {
602
- delete node[key];
603
- }
604
- }
605
- break;
606
- }
607
- case 'set_viewport': {
608
- const { newProperties } = op;
609
- if (newProperties == null) {
610
- viewport = newProperties;
611
- }
612
- else {
613
- if (viewport == null) {
614
- if (!Viewport.isViewport(newProperties)) {
615
- throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
616
- }
617
- viewport = { ...newProperties };
618
- }
619
- for (const key in newProperties) {
620
- const value = newProperties[key];
621
- if (value == null) {
622
- delete viewport[key];
623
- }
624
- else {
625
- viewport[key] = value;
626
- }
627
- }
628
- }
629
- break;
630
- }
631
- case 'set_selection': {
632
- const { newProperties } = op;
633
- if (newProperties == null) {
634
- selection = newProperties;
635
- }
636
- else {
637
- if (selection === null) {
638
- selection = op.newProperties;
639
- }
640
- else {
641
- selection.ranges = newProperties.ranges;
642
- }
643
- }
644
- break;
645
- }
646
- case 'set_theme': {
647
- const { newProperties } = op;
648
- theme = newProperties;
649
- break;
685
+ function transformPoints(board, points) {
686
+ const newPoints = points.map(point => {
687
+ return transformPoint(board, point);
688
+ });
689
+ return newPoints;
690
+ }
691
+ function transformPoint(board, point) {
692
+ const { width, height } = PlaitBoard.getHost(board).getBoundingClientRect();
693
+ const viewBox = PlaitBoard.getHost(board).viewBox.baseVal;
694
+ const x = (point[0] / width) * viewBox.width + viewBox.x;
695
+ const y = (point[1] / height) * viewBox.height + viewBox.y;
696
+ const newPoint = [x, y];
697
+ return newPoint;
698
+ }
699
+ function isInPlaitBoard(board, x, y) {
700
+ const plaitBoardElement = PlaitBoard.getBoardContainer(board);
701
+ const plaitBoardRect = plaitBoardElement.getBoundingClientRect();
702
+ const distances = distanceBetweenPointAndRectangle(x, y, plaitBoardRect);
703
+ return distances === 0;
704
+ }
705
+ function getRealScrollBarWidth(board) {
706
+ const { hideScrollbar } = board.options;
707
+ let scrollBarWidth = 0;
708
+ if (!hideScrollbar) {
709
+ const viewportContainer = PlaitBoard.getViewportContainer(board);
710
+ scrollBarWidth = viewportContainer.offsetWidth - viewportContainer.clientWidth;
711
+ }
712
+ return scrollBarWidth;
713
+ }
714
+
715
+ function createForeignObject(x, y, width, height) {
716
+ var newForeignObject = document.createElementNS(NS, 'foreignObject');
717
+ newForeignObject.setAttribute('x', `${x}`);
718
+ newForeignObject.setAttribute('y', `${y}`);
719
+ newForeignObject.setAttribute('width', `${width}`);
720
+ newForeignObject.setAttribute('height', `${height}`);
721
+ return newForeignObject;
722
+ }
723
+ function updateForeignObject(target, width, height, x, y) {
724
+ const foreignObject = target instanceof SVGForeignObjectElement ? target : target.querySelector('foreignObject');
725
+ if (foreignObject) {
726
+ foreignObject.setAttribute('width', `${width}`);
727
+ foreignObject.setAttribute('height', `${height}`);
728
+ foreignObject.setAttribute('x', `${x}`);
729
+ foreignObject.setAttribute('y', `${y}`);
730
+ }
731
+ }
732
+ function updateForeignObjectWidth(target, width) {
733
+ const foreignObject = target instanceof SVGForeignObjectElement ? target : target.querySelector('foreignObject');
734
+ if (foreignObject) {
735
+ foreignObject.setAttribute('width', `${width}`);
736
+ }
737
+ }
738
+
739
+ const IS_MAC = typeof window != 'undefined' && /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
740
+
741
+ const IS_IOS = typeof navigator !== 'undefined' &&
742
+ typeof window !== 'undefined' &&
743
+ /iPad|iPhone|iPod/.test(navigator.userAgent) &&
744
+ !window.MSStream;
745
+ const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
746
+ const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
747
+ const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
748
+ // "modern" Edge was released at 79.x
749
+ const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
750
+ const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
751
+ // Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
752
+ const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
753
+
754
+ function isNullOrUndefined(value) {
755
+ return value === null || value === undefined;
756
+ }
757
+ /**
758
+ * 规范 point
759
+ * @param point
760
+ * @returns point
761
+ */
762
+ function normalizePoint(point) {
763
+ return Array.isArray(point)
764
+ ? {
765
+ x: point[0],
766
+ y: point[1]
650
767
  }
768
+ : point;
769
+ }
770
+
771
+ /**
772
+ * Check whether to merge an operation into the previous operation.
773
+ */
774
+ const shouldMerge = (op, prev) => {
775
+ if (op.type === 'set_viewport' && op.type === prev?.type) {
776
+ return true;
651
777
  }
652
- return { selection, viewport, theme };
778
+ return false;
653
779
  };
654
- const GeneralTransforms = {
780
+ /**
781
+ * Check whether an operation needs to be saved to the history.
782
+ */
783
+ const shouldSave = (op, prev) => {
784
+ if (op.type === 'set_selection' || op.type === 'set_viewport') {
785
+ return false;
786
+ }
787
+ return true;
788
+ };
789
+ /**
790
+ * Check whether an operation should clear the redos stack.
791
+ */
792
+ const shouldClear = (op) => {
793
+ if (op.type === 'set_selection') {
794
+ return false;
795
+ }
796
+ return true;
797
+ };
798
+ const PlaitHistoryBoard = {
655
799
  /**
656
- * Transform the board by an operation.
800
+ * Get the saving flag's current value.
657
801
  */
658
- transform(board, op) {
659
- board.children = createDraft(board.children);
660
- let viewport = board.viewport && createDraft(board.viewport);
661
- let selection = board.selection && createDraft(board.selection);
662
- let theme = board.theme && createDraft(board.theme);
663
- try {
664
- const state = applyToDraft(board, selection, viewport, theme, op);
665
- viewport = state.viewport;
666
- selection = state.selection;
667
- theme = state.theme;
668
- }
669
- finally {
670
- board.children = finishDraft(board.children);
671
- if (selection) {
672
- board.selection = isDraft(selection) ? finishDraft(selection) : selection;
673
- }
674
- else {
675
- board.selection = null;
676
- }
677
- board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
678
- board.theme = isDraft(theme) ? finishDraft(theme) : theme;
679
- }
802
+ isSaving(board) {
803
+ return SAVING.get(board);
804
+ },
805
+ /**
806
+ * Get the merge flag's current value.
807
+ */
808
+ isMerging(board) {
809
+ return MERGING.get(board);
810
+ },
811
+ /**
812
+ * Apply a series of changes inside a synchronous `fn`, without merging any of
813
+ * the new operations into previous save point in the history.
814
+ */
815
+ withoutMerging(board, fn) {
816
+ const prev = PlaitHistoryBoard.isMerging(board);
817
+ MERGING.set(board, false);
818
+ fn();
819
+ MERGING.set(board, prev);
820
+ },
821
+ /**
822
+ * Apply a series of changes inside a synchronous `fn`, without saving any of
823
+ * their operations into the history.
824
+ */
825
+ withoutSaving(board, fn) {
826
+ const prev = PlaitHistoryBoard.isSaving(board);
827
+ SAVING.set(board, false);
828
+ fn();
829
+ SAVING.set(board, prev);
680
830
  }
681
831
  };
682
832
 
683
- function insertNode(board, node, path) {
684
- const operation = { type: 'insert_node', node, path };
685
- board.apply(operation);
686
- }
687
- function setNode(board, props, path) {
688
- const properties = {};
689
- const newProperties = {};
690
- const node = PlaitNode.get(board, path);
691
- for (const k in props) {
692
- if (node[k] !== props[k]) {
693
- if (node.hasOwnProperty(k)) {
694
- properties[k] = node[k];
695
- }
696
- if (props[k] != null)
697
- newProperties[k] = props[k];
698
- }
699
- }
700
- const operation = { type: 'set_node', properties, newProperties, path };
701
- board.apply(operation);
702
- }
703
- function removeNode(board, path) {
704
- const node = PlaitNode.get(board, path);
705
- const operation = { type: 'remove_node', path, node };
706
- board.apply(operation);
707
- }
708
- function moveNode(board, path, newPath) {
709
- const operation = { type: 'move_node', path, newPath };
710
- board.apply(operation);
711
- }
712
- const NodeTransforms = {
713
- insertNode,
714
- setNode,
715
- removeNode,
716
- moveNode
833
+ /**
834
+ * Hotkey mappings for each platform.
835
+ */
836
+ const HOTKEYS = {
837
+ bold: 'mod+b',
838
+ compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
839
+ moveBackward: 'left',
840
+ moveForward: 'right',
841
+ moveUp: 'up',
842
+ moveDown: 'down',
843
+ moveWordBackward: 'ctrl+left',
844
+ moveWordForward: 'ctrl+right',
845
+ deleteBackward: 'shift?+backspace',
846
+ deleteForward: 'shift?+delete',
847
+ extendBackward: 'shift+left',
848
+ extendForward: 'shift+right',
849
+ italic: 'mod+i',
850
+ splitBlock: 'shift?+enter',
851
+ undo: 'mod+z'
717
852
  };
718
-
719
- function setSelection(board, selection) {
720
- const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
721
- board.apply(operation);
722
- }
723
- const SelectionTransforms = {
724
- setSelection,
725
- setSelectionWithTemporaryElements
853
+ const APPLE_HOTKEYS = {
854
+ moveLineBackward: 'opt+up',
855
+ moveLineForward: 'opt+down',
856
+ moveWordBackward: 'opt+left',
857
+ moveWordForward: 'opt+right',
858
+ deleteBackward: ['ctrl+backspace', 'ctrl+h'],
859
+ deleteForward: ['ctrl+delete', 'ctrl+d'],
860
+ deleteLineBackward: 'cmd+shift?+backspace',
861
+ deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
862
+ deleteWordBackward: 'opt+shift?+backspace',
863
+ deleteWordForward: 'opt+shift?+delete',
864
+ extendLineBackward: 'opt+shift+up',
865
+ extendLineForward: 'opt+shift+down',
866
+ redo: 'cmd+shift+z',
867
+ transposeCharacter: 'ctrl+t'
726
868
  };
727
- function setSelectionWithTemporaryElements(board, elements) {
728
- setTimeout(() => {
729
- BOARD_TO_TEMPORARY_ELEMENTS.set(board, elements);
730
- setSelection(board, { ranges: [] });
731
- });
732
- }
733
-
734
- function setViewport(board, viewport) {
735
- const operation = { type: 'set_viewport', properties: board.viewport, newProperties: viewport };
736
- board.apply(operation);
737
- }
738
- const ViewportTransforms$1 = {
739
- setViewport
869
+ const WINDOWS_HOTKEYS = {
870
+ deleteWordBackward: 'ctrl+shift?+backspace',
871
+ deleteWordForward: 'ctrl+shift?+delete',
872
+ redo: ['ctrl+y', 'ctrl+shift+z']
740
873
  };
741
-
742
874
  /**
743
- * @license
744
- * Copyright Google LLC All Rights Reserved.
745
- *
746
- * Use of this source code is governed by an MIT-style license that can be
747
- * found in the LICENSE file at https://angular.io/license
875
+ * Create a platform-aware hotkey checker.
748
876
  */
749
- const MAC_ENTER = 3;
750
- const BACKSPACE = 8;
751
- const TAB = 9;
752
- const NUM_CENTER = 12;
753
- const ENTER = 13;
754
- const SHIFT = 16;
755
- const CONTROL = 17;
756
- const ALT = 18;
757
- const PAUSE = 19;
758
- const CAPS_LOCK = 20;
759
- const ESCAPE = 27;
760
- const SPACE = 32;
761
- const PAGE_UP = 33;
762
- const PAGE_DOWN = 34;
763
- const END = 35;
764
- const HOME = 36;
765
- const LEFT_ARROW = 37;
766
- const UP_ARROW = 38;
767
- const RIGHT_ARROW = 39;
768
- const DOWN_ARROW = 40;
769
- const PLUS_SIGN = 43;
770
- const PRINT_SCREEN = 44;
771
- const INSERT = 45;
772
- const DELETE = 46;
773
- const ZERO = 48;
774
- const ONE = 49;
775
- const TWO = 50;
776
- const THREE = 51;
777
- const FOUR = 52;
778
- const FIVE = 53;
779
- const SIX = 54;
780
- const SEVEN = 55;
781
- const EIGHT = 56;
782
- const NINE = 57;
783
- const FF_SEMICOLON = 59; // Firefox (Gecko) fires this for semicolon instead of 186
784
- const FF_EQUALS = 61; // Firefox (Gecko) fires this for equals instead of 187
785
- const QUESTION_MARK = 63;
786
- const AT_SIGN = 64;
787
- const A = 65;
788
- const B = 66;
789
- const C = 67;
790
- const D = 68;
791
- const E = 69;
792
- const F = 70;
793
- const G = 71;
794
- const H = 72;
795
- const I = 73;
796
- const J = 74;
797
- const K = 75;
798
- const L = 76;
799
- const M = 77;
800
- const N = 78;
801
- const O = 79;
802
- const P = 80;
803
- const Q = 81;
804
- const R = 82;
805
- const S = 83;
806
- const T = 84;
807
- const U = 85;
808
- const V = 86;
809
- const W = 87;
810
- const X = 88;
811
- const Y = 89;
812
- const Z = 90;
813
- const META = 91; // WIN_KEY_LEFT
814
- const MAC_WK_CMD_LEFT = 91;
815
- const MAC_WK_CMD_RIGHT = 93;
816
- const CONTEXT_MENU = 93;
817
- const NUMPAD_ZERO = 96;
818
- const NUMPAD_ONE = 97;
819
- const NUMPAD_TWO = 98;
820
- const NUMPAD_THREE = 99;
821
- const NUMPAD_FOUR = 100;
822
- const NUMPAD_FIVE = 101;
823
- const NUMPAD_SIX = 102;
824
- const NUMPAD_SEVEN = 103;
825
- const NUMPAD_EIGHT = 104;
826
- const NUMPAD_NINE = 105;
827
- const NUMPAD_MULTIPLY = 106;
828
- const NUMPAD_PLUS = 107;
829
- const NUMPAD_MINUS = 109;
830
- const NUMPAD_PERIOD = 110;
831
- const NUMPAD_DIVIDE = 111;
832
- const F1 = 112;
833
- const F2 = 113;
834
- const F3 = 114;
835
- const F4 = 115;
836
- const F5 = 116;
837
- const F6 = 117;
838
- const F7 = 118;
839
- const F8 = 119;
840
- const F9 = 120;
841
- const F10 = 121;
842
- const F11 = 122;
843
- const F12 = 123;
844
- const NUM_LOCK = 144;
845
- const SCROLL_LOCK = 145;
846
- const FIRST_MEDIA = 166;
847
- const FF_MINUS = 173;
848
- const MUTE = 173; // Firefox (Gecko) fires 181 for MUTE
849
- const VOLUME_DOWN = 174; // Firefox (Gecko) fires 182 for VOLUME_DOWN
850
- const VOLUME_UP = 175; // Firefox (Gecko) fires 183 for VOLUME_UP
851
- const FF_MUTE = 181;
852
- const FF_VOLUME_DOWN = 182;
853
- const LAST_MEDIA = 183;
854
- const FF_VOLUME_UP = 183;
855
- const SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON
856
- const EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS
857
- const COMMA = 188;
858
- const DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS
859
- const PERIOD = 190;
860
- const SLASH = 191;
861
- const APOSTROPHE = 192;
862
- const TILDE = 192;
863
- const OPEN_SQUARE_BRACKET = 219;
864
- const BACKSLASH = 220;
865
- const CLOSE_SQUARE_BRACKET = 221;
866
- const SINGLE_QUOTE = 222;
867
- const MAC_META = 224;
868
-
869
- var ResizeCursorClass;
870
- (function (ResizeCursorClass) {
871
- ResizeCursorClass["ew-resize"] = "ew-resize";
872
- })(ResizeCursorClass || (ResizeCursorClass = {}));
873
-
874
- const ATTACHED_ELEMENT_CLASS_NAME = 'plait-board-attached';
875
-
876
- const CLIP_BOARD_FORMAT_KEY = 'x-plait-fragment';
877
- const HOST_CLASS_NAME = 'plait-board-container';
878
- const SCROLL_BAR_WIDTH = 20;
879
- const MAX_RADIUS = 16;
880
- const POINTER_BUTTON = {
881
- MAIN: 0,
882
- WHEEL: 1,
883
- SECONDARY: 2,
884
- TOUCH: -1
885
- };
886
- const PRESS_AND_MOVE_BUFFER = 5;
887
-
888
- const NS = 'http://www.w3.org/2000/svg';
889
- function toPoint(x, y, container) {
890
- const rect = container.getBoundingClientRect();
891
- return [x - rect.x, y - rect.y];
892
- }
893
- function createG() {
894
- const newG = document.createElementNS(NS, 'g');
895
- return newG;
896
- }
897
- function createPath() {
898
- const newG = document.createElementNS(NS, 'path');
899
- return newG;
900
- }
901
- function createSVG() {
902
- const svg = document.createElementNS(NS, 'svg');
903
- return svg;
904
- }
905
- function createText(x, y, fill, textContent) {
906
- var text = document.createElementNS(NS, 'text');
907
- text.setAttribute('x', `${x}`);
908
- text.setAttribute('y', `${y}`);
909
- text.setAttribute('fill', fill);
910
- text.textContent = textContent;
911
- return text;
912
- }
913
- /**
914
- * Check if a DOM node is an element node.
915
- */
916
- const isDOMElement = (value) => {
917
- return isDOMNode(value) && value.nodeType === 1;
877
+ const create = (key) => {
878
+ const generic = HOTKEYS[key];
879
+ const apple = APPLE_HOTKEYS[key];
880
+ const windows = WINDOWS_HOTKEYS[key];
881
+ const isGeneric = generic && isKeyHotkey(generic);
882
+ const isApple = apple && isKeyHotkey(apple);
883
+ const isWindows = windows && isKeyHotkey(windows);
884
+ return (event) => {
885
+ if (isGeneric && isGeneric(event)) {
886
+ return true;
887
+ }
888
+ if (IS_APPLE && isApple && isApple(event)) {
889
+ return true;
890
+ }
891
+ if (!IS_APPLE && isWindows && isWindows(event)) {
892
+ return true;
893
+ }
894
+ return false;
895
+ };
918
896
  };
919
897
  /**
920
- * Check if a value is a DOM node.
898
+ * Hotkeys.
921
899
  */
922
- const isDOMNode = (value) => {
923
- return value instanceof window.Node;
900
+ const hotkeys = {
901
+ isBold: create('bold'),
902
+ isCompose: create('compose'),
903
+ isMoveBackward: create('moveBackward'),
904
+ isMoveForward: create('moveForward'),
905
+ isMoveUp: create('moveUp'),
906
+ isMoveDown: create('moveDown'),
907
+ isDeleteBackward: create('deleteBackward'),
908
+ isDeleteForward: create('deleteForward'),
909
+ isDeleteLineBackward: create('deleteLineBackward'),
910
+ isDeleteLineForward: create('deleteLineForward'),
911
+ isDeleteWordBackward: create('deleteWordBackward'),
912
+ isDeleteWordForward: create('deleteWordForward'),
913
+ isExtendBackward: create('extendBackward'),
914
+ isExtendForward: create('extendForward'),
915
+ isExtendLineBackward: create('extendLineBackward'),
916
+ isExtendLineForward: create('extendLineForward'),
917
+ isItalic: create('italic'),
918
+ isMoveLineBackward: create('moveLineBackward'),
919
+ isMoveLineForward: create('moveLineForward'),
920
+ isMoveWordBackward: create('moveWordBackward'),
921
+ isMoveWordForward: create('moveWordForward'),
922
+ isRedo: create('redo'),
923
+ isSplitBlock: create('splitBlock'),
924
+ isTransposeCharacter: create('transposeCharacter'),
925
+ isUndo: create('undo')
924
926
  };
925
- const hasInputOrTextareaTarget = (target) => {
926
- if (isDOMElement(target)) {
927
- if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
928
- return true;
929
- }
927
+
928
+ function idCreator(length = 5) {
929
+ // remove numeral
930
+ const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
931
+ const maxPosition = $chars.length;
932
+ let key = '';
933
+ for (let i = 0; i < length; i++) {
934
+ key += $chars.charAt(Math.floor(Math.random() * maxPosition));
930
935
  }
931
- return false;
932
- };
933
- const isSecondaryPointer = (event) => {
934
- return event.button === POINTER_BUTTON.SECONDARY;
935
- };
936
- const isMainPointer = (event) => {
937
- return event.button === POINTER_BUTTON.MAIN;
938
- };
936
+ return key;
937
+ }
939
938
 
940
939
  /**
941
- * Extendable Custom Types Interface
940
+ * drawRoundRectangle
942
941
  */
943
-
944
- const SELECTION_BORDER_COLOR = '#6698FF';
945
- const SELECTION_FILL_COLOR = '#6698FF19'; // 主色 0.1 透明度
946
- const Selection = {
947
- isCollapsed(selection) {
948
- if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
949
- return true;
950
- }
951
- else {
952
- return false;
942
+ function drawRoundRectangle(rs, x1, y1, x2, y2, options, outline = false, borderRadius) {
943
+ const width = Math.abs(x1 - x2);
944
+ const height = Math.abs(y1 - y2);
945
+ let radius = borderRadius || 0;
946
+ if (radius === 0) {
947
+ const defaultRadius = Math.min(width, height) / 8;
948
+ let radius = defaultRadius;
949
+ if (defaultRadius > MAX_RADIUS) {
950
+ radius = outline ? MAX_RADIUS + 2 : MAX_RADIUS;
953
951
  }
954
952
  }
953
+ const point1 = [x1 + radius, y1];
954
+ const point2 = [x2 - radius, y1];
955
+ const point3 = [x2, y1 + radius];
956
+ const point4 = [x2, y2 - radius];
957
+ const point5 = [x2 - radius, y2];
958
+ const point6 = [x1 + radius, y2];
959
+ const point7 = [x1, y2 - radius];
960
+ const point8 = [x1, y1 + radius];
961
+ return rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, options);
962
+ }
963
+ const drawRectangle = (board, rectangle, options) => {
964
+ const roughSVG = PlaitBoard.getRoughSVG(board);
965
+ const rectangleG = roughSVG.rectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height, options);
966
+ setStrokeLinecap(rectangleG, 'round');
967
+ return rectangleG;
955
968
  };
956
969
 
957
- const getHitElements = (board, selection, match = () => true) => {
958
- const realSelection = selection || board.selection;
959
- const selectedElements = [];
960
- const isCollapsed = realSelection && realSelection.ranges.length === 1 && Selection.isCollapsed(realSelection.ranges[0]);
961
- depthFirstRecursion(board, node => {
962
- if (selectedElements.length > 0 && isCollapsed) {
963
- return;
964
- }
965
- if (!PlaitBoard.isBoard(node) &&
966
- match(node) &&
967
- realSelection &&
968
- realSelection.ranges.some(range => {
969
- return board.isHitSelection(node, range);
970
- })) {
971
- selectedElements.push(node);
972
- }
973
- }, getIsRecursionFunc(board), true);
974
- return selectedElements;
975
- };
976
- const getHitElementOfRoot = (board, rootElements, range) => {
977
- const newRootElements = [...rootElements].reverse();
978
- return newRootElements.find(item => {
979
- return board.isHitSelection(item, range);
980
- });
981
- };
982
- const isHitElements = (board, elements, ranges) => {
983
- let isIntersectionElements = false;
984
- if (elements.length) {
985
- elements.map(item => {
986
- if (!isIntersectionElements) {
987
- isIntersectionElements = ranges.some(range => {
988
- return board.isHitSelection(item, range);
989
- });
990
- }
991
- });
992
- }
993
- return isIntersectionElements;
994
- };
995
- const cacheSelectedElements = (board, selectedElements) => {
996
- BOARD_TO_SELECTED_ELEMENT.set(board, selectedElements);
997
- };
998
- const getSelectedElements = (board) => {
999
- return BOARD_TO_SELECTED_ELEMENT.get(board) || [];
1000
- };
1001
- const addSelectedElement = (board, element) => {
1002
- const selectedElements = getSelectedElements(board);
1003
- cacheSelectedElements(board, [...selectedElements, element]);
1004
- };
1005
- const removeSelectedElement = (board, element) => {
1006
- const selectedElements = getSelectedElements(board);
1007
- const newSelectedElements = selectedElements.filter(value => value !== element);
1008
- cacheSelectedElements(board, newSelectedElements);
1009
- };
1010
- const clearSelectedElement = (board) => {
1011
- cacheSelectedElements(board, []);
1012
- };
1013
- const isSelectedElement = (board, element) => {
1014
- const selectedElements = getSelectedElements(board);
1015
- return !!selectedElements.find(value => value === element);
1016
- };
1017
-
1018
- function hasBeforeContextChange(value) {
1019
- if (value.beforeContextChange) {
1020
- return true;
1021
- }
1022
- return false;
970
+ function arrowPoints(start, end, maxHypotenuseLength = 10, degree = 40) {
971
+ const width = Math.abs(start[0] - end[0]);
972
+ const height = Math.abs(start[1] - end[1]);
973
+ let hypotenuse = Math.hypot(width, height); // 斜边
974
+ const realRotateLine = hypotenuse > maxHypotenuseLength * 2 ? maxHypotenuseLength : hypotenuse / 2;
975
+ const rotateWidth = (realRotateLine / hypotenuse) * width;
976
+ const rotateHeight = (realRotateLine / hypotenuse) * height;
977
+ const rotatePoint = [
978
+ end[0] > start[0] ? end[0] - rotateWidth : end[0] + rotateWidth,
979
+ end[1] > start[1] ? end[1] - rotateHeight : end[1] + rotateHeight
980
+ ];
981
+ const pointRight = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (degree * Math.PI) / 180);
982
+ const pointLeft = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (-degree * Math.PI) / 180);
983
+ return { pointLeft, pointRight };
1023
984
  }
1024
- function hasOnContextChanged(value) {
1025
- if (value.onContextChanged) {
1026
- return true;
1027
- }
1028
- return false;
985
+ function drawArrow(rs, start, end, options, maxHypotenuseLength = 10, degree = 40) {
986
+ const { pointLeft, pointRight } = arrowPoints(start, end, maxHypotenuseLength, degree);
987
+ const arrowLineLeft = rs.linearPath([pointLeft, end], options);
988
+ const arrowLineRight = rs.linearPath([pointRight, end], options);
989
+ return [arrowLineLeft, arrowLineRight];
1029
990
  }
1030
991
 
1031
- class PlaitPluginElementComponent {
1032
- set context(value) {
1033
- if (hasBeforeContextChange(this)) {
1034
- this.beforeContextChange(value);
1035
- }
1036
- const previousContext = this._context;
1037
- this._context = value;
1038
- if (this.element) {
1039
- ELEMENT_TO_COMPONENT.set(this.element, this);
1040
- }
1041
- if (this.initialized) {
1042
- this.cdr.markForCheck();
1043
- if (hasOnContextChanged(this)) {
1044
- this.onContextChanged(value, previousContext);
1045
- }
992
+ function drawCircle(roughSVG, point, diameter, options) {
993
+ return roughSVG.circle(point[0], point[1], diameter, options);
994
+ }
995
+
996
+ function drawLine(rs, start, end, options) {
997
+ return rs.linearPath([start, end], options);
998
+ }
999
+ function drawLinearPath(points, options, closePath) {
1000
+ const g = createG();
1001
+ const path = createPath();
1002
+ let polylinePath = '';
1003
+ points.forEach((point, index) => {
1004
+ if (index === 0) {
1005
+ polylinePath += `M ${point[0]} ${point[1]} `;
1046
1006
  }
1047
1007
  else {
1048
- if (PlaitElement.isRootElement(this.element) && this.element.children) {
1049
- this.g = createG();
1050
- this.rootG = createG();
1051
- this.rootG.append(this.g);
1052
- }
1053
- else {
1054
- this.g = createG();
1055
- }
1008
+ polylinePath += `L ${point[0]} ${point[1]} `;
1056
1009
  }
1010
+ });
1011
+ if (closePath) {
1012
+ polylinePath += 'Z';
1057
1013
  }
1058
- get context() {
1059
- return this._context;
1060
- }
1061
- get element() {
1062
- return this.context && this.context.element;
1063
- }
1064
- get board() {
1065
- return this.context && this.context.board;
1066
- }
1067
- get selected() {
1068
- return this.context && this.context.selected;
1069
- }
1070
- get effect() {
1071
- return this.context && this.context.effect;
1072
- }
1073
- constructor(cdr) {
1074
- this.cdr = cdr;
1075
- this.initialized = false;
1076
- }
1077
- ngOnInit() {
1078
- if (this.element.type) {
1079
- (this.rootG || this.g).setAttribute(`plait-${this.element.type}`, 'true');
1014
+ path.setAttribute('d', polylinePath);
1015
+ path.setAttribute('stroke', `${options?.stroke}`);
1016
+ path.setAttribute('stroke-width', `${options?.strokeWidth}`);
1017
+ path.setAttribute('fill', `${options?.fill || 'none'}`);
1018
+ options?.strokeLineDash && path.setAttribute('stroke-dasharray', `${options.strokeLineDash}`);
1019
+ g.appendChild(path);
1020
+ return g;
1021
+ }
1022
+ function drawBezierPath(points, options) {
1023
+ const g = createG();
1024
+ const path = createPath();
1025
+ let polylinePath = '';
1026
+ for (let i = 0; i < points.length - 3; i += 3) {
1027
+ if (i === 0) {
1028
+ polylinePath += `M ${points[0][0]} ${points[0][1]} `;
1080
1029
  }
1081
- this.initialized = true;
1082
- }
1083
- ngOnDestroy() {
1084
- if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
1085
- ELEMENT_TO_COMPONENT.delete(this.element);
1030
+ else {
1031
+ polylinePath += `C ${points[i + 1][0]} ${points[i + 1][1]}, ${points[i + 2][0]} ${points[i + 2][1]}, ${points[i + 3][0]} ${points[i + 3][1]}`;
1086
1032
  }
1087
- removeSelectedElement(this.board, this.element);
1088
- (this.rootG || this.g).remove();
1089
1033
  }
1034
+ path.setAttribute('d', polylinePath);
1035
+ path.setAttribute('stroke', `${options?.stroke}`);
1036
+ path.setAttribute('stroke-width', `${options?.strokeWidth}`);
1037
+ path.setAttribute('fill', `none`);
1038
+ g.appendChild(path);
1039
+ return g;
1090
1040
  }
1091
- PlaitPluginElementComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitPluginElementComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
1092
- PlaitPluginElementComponent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.5", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 });
1093
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitPluginElementComponent, decorators: [{
1094
- type: Directive
1095
- }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { context: [{
1096
- type: Input
1097
- }] } });
1098
- const ELEMENT_TO_COMPONENT = new WeakMap();
1099
1041
 
1100
- function transformPoints(board, points) {
1101
- const newPoints = points.map(point => {
1102
- return transformPoint(board, point);
1103
- });
1104
- return newPoints;
1105
- }
1106
- function transformPoint(board, point) {
1107
- const { width, height } = PlaitBoard.getHost(board).getBoundingClientRect();
1108
- const viewBox = PlaitBoard.getHost(board).viewBox.baseVal;
1109
- const x = (point[0] / width) * viewBox.width + viewBox.x;
1110
- const y = (point[1] / height) * viewBox.height + viewBox.y;
1111
- const newPoint = [x, y];
1112
- return newPoint;
1113
- }
1114
- function isInPlaitBoard(board, x, y) {
1115
- const plaitBoardElement = PlaitBoard.getBoardContainer(board);
1116
- const plaitBoardRect = plaitBoardElement.getBoundingClientRect();
1117
- const distances = distanceBetweenPointAndRectangle(x, y, plaitBoardRect);
1118
- return distances === 0;
1119
- }
1120
- function getRealScrollBarWidth(board) {
1042
+ const IS_FROM_SCROLLING = new WeakMap();
1043
+ const IS_FROM_VIEWPORT_CHANGE = new WeakMap();
1044
+ function getViewportContainerRect(board) {
1121
1045
  const { hideScrollbar } = board.options;
1122
- let scrollBarWidth = 0;
1123
- if (!hideScrollbar) {
1124
- const viewportContainer = PlaitBoard.getViewportContainer(board);
1125
- scrollBarWidth = viewportContainer.offsetWidth - viewportContainer.clientWidth;
1126
- }
1127
- return scrollBarWidth;
1128
- }
1129
-
1130
- function createForeignObject(x, y, width, height) {
1131
- var newForeignObject = document.createElementNS(NS, 'foreignObject');
1132
- newForeignObject.setAttribute('x', `${x}`);
1133
- newForeignObject.setAttribute('y', `${y}`);
1134
- newForeignObject.setAttribute('width', `${width}`);
1135
- newForeignObject.setAttribute('height', `${height}`);
1136
- return newForeignObject;
1046
+ const scrollBarWidth = hideScrollbar ? SCROLL_BAR_WIDTH : 0;
1047
+ const viewportRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1048
+ return {
1049
+ width: viewportRect.width + scrollBarWidth,
1050
+ height: viewportRect.height + scrollBarWidth
1051
+ };
1137
1052
  }
1138
- function updateForeignObject(g, width, height, x, y) {
1139
- const foreignObject = g.querySelector('foreignObject');
1140
- if (foreignObject) {
1141
- foreignObject.setAttribute('width', `${width}`);
1142
- foreignObject.setAttribute('height', `${height}`);
1143
- foreignObject.setAttribute('x', `${x}`);
1144
- foreignObject.setAttribute('y', `${y}`);
1053
+ function getElementHostBBox(board, zoom) {
1054
+ const childrenRect = getRectangleByElements(board, board.children, true);
1055
+ const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1056
+ const containerWidth = viewportContainerRect.width / zoom;
1057
+ const containerHeight = viewportContainerRect.height / zoom;
1058
+ let left;
1059
+ let right;
1060
+ let top;
1061
+ let bottom;
1062
+ if (childrenRect.width < containerWidth) {
1063
+ const centerX = childrenRect.x + childrenRect.width / 2;
1064
+ const halfContainerWidth = containerWidth / 2;
1065
+ left = centerX - halfContainerWidth;
1066
+ right = centerX + halfContainerWidth;
1145
1067
  }
1146
- }
1147
-
1148
- const IS_MAC = typeof window != 'undefined' && /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
1149
-
1150
- const IS_IOS = typeof navigator !== 'undefined' &&
1151
- typeof window !== 'undefined' &&
1152
- /iPad|iPhone|iPod/.test(navigator.userAgent) &&
1153
- !window.MSStream;
1154
- const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
1155
- const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
1156
- const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
1157
- // "modern" Edge was released at 79.x
1158
- const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
1159
- const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
1160
- // Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
1161
- const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
1162
-
1163
- /**
1164
- * Check whether to merge an operation into the previous operation.
1165
- */
1166
- const shouldMerge = (op, prev) => {
1167
- if (op.type === 'set_viewport' && op.type === prev?.type) {
1168
- return true;
1068
+ else {
1069
+ left = childrenRect.x;
1070
+ right = childrenRect.x + childrenRect.width;
1169
1071
  }
1170
- return false;
1171
- };
1172
- /**
1173
- * Check whether an operation needs to be saved to the history.
1174
- */
1175
- const shouldSave = (op, prev) => {
1176
- if (op.type === 'set_selection' || op.type === 'set_viewport') {
1177
- return false;
1072
+ if (childrenRect.height < containerHeight) {
1073
+ const centerY = childrenRect.y + childrenRect.height / 2;
1074
+ const halfContainerHeight = containerHeight / 2;
1075
+ top = centerY - halfContainerHeight;
1076
+ bottom = centerY + halfContainerHeight;
1178
1077
  }
1179
- return true;
1180
- };
1078
+ else {
1079
+ top = childrenRect.y;
1080
+ bottom = childrenRect.y + childrenRect.height;
1081
+ }
1082
+ return {
1083
+ left,
1084
+ right,
1085
+ top,
1086
+ bottom
1087
+ };
1088
+ }
1181
1089
  /**
1182
- * Check whether an operation should clear the redos stack.
1090
+ * 验证缩放比是否符合限制,如果超出限制,则返回合适的缩放比
1091
+ * @param zoom 缩放比
1092
+ * @param minZoom 最小缩放比
1093
+ * @param maxZoom 最大缩放比
1094
+ * @returns 正确的缩放比
1183
1095
  */
1184
- const shouldClear = (op) => {
1185
- if (op.type === 'set_selection') {
1186
- return false;
1096
+ function clampZoomLevel(zoom, minZoom = 0.2, maxZoom = 4) {
1097
+ return zoom < minZoom ? minZoom : zoom > maxZoom ? maxZoom : zoom;
1098
+ }
1099
+ function getViewBox(board, zoom) {
1100
+ const boardContainerRectangle = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1101
+ const elementHostBBox = getElementHostBBox(board, zoom);
1102
+ const horizontalPadding = boardContainerRectangle.width / 2;
1103
+ const verticalPadding = boardContainerRectangle.height / 2;
1104
+ const viewBox = [
1105
+ elementHostBBox.left - horizontalPadding / zoom,
1106
+ elementHostBBox.top - verticalPadding / zoom,
1107
+ elementHostBBox.right - elementHostBBox.left + (horizontalPadding * 2) / zoom,
1108
+ elementHostBBox.bottom - elementHostBBox.top + (verticalPadding * 2) / zoom
1109
+ ];
1110
+ return viewBox;
1111
+ }
1112
+ function getViewBoxCenterPoint(board) {
1113
+ const childrenRectangle = getRectangleByElements(board, board.children, true);
1114
+ return [childrenRectangle.x + childrenRectangle.width / 2, childrenRectangle.y + childrenRectangle.height / 2];
1115
+ }
1116
+ function setSVGViewBox(board, viewBox) {
1117
+ const zoom = board.viewport.zoom;
1118
+ const hostElement = PlaitBoard.getHost(board);
1119
+ hostElement.style.display = 'block';
1120
+ hostElement.style.width = `${viewBox[2] * zoom}px`;
1121
+ hostElement.style.height = `${viewBox[3] * zoom}px`;
1122
+ if (viewBox && viewBox[2] > 0 && viewBox[3] > 0) {
1123
+ hostElement.setAttribute('viewBox', viewBox.join(' '));
1187
1124
  }
1188
- return true;
1125
+ }
1126
+ function updateViewportOffset(board) {
1127
+ const origination = getViewportOrigination(board);
1128
+ if (!origination)
1129
+ return;
1130
+ const { zoom } = board.viewport;
1131
+ const viewBox = getViewBox(board, zoom);
1132
+ const scrollLeft = (origination[0] - viewBox[0]) * zoom;
1133
+ const scrollTop = (origination[1] - viewBox[1]) * zoom;
1134
+ updateViewportContainerScroll(board, scrollLeft, scrollTop);
1135
+ }
1136
+ function updateViewportContainerScroll(board, left, top, isFromViewportChange = true) {
1137
+ const viewportContainer = PlaitBoard.getViewportContainer(board);
1138
+ if (viewportContainer.scrollLeft !== left || viewportContainer.scrollTop !== top) {
1139
+ viewportContainer.scrollLeft = left;
1140
+ viewportContainer.scrollTop = top;
1141
+ isFromViewportChange && setIsFromViewportChange(board, true);
1142
+ }
1143
+ }
1144
+ function initializeViewportContainer(board) {
1145
+ const { width, height } = getViewportContainerRect(board);
1146
+ const viewportContainer = PlaitBoard.getViewportContainer(board);
1147
+ viewportContainer.style.width = `${width}px`;
1148
+ viewportContainer.style.height = `${height}px`;
1149
+ }
1150
+ function initializeViewBox(board) {
1151
+ const zoom = board.viewport.zoom;
1152
+ const viewBox = getViewBox(board, zoom);
1153
+ setSVGViewBox(board, viewBox);
1154
+ }
1155
+ function initializeViewportOffset(board) {
1156
+ if (!board.viewport?.origination) {
1157
+ const zoom = board.viewport.zoom;
1158
+ const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1159
+ const viewBox = getViewBox(board, zoom);
1160
+ const centerX = viewBox[0] + viewBox[2] / 2;
1161
+ const centerY = viewBox[1] + viewBox[3] / 2;
1162
+ const origination = [centerX - viewportContainerRect.width / 2 / zoom, centerY - viewportContainerRect.height / 2 / zoom];
1163
+ updateViewportOrigination(board, origination);
1164
+ updateViewportOffset(board);
1165
+ return;
1166
+ }
1167
+ updateViewportOffset(board);
1168
+ }
1169
+ const updateViewportOrigination = (board, origination) => {
1170
+ BOARD_TO_VIEWPORT_ORIGINATION.set(board, origination);
1189
1171
  };
1190
- const PlaitHistoryBoard = {
1191
- /**
1192
- * Get the saving flag's current value.
1193
- */
1194
- isSaving(board) {
1195
- return SAVING.get(board);
1196
- },
1197
- /**
1198
- * Get the merge flag's current value.
1199
- */
1200
- isMerging(board) {
1201
- return MERGING.get(board);
1202
- },
1203
- /**
1204
- * Apply a series of changes inside a synchronous `fn`, without merging any of
1205
- * the new operations into previous save point in the history.
1206
- */
1207
- withoutMerging(board, fn) {
1208
- const prev = PlaitHistoryBoard.isMerging(board);
1209
- MERGING.set(board, false);
1210
- fn();
1211
- MERGING.set(board, prev);
1212
- },
1213
- /**
1214
- * Apply a series of changes inside a synchronous `fn`, without saving any of
1215
- * their operations into the history.
1216
- */
1217
- withoutSaving(board, fn) {
1218
- const prev = PlaitHistoryBoard.isSaving(board);
1219
- SAVING.set(board, false);
1220
- fn();
1221
- SAVING.set(board, prev);
1172
+ const clearViewportOrigination = (board) => {
1173
+ BOARD_TO_VIEWPORT_ORIGINATION.delete(board);
1174
+ };
1175
+ const getViewportOrigination = (board) => {
1176
+ const origination = BOARD_TO_VIEWPORT_ORIGINATION.get(board);
1177
+ if (origination) {
1178
+ return origination;
1179
+ }
1180
+ else {
1181
+ return board.viewport.origination;
1222
1182
  }
1223
1183
  };
1224
-
1225
- /**
1226
- * Hotkey mappings for each platform.
1227
- */
1228
- const HOTKEYS = {
1229
- bold: 'mod+b',
1230
- compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
1231
- moveBackward: 'left',
1232
- moveForward: 'right',
1233
- moveUp: 'up',
1234
- moveDown: 'down',
1235
- moveWordBackward: 'ctrl+left',
1236
- moveWordForward: 'ctrl+right',
1237
- deleteBackward: 'shift?+backspace',
1238
- deleteForward: 'shift?+delete',
1239
- extendBackward: 'shift+left',
1240
- extendForward: 'shift+right',
1241
- italic: 'mod+i',
1242
- splitBlock: 'shift?+enter',
1243
- undo: 'mod+z'
1184
+ const isFromScrolling = (board) => {
1185
+ return !!IS_FROM_SCROLLING.get(board);
1244
1186
  };
1245
- const APPLE_HOTKEYS = {
1246
- moveLineBackward: 'opt+up',
1247
- moveLineForward: 'opt+down',
1248
- moveWordBackward: 'opt+left',
1249
- moveWordForward: 'opt+right',
1250
- deleteBackward: ['ctrl+backspace', 'ctrl+h'],
1251
- deleteForward: ['ctrl+delete', 'ctrl+d'],
1252
- deleteLineBackward: 'cmd+shift?+backspace',
1253
- deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
1254
- deleteWordBackward: 'opt+shift?+backspace',
1255
- deleteWordForward: 'opt+shift?+delete',
1256
- extendLineBackward: 'opt+shift+up',
1257
- extendLineForward: 'opt+shift+down',
1258
- redo: 'cmd+shift+z',
1259
- transposeCharacter: 'ctrl+t'
1187
+ const setIsFromScrolling = (board, state) => {
1188
+ IS_FROM_SCROLLING.set(board, state);
1260
1189
  };
1261
- const WINDOWS_HOTKEYS = {
1262
- deleteWordBackward: 'ctrl+shift?+backspace',
1263
- deleteWordForward: 'ctrl+shift?+delete',
1264
- redo: ['ctrl+y', 'ctrl+shift+z']
1190
+ const isFromViewportChange = (board) => {
1191
+ return !!IS_FROM_VIEWPORT_CHANGE.get(board);
1265
1192
  };
1266
- /**
1267
- * Create a platform-aware hotkey checker.
1268
- */
1269
- const create = (key) => {
1270
- const generic = HOTKEYS[key];
1271
- const apple = APPLE_HOTKEYS[key];
1272
- const windows = WINDOWS_HOTKEYS[key];
1273
- const isGeneric = generic && isKeyHotkey(generic);
1274
- const isApple = apple && isKeyHotkey(apple);
1275
- const isWindows = windows && isKeyHotkey(windows);
1276
- return (event) => {
1277
- if (isGeneric && isGeneric(event)) {
1278
- return true;
1279
- }
1280
- if (IS_APPLE && isApple && isApple(event)) {
1281
- return true;
1282
- }
1283
- if (!IS_APPLE && isWindows && isWindows(event)) {
1284
- return true;
1285
- }
1286
- return false;
1193
+ const setIsFromViewportChange = (board, state) => {
1194
+ IS_FROM_VIEWPORT_CHANGE.set(board, state);
1195
+ };
1196
+ function scrollToRectangle(board, client) { }
1197
+
1198
+ let timerId = null;
1199
+ const throttleRAF = (fn) => {
1200
+ const scheduleFunc = () => {
1201
+ timerId = requestAnimationFrame(() => {
1202
+ timerId = null;
1203
+ fn();
1204
+ });
1287
1205
  };
1206
+ if (timerId !== null) {
1207
+ cancelAnimationFrame(timerId);
1208
+ timerId = null;
1209
+ }
1210
+ scheduleFunc();
1288
1211
  };
1289
- /**
1290
- * Hotkeys.
1291
- */
1292
- const hotkeys = {
1293
- isBold: create('bold'),
1294
- isCompose: create('compose'),
1295
- isMoveBackward: create('moveBackward'),
1296
- isMoveForward: create('moveForward'),
1297
- isMoveUp: create('moveUp'),
1298
- isMoveDown: create('moveDown'),
1299
- isDeleteBackward: create('deleteBackward'),
1300
- isDeleteForward: create('deleteForward'),
1301
- isDeleteLineBackward: create('deleteLineBackward'),
1302
- isDeleteLineForward: create('deleteLineForward'),
1303
- isDeleteWordBackward: create('deleteWordBackward'),
1304
- isDeleteWordForward: create('deleteWordForward'),
1305
- isExtendBackward: create('extendBackward'),
1306
- isExtendForward: create('extendForward'),
1307
- isExtendLineBackward: create('extendLineBackward'),
1308
- isExtendLineForward: create('extendLineForward'),
1309
- isItalic: create('italic'),
1310
- isMoveLineBackward: create('moveLineBackward'),
1311
- isMoveLineForward: create('moveLineForward'),
1312
- isMoveWordBackward: create('moveWordBackward'),
1313
- isMoveWordForward: create('moveWordForward'),
1314
- isRedo: create('redo'),
1315
- isSplitBlock: create('splitBlock'),
1316
- isTransposeCharacter: create('transposeCharacter'),
1317
- isUndo: create('undo')
1212
+ const debounce = (func, wait, options) => {
1213
+ let timerSubscription = null;
1214
+ return () => {
1215
+ if (timerSubscription && !timerSubscription.closed) {
1216
+ timerSubscription.unsubscribe();
1217
+ timerSubscription = timer(wait).subscribe(() => {
1218
+ func();
1219
+ });
1220
+ }
1221
+ else {
1222
+ if (options?.leading) {
1223
+ func();
1224
+ }
1225
+ timerSubscription = timer(wait).subscribe();
1226
+ }
1227
+ };
1318
1228
  };
1319
1229
 
1320
- function idCreator(length = 5) {
1321
- // remove numeral
1322
- const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
1323
- const maxPosition = $chars.length;
1324
- let key = '';
1325
- for (let i = 0; i < length; i++) {
1326
- key += $chars.charAt(Math.floor(Math.random() * maxPosition));
1230
+ const getMovingElements = (board) => {
1231
+ return BOARD_TO_MOVING_ELEMENT.get(board) || [];
1232
+ };
1233
+ const addMovingElements = (board, elements) => {
1234
+ const movingElements = getMovingElements(board);
1235
+ const newElements = elements.filter(item => !movingElements.find(movingElement => movingElement.key === item.key));
1236
+ cacheMovingElements(board, [...movingElements, ...newElements]);
1237
+ };
1238
+ const removeMovingElements = (board) => {
1239
+ BOARD_TO_MOVING_ELEMENT.delete(board);
1240
+ };
1241
+ const cacheMovingElements = (board, elements) => {
1242
+ BOARD_TO_MOVING_ELEMENT.set(board, elements);
1243
+ };
1244
+
1245
+ function cloneCSSStyle(nativeNode, clonedNode) {
1246
+ const targetStyle = clonedNode.style;
1247
+ if (!targetStyle) {
1248
+ return;
1249
+ }
1250
+ const sourceStyle = window.getComputedStyle(nativeNode);
1251
+ if (sourceStyle.cssText) {
1252
+ targetStyle.cssText = sourceStyle.cssText;
1253
+ targetStyle.transformOrigin = sourceStyle.transformOrigin;
1254
+ }
1255
+ else {
1256
+ Array.from(sourceStyle).forEach(name => {
1257
+ let value = sourceStyle.getPropertyValue(name);
1258
+ targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
1259
+ });
1327
1260
  }
1328
- return key;
1329
1261
  }
1330
-
1331
- /**
1332
- * drawRoundRectangle
1333
- */
1334
- function drawRoundRectangle(rs, x1, y1, x2, y2, options, outline = false, borderRadius) {
1335
- const width = Math.abs(x1 - x2);
1336
- const height = Math.abs(y1 - y2);
1337
- let radius = borderRadius || 0;
1338
- if (radius === 0) {
1339
- const defaultRadius = Math.min(width, height) / 8;
1340
- let radius = defaultRadius;
1341
- if (defaultRadius > MAX_RADIUS) {
1342
- radius = outline ? MAX_RADIUS + 2 : MAX_RADIUS;
1343
- }
1262
+ function createCanvas(width, height, fillStyle) {
1263
+ const canvas = document.createElement('canvas');
1264
+ const ctx = canvas.getContext('2d');
1265
+ canvas.width = width;
1266
+ canvas.height = height;
1267
+ canvas.style.width = `${width}px`;
1268
+ canvas.style.height = `${height}px`;
1269
+ ctx.strokeStyle = '#ffffff';
1270
+ ctx.fillStyle = fillStyle;
1271
+ ctx.fillRect(0, 0, width, height);
1272
+ return {
1273
+ canvas,
1274
+ ctx
1275
+ };
1276
+ }
1277
+ function isElementNode(node) {
1278
+ return node.nodeType === Node.ELEMENT_NODE;
1279
+ }
1280
+ function cloneSvg(board, options) {
1281
+ const elementHostBox = getRectangleByElements(board, board.children, true);
1282
+ const { width, height, x, y } = elementHostBox;
1283
+ const { padding = 4, inlineStyleClassNames } = options;
1284
+ const sourceSvg = PlaitBoard.getHost(board);
1285
+ const cloneSvgElement = sourceSvg.cloneNode(true);
1286
+ cloneSvgElement.style.width = `${width}px`;
1287
+ cloneSvgElement.style.height = `${height}px`;
1288
+ cloneSvgElement.style.backgroundColor = '';
1289
+ cloneSvgElement.setAttribute('width', `${width}`);
1290
+ cloneSvgElement.setAttribute('height', `${height}`);
1291
+ cloneSvgElement.setAttribute('viewBox', [x - padding, y - padding, width + 2 * padding, height + 2 * padding].join(','));
1292
+ if (inlineStyleClassNames) {
1293
+ const sourceNodes = Array.from(sourceSvg.querySelectorAll(inlineStyleClassNames));
1294
+ const cloneNodes = Array.from(cloneSvgElement.querySelectorAll(inlineStyleClassNames));
1295
+ sourceNodes.forEach((node, index) => {
1296
+ const cloneNode = cloneNodes[index];
1297
+ const childElements = Array.from(node.querySelectorAll('*')).filter(isElementNode);
1298
+ const cloneChildElements = Array.from(cloneNode.querySelectorAll('*')).filter(isElementNode);
1299
+ sourceNodes.push(...childElements);
1300
+ cloneNodes.push(...cloneChildElements);
1301
+ });
1302
+ sourceNodes.forEach((node, index) => {
1303
+ const cloneNode = cloneNodes[index];
1304
+ cloneCSSStyle(node, cloneNode);
1305
+ });
1344
1306
  }
1345
- const point1 = [x1 + radius, y1];
1346
- const point2 = [x2 - radius, y1];
1347
- const point3 = [x2, y1 + radius];
1348
- const point4 = [x2, y2 - radius];
1349
- const point5 = [x2 - radius, y2];
1350
- const point6 = [x1 + radius, y2];
1351
- const point7 = [x1, y2 - radius];
1352
- const point8 = [x1, y1 + radius];
1353
- return rs.path(`M${point2[0]} ${point2[1]} A ${radius} ${radius}, 0, 0, 1, ${point3[0]} ${point3[1]} L ${point4[0]} ${point4[1]} A ${radius} ${radius}, 0, 0, 1, ${point5[0]} ${point5[1]} L ${point6[0]} ${point6[1]} A ${radius} ${radius}, 0, 0, 1, ${point7[0]} ${point7[1]} L ${point8[0]} ${point8[1]} A ${radius} ${radius}, 0, 0, 1, ${point1[0]} ${point1[1]} Z`, options);
1307
+ return cloneSvgElement;
1354
1308
  }
1355
-
1356
- function arrowPoints(start, end, maxHypotenuseLength = 10, degree = 40) {
1357
- const width = Math.abs(start[0] - end[0]);
1358
- const height = Math.abs(start[1] - end[1]);
1359
- let hypotenuse = Math.hypot(width, height); // 斜边
1360
- const realRotateLine = hypotenuse > maxHypotenuseLength * 2 ? maxHypotenuseLength : hypotenuse / 2;
1361
- const rotateWidth = (realRotateLine / hypotenuse) * width;
1362
- const rotateHeight = (realRotateLine / hypotenuse) * height;
1363
- const rotatePoint = [
1364
- end[0] > start[0] ? end[0] - rotateWidth : end[0] + rotateWidth,
1365
- end[1] > start[1] ? end[1] - rotateHeight : end[1] + rotateHeight
1366
- ];
1367
- const pointRight = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (degree * Math.PI) / 180);
1368
- const pointLeft = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (-degree * Math.PI) / 180);
1369
- return { pointLeft, pointRight };
1309
+ function loadImage(src) {
1310
+ return new Promise((resolve, reject) => {
1311
+ const img = new Image();
1312
+ img.onload = () => resolve(img);
1313
+ img.onerror = () => reject(new Error('Failed to load image'));
1314
+ img.src = src;
1315
+ });
1370
1316
  }
1371
- function drawArrow(rs, start, end, options, maxHypotenuseLength = 10, degree = 40) {
1372
- const { pointLeft, pointRight } = arrowPoints(start, end, maxHypotenuseLength, degree);
1373
- const arrowLineLeft = rs.linearPath([pointLeft, end], options);
1374
- const arrowLineRight = rs.linearPath([pointRight, end], options);
1375
- return [arrowLineLeft, arrowLineRight];
1317
+ async function toImage(board, options) {
1318
+ if (!board) {
1319
+ return undefined;
1320
+ }
1321
+ const elementHostBox = getRectangleByElements(board, board.children, true);
1322
+ const { ratio = 2, fillStyle = 'transparent' } = options;
1323
+ const { width, height } = elementHostBox;
1324
+ const ratioWidth = width * ratio;
1325
+ const ratioHeight = height * ratio;
1326
+ const cloneSvgElement = cloneSvg(board, options);
1327
+ const { canvas, ctx } = createCanvas(ratioWidth, ratioHeight, fillStyle);
1328
+ const svgStr = new XMLSerializer().serializeToString(cloneSvgElement);
1329
+ const imgSrc = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;
1330
+ try {
1331
+ const img = await loadImage(imgSrc);
1332
+ ctx.drawImage(img, 0, 0, ratioWidth, ratioHeight);
1333
+ const url = canvas.toDataURL('image/png');
1334
+ return url;
1335
+ }
1336
+ catch (error) {
1337
+ console.error('Error converting SVG to image:', error);
1338
+ return undefined;
1339
+ }
1376
1340
  }
1377
-
1378
- function drawCircle(roughSVG, point, diameter, options) {
1379
- return roughSVG.circle(point[0], point[1], diameter, options);
1341
+ function downloadImage(url, name) {
1342
+ const a = document.createElement('a');
1343
+ a.href = url;
1344
+ a.download = name;
1345
+ a.click();
1346
+ a.remove();
1380
1347
  }
1381
1348
 
1382
- function drawLine(rs, start, end, options) {
1383
- return rs.linearPath([start, end], options);
1384
- }
1385
- function drawLinearPath(points, options) {
1386
- const g = createG();
1387
- const path = createPath();
1388
- let polylinePath = '';
1389
- points.forEach((point, index) => {
1390
- if (index === 0) {
1391
- polylinePath += `M ${point[0]} ${point[1]} `;
1349
+ const getClipboardByKey = (key) => {
1350
+ return `application/x-plait-${key}-fragment`;
1351
+ };
1352
+ const setClipboardData = (data, elements) => {
1353
+ const result = [...elements];
1354
+ const pluginContextResult = getDataFromClipboard(data);
1355
+ if (pluginContextResult) {
1356
+ result.push(...pluginContextResult);
1357
+ }
1358
+ const stringObj = JSON.stringify(result);
1359
+ const encoded = window.btoa(encodeURIComponent(stringObj));
1360
+ data?.setData(`application/${CLIP_BOARD_FORMAT_KEY}`, encoded);
1361
+ };
1362
+ const setClipboardDataByText = (data, text) => {
1363
+ const pluginContextResult = getTextFromClipboard(data);
1364
+ data?.setData(`text/plain`, text + '\n' + pluginContextResult);
1365
+ };
1366
+ const setClipboardDataByMedia = (data, media, key) => {
1367
+ const stringObj = JSON.stringify(media);
1368
+ const encoded = window.btoa(encodeURIComponent(stringObj));
1369
+ data?.setData(getClipboardByKey(key), encoded);
1370
+ };
1371
+ const getDataFromClipboard = (data) => {
1372
+ const encoded = data?.getData(`application/${CLIP_BOARD_FORMAT_KEY}`);
1373
+ let nodesData = [];
1374
+ if (encoded) {
1375
+ const decoded = decodeURIComponent(window.atob(encoded));
1376
+ nodesData = JSON.parse(decoded);
1377
+ }
1378
+ return nodesData;
1379
+ };
1380
+ const getTextFromClipboard = (data) => {
1381
+ return (data ? data.getData(`text/plain`) : '');
1382
+ };
1383
+ const getClipboardDataByMedia = (data, key) => {
1384
+ const encoded = data?.getData(getClipboardByKey(key));
1385
+ let imageItem = null;
1386
+ if (encoded) {
1387
+ const decoded = decodeURIComponent(window.atob(encoded));
1388
+ imageItem = JSON.parse(decoded);
1389
+ }
1390
+ return imageItem;
1391
+ };
1392
+
1393
+ const BOARD_TO_TOUCH_REF = new WeakMap();
1394
+ const isPreventTouchMove = (board) => {
1395
+ return !!BOARD_TO_TOUCH_REF.get(board);
1396
+ };
1397
+ const preventTouchMove = (board, event, state) => {
1398
+ if (state && (event.target instanceof HTMLElement || event.target instanceof SVGElement)) {
1399
+ BOARD_TO_TOUCH_REF.set(board, { state, target: event.target instanceof SVGElement ? event.target : undefined });
1400
+ }
1401
+ else {
1402
+ const ref = BOARD_TO_TOUCH_REF.get(board);
1403
+ if (ref) {
1404
+ BOARD_TO_TOUCH_REF.delete(board);
1405
+ ref.host?.remove();
1406
+ }
1407
+ }
1408
+ };
1409
+ /**
1410
+ * some intersection maybe cause target is removed from current browser window,
1411
+ * after it was removed touch move event will not be fired
1412
+ * so scroll behavior will can not be prevented in mobile browser device
1413
+ * this function will prevent target element being remove.
1414
+ */
1415
+ const handleTouchTarget = (board) => {
1416
+ const touchRef = BOARD_TO_TOUCH_REF.get(board);
1417
+ if (touchRef && touchRef.target && !touchRef.target.contains(PlaitBoard.getElementActiveHost(board))) {
1418
+ touchRef.target.style.opacity = '0';
1419
+ const host = createG();
1420
+ host.appendChild(touchRef.target);
1421
+ touchRef.host = host;
1422
+ PlaitBoard.getElementActiveHost(board).append(host);
1423
+ }
1424
+ };
1425
+
1426
+ const PlaitElement = {
1427
+ isRootElement(value) {
1428
+ const parent = NODE_TO_PARENT.get(value);
1429
+ if (parent && PlaitBoard.isBoard(parent)) {
1430
+ return true;
1392
1431
  }
1393
1432
  else {
1394
- polylinePath += `L ${point[0]} ${point[1]} `;
1433
+ return false;
1395
1434
  }
1396
- });
1397
- path.setAttribute('d', polylinePath);
1398
- path.setAttribute('stroke', `${options?.stroke}`);
1399
- path.setAttribute('stroke-width', `${options?.strokeWidth}`);
1400
- path.setAttribute('fill', `none`);
1401
- g.appendChild(path);
1402
- return g;
1403
- }
1404
- function drawBezierPath(points, options) {
1405
- const g = createG();
1406
- const path = createPath();
1407
- let polylinePath = '';
1408
- for (let i = 0; i < points.length - 3; i += 3) {
1409
- if (i === 0) {
1410
- polylinePath += `M ${points[0][0]} ${points[0][1]} `;
1435
+ },
1436
+ getComponent(value) {
1437
+ return ELEMENT_TO_COMPONENT.get(value);
1438
+ }
1439
+ };
1440
+
1441
+ const Path = {
1442
+ /**
1443
+ * Get a list of ancestor paths for a given path.
1444
+ *
1445
+ * The paths are sorted from shallowest to deepest ancestor. However, if the
1446
+ * `reverse: true` option is passed, they are reversed.
1447
+ */
1448
+ ancestors(path, options = {}) {
1449
+ const { reverse = false } = options;
1450
+ let paths = Path.levels(path, options);
1451
+ if (reverse) {
1452
+ paths = paths.slice(1);
1453
+ }
1454
+ else {
1455
+ paths = paths.slice(0, -1);
1456
+ }
1457
+ return paths;
1458
+ },
1459
+ /**
1460
+ * Get a list of paths at every level down to a path. Note: this is the same
1461
+ * as `Path.ancestors`, but including the path itself.
1462
+ *
1463
+ * The paths are sorted from shallowest to deepest. However, if the `reverse:
1464
+ * true` option is passed, they are reversed.
1465
+ */
1466
+ levels(path, options = {}) {
1467
+ const { reverse = false } = options;
1468
+ const list = [];
1469
+ for (let i = 0; i <= path.length; i++) {
1470
+ list.push(path.slice(0, i));
1471
+ }
1472
+ if (reverse) {
1473
+ list.reverse();
1474
+ }
1475
+ return list;
1476
+ },
1477
+ parent(path) {
1478
+ if (path.length === 0) {
1479
+ throw new Error(`Cannot get the parent path of the root path [${path}].`);
1480
+ }
1481
+ return path.slice(0, -1);
1482
+ },
1483
+ next(path) {
1484
+ if (path.length === 0) {
1485
+ throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
1486
+ }
1487
+ const last = path[path.length - 1];
1488
+ return path.slice(0, -1).concat(last + 1);
1489
+ },
1490
+ hasPrevious(path) {
1491
+ return path[path.length - 1] > 0;
1492
+ },
1493
+ previous(path) {
1494
+ if (path.length === 0) {
1495
+ throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
1496
+ }
1497
+ const last = path[path.length - 1];
1498
+ return path.slice(0, -1).concat(last - 1);
1499
+ },
1500
+ /**
1501
+ * Check if a path is an ancestor of another.
1502
+ */
1503
+ isAncestor(path, another) {
1504
+ return path.length < another.length && Path.compare(path, another) === 0;
1505
+ },
1506
+ /**
1507
+ * Compare a path to another, returning an integer indicating whether the path
1508
+ * was before, at, or after the other.
1509
+ *
1510
+ * Note: Two paths of unequal length can still receive a `0` result if one is
1511
+ * directly above or below the other. If you want exact matching, use
1512
+ * [[Path.equals]] instead.
1513
+ */
1514
+ compare(path, another) {
1515
+ const min = Math.min(path.length, another.length);
1516
+ for (let i = 0; i < min; i++) {
1517
+ if (path[i] < another[i])
1518
+ return -1;
1519
+ if (path[i] > another[i])
1520
+ return 1;
1521
+ }
1522
+ return 0;
1523
+ },
1524
+ /**
1525
+ * Check if a path is exactly equal to another.
1526
+ */
1527
+ equals(path, another) {
1528
+ return path.length === another.length && path.every((n, i) => n === another[i]);
1529
+ },
1530
+ /**
1531
+ * Check if a path ends before one of the indexes in another.
1532
+ */
1533
+ endsBefore(path, another) {
1534
+ const i = path.length - 1;
1535
+ const as = path.slice(0, i);
1536
+ const bs = another.slice(0, i);
1537
+ const av = path[i];
1538
+ const bv = another[i];
1539
+ return Path.equals(as, bs) && av < bv;
1540
+ },
1541
+ /**
1542
+ * Check if a path is a sibling of another.
1543
+ */
1544
+ isSibling(path, another) {
1545
+ if (path.length !== another.length) {
1546
+ return false;
1547
+ }
1548
+ const as = path.slice(0, -1);
1549
+ const bs = another.slice(0, -1);
1550
+ const al = path[path.length - 1];
1551
+ const bl = another[another.length - 1];
1552
+ return al !== bl && Path.equals(as, bs);
1553
+ },
1554
+ transform(path, operation) {
1555
+ return produce(path, p => {
1556
+ // PERF: Exit early if the operation is guaranteed not to have an effect.
1557
+ if (!path || path?.length === 0) {
1558
+ return;
1559
+ }
1560
+ if (p === null) {
1561
+ return null;
1562
+ }
1563
+ switch (operation.type) {
1564
+ case 'insert_node': {
1565
+ const { path: op } = operation;
1566
+ if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
1567
+ p[op.length - 1] += 1;
1568
+ }
1569
+ break;
1570
+ }
1571
+ case 'remove_node': {
1572
+ const { path: op } = operation;
1573
+ if (Path.equals(op, p) || Path.isAncestor(op, p)) {
1574
+ return null;
1575
+ }
1576
+ else if (Path.endsBefore(op, p)) {
1577
+ p[op.length - 1] -= 1;
1578
+ }
1579
+ break;
1580
+ }
1581
+ case 'move_node': {
1582
+ const { path: op, newPath: onp } = operation;
1583
+ // If the old and new path are the same, it's a no-op.
1584
+ if (Path.equals(op, onp)) {
1585
+ return;
1586
+ }
1587
+ if (Path.isAncestor(op, p) || Path.equals(op, p)) {
1588
+ const copy = onp.slice();
1589
+ // op.length <= onp.length is different for slate
1590
+ // resolve drag from [0, 0] to [0, 3] issue
1591
+ if (Path.endsBefore(op, onp) && op.length <= onp.length) {
1592
+ copy[op.length - 1] -= 1;
1593
+ }
1594
+ return copy.concat(p.slice(op.length));
1595
+ }
1596
+ else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
1597
+ if (Path.endsBefore(op, p)) {
1598
+ p[op.length - 1] -= 1;
1599
+ }
1600
+ else {
1601
+ p[op.length - 1] += 1;
1602
+ }
1603
+ }
1604
+ else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
1605
+ if (Path.endsBefore(op, p)) {
1606
+ p[op.length - 1] -= 1;
1607
+ }
1608
+ p[onp.length - 1] += 1;
1609
+ }
1610
+ else if (Path.endsBefore(op, p)) {
1611
+ if (Path.equals(onp, p)) {
1612
+ p[onp.length - 1] += 1;
1613
+ }
1614
+ p[op.length - 1] -= 1;
1615
+ }
1616
+ break;
1617
+ }
1618
+ }
1619
+ return p;
1620
+ });
1621
+ }
1622
+ };
1623
+
1624
+ const PlaitNode = {
1625
+ parent: (board, path) => {
1626
+ const parentPath = Path.parent(path);
1627
+ const p = PlaitNode.get(board, parentPath);
1628
+ return p;
1629
+ },
1630
+ /**
1631
+ * Return a generator of all the ancestor nodes above a specific path.
1632
+ *
1633
+ * By default the order is top-down, from highest to lowest ancestor in
1634
+ * the tree, but you can pass the `reverse: true` option to go bottom-up.
1635
+ */
1636
+ *parents(root, path, options = {}) {
1637
+ for (const p of Path.ancestors(path, options)) {
1638
+ const n = PlaitNode.get(root, p);
1639
+ yield n;
1411
1640
  }
1412
- else {
1413
- polylinePath += `C ${points[i + 1][0]} ${points[i + 1][1]}, ${points[i + 2][0]} ${points[i + 2][1]}, ${points[i + 3][0]} ${points[i + 3][1]}`;
1641
+ },
1642
+ get(root, path) {
1643
+ let node = root;
1644
+ for (let i = 0; i < path.length; i++) {
1645
+ const p = path[i];
1646
+ if (!node || !node.children || !node.children[p]) {
1647
+ throw new Error(`Cannot find a descendant at path [${path}]`);
1648
+ }
1649
+ node = node.children[p];
1650
+ }
1651
+ return node;
1652
+ },
1653
+ last(board, path) {
1654
+ let n = PlaitNode.get(board, path);
1655
+ while (n && n.children && n.children.length > 0) {
1656
+ const i = n.children.length - 1;
1657
+ n = n.children[i];
1414
1658
  }
1659
+ return n;
1415
1660
  }
1416
- path.setAttribute('d', polylinePath);
1417
- path.setAttribute('stroke', `${options?.stroke}`);
1418
- path.setAttribute('stroke-width', `${options?.strokeWidth}`);
1419
- path.setAttribute('fill', `none`);
1420
- g.appendChild(path);
1421
- return g;
1422
- }
1661
+ };
1423
1662
 
1424
- let timerId = null;
1425
- const throttleRAF = (fn) => {
1426
- const scheduleFunc = () => {
1427
- timerId = requestAnimationFrame(() => {
1428
- timerId = null;
1429
- fn();
1430
- });
1431
- };
1432
- if (timerId !== null) {
1433
- cancelAnimationFrame(timerId);
1434
- timerId = null;
1435
- }
1436
- scheduleFunc();
1663
+ const isSetViewportOperation = (value) => {
1664
+ return value.type === 'set_viewport';
1437
1665
  };
1438
- const debounce = (func, wait, options) => {
1439
- let timerSubscription = null;
1440
- return () => {
1441
- if (timerSubscription && !timerSubscription.closed) {
1442
- timerSubscription.unsubscribe();
1443
- timerSubscription = timer(wait).subscribe(() => {
1444
- func();
1445
- });
1666
+ const inverse = (op) => {
1667
+ switch (op.type) {
1668
+ case 'insert_node': {
1669
+ return { ...op, type: 'remove_node' };
1446
1670
  }
1447
- else {
1448
- if (options?.leading) {
1449
- func();
1671
+ case 'remove_node': {
1672
+ return { ...op, type: 'insert_node' };
1673
+ }
1674
+ case 'move_node': {
1675
+ const { newPath, path } = op;
1676
+ // PERF: in this case the move operation is a no-op anyways.
1677
+ if (Path.equals(newPath, path)) {
1678
+ return op;
1450
1679
  }
1451
- timerSubscription = timer(wait).subscribe();
1680
+ // when operation path is [0,0] -> [0,2], should exec Path.transform to get [0,1] -> [0,0]
1681
+ // shoud not return [0,2] -> [0,0] #WIK-8981
1682
+ // if (Path.isSibling(path, newPath)) {
1683
+ // return { ...op, path: newPath, newPath: path };
1684
+ // }
1685
+ // If the move does not happen within a single parent it is possible
1686
+ // for the move to impact the true path to the location where the node
1687
+ // was removed from and where it was inserted. We have to adjust for this
1688
+ // and find the original path. We can accomplish this (only in non-sibling)
1689
+ // moves by looking at the impact of the move operation on the node
1690
+ // after the original move path.
1691
+ const inversePath = Path.transform(path, op);
1692
+ const inverseNewPath = Path.transform(Path.next(path), op);
1693
+ return { ...op, path: inversePath, newPath: inverseNewPath };
1452
1694
  }
1453
- };
1695
+ case 'set_node': {
1696
+ const { properties, newProperties } = op;
1697
+ return { ...op, properties: newProperties, newProperties: properties };
1698
+ }
1699
+ case 'set_selection': {
1700
+ const { properties, newProperties } = op;
1701
+ if (properties == null) {
1702
+ return {
1703
+ ...op,
1704
+ properties: newProperties,
1705
+ newProperties: null
1706
+ };
1707
+ }
1708
+ else if (newProperties == null) {
1709
+ return {
1710
+ ...op,
1711
+ properties: null,
1712
+ newProperties: properties
1713
+ };
1714
+ }
1715
+ else {
1716
+ return { ...op, properties: newProperties, newProperties: properties };
1717
+ }
1718
+ }
1719
+ case 'set_viewport': {
1720
+ const { properties, newProperties } = op;
1721
+ if (properties == null) {
1722
+ return {
1723
+ ...op,
1724
+ properties: newProperties,
1725
+ newProperties: newProperties
1726
+ };
1727
+ }
1728
+ else if (newProperties == null) {
1729
+ return {
1730
+ ...op,
1731
+ properties: properties,
1732
+ newProperties: properties
1733
+ };
1734
+ }
1735
+ else {
1736
+ return { ...op, properties: newProperties, newProperties: properties };
1737
+ }
1738
+ }
1739
+ case 'set_theme': {
1740
+ const { properties, newProperties } = op;
1741
+ return { ...op, properties: newProperties, newProperties: properties };
1742
+ }
1743
+ }
1744
+ };
1745
+ const PlaitOperation = {
1746
+ isSetViewportOperation,
1747
+ inverse
1454
1748
  };
1455
1749
 
1456
- const getMovingElements = (board) => {
1457
- return BOARD_TO_MOVING_ELEMENT.get(board) || [];
1750
+ const Point = {
1751
+ isEquals(point, otherPoint) {
1752
+ return point && otherPoint && point[0] === otherPoint[0] && point[1] === otherPoint[1];
1753
+ }
1458
1754
  };
1459
- const addMovingElements = (board, elements) => {
1460
- const movingElements = getMovingElements(board);
1461
- const newElements = elements.filter(item => !movingElements.find(movingElement => movingElement.key === item.key));
1462
- cacheMovingElements(board, [...movingElements, ...newElements]);
1755
+
1756
+ const Viewport = {
1757
+ isViewport: (value) => {
1758
+ return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
1759
+ },
1463
1760
  };
1464
- const removeMovingElements = (board) => {
1465
- BOARD_TO_MOVING_ELEMENT.delete(board);
1761
+
1762
+ const SAVING = new WeakMap();
1763
+ const MERGING = new WeakMap();
1764
+
1765
+ var PlaitPluginKey;
1766
+ (function (PlaitPluginKey) {
1767
+ PlaitPluginKey["withSelection"] = "withSelection";
1768
+ })(PlaitPluginKey || (PlaitPluginKey = {}));
1769
+
1770
+ var ThemeColorMode;
1771
+ (function (ThemeColorMode) {
1772
+ ThemeColorMode["default"] = "default";
1773
+ ThemeColorMode["colorful"] = "colorful";
1774
+ ThemeColorMode["soft"] = "soft";
1775
+ ThemeColorMode["retro"] = "retro";
1776
+ ThemeColorMode["dark"] = "dark";
1777
+ ThemeColorMode["starry"] = "starry";
1778
+ })(ThemeColorMode || (ThemeColorMode = {}));
1779
+ const DefaultThemeColor = {
1780
+ mode: ThemeColorMode.default,
1781
+ boardBackground: '#ffffff',
1782
+ textColor: '#333333'
1466
1783
  };
1467
- const cacheMovingElements = (board, elements) => {
1468
- BOARD_TO_MOVING_ELEMENT.set(board, elements);
1784
+ const ColorfulThemeColor = {
1785
+ mode: ThemeColorMode.colorful,
1786
+ boardBackground: '#ffffff',
1787
+ textColor: '#333333'
1788
+ };
1789
+ const SoftThemeColor = {
1790
+ mode: ThemeColorMode.soft,
1791
+ boardBackground: '#f5f5f5',
1792
+ textColor: '#333333'
1793
+ };
1794
+ const RetroThemeColor = {
1795
+ mode: ThemeColorMode.retro,
1796
+ boardBackground: '#f9f8ed',
1797
+ textColor: '#333333'
1798
+ };
1799
+ const DarkThemeColor = {
1800
+ mode: ThemeColorMode.dark,
1801
+ boardBackground: '#141414',
1802
+ textColor: '#FFFFFF'
1803
+ };
1804
+ const StarryThemeColor = {
1805
+ mode: ThemeColorMode.starry,
1806
+ boardBackground: '#0d2537',
1807
+ textColor: '#FFFFFF'
1469
1808
  };
1809
+ const ThemeColors = [
1810
+ DefaultThemeColor,
1811
+ ColorfulThemeColor,
1812
+ SoftThemeColor,
1813
+ RetroThemeColor,
1814
+ DarkThemeColor,
1815
+ StarryThemeColor
1816
+ ];
1470
1817
 
1471
- function cloneCSSStyle(nativeNode, clonedNode) {
1472
- const targetStyle = clonedNode.style;
1473
- if (!targetStyle) {
1474
- return;
1475
- }
1476
- const sourceStyle = window.getComputedStyle(nativeNode);
1477
- if (sourceStyle.cssText) {
1478
- targetStyle.cssText = sourceStyle.cssText;
1479
- targetStyle.transformOrigin = sourceStyle.transformOrigin;
1480
- }
1481
- else {
1482
- Array.from(sourceStyle).forEach(name => {
1483
- let value = sourceStyle.getPropertyValue(name);
1484
- targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
1485
- });
1818
+ function getRectangleByElements(board, elements, recursion) {
1819
+ const boundaryBox = {
1820
+ left: Number.MAX_VALUE,
1821
+ top: Number.MAX_VALUE,
1822
+ right: Number.NEGATIVE_INFINITY,
1823
+ bottom: Number.NEGATIVE_INFINITY
1824
+ };
1825
+ const calcRectangleClient = (node) => {
1826
+ const nodeRectangle = board.getRectangle(node);
1827
+ if (nodeRectangle) {
1828
+ boundaryBox.left = Math.min(boundaryBox.left, nodeRectangle.x);
1829
+ boundaryBox.top = Math.min(boundaryBox.top, nodeRectangle.y);
1830
+ boundaryBox.right = Math.max(boundaryBox.right, nodeRectangle.x + nodeRectangle.width);
1831
+ boundaryBox.bottom = Math.max(boundaryBox.bottom, nodeRectangle.y + nodeRectangle.height);
1832
+ }
1833
+ else {
1834
+ console.error(`can not get rectangle of element:`, node);
1835
+ }
1836
+ };
1837
+ elements.forEach(element => {
1838
+ if (recursion) {
1839
+ depthFirstRecursion(element, node => calcRectangleClient(node), node => board.isRecursion(node));
1840
+ }
1841
+ else {
1842
+ calcRectangleClient(element);
1843
+ }
1844
+ });
1845
+ if (boundaryBox.left === Number.MAX_VALUE) {
1846
+ return {
1847
+ x: 0,
1848
+ y: 0,
1849
+ width: 0,
1850
+ height: 0
1851
+ };
1486
1852
  }
1487
- }
1488
- function createCanvas(width, height, fillStyle) {
1489
- const canvas = document.createElement('canvas');
1490
- const ctx = canvas.getContext('2d');
1491
- canvas.width = width;
1492
- canvas.height = height;
1493
- canvas.style.width = `${width}px`;
1494
- canvas.style.height = `${height}px`;
1495
- ctx.strokeStyle = '#ffffff';
1496
- ctx.fillStyle = fillStyle;
1497
- ctx.fillRect(0, 0, width, height);
1498
1853
  return {
1499
- canvas,
1500
- ctx
1854
+ x: boundaryBox.left,
1855
+ y: boundaryBox.top,
1856
+ width: boundaryBox.right - boundaryBox.left,
1857
+ height: boundaryBox.bottom - boundaryBox.top
1501
1858
  };
1502
1859
  }
1503
- function isElementNode(node) {
1504
- return node.nodeType === Node.ELEMENT_NODE;
1505
- }
1506
- function cloneSvg(board, options) {
1507
- const elementHostBox = getRectangleByElements(board, board.children, true);
1508
- const { width, height, x, y } = elementHostBox;
1509
- const { padding = 4, inlineStyleClassNames } = options;
1510
- const sourceSvg = PlaitBoard.getHost(board);
1511
- const cloneSvgElement = sourceSvg.cloneNode(true);
1512
- cloneSvgElement.style.width = `${width}px`;
1513
- cloneSvgElement.style.height = `${height}px`;
1514
- cloneSvgElement.style.backgroundColor = '';
1515
- cloneSvgElement.setAttribute('width', `${width}`);
1516
- cloneSvgElement.setAttribute('height', `${height}`);
1517
- cloneSvgElement.setAttribute('viewBox', [x - padding, y - padding, width + 2 * padding, height + 2 * padding].join(','));
1518
- if (inlineStyleClassNames) {
1519
- const sourceNodes = Array.from(sourceSvg.querySelectorAll(inlineStyleClassNames));
1520
- const cloneNodes = Array.from(cloneSvgElement.querySelectorAll(inlineStyleClassNames));
1521
- sourceNodes.forEach((node, index) => {
1522
- const cloneNode = cloneNodes[index];
1523
- const childElements = Array.from(node.querySelectorAll('*')).filter(isElementNode);
1524
- const cloneChildElements = Array.from(cloneNode.querySelectorAll('*')).filter(isElementNode);
1525
- sourceNodes.push(...childElements);
1526
- cloneNodes.push(...cloneChildElements);
1527
- });
1528
- sourceNodes.forEach((node, index) => {
1529
- const cloneNode = cloneNodes[index];
1530
- cloneCSSStyle(node, cloneNode);
1531
- });
1532
- }
1533
- return cloneSvgElement;
1534
- }
1535
- function loadImage(src) {
1536
- return new Promise((resolve, reject) => {
1537
- const img = new Image();
1538
- img.onload = () => resolve(img);
1539
- img.onerror = () => reject(new Error('Failed to load image'));
1540
- img.src = src;
1541
- });
1860
+ function getBoardRectangle(board) {
1861
+ return getRectangleByElements(board, board.children, true);
1542
1862
  }
1543
- async function toImage(board, options) {
1544
- if (!board) {
1545
- return undefined;
1546
- }
1547
- const elementHostBox = getRectangleByElements(board, board.children, true);
1548
- const { ratio = 2, fillStyle = 'transparent' } = options;
1549
- const { width, height } = elementHostBox;
1550
- const ratioWidth = width * ratio;
1551
- const ratioHeight = height * ratio;
1552
- const cloneSvgElement = cloneSvg(board, options);
1553
- const { canvas, ctx } = createCanvas(ratioWidth, ratioHeight, fillStyle);
1554
- const svgStr = new XMLSerializer().serializeToString(cloneSvgElement);
1555
- const imgSrc = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;
1556
- try {
1557
- const img = await loadImage(imgSrc);
1558
- ctx.drawImage(img, 0, 0, ratioWidth, ratioHeight);
1559
- const url = canvas.toDataURL('image/png');
1560
- return url;
1561
- }
1562
- catch (error) {
1563
- console.error('Error converting SVG to image:', error);
1564
- return undefined;
1863
+ function getElementById(board, id, dataSource) {
1864
+ if (!dataSource) {
1865
+ dataSource = findElements(board, { match: (element) => true, recursion: (element) => true });
1565
1866
  }
1867
+ let element = dataSource.find((element) => element.id === id);
1868
+ return element;
1566
1869
  }
1567
- function downloadImage(url, name) {
1568
- const a = document.createElement('a');
1569
- a.href = url;
1570
- a.download = name;
1571
- a.click();
1572
- a.remove();
1870
+ function findElements(board, options) {
1871
+ let elements = [];
1872
+ depthFirstRecursion(board, node => {
1873
+ if (!PlaitBoard.isBoard(node) && options.match(node)) {
1874
+ elements.push(node);
1875
+ }
1876
+ }, (value) => {
1877
+ if (PlaitBoard.isBoard(value)) {
1878
+ return true;
1879
+ }
1880
+ else {
1881
+ return getIsRecursionFunc(board)(value) && options.recursion(value);
1882
+ }
1883
+ }, true);
1884
+ return elements;
1573
1885
  }
1574
1886
 
1575
- const getClipboardByKey = (key) => {
1576
- return `application/x-plait-${key}-fragment`;
1577
- };
1578
- const setClipboardData = (data, elements) => {
1579
- const result = [...elements];
1580
- const pluginContextResult = getDataFromClipboard(data);
1581
- if (pluginContextResult) {
1582
- result.push(...pluginContextResult);
1583
- }
1584
- const stringObj = JSON.stringify(result);
1585
- const encoded = window.btoa(encodeURIComponent(stringObj));
1586
- data?.setData(`application/${CLIP_BOARD_FORMAT_KEY}`, encoded);
1587
- };
1588
- const setClipboardDataByText = (data, text) => {
1589
- const pluginContextResult = getTextFromClipboard(data);
1590
- data?.setData(`text/plain`, text + '\n' + pluginContextResult);
1591
- };
1592
- const setClipboardDataByMedia = (data, media, key) => {
1593
- const stringObj = JSON.stringify(media);
1594
- const encoded = window.btoa(encodeURIComponent(stringObj));
1595
- data?.setData(getClipboardByKey(key), encoded);
1596
- };
1597
- const getDataFromClipboard = (data) => {
1598
- const encoded = data?.getData(`application/${CLIP_BOARD_FORMAT_KEY}`);
1599
- let nodesData = [];
1600
- if (encoded) {
1601
- const decoded = decodeURIComponent(window.atob(encoded));
1602
- nodesData = JSON.parse(decoded);
1887
+ const PlaitBoard = {
1888
+ isBoard(value) {
1889
+ const cachedIsBoard = IS_BOARD_CACHE.get(value);
1890
+ if (cachedIsBoard !== undefined) {
1891
+ return cachedIsBoard;
1892
+ }
1893
+ const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
1894
+ IS_BOARD_CACHE.set(value, isBoard);
1895
+ return isBoard;
1896
+ },
1897
+ findPath(board, node) {
1898
+ const path = [];
1899
+ let child = node;
1900
+ while (true) {
1901
+ const parent = NODE_TO_PARENT.get(child);
1902
+ if (parent == null) {
1903
+ if (PlaitBoard.isBoard(child)) {
1904
+ return path;
1905
+ }
1906
+ else {
1907
+ break;
1908
+ }
1909
+ }
1910
+ const i = NODE_TO_INDEX.get(child);
1911
+ if (i == null) {
1912
+ break;
1913
+ }
1914
+ path.unshift(i);
1915
+ child = parent;
1916
+ }
1917
+ throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
1918
+ },
1919
+ getHost(board) {
1920
+ return BOARD_TO_HOST.get(board);
1921
+ },
1922
+ getElementHost(board) {
1923
+ return BOARD_TO_ELEMENT_HOST.get(board)?.host;
1924
+ },
1925
+ getElementUpperHost(board) {
1926
+ return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
1927
+ },
1928
+ getElementActiveHost(board) {
1929
+ return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
1930
+ },
1931
+ getRoughSVG(board) {
1932
+ return BOARD_TO_ROUGH_SVG.get(board);
1933
+ },
1934
+ getComponent(board) {
1935
+ return BOARD_TO_COMPONENT.get(board);
1936
+ },
1937
+ getBoardContainer(board) {
1938
+ return PlaitBoard.getComponent(board).nativeElement;
1939
+ },
1940
+ getRectangle(board) {
1941
+ return getRectangleByElements(board, board.children, true);
1942
+ },
1943
+ getViewportContainer(board) {
1944
+ return PlaitBoard.getHost(board).parentElement;
1945
+ },
1946
+ isFocus(board) {
1947
+ return !!board.selection;
1948
+ },
1949
+ isReadonly(board) {
1950
+ return board.options.readonly;
1951
+ },
1952
+ hasBeenTextEditing(board) {
1953
+ return !!IS_TEXT_EDITABLE.get(board);
1954
+ },
1955
+ getPointer(board) {
1956
+ return board.pointer;
1957
+ },
1958
+ isPointer(board, pointer) {
1959
+ return board.pointer === pointer;
1960
+ },
1961
+ isInPointer(board, pointers) {
1962
+ const point = board.pointer;
1963
+ return pointers.includes(point);
1964
+ },
1965
+ getMovingPointInBoard(board) {
1966
+ return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
1967
+ },
1968
+ isMovingPointInBoard(board) {
1969
+ const point = BOARD_TO_MOVING_POINT.get(board);
1970
+ const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1971
+ if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
1972
+ return true;
1973
+ }
1974
+ return false;
1975
+ },
1976
+ getThemeColors(board) {
1977
+ return (board.options.themeColors || ThemeColors);
1603
1978
  }
1604
- return nodesData;
1605
- };
1606
- const getTextFromClipboard = (data) => {
1607
- return (data ? data.getData(`text/plain`) : '');
1608
1979
  };
1609
- const getClipboardDataByMedia = (data, key) => {
1610
- const encoded = data?.getData(getClipboardByKey(key));
1611
- let imageItem = null;
1612
- if (encoded) {
1613
- const decoded = decodeURIComponent(window.atob(encoded));
1614
- imageItem = JSON.parse(decoded);
1980
+
1981
+ const applyToDraft = (board, selection, viewport, theme, op) => {
1982
+ switch (op.type) {
1983
+ case 'insert_node': {
1984
+ const { path, node } = op;
1985
+ const parent = PlaitNode.parent(board, path);
1986
+ const index = path[path.length - 1];
1987
+ if (!parent.children || index > parent.children.length) {
1988
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
1989
+ }
1990
+ parent.children.splice(index, 0, node);
1991
+ break;
1992
+ }
1993
+ case 'remove_node': {
1994
+ const { path } = op;
1995
+ const parent = PlaitNode.parent(board, path);
1996
+ const index = path[path.length - 1];
1997
+ if (!parent.children || index > parent.children.length) {
1998
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
1999
+ }
2000
+ parent.children.splice(index, 1);
2001
+ break;
2002
+ }
2003
+ case 'move_node': {
2004
+ const { path, newPath } = op;
2005
+ if (Path.isAncestor(path, newPath)) {
2006
+ throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
2007
+ }
2008
+ const node = PlaitNode.get(board, path);
2009
+ const parent = PlaitNode.parent(board, path);
2010
+ const index = path[path.length - 1];
2011
+ // This is tricky, but since the `path` and `newPath` both refer to
2012
+ // the same snapshot in time, there's a mismatch. After either
2013
+ // removing the original position, the second step's path can be out
2014
+ // of date. So instead of using the `op.newPath` directly, we
2015
+ // transform `op.path` to ascertain what the `newPath` would be after
2016
+ // the operation was applied.
2017
+ parent.children?.splice(index, 1);
2018
+ const truePath = Path.transform(path, op);
2019
+ const newParent = PlaitNode.get(board, Path.parent(truePath));
2020
+ const newIndex = truePath[truePath.length - 1];
2021
+ newParent.children?.splice(newIndex, 0, node);
2022
+ break;
2023
+ }
2024
+ case 'set_node': {
2025
+ const { path, properties, newProperties } = op;
2026
+ if (path.length === 0) {
2027
+ throw new Error(`Cannot set properties on the root node!`);
2028
+ }
2029
+ const node = PlaitNode.get(board, path);
2030
+ for (const key in newProperties) {
2031
+ const value = newProperties[key];
2032
+ if (value == null) {
2033
+ delete node[key];
2034
+ }
2035
+ else {
2036
+ node[key] = value;
2037
+ }
2038
+ }
2039
+ // properties that were previously defined, but are now missing, must be deleted
2040
+ for (const key in properties) {
2041
+ if (!newProperties.hasOwnProperty(key)) {
2042
+ delete node[key];
2043
+ }
2044
+ }
2045
+ break;
2046
+ }
2047
+ case 'set_viewport': {
2048
+ const { newProperties } = op;
2049
+ if (newProperties == null) {
2050
+ viewport = newProperties;
2051
+ }
2052
+ else {
2053
+ if (viewport == null) {
2054
+ if (!Viewport.isViewport(newProperties)) {
2055
+ throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
2056
+ }
2057
+ viewport = { ...newProperties };
2058
+ }
2059
+ for (const key in newProperties) {
2060
+ const value = newProperties[key];
2061
+ if (value == null) {
2062
+ delete viewport[key];
2063
+ }
2064
+ else {
2065
+ viewport[key] = value;
2066
+ }
2067
+ }
2068
+ }
2069
+ break;
2070
+ }
2071
+ case 'set_selection': {
2072
+ const { newProperties } = op;
2073
+ if (newProperties == null) {
2074
+ selection = newProperties;
2075
+ }
2076
+ else {
2077
+ if (selection === null) {
2078
+ selection = op.newProperties;
2079
+ }
2080
+ else {
2081
+ selection.ranges = newProperties.ranges;
2082
+ }
2083
+ }
2084
+ break;
2085
+ }
2086
+ case 'set_theme': {
2087
+ const { newProperties } = op;
2088
+ theme = newProperties;
2089
+ break;
2090
+ }
1615
2091
  }
1616
- return imageItem;
1617
- };
1618
-
1619
- const isPreventTouchMove = (board) => {
1620
- return !!IS_PREVENT_TOUCH_MOVE.get(board);
1621
- };
1622
- const preventTouchMove = (board, state) => {
1623
- IS_PREVENT_TOUCH_MOVE.set(board, state);
2092
+ return { selection, viewport, theme };
1624
2093
  };
1625
-
1626
- const PlaitElement = {
1627
- isRootElement(value) {
1628
- const parent = NODE_TO_PARENT.get(value);
1629
- if (parent && PlaitBoard.isBoard(parent)) {
1630
- return true;
2094
+ const GeneralTransforms = {
2095
+ /**
2096
+ * Transform the board by an operation.
2097
+ */
2098
+ transform(board, op) {
2099
+ board.children = createDraft(board.children);
2100
+ let viewport = board.viewport && createDraft(board.viewport);
2101
+ let selection = board.selection && createDraft(board.selection);
2102
+ let theme = board.theme && createDraft(board.theme);
2103
+ try {
2104
+ const state = applyToDraft(board, selection, viewport, theme, op);
2105
+ viewport = state.viewport;
2106
+ selection = state.selection;
2107
+ theme = state.theme;
1631
2108
  }
1632
- else {
1633
- return false;
2109
+ finally {
2110
+ board.children = finishDraft(board.children);
2111
+ if (selection) {
2112
+ board.selection = isDraft(selection) ? finishDraft(selection) : selection;
2113
+ }
2114
+ else {
2115
+ board.selection = null;
2116
+ }
2117
+ board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
2118
+ board.theme = isDraft(theme) ? finishDraft(theme) : theme;
1634
2119
  }
1635
- },
1636
- getComponent(value) {
1637
- return ELEMENT_TO_COMPONENT.get(value);
1638
2120
  }
1639
2121
  };
1640
2122
 
1641
- const RectangleClient = {
1642
- isHit: (origin, target) => {
1643
- const minX = origin.x < target.x ? origin.x : target.x;
1644
- const maxX = origin.x + origin.width > target.x + target.width ? origin.x + origin.width : target.x + target.width;
1645
- const minY = origin.y < target.y ? origin.y : target.y;
1646
- const maxY = origin.y + origin.height > target.y + target.height ? origin.y + origin.height : target.y + target.height;
1647
- // float calculate error( eg: 1.4210854715202004e-14 > 0)
1648
- if (Math.floor(maxX - minX - origin.width - target.width) <= 0 && Math.floor(maxY - minY - origin.height - target.height) <= 0) {
1649
- return true;
1650
- }
1651
- else {
1652
- return false;
2123
+ function insertNode(board, node, path) {
2124
+ const operation = { type: 'insert_node', node, path };
2125
+ board.apply(operation);
2126
+ }
2127
+ function setNode(board, props, path) {
2128
+ const properties = {};
2129
+ const newProperties = {};
2130
+ const node = PlaitNode.get(board, path);
2131
+ for (const k in props) {
2132
+ if (node[k] !== props[k]) {
2133
+ if (node.hasOwnProperty(k)) {
2134
+ properties[k] = node[k];
2135
+ }
2136
+ if (props[k] != null)
2137
+ newProperties[k] = props[k];
1653
2138
  }
1654
- },
1655
- toRectangleClient: (points) => {
1656
- const xArray = points.map(ele => ele[0]);
1657
- const yArray = points.map(ele => ele[1]);
1658
- const xMin = Math.min(...xArray);
1659
- const xMax = Math.max(...xArray);
1660
- const yMin = Math.min(...yArray);
1661
- const yMax = Math.max(...yArray);
1662
- const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
1663
- return rect;
1664
- },
1665
- getOutlineRectangle: (rectangle, offset) => {
1666
- return {
1667
- x: rectangle.x + offset,
1668
- y: rectangle.y + offset,
1669
- width: rectangle.width + Math.abs(offset) * 2,
1670
- height: rectangle.height + Math.abs(offset) * 2
1671
- };
1672
- },
1673
- isEqual: (rectangle, otherRectangle) => {
1674
- return (rectangle.x === otherRectangle.x &&
1675
- rectangle.y === otherRectangle.y &&
1676
- rectangle.width === otherRectangle.width &&
1677
- rectangle.height === otherRectangle.height);
1678
2139
  }
2140
+ const operation = { type: 'set_node', properties, newProperties, path };
2141
+ board.apply(operation);
2142
+ }
2143
+ function removeNode(board, path) {
2144
+ const node = PlaitNode.get(board, path);
2145
+ const operation = { type: 'remove_node', path, node };
2146
+ board.apply(operation);
2147
+ }
2148
+ function moveNode(board, path, newPath) {
2149
+ const operation = { type: 'move_node', path, newPath };
2150
+ board.apply(operation);
2151
+ }
2152
+ const NodeTransforms = {
2153
+ insertNode,
2154
+ setNode,
2155
+ removeNode,
2156
+ moveNode
1679
2157
  };
1680
2158
 
1681
- const isSetViewportOperation = (value) => {
1682
- return value.type === 'set_viewport';
1683
- };
1684
- const inverse = (op) => {
1685
- switch (op.type) {
1686
- case 'insert_node': {
1687
- return { ...op, type: 'remove_node' };
2159
+ function withSelection(board) {
2160
+ const { pointerDown, globalPointerMove, globalPointerUp, onChange } = board;
2161
+ let start = null;
2162
+ let end = null;
2163
+ let selectionMovingG;
2164
+ let selectionRectangleG;
2165
+ let previousSelectedElements;
2166
+ // prevent text from being selected when user pressed main pointer and is moving
2167
+ let needPreventNativeSelectionWhenMoving = false;
2168
+ board.pointerDown = (event) => {
2169
+ if (event.target instanceof Element && !event.target.closest('.plait-richtext-container')) {
2170
+ needPreventNativeSelectionWhenMoving = true;
1688
2171
  }
1689
- case 'remove_node': {
1690
- return { ...op, type: 'insert_node' };
2172
+ if (!isMainPointer(event)) {
2173
+ pointerDown(event);
2174
+ return;
1691
2175
  }
1692
- case 'move_node': {
1693
- const { newPath, path } = op;
1694
- // PERF: in this case the move operation is a no-op anyways.
1695
- if (Path.equals(newPath, path)) {
1696
- return op;
1697
- }
1698
- // when operation path is [0,0] -> [0,2], should exec Path.transform to get [0,1] -> [0,0]
1699
- // shoud not return [0,2] -> [0,0] #WIK-8981
1700
- // if (Path.isSibling(path, newPath)) {
1701
- // return { ...op, path: newPath, newPath: path };
1702
- // }
1703
- // If the move does not happen within a single parent it is possible
1704
- // for the move to impact the true path to the location where the node
1705
- // was removed from and where it was inserted. We have to adjust for this
1706
- // and find the original path. We can accomplish this (only in non-sibling)
1707
- // moves by looking at the impact of the move operation on the node
1708
- // after the original move path.
1709
- const inversePath = Path.transform(path, op);
1710
- const inverseNewPath = Path.transform(Path.next(path), op);
1711
- return { ...op, path: inversePath, newPath: inverseNewPath };
2176
+ const options = board.getPluginOptions(PlaitPluginKey.withSelection);
2177
+ const point = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
2178
+ const range = { anchor: point, focus: point };
2179
+ const hitElements = getHitElements(board, { ranges: [range] });
2180
+ const selectedElements = getSelectedElements(board);
2181
+ if (hitElements.length === 1 && selectedElements.includes(hitElements[0]) && !options.isDisabledSelect) {
2182
+ pointerDown(event);
2183
+ return;
2184
+ }
2185
+ if (PlaitBoard.isPointer(board, PlaitPointerType.selection) &&
2186
+ hitElements.length === 0 &&
2187
+ options.isMultiple &&
2188
+ !options.isDisabledSelect) {
2189
+ selectionRectangleG?.remove();
2190
+ start = point;
2191
+ preventTouchMove(board, event, true);
2192
+ }
2193
+ Transforms.setSelection(board, { ranges: [range] });
2194
+ pointerDown(event);
2195
+ };
2196
+ board.globalPointerMove = (event) => {
2197
+ if (needPreventNativeSelectionWhenMoving) {
2198
+ // prevent text from being selected
2199
+ event.preventDefault();
2200
+ }
2201
+ if (start) {
2202
+ const movedTarget = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
2203
+ const rectangle = RectangleClient.toRectangleClient([start, movedTarget]);
2204
+ selectionMovingG?.remove();
2205
+ if (Math.hypot(rectangle.width, rectangle.height) > 5) {
2206
+ end = movedTarget;
2207
+ throttleRAF(() => {
2208
+ if (start && end) {
2209
+ Transforms.setSelection(board, { ranges: [{ anchor: start, focus: end }] });
2210
+ }
2211
+ });
2212
+ setSelectionMoving(board);
2213
+ selectionMovingG = drawRectangle(board, rectangle, {
2214
+ stroke: SELECTION_BORDER_COLOR,
2215
+ strokeWidth: 1,
2216
+ fill: SELECTION_FILL_COLOR,
2217
+ fillStyle: 'solid'
2218
+ });
2219
+ PlaitBoard.getHost(board).append(selectionMovingG);
2220
+ }
1712
2221
  }
1713
- case 'set_node': {
1714
- const { properties, newProperties } = op;
1715
- return { ...op, properties: newProperties, newProperties: properties };
2222
+ globalPointerMove(event);
2223
+ };
2224
+ board.globalPointerUp = (event) => {
2225
+ if (start && end) {
2226
+ selectionMovingG?.remove();
2227
+ clearSelectionMoving(board);
2228
+ Transforms.setSelection(board, { ranges: [{ anchor: start, focus: end }] });
1716
2229
  }
1717
- case 'set_selection': {
1718
- const { properties, newProperties } = op;
1719
- if (properties == null) {
1720
- return {
1721
- ...op,
1722
- properties: newProperties,
1723
- newProperties: null
1724
- };
1725
- }
1726
- else if (newProperties == null) {
1727
- return {
1728
- ...op,
1729
- properties: null,
1730
- newProperties: properties
1731
- };
1732
- }
1733
- else {
1734
- return { ...op, properties: newProperties, newProperties: properties };
2230
+ if (PlaitBoard.isFocus(board)) {
2231
+ const isInBoard = event.target instanceof Node && PlaitBoard.getBoardContainer(board).contains(event.target);
2232
+ const isInDocument = event.target instanceof Node && document.contains(event.target);
2233
+ const isAttachedElement = event.target instanceof Element && event.target.closest(`.${ATTACHED_ELEMENT_CLASS_NAME}`);
2234
+ // Clear selection when mouse board outside area
2235
+ // The framework needs to determine whether the board is focused through selection
2236
+ if (!isInBoard && !start && !isAttachedElement && isInDocument) {
2237
+ Transforms.setSelection(board, null);
1735
2238
  }
1736
2239
  }
1737
- case 'set_viewport': {
1738
- const { properties, newProperties } = op;
1739
- if (properties == null) {
1740
- return {
1741
- ...op,
1742
- properties: newProperties,
1743
- newProperties: newProperties
1744
- };
1745
- }
1746
- else if (newProperties == null) {
1747
- return {
1748
- ...op,
1749
- properties: properties,
1750
- newProperties: properties
1751
- };
2240
+ start = null;
2241
+ end = null;
2242
+ needPreventNativeSelectionWhenMoving = false;
2243
+ preventTouchMove(board, event, false);
2244
+ globalPointerUp(event);
2245
+ };
2246
+ board.onChange = () => {
2247
+ const options = board.getPluginOptions(PlaitPluginKey.withSelection);
2248
+ if (options.isDisabledSelect) {
2249
+ clearSelectedElement(board);
2250
+ }
2251
+ // calc selected elements entry
2252
+ if (board.pointer !== PlaitPointerType.hand && !options.isDisabledSelect) {
2253
+ try {
2254
+ if (board.operations.find(value => value.type === 'set_selection')) {
2255
+ selectionRectangleG?.remove();
2256
+ const temporaryElements = getTemporaryElements(board);
2257
+ let elements = temporaryElements ? temporaryElements : getHitElements(board);
2258
+ if (!options.isMultiple && elements.length > 1) {
2259
+ elements = [elements[0]];
2260
+ }
2261
+ cacheSelectedElements(board, elements);
2262
+ previousSelectedElements = elements;
2263
+ deleteTemporaryElements(board);
2264
+ if (!isSelectionMoving(board) && elements.length > 1) {
2265
+ selectionRectangleG = createSelectionRectangleG(board);
2266
+ }
2267
+ }
2268
+ else {
2269
+ // wait node destroy and remove selected element state
2270
+ setTimeout(() => {
2271
+ const currentSelectedElements = getSelectedElements(board);
2272
+ if (currentSelectedElements.length && currentSelectedElements.length > 1) {
2273
+ if (currentSelectedElements.length !== previousSelectedElements.length ||
2274
+ currentSelectedElements.some((c, index) => c !== previousSelectedElements[index])) {
2275
+ selectionRectangleG?.remove();
2276
+ selectionRectangleG = createSelectionRectangleG(board);
2277
+ previousSelectedElements = currentSelectedElements;
2278
+ }
2279
+ }
2280
+ else {
2281
+ selectionRectangleG?.remove();
2282
+ }
2283
+ });
2284
+ }
1752
2285
  }
1753
- else {
1754
- return { ...op, properties: newProperties, newProperties: properties };
2286
+ catch (error) {
2287
+ console.error(error);
1755
2288
  }
1756
2289
  }
1757
- case 'set_theme': {
1758
- const { properties, newProperties } = op;
1759
- return { ...op, properties: newProperties, newProperties: properties };
1760
- }
1761
- }
1762
- };
1763
- const PlaitOperation = {
1764
- isSetViewportOperation,
1765
- inverse
1766
- };
1767
-
1768
- const Point = {
1769
- isEquals(point, otherPoint) {
1770
- return point && otherPoint && point[0] === otherPoint[0] && point[1] === otherPoint[1];
1771
- }
1772
- };
1773
-
1774
- const SAVING = new WeakMap();
1775
- const MERGING = new WeakMap();
1776
-
1777
- var PlaitPluginKey;
1778
- (function (PlaitPluginKey) {
1779
- PlaitPluginKey["withSelection"] = "withSelection";
1780
- })(PlaitPluginKey || (PlaitPluginKey = {}));
1781
-
1782
- const IS_FROM_SCROLLING = new WeakMap();
1783
- const IS_FROM_VIEWPORT_CHANGE = new WeakMap();
1784
- function getViewportContainerRect(board) {
1785
- const { hideScrollbar } = board.options;
1786
- const scrollBarWidth = hideScrollbar ? SCROLL_BAR_WIDTH : 0;
1787
- const viewportRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1788
- return {
1789
- width: viewportRect.width + scrollBarWidth,
1790
- height: viewportRect.height + scrollBarWidth
2290
+ onChange();
1791
2291
  };
2292
+ board.setPluginOptions(PlaitPluginKey.withSelection, {
2293
+ isMultiple: true,
2294
+ isDisabledSelect: false
2295
+ });
2296
+ return board;
1792
2297
  }
1793
- function getElementHostBBox(board, zoom) {
1794
- const childrenRect = getRectangleByElements(board, board.children, true);
1795
- const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1796
- const containerWidth = viewportContainerRect.width / zoom;
1797
- const containerHeight = viewportContainerRect.height / zoom;
1798
- let left;
1799
- let right;
1800
- let top;
1801
- let bottom;
1802
- if (childrenRect.width < containerWidth) {
1803
- const centerX = childrenRect.x + childrenRect.width / 2;
1804
- const halfContainerWidth = containerWidth / 2;
1805
- left = centerX - halfContainerWidth;
1806
- right = centerX + halfContainerWidth;
1807
- }
1808
- else {
1809
- left = childrenRect.x;
1810
- right = childrenRect.x + childrenRect.width;
1811
- }
1812
- if (childrenRect.height < containerHeight) {
1813
- const centerY = childrenRect.y + childrenRect.height / 2;
1814
- const halfContainerHeight = containerHeight / 2;
1815
- top = centerY - halfContainerHeight;
1816
- bottom = centerY + halfContainerHeight;
2298
+ function getTemporaryElements(board) {
2299
+ const ref = BOARD_TO_TEMPORARY_ELEMENTS.get(board);
2300
+ if (ref) {
2301
+ return ref.elements;
1817
2302
  }
1818
2303
  else {
1819
- top = childrenRect.y;
1820
- bottom = childrenRect.y + childrenRect.height;
1821
- }
1822
- return {
1823
- left,
1824
- right,
1825
- top,
1826
- bottom
1827
- };
1828
- }
1829
- /**
1830
- * 验证缩放比是否符合限制,如果超出限制,则返回合适的缩放比
1831
- * @param zoom 缩放比
1832
- * @param minZoom 最小缩放比
1833
- * @param maxZoom 最大缩放比
1834
- * @returns 正确的缩放比
1835
- */
1836
- function clampZoomLevel(zoom, minZoom = 0.2, maxZoom = 4) {
1837
- return zoom < minZoom ? minZoom : zoom > maxZoom ? maxZoom : zoom;
1838
- }
1839
- function getViewBox(board, zoom) {
1840
- const boardContainerRectangle = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1841
- const elementHostBBox = getElementHostBBox(board, zoom);
1842
- const horizontalPadding = boardContainerRectangle.width / 2;
1843
- const verticalPadding = boardContainerRectangle.height / 2;
1844
- const viewBox = [
1845
- elementHostBBox.left - horizontalPadding / zoom,
1846
- elementHostBBox.top - verticalPadding / zoom,
1847
- elementHostBBox.right - elementHostBBox.left + (horizontalPadding * 2) / zoom,
1848
- elementHostBBox.bottom - elementHostBBox.top + (verticalPadding * 2) / zoom
1849
- ];
1850
- return viewBox;
1851
- }
1852
- function getViewBoxCenterPoint(board) {
1853
- const childrenRectangle = getRectangleByElements(board, board.children, true);
1854
- return [childrenRectangle.x + childrenRectangle.width / 2, childrenRectangle.y + childrenRectangle.height / 2];
1855
- }
1856
- function setSVGViewBox(board, viewBox) {
1857
- const zoom = board.viewport.zoom;
1858
- const hostElement = PlaitBoard.getHost(board);
1859
- hostElement.style.display = 'block';
1860
- hostElement.style.width = `${viewBox[2] * zoom}px`;
1861
- hostElement.style.height = `${viewBox[3] * zoom}px`;
1862
- if (viewBox && viewBox[2] > 0 && viewBox[3] > 0) {
1863
- hostElement.setAttribute('viewBox', viewBox.join(' '));
1864
- }
1865
- }
1866
- function updateViewportOffset(board) {
1867
- const origination = getViewportOrigination(board);
1868
- if (!origination)
1869
- return;
1870
- const { zoom } = board.viewport;
1871
- const viewBox = getViewBox(board, zoom);
1872
- const scrollLeft = (origination[0] - viewBox[0]) * zoom;
1873
- const scrollTop = (origination[1] - viewBox[1]) * zoom;
1874
- updateViewportContainerScroll(board, scrollLeft, scrollTop);
1875
- }
1876
- function updateViewportContainerScroll(board, left, top, isFromViewportChange = true) {
1877
- const viewportContainer = PlaitBoard.getViewportContainer(board);
1878
- if (viewportContainer.scrollLeft !== left || viewportContainer.scrollTop !== top) {
1879
- viewportContainer.scrollLeft = left;
1880
- viewportContainer.scrollTop = top;
1881
- isFromViewportChange && setIsFromViewportChange(board, true);
2304
+ return undefined;
1882
2305
  }
1883
2306
  }
1884
- function initializeViewportContainer(board) {
1885
- const { width, height } = getViewportContainerRect(board);
1886
- const viewportContainer = PlaitBoard.getViewportContainer(board);
1887
- viewportContainer.style.width = `${width}px`;
1888
- viewportContainer.style.height = `${height}px`;
2307
+ function getTemporaryRef(board) {
2308
+ return BOARD_TO_TEMPORARY_ELEMENTS.get(board);
1889
2309
  }
1890
- function initializeViewBox(board) {
1891
- const zoom = board.viewport.zoom;
1892
- const viewBox = getViewBox(board, zoom);
1893
- setSVGViewBox(board, viewBox);
2310
+ function deleteTemporaryElements(board) {
2311
+ BOARD_TO_TEMPORARY_ELEMENTS.delete(board);
1894
2312
  }
1895
- function initializeViewportOffset(board) {
1896
- if (!board.viewport?.origination) {
1897
- const zoom = board.viewport.zoom;
1898
- const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1899
- const viewBox = getViewBox(board, zoom);
1900
- const centerX = viewBox[0] + viewBox[2] / 2;
1901
- const centerY = viewBox[1] + viewBox[3] / 2;
1902
- const origination = [centerX - viewportContainerRect.width / 2 / zoom, centerY - viewportContainerRect.height / 2 / zoom];
1903
- updateViewportOrigination(board, origination);
1904
- updateViewportOffset(board);
1905
- return;
2313
+ function isSelectionMoving(board) {
2314
+ return !!BOARD_TO_IS_SELECTION_MOVING.get(board);
2315
+ }
2316
+ function setSelectionMoving(board) {
2317
+ PlaitBoard.getBoardContainer(board).classList.add('selection-moving');
2318
+ BOARD_TO_IS_SELECTION_MOVING.set(board, true);
2319
+ }
2320
+ function clearSelectionMoving(board) {
2321
+ PlaitBoard.getBoardContainer(board).classList.remove('selection-moving');
2322
+ BOARD_TO_IS_SELECTION_MOVING.delete(board);
2323
+ }
2324
+ function createSelectionRectangleG(board) {
2325
+ const elements = getSelectedElements(board);
2326
+ const rectangle = getRectangleByElements(board, elements, false);
2327
+ if (rectangle.width > 0 && rectangle.height > 0 && elements.length > 1) {
2328
+ const selectionRectangleG = drawRectangle(board, RectangleClient.inflate(rectangle, ACTIVE_STROKE_WIDTH), {
2329
+ stroke: SELECTION_BORDER_COLOR,
2330
+ strokeWidth: ACTIVE_STROKE_WIDTH,
2331
+ fillStyle: 'solid'
2332
+ });
2333
+ selectionRectangleG.classList.add(SELECTION_RECTANGLE_CLASS_NAME);
2334
+ PlaitBoard.getHost(board).append(selectionRectangleG);
2335
+ return selectionRectangleG;
1906
2336
  }
1907
- updateViewportOffset(board);
2337
+ return null;
1908
2338
  }
1909
- const updateViewportOrigination = (board, origination) => {
1910
- BOARD_TO_VIEWPORT_ORIGINATION.set(board, origination);
1911
- };
1912
- const clearViewportOrigination = (board) => {
1913
- BOARD_TO_VIEWPORT_ORIGINATION.delete(board);
2339
+
2340
+ function setSelection(board, selection) {
2341
+ const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
2342
+ board.apply(operation);
2343
+ }
2344
+ const SelectionTransforms = {
2345
+ setSelection,
2346
+ addSelectionWithTemporaryElements
1914
2347
  };
1915
- const getViewportOrigination = (board) => {
1916
- const origination = BOARD_TO_VIEWPORT_ORIGINATION.get(board);
1917
- if (origination) {
1918
- return origination;
2348
+ function addSelectionWithTemporaryElements(board, elements) {
2349
+ const timeoutId = setTimeout(() => {
2350
+ setSelection(board, { ranges: [] });
2351
+ }, 0);
2352
+ let ref = getTemporaryRef(board);
2353
+ if (ref) {
2354
+ clearTimeout(ref.timeoutId);
2355
+ const currentElements = ref.elements;
2356
+ ref.elements.push(...elements.filter(element => !currentElements.includes(element)));
2357
+ ref.timeoutId = timeoutId;
1919
2358
  }
1920
2359
  else {
1921
- return board.viewport.origination;
2360
+ BOARD_TO_TEMPORARY_ELEMENTS.set(board, { timeoutId, elements });
1922
2361
  }
2362
+ }
2363
+
2364
+ function setViewport(board, viewport) {
2365
+ const operation = { type: 'set_viewport', properties: board.viewport, newProperties: viewport };
2366
+ board.apply(operation);
2367
+ }
2368
+ const ViewportTransforms$1 = {
2369
+ setViewport
1923
2370
  };
1924
- const isFromScrolling = (board) => {
1925
- return !!IS_FROM_SCROLLING.get(board);
1926
- };
1927
- const setIsFromScrolling = (board, state) => {
1928
- IS_FROM_SCROLLING.set(board, state);
1929
- };
1930
- const isFromViewportChange = (board) => {
1931
- return !!IS_FROM_VIEWPORT_CHANGE.get(board);
1932
- };
1933
- const setIsFromViewportChange = (board, state) => {
1934
- IS_FROM_VIEWPORT_CHANGE.set(board, state);
1935
- };
1936
- function scrollToRectangle(board, client) { }
1937
2371
 
1938
2372
  function setTheme(board, themeColorMode) {
1939
2373
  const operation = { type: 'set_theme', properties: board.theme, newProperties: themeColorMode };
@@ -2053,6 +2487,25 @@ const BoardTransforms = {
2053
2487
  fitViewportWidth
2054
2488
  };
2055
2489
 
2490
+ const removeElements = (board, elements) => {
2491
+ elements
2492
+ .map(element => {
2493
+ const path = PlaitBoard.findPath(board, element);
2494
+ const ref = board.pathRef(path);
2495
+ return () => {
2496
+ removeNode(board, ref.current);
2497
+ ref.unref();
2498
+ removeSelectedElement(board, element);
2499
+ };
2500
+ })
2501
+ .forEach(action => {
2502
+ action();
2503
+ });
2504
+ };
2505
+ const CoreTransforms = {
2506
+ removeElements
2507
+ };
2508
+
2056
2509
  const Transforms = {
2057
2510
  ...GeneralTransforms,
2058
2511
  ...ViewportTransforms$1,
@@ -2148,7 +2601,11 @@ function createBoard(children, options) {
2148
2601
  dblclick: (event) => { },
2149
2602
  setFragment: (data) => { },
2150
2603
  insertFragment: (data) => { },
2151
- deleteFragment: (data) => { },
2604
+ deleteFragment: (data) => {
2605
+ const elements = board.getDeletedFragment([]);
2606
+ CoreTransforms.removeElements(board, elements);
2607
+ },
2608
+ getDeletedFragment: (data) => data,
2152
2609
  drawElement: (context) => [],
2153
2610
  redrawElement: (context, previousContext) => { },
2154
2611
  destroyElement: (context) => { },
@@ -2320,177 +2777,6 @@ function withHandPointer(board) {
2320
2777
  return board;
2321
2778
  }
2322
2779
 
2323
- function withSelection(board) {
2324
- const { pointerDown, globalPointerMove, globalPointerUp, onChange } = board;
2325
- let start = null;
2326
- let end = null;
2327
- let selectionMovingG;
2328
- let selectionOuterG;
2329
- let previousSelectedElements;
2330
- // prevent text from being selected when user pressed main pointer and is moving
2331
- let needPreventNativeSelectionWhenMoving = false;
2332
- board.pointerDown = (event) => {
2333
- if (event.target instanceof Element && !event.target.closest('.plait-richtext-container')) {
2334
- needPreventNativeSelectionWhenMoving = true;
2335
- }
2336
- if (!isMainPointer(event)) {
2337
- pointerDown(event);
2338
- return;
2339
- }
2340
- const options = board.getPluginOptions(PlaitPluginKey.withSelection);
2341
- const point = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
2342
- const range = { anchor: point, focus: point };
2343
- const hitElements = getHitElements(board, { ranges: [range] });
2344
- const selectedElements = getSelectedElements(board);
2345
- if (hitElements.length === 1 && selectedElements.includes(hitElements[0]) && !options.isDisabledSelect) {
2346
- pointerDown(event);
2347
- return;
2348
- }
2349
- if (PlaitBoard.isPointer(board, PlaitPointerType.selection) &&
2350
- hitElements.length === 0 &&
2351
- options.isMultiple &&
2352
- !options.isDisabledSelect) {
2353
- start = point;
2354
- preventTouchMove(board, true);
2355
- }
2356
- Transforms.setSelection(board, { ranges: [range] });
2357
- pointerDown(event);
2358
- };
2359
- board.globalPointerMove = (event) => {
2360
- if (needPreventNativeSelectionWhenMoving) {
2361
- // prevent text from being selected
2362
- event.preventDefault();
2363
- }
2364
- if (start) {
2365
- const movedTarget = transformPoint(board, toPoint(event.x, event.y, PlaitBoard.getHost(board)));
2366
- const { x, y, width, height } = RectangleClient.toRectangleClient([start, movedTarget]);
2367
- selectionMovingG?.remove();
2368
- if (Math.hypot(width, height) > 5) {
2369
- end = movedTarget;
2370
- throttleRAF(() => {
2371
- if (start && end) {
2372
- Transforms.setSelection(board, { ranges: [{ anchor: start, focus: end }] });
2373
- }
2374
- });
2375
- setSelectionMoving(board);
2376
- const rough = PlaitBoard.getRoughSVG(board);
2377
- selectionMovingG = rough.rectangle(x, y, width, height, {
2378
- stroke: SELECTION_BORDER_COLOR,
2379
- strokeWidth: 1,
2380
- fill: SELECTION_FILL_COLOR,
2381
- fillStyle: 'solid'
2382
- });
2383
- PlaitBoard.getHost(board).append(selectionMovingG);
2384
- }
2385
- }
2386
- globalPointerMove(event);
2387
- };
2388
- board.globalPointerUp = (event) => {
2389
- if (start && end) {
2390
- selectionMovingG?.remove();
2391
- clearSelectionMoving(board);
2392
- Transforms.setSelection(board, { ranges: [{ anchor: start, focus: end }] });
2393
- }
2394
- if (PlaitBoard.isFocus(board)) {
2395
- const isInBoard = event.target instanceof Node && PlaitBoard.getBoardContainer(board).contains(event.target);
2396
- const isInDocument = event.target instanceof Node && document.contains(event.target);
2397
- const isAttachedElement = event.target instanceof Element && event.target.closest(`.${ATTACHED_ELEMENT_CLASS_NAME}`);
2398
- // Clear selection when mouse board outside area
2399
- // The framework needs to determine whether the board is focused through selection
2400
- if (!isInBoard && !start && !isAttachedElement && isInDocument) {
2401
- Transforms.setSelection(board, null);
2402
- }
2403
- }
2404
- start = null;
2405
- end = null;
2406
- needPreventNativeSelectionWhenMoving = false;
2407
- preventTouchMove(board, false);
2408
- globalPointerUp(event);
2409
- };
2410
- board.onChange = () => {
2411
- const options = board.getPluginOptions(PlaitPluginKey.withSelection);
2412
- if (options.isDisabledSelect) {
2413
- selectionOuterG?.remove();
2414
- clearSelectedElement(board);
2415
- }
2416
- // calc selected elements entry
2417
- if (board.pointer !== PlaitPointerType.hand && !options.isDisabledSelect) {
2418
- try {
2419
- if (board.operations.find(value => value.type === 'set_selection')) {
2420
- selectionOuterG?.remove();
2421
- const temporaryElements = getTemporaryElements(board);
2422
- let elements = temporaryElements ? temporaryElements : getHitElements(board);
2423
- if (!options.isMultiple && elements.length > 1) {
2424
- elements = [elements[0]];
2425
- }
2426
- cacheSelectedElements(board, elements);
2427
- previousSelectedElements = elements;
2428
- const { width, height } = getRectangleByElements(board, elements, false);
2429
- if (width > 0 && height > 0 && elements.length > 1) {
2430
- selectionOuterG = createSelectionOuterG(board, elements);
2431
- selectionOuterG.classList.add('selection-outer');
2432
- PlaitBoard.getHost(board).append(selectionOuterG);
2433
- }
2434
- deleteTemporaryElements(board);
2435
- }
2436
- else {
2437
- // wait node destroy and remove selected element state
2438
- setTimeout(() => {
2439
- const currentSelectedElements = getSelectedElements(board);
2440
- if (currentSelectedElements.length && currentSelectedElements.length > 1) {
2441
- const selectedElementChange = currentSelectedElements.some(item => !previousSelectedElements.includes(item));
2442
- if (selectedElementChange) {
2443
- selectionOuterG?.remove();
2444
- selectionOuterG = createSelectionOuterG(board, currentSelectedElements);
2445
- selectionOuterG.classList.add('selection-outer');
2446
- PlaitBoard.getHost(board).append(selectionOuterG);
2447
- }
2448
- }
2449
- else {
2450
- selectionOuterG?.remove();
2451
- }
2452
- });
2453
- }
2454
- }
2455
- catch (error) {
2456
- console.error(error);
2457
- }
2458
- }
2459
- onChange();
2460
- };
2461
- board.setPluginOptions(PlaitPluginKey.withSelection, {
2462
- isMultiple: true,
2463
- isDisabledSelect: false
2464
- });
2465
- return board;
2466
- }
2467
- function getTemporaryElements(board) {
2468
- return BOARD_TO_TEMPORARY_ELEMENTS.get(board);
2469
- }
2470
- function deleteTemporaryElements(board) {
2471
- BOARD_TO_TEMPORARY_ELEMENTS.delete(board);
2472
- }
2473
- function isSelectionMoving(board) {
2474
- return !!BOARD_TO_IS_SELECTION_MOVING.get(board);
2475
- }
2476
- function setSelectionMoving(board) {
2477
- PlaitBoard.getBoardContainer(board).classList.add('selection-moving');
2478
- BOARD_TO_IS_SELECTION_MOVING.set(board, true);
2479
- }
2480
- function clearSelectionMoving(board) {
2481
- PlaitBoard.getBoardContainer(board).classList.remove('selection-moving');
2482
- BOARD_TO_IS_SELECTION_MOVING.delete(board);
2483
- }
2484
- function createSelectionOuterG(board, selectElements) {
2485
- const { x, y, width, height } = getRectangleByElements(board, selectElements, false);
2486
- const rough = PlaitBoard.getRoughSVG(board);
2487
- return rough.rectangle(x - 2.5, y - 2.5, width + 5, height + 5, {
2488
- stroke: SELECTION_BORDER_COLOR,
2489
- strokeWidth: 1,
2490
- fillStyle: 'solid'
2491
- });
2492
- }
2493
-
2494
2780
  function withViewport(board) {
2495
2781
  const { onChange } = board;
2496
2782
  const throttleUpdate = debounce(() => {
@@ -2519,19 +2805,201 @@ function withViewport(board) {
2519
2805
  return board;
2520
2806
  }
2521
2807
 
2808
+ class ReactionManager {
2809
+ constructor(board, activeElements, activeRectangle) {
2810
+ this.board = board;
2811
+ this.activeElements = activeElements;
2812
+ this.activeRectangle = activeRectangle;
2813
+ this.alignRectangles = this.getAlignRectangle();
2814
+ }
2815
+ getAlignRectangle() {
2816
+ const result = [];
2817
+ depthFirstRecursion(this.board, node => {
2818
+ if (PlaitBoard.isBoard(node) || this.activeElements.some(element => node.id === element.id) || node.type !== 'geometry') {
2819
+ return;
2820
+ }
2821
+ const rectangle = this.board.getRectangle(node);
2822
+ rectangle && result.push(rectangle);
2823
+ }, node => {
2824
+ if (node && (PlaitBoard.isBoard(node) || this.board.isRecursion(node))) {
2825
+ return true;
2826
+ }
2827
+ else {
2828
+ return false;
2829
+ }
2830
+ }, true);
2831
+ return result;
2832
+ }
2833
+ handleAlign() {
2834
+ const alignRectangles = this.getAlignRectangle();
2835
+ const g = createG();
2836
+ const alignLines = [];
2837
+ const offset = 12;
2838
+ const options = {
2839
+ stroke: SELECTION_BORDER_COLOR,
2840
+ strokeWidth: 1,
2841
+ strokeLineDash: [4, 4]
2842
+ };
2843
+ let deltaX = 0;
2844
+ let deltaY = 0;
2845
+ let isCorrectX = false;
2846
+ let isCorrectY = false;
2847
+ for (let alignRectangle of alignRectangles) {
2848
+ const closestDistances = this.calculateClosestDistances(this.activeRectangle, alignRectangle);
2849
+ let canDrawHorizontal = false;
2850
+ if (!isCorrectX && closestDistances.absXDistance < 5) {
2851
+ deltaX = closestDistances.xDistance;
2852
+ this.activeRectangle.x -= deltaX;
2853
+ isCorrectX = true;
2854
+ canDrawHorizontal = true;
2855
+ }
2856
+ if (closestDistances.absXDistance === 0) {
2857
+ canDrawHorizontal = true;
2858
+ }
2859
+ if (canDrawHorizontal) {
2860
+ const verticalY = [
2861
+ alignRectangle.y,
2862
+ alignRectangle.y + alignRectangle.height,
2863
+ this.activeRectangle.y,
2864
+ this.activeRectangle.y + this.activeRectangle.height
2865
+ ];
2866
+ const lineTopY = Math.min(...verticalY) - offset;
2867
+ const lineBottomY = Math.max(...verticalY) + offset;
2868
+ const leftLine = [this.activeRectangle.x, lineTopY, this.activeRectangle.x, lineBottomY];
2869
+ const middleLine = [
2870
+ this.activeRectangle.x + this.activeRectangle.width / 2,
2871
+ lineTopY,
2872
+ this.activeRectangle.x + this.activeRectangle.width / 2,
2873
+ lineBottomY
2874
+ ];
2875
+ const rightLine = [
2876
+ this.activeRectangle.x + this.activeRectangle.width,
2877
+ lineTopY,
2878
+ this.activeRectangle.x + this.activeRectangle.width,
2879
+ lineBottomY
2880
+ ];
2881
+ const shouldDrawLeftLine = closestDistances.indexX === 0 ||
2882
+ closestDistances.indexX === 1 ||
2883
+ (closestDistances.indexX === 2 && this.activeRectangle.width === alignRectangle.width);
2884
+ if (shouldDrawLeftLine && !alignLines[0]) {
2885
+ alignLines[0] = leftLine;
2886
+ }
2887
+ const shouldDrawRightLine = closestDistances.indexX === 2 ||
2888
+ closestDistances.indexX === 3 ||
2889
+ (closestDistances.indexX === 0 && this.activeRectangle.width === alignRectangle.width);
2890
+ if (shouldDrawRightLine && !alignLines[2]) {
2891
+ alignLines[2] = rightLine;
2892
+ }
2893
+ const shouldDrawMiddleLine = closestDistances.indexX === 4 || (!shouldDrawLeftLine && !shouldDrawRightLine);
2894
+ if (shouldDrawMiddleLine && !alignLines[1]) {
2895
+ alignLines[1] = middleLine;
2896
+ }
2897
+ isCorrectX = true;
2898
+ }
2899
+ let canDrawVertical = false;
2900
+ if (!isCorrectY && closestDistances.absYDistance < 5) {
2901
+ deltaY = closestDistances.yDistance;
2902
+ this.activeRectangle.y -= deltaY;
2903
+ isCorrectY = true;
2904
+ canDrawVertical = true;
2905
+ }
2906
+ if (closestDistances.absYDistance === 0) {
2907
+ canDrawVertical = true;
2908
+ }
2909
+ if (canDrawVertical) {
2910
+ const horizontalX = [
2911
+ alignRectangle.x,
2912
+ alignRectangle.x + alignRectangle.width,
2913
+ this.activeRectangle.x,
2914
+ this.activeRectangle.x + this.activeRectangle.width
2915
+ ];
2916
+ const lineLeftX = Math.min(...horizontalX) - offset;
2917
+ const lineRightX = Math.max(...horizontalX) + offset;
2918
+ const topLine = [lineLeftX, this.activeRectangle.y, lineRightX, this.activeRectangle.y];
2919
+ const horizontalMiddleLine = [
2920
+ lineLeftX,
2921
+ this.activeRectangle.y + this.activeRectangle.height / 2,
2922
+ lineRightX,
2923
+ this.activeRectangle.y + this.activeRectangle.height / 2
2924
+ ];
2925
+ const bottomLine = [
2926
+ lineLeftX,
2927
+ this.activeRectangle.y + this.activeRectangle.height,
2928
+ lineRightX,
2929
+ this.activeRectangle.y + this.activeRectangle.height
2930
+ ];
2931
+ const shouldDrawTopLine = closestDistances.indexY === 0 ||
2932
+ closestDistances.indexY === 1 ||
2933
+ (closestDistances.indexY === 2 && this.activeRectangle.height === alignRectangle.height);
2934
+ if (shouldDrawTopLine && !alignLines[3]) {
2935
+ alignLines[3] = topLine;
2936
+ }
2937
+ const shouldDrawBottomLine = closestDistances.indexY === 2 ||
2938
+ closestDistances.indexY === 3 ||
2939
+ (closestDistances.indexY === 0 && this.activeRectangle.width === alignRectangle.width);
2940
+ if (shouldDrawBottomLine && !alignLines[5]) {
2941
+ alignLines[5] = bottomLine;
2942
+ }
2943
+ const shouldDrawMiddleLine = closestDistances.indexY === 4 || (!shouldDrawTopLine && !shouldDrawBottomLine);
2944
+ if (shouldDrawMiddleLine && !alignLines[4]) {
2945
+ alignLines[4] = horizontalMiddleLine;
2946
+ }
2947
+ }
2948
+ }
2949
+ if (alignLines.length) {
2950
+ alignLines.forEach(points => {
2951
+ if (!points.length)
2952
+ return;
2953
+ const xAlign = PlaitBoard.getRoughSVG(this.board).line(points[0], points[1], points[2], points[3], options);
2954
+ g.appendChild(xAlign);
2955
+ });
2956
+ }
2957
+ return { deltaX, deltaY, g };
2958
+ }
2959
+ calculateClosestDistances(activeRectangle, alignRectangle) {
2960
+ const activeRectangleCenter = [activeRectangle.x + activeRectangle.width / 2, activeRectangle.y + activeRectangle.height / 2];
2961
+ const alignRectangleCenter = [alignRectangle.x + alignRectangle.width / 2, alignRectangle.y + alignRectangle.height / 2];
2962
+ const centerXDistance = activeRectangleCenter[0] - alignRectangleCenter[0];
2963
+ const centerYDistance = activeRectangleCenter[1] - alignRectangleCenter[1];
2964
+ const leftToLeft = activeRectangle.x - alignRectangle.x;
2965
+ const leftToRight = activeRectangle.x - (alignRectangle.x + alignRectangle.width);
2966
+ const rightToRight = activeRectangle.x + activeRectangle.width - (alignRectangle.x + alignRectangle.width);
2967
+ const rightToLeft = activeRectangle.x + activeRectangle.width - alignRectangle.x;
2968
+ const topToTop = activeRectangle.y - alignRectangle.y;
2969
+ const topToBottom = activeRectangle.y - (alignRectangle.y + alignRectangle.height);
2970
+ const bottomToTop = activeRectangle.y + activeRectangle.height - alignRectangle.y;
2971
+ const bottomToBottom = activeRectangle.y + activeRectangle.height - (alignRectangle.y + alignRectangle.height);
2972
+ const xDistances = [leftToLeft, leftToRight, rightToRight, rightToLeft, centerXDistance];
2973
+ const yDistances = [topToTop, topToBottom, bottomToBottom, bottomToTop, centerYDistance];
2974
+ const xDistancesAbs = xDistances.map(distance => Math.abs(distance));
2975
+ const yDistancesAbs = yDistances.map(distance => Math.abs(distance));
2976
+ const indexX = xDistancesAbs.indexOf(Math.min(...xDistancesAbs));
2977
+ const indexY = yDistancesAbs.indexOf(Math.min(...yDistancesAbs));
2978
+ return {
2979
+ absXDistance: xDistancesAbs[indexX],
2980
+ xDistance: xDistances[indexX],
2981
+ absYDistance: yDistancesAbs[indexY],
2982
+ yDistance: yDistances[indexY],
2983
+ indexX,
2984
+ indexY
2985
+ };
2986
+ }
2987
+ }
2988
+
2522
2989
  function withMoving(board) {
2523
- const { mousedown, mousemove, globalMouseup, globalMousemove } = board;
2990
+ const { pointerDown, pointerMove, globalPointerUp, globalPointerMove } = board;
2524
2991
  let offsetX = 0;
2525
2992
  let offsetY = 0;
2526
2993
  let isPreventDefault = false;
2527
2994
  let startPoint;
2528
2995
  let activeElements = [];
2529
- board.mousedown = event => {
2996
+ let alignG = null;
2997
+ board.pointerDown = (event) => {
2530
2998
  const host = BOARD_TO_HOST.get(board);
2531
2999
  const point = transformPoint(board, toPoint(event.x, event.y, host));
2532
3000
  const range = { anchor: point, focus: point };
2533
3001
  let movableElements = board.children.filter(item => board.isMovable(item));
2534
- if (movableElements.length) {
3002
+ if (movableElements.length && !isPreventTouchMove(board)) {
2535
3003
  startPoint = point;
2536
3004
  const selectedRootElements = getSelectedElements(board).filter(item => movableElements.includes(item));
2537
3005
  const hitElement = getHitElementOfRoot(board, movableElements, range);
@@ -2541,21 +3009,35 @@ function withMoving(board) {
2541
3009
  else if (hitElement) {
2542
3010
  activeElements = [hitElement];
2543
3011
  }
3012
+ if (activeElements.length > 0) {
3013
+ preventTouchMove(board, event, true);
3014
+ }
2544
3015
  }
2545
- mousedown(event);
3016
+ pointerDown(event);
2546
3017
  };
2547
- board.mousemove = event => {
3018
+ board.pointerMove = (event) => {
2548
3019
  if (startPoint && activeElements.length && !PlaitBoard.hasBeenTextEditing(board)) {
2549
3020
  if (!isPreventDefault) {
2550
3021
  isPreventDefault = true;
2551
3022
  }
3023
+ alignG?.remove();
2552
3024
  const host = BOARD_TO_HOST.get(board);
2553
3025
  const endPoint = transformPoint(board, toPoint(event.x, event.y, host));
2554
3026
  offsetX = endPoint[0] - startPoint[0];
2555
3027
  offsetY = endPoint[1] - startPoint[1];
3028
+ const activeElementsRectangle = getRectangleByElements(board, activeElements, true);
3029
+ activeElementsRectangle.x += offsetX;
3030
+ activeElementsRectangle.y += offsetY;
3031
+ const reactionManager = new ReactionManager(board, activeElements, activeElementsRectangle);
3032
+ const ref = reactionManager.handleAlign();
3033
+ offsetX -= ref.deltaX;
3034
+ offsetY -= ref.deltaY;
3035
+ alignG = ref.g;
3036
+ PlaitBoard.getElementActiveHost(board).append(alignG);
2556
3037
  const offsetBuffer = 5;
2557
- if (Math.abs(offsetX) > offsetBuffer || Math.abs(offsetY) > offsetBuffer) {
3038
+ if (Math.abs(offsetX) > offsetBuffer || Math.abs(offsetY) > offsetBuffer || getMovingElements(board).length > 0 || ref.deltaX) {
2558
3039
  throttleRAF(() => {
3040
+ handleTouchTarget(board);
2559
3041
  const currentElements = activeElements.map(activeElement => {
2560
3042
  const points = activeElement.points || [];
2561
3043
  const [x, y] = activeElement.points[0];
@@ -2576,25 +3058,27 @@ function withMoving(board) {
2576
3058
  // 阻止 move 过程中触发画布滚动行为
2577
3059
  event.preventDefault();
2578
3060
  }
2579
- mousemove(event);
3061
+ pointerMove(event);
2580
3062
  };
2581
- board.globalMousemove = event => {
3063
+ board.globalPointerMove = (event) => {
2582
3064
  if (startPoint) {
2583
3065
  const inPlaitBoardElement = isInPlaitBoard(board, event.x, event.y);
2584
3066
  if (!inPlaitBoardElement) {
2585
3067
  cancelMove(board);
2586
3068
  }
2587
3069
  }
2588
- globalMousemove(event);
3070
+ globalPointerMove(event);
2589
3071
  };
2590
- board.globalMouseup = event => {
3072
+ board.globalPointerUp = event => {
2591
3073
  isPreventDefault = false;
2592
3074
  if (startPoint) {
2593
3075
  cancelMove(board);
2594
3076
  }
2595
- globalMouseup(event);
3077
+ preventTouchMove(board, event, false);
3078
+ globalPointerUp(event);
2596
3079
  };
2597
3080
  function cancelMove(board) {
3081
+ alignG?.remove();
2598
3082
  startPoint = null;
2599
3083
  offsetX = 0;
2600
3084
  offsetY = 0;
@@ -2630,10 +3114,10 @@ class PlaitIslandBaseComponent {
2630
3114
  markForCheck() {
2631
3115
  this.cdr.markForCheck();
2632
3116
  }
3117
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitIslandBaseComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
3118
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.3", type: PlaitIslandBaseComponent, host: { classAttribute: "plait-island-container" }, ngImport: i0 }); }
2633
3119
  }
2634
- PlaitIslandBaseComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitIslandBaseComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
2635
- PlaitIslandBaseComponent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.5", type: PlaitIslandBaseComponent, host: { classAttribute: "plait-island-container" }, ngImport: i0 });
2636
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitIslandBaseComponent, decorators: [{
3120
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitIslandBaseComponent, decorators: [{
2637
3121
  type: Directive,
2638
3122
  args: [{
2639
3123
  host: {
@@ -2642,16 +3126,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImpor
2642
3126
  }]
2643
3127
  }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; } });
2644
3128
  class PlaitIslandPopoverBaseComponent {
3129
+ constructor(cdr) {
3130
+ this.cdr = cdr;
3131
+ }
2645
3132
  initialize(board) {
2646
3133
  this.board = board;
2647
- const onChange = board.onChange;
2648
- board.onChange = () => {
2649
- onChange();
3134
+ const boardComponent = PlaitBoard.getComponent(board);
3135
+ this.subscription = boardComponent.plaitChange.subscribe(() => {
2650
3136
  if (hasOnBoardChange(this)) {
2651
3137
  this.onBoardChange();
2652
3138
  }
2653
- };
2654
- this.onChange = onChange;
3139
+ this.cdr.markForCheck();
3140
+ });
2655
3141
  }
2656
3142
  ngOnInit() {
2657
3143
  if (!this.board) {
@@ -2661,20 +3147,20 @@ class PlaitIslandPopoverBaseComponent {
2661
3147
  this.islandOnInit();
2662
3148
  }
2663
3149
  ngOnDestroy() {
2664
- this.board.onChange = this.onChange;
3150
+ this.subscription?.unsubscribe();
2665
3151
  this.islandOnDestroy();
2666
3152
  }
3153
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitIslandPopoverBaseComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
3154
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.3", type: PlaitIslandPopoverBaseComponent, inputs: { board: "board" }, host: { classAttribute: "plait-island-popover-container" }, ngImport: i0 }); }
2667
3155
  }
2668
- PlaitIslandPopoverBaseComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitIslandPopoverBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2669
- PlaitIslandPopoverBaseComponent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.5", type: PlaitIslandPopoverBaseComponent, inputs: { board: "board" }, host: { classAttribute: "plait-island-popover-container" }, ngImport: i0 });
2670
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitIslandPopoverBaseComponent, decorators: [{
3156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitIslandPopoverBaseComponent, decorators: [{
2671
3157
  type: Directive,
2672
3158
  args: [{
2673
3159
  host: {
2674
3160
  class: 'plait-island-popover-container'
2675
3161
  }
2676
3162
  }]
2677
- }], propDecorators: { board: [{
3163
+ }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { board: [{
2678
3164
  type: Input
2679
3165
  }] } });
2680
3166
  const hasOnBoardChange = (value) => {
@@ -2706,9 +3192,14 @@ const withHotkey = (board) => {
2706
3192
  return false;
2707
3193
  }
2708
3194
  }, true);
2709
- Transforms.setSelectionWithTemporaryElements(board, elements);
3195
+ Transforms.addSelectionWithTemporaryElements(board, elements);
2710
3196
  return;
2711
3197
  }
3198
+ const selectedElements = getSelectedElements(board);
3199
+ if (!PlaitBoard.isReadonly(board) && selectedElements.length > 0 && (hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event))) {
3200
+ event.preventDefault();
3201
+ board.deleteFragment(null);
3202
+ }
2712
3203
  keydown(event);
2713
3204
  };
2714
3205
  board.globalKeydown = (event) => {
@@ -2752,10 +3243,10 @@ class PlaitContextService {
2752
3243
  removeUploadingFile(fileEntry) {
2753
3244
  this.uploadingFiles = this.uploadingFiles.filter(file => file.url !== fileEntry.url);
2754
3245
  }
3246
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3247
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitContextService }); }
2755
3248
  }
2756
- PlaitContextService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2757
- PlaitContextService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitContextService });
2758
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitContextService, decorators: [{
3249
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitContextService, decorators: [{
2759
3250
  type: Injectable
2760
3251
  }] });
2761
3252
 
@@ -2832,10 +3323,10 @@ class PlaitElementComponent {
2832
3323
  ngOnDestroy() {
2833
3324
  this.board.destroyElement(this.getContext());
2834
3325
  }
3326
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitElementComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Component }); }
3327
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.3", type: PlaitElementComponent, selector: "plait-element", inputs: { index: "index", element: "element", parent: "parent", board: "board", effect: "effect", parentG: "parentG" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2835
3328
  }
2836
- PlaitElementComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitElementComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Component });
2837
- PlaitElementComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.5", type: PlaitElementComponent, selector: "plait-element", inputs: { index: "index", element: "element", parent: "parent", board: "board", effect: "effect", parentG: "parentG" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
2838
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitElementComponent, decorators: [{
3329
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitElementComponent, decorators: [{
2839
3330
  type: Component,
2840
3331
  args: [{
2841
3332
  selector: 'plait-element',
@@ -2870,9 +3361,8 @@ class PlaitChildrenElement {
2870
3361
  this.parentG = PlaitBoard.getElementHost(this.board);
2871
3362
  }
2872
3363
  }
2873
- }
2874
- PlaitChildrenElementfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitChildrenElement, deps: [], target: i0.ɵɵFactoryTarget.Component });
2875
- PlaitChildrenElement.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.5", type: PlaitChildrenElement, selector: "plait-children", inputs: { board: "board", parent: "parent", effect: "effect", parentG: "parentG" }, ngImport: i0, template: `
3364
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitChildrenElement, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3365
+ static { thiscmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.3", type: PlaitChildrenElement, selector: "plait-children", inputs: { board: "board", parent: "parent", effect: "effect", parentG: "parentG" }, ngImport: i0, template: `
2876
3366
  <plait-element
2877
3367
  *ngFor="let item of parent.children; let index = index; trackBy: trackBy"
2878
3368
  [index]="index"
@@ -2882,8 +3372,9 @@ PlaitChildrenElement.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", v
2882
3372
  [effect]="effect"
2883
3373
  [parentG]="parentG"
2884
3374
  ></plait-element>
2885
- `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: PlaitElementComponent, selector: "plait-element", inputs: ["index", "element", "parent", "board", "effect", "parentG"] }] });
2886
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitChildrenElement, decorators: [{
3375
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: PlaitElementComponent, selector: "plait-element", inputs: ["index", "element", "parent", "board", "effect", "parentG"] }] }); }
3376
+ }
3377
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitChildrenElement, decorators: [{
2887
3378
  type: Component,
2888
3379
  args: [{
2889
3380
  selector: 'plait-children',
@@ -3116,10 +3607,6 @@ class PlaitBoardComponent {
3116
3607
  this.board.globalKeydown(event);
3117
3608
  }), filter(event => this.isFocused && !PlaitBoard.hasBeenTextEditing(this.board) && !hasInputOrTextareaTarget(event.target)))
3118
3609
  .subscribe((event) => {
3119
- const selectedElements = getSelectedElements(this.board);
3120
- if (selectedElements.length > 0 && (hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event))) {
3121
- this.board.deleteFragment(null);
3122
- }
3123
3610
  this.board.keydown(event);
3124
3611
  });
3125
3612
  fromEvent(document, 'keyup')
@@ -3133,7 +3620,7 @@ class PlaitBoardComponent {
3133
3620
  const selectedElements = getSelectedElements(this.board);
3134
3621
  event.preventDefault();
3135
3622
  const rectangle = getRectangleByElements(this.board, selectedElements, false);
3136
- this.board.setFragment(event.clipboardData, rectangle);
3623
+ this.board.setFragment(event.clipboardData, rectangle, 'copy');
3137
3624
  });
3138
3625
  fromEvent(document, 'paste')
3139
3626
  .pipe(takeUntil(this.destroy$), filter(() => this.isFocused && !PlaitBoard.isReadonly(this.board) && !PlaitBoard.hasBeenTextEditing(this.board)))
@@ -3150,7 +3637,7 @@ class PlaitBoardComponent {
3150
3637
  const selectedElements = getSelectedElements(this.board);
3151
3638
  event.preventDefault();
3152
3639
  const rectangle = getRectangleByElements(this.board, selectedElements, false);
3153
- this.board.setFragment(event.clipboardData, rectangle);
3640
+ this.board.setFragment(event.clipboardData, rectangle, 'cut');
3154
3641
  this.board.deleteFragment(event.clipboardData);
3155
3642
  });
3156
3643
  }
@@ -3177,7 +3664,9 @@ class PlaitBoardComponent {
3177
3664
  });
3178
3665
  });
3179
3666
  this.ngZone.runOutsideAngular(() => {
3180
- fromEvent(this.viewportContainer.nativeElement, 'touchmove', { passive: false }).subscribe((event) => {
3667
+ fromEvent(this.viewportContainer.nativeElement, 'touchmove', { passive: false })
3668
+ .pipe(takeUntil(this.destroy$))
3669
+ .subscribe((event) => {
3181
3670
  if (isPreventTouchMove(this.board)) {
3182
3671
  event.preventDefault();
3183
3672
  }
@@ -3222,9 +3711,8 @@ class PlaitBoardComponent {
3222
3711
  this.updateIslands();
3223
3712
  });
3224
3713
  }
3225
- }
3226
- PlaitBoardComponentfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitBoardComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ViewContainerRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
3227
- PlaitBoardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.5", type: PlaitBoardComponent, selector: "plait-board", inputs: { plaitValue: "plaitValue", plaitViewport: "plaitViewport", plaitPlugins: "plaitPlugins", plaitOptions: "plaitOptions", plaitTheme: "plaitTheme" }, outputs: { plaitChange: "plaitChange", plaitBoardInitialized: "plaitBoardInitialized" }, host: { properties: { "class": "this.hostClass", "class.readonly": "this.readonly", "class.focused": "this.isFocused", "class.disabled-scroll": "this.disabledScrollOnNonFocus" } }, providers: [PlaitContextService], queries: [{ propertyName: "islands", predicate: PlaitIslandBaseComponent, descendants: true }], viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true, static: true }, { propertyName: "viewportContainer", first: true, predicate: ["viewportContainer"], descendants: true, read: ElementRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
3714
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitBoardComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ViewContainerRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
3715
+ static { thiscmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.3", type: PlaitBoardComponent, selector: "plait-board", inputs: { plaitValue: "plaitValue", plaitViewport: "plaitViewport", plaitPlugins: "plaitPlugins", plaitOptions: "plaitOptions", plaitTheme: "plaitTheme" }, outputs: { plaitChange: "plaitChange", plaitBoardInitialized: "plaitBoardInitialized" }, host: { properties: { "class": "this.hostClass", "class.readonly": "this.readonly", "class.focused": "this.isFocused", "class.disabled-scroll": "this.disabledScrollOnNonFocus" } }, providers: [PlaitContextService], queries: [{ propertyName: "islands", predicate: PlaitIslandBaseComponent, descendants: true }], viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true, static: true }, { propertyName: "viewportContainer", first: true, predicate: ["viewportContainer"], descendants: true, read: ElementRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
3228
3716
  <div class="viewport-container" #viewportContainer>
3229
3717
  <svg #svg width="100%" height="100%" style="position: relative;" class="board-host-svg">
3230
3718
  <g class="element-host"></g>
@@ -3234,8 +3722,9 @@ PlaitBoardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", ve
3234
3722
  <plait-children [board]="board" [effect]="effect"></plait-children>
3235
3723
  </div>
3236
3724
  <ng-content></ng-content>
3237
- `, isInline: true, dependencies: [{ kind: "component", type: PlaitChildrenElement, selector: "plait-children", inputs: ["board", "parent", "effect", "parentG"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3238
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitBoardComponent, decorators: [{
3725
+ `, isInline: true, dependencies: [{ kind: "component", type: PlaitChildrenElement, selector: "plait-children", inputs: ["board", "parent", "effect", "parentG"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3726
+ }
3727
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitBoardComponent, decorators: [{
3239
3728
  type: Component,
3240
3729
  args: [{
3241
3730
  selector: 'plait-board',
@@ -3292,11 +3781,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImpor
3292
3781
 
3293
3782
  const COMPONENTS = [PlaitBoardComponent, PlaitChildrenElement, PlaitElementComponent];
3294
3783
  class PlaitModule {
3784
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
3785
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.3", ngImport: i0, type: PlaitModule, declarations: [PlaitBoardComponent, PlaitChildrenElement, PlaitElementComponent], imports: [CommonModule], exports: [PlaitBoardComponent, PlaitChildrenElement, PlaitElementComponent] }); }
3786
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitModule, imports: [CommonModule] }); }
3295
3787
  }
3296
- PlaitModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
3297
- PlaitModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.5", ngImport: i0, type: PlaitModule, declarations: [PlaitBoardComponent, PlaitChildrenElement, PlaitElementComponent], imports: [CommonModule], exports: [PlaitBoardComponent, PlaitChildrenElement, PlaitElementComponent] });
3298
- PlaitModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitModule, imports: [CommonModule] });
3299
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.5", ngImport: i0, type: PlaitModule, decorators: [{
3788
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitModule, decorators: [{
3300
3789
  type: NgModule,
3301
3790
  args: [{
3302
3791
  declarations: [...COMPONENTS],
@@ -3473,5 +3962,5 @@ function createModModifierKeys() {
3473
3962
  * Generated bundle index. Do not edit.
3474
3963
  */
3475
3964
 
3476
- export { A, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, 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_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLIP_BOARD_FORMAT_KEY, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DefaultThemeColor, 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_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_PREVENT_TOUCH_MOVE, 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, PlaitChildrenElement, PlaitContextService, PlaitElement, PlaitElementComponent, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitModule, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RIGHT_ARROW, RectangleClient, ResizeCursorClass, RetroThemeColor, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, 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, X, Y, Z, ZERO, addMovingElements, addSelectedElement, arrowPoints, cacheMovingElements, cacheSelectedElements, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createFakeEvent, createForeignObject, createG, createKeyboardEvent, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createSVG, createSelectionOuterG, createTestingBoard, createText, createTouchEvent, debounce, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, downloadImage, drawArrow, drawBezierPath, drawCircle, drawLine, drawLinearPath, drawRoundRectangle, fakeNodeWeakMap, getBoardRectangle, getClipboardByKey, getClipboardDataByMedia, getDataFromClipboard, getElementHostBBox, getHitElementOfRoot, getHitElements, getIsRecursionFunc, getMovingElements, getRealScrollBarWidth, getRectangleByElements, getSelectedElements, getTemporaryElements, getTextFromClipboard, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isDOMElement, isDOMNode, isFromScrolling, isFromViewportChange, isHitElements, isInPlaitBoard, isMainPointer, isNullOrUndefined, isPreventTouchMove, isSecondaryPointer, isSelectedElement, isSelectionMoving, isSetViewportOperation, normalizePoint, preventTouchMove, removeMovingElements, removeSelectedElement, rotate, scrollToRectangle, setClipboardData, setClipboardDataByMedia, setClipboardDataByText, setIsFromScrolling, setIsFromViewportChange, setSVGViewBox, setSelectionMoving, shouldClear, shouldMerge, shouldSave, throttleRAF, toImage, toPoint, transformPoint, transformPoints, updateForeignObject, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withMoving, withOptions, withSelection };
3965
+ export { A, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, 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, CLIP_BOARD_FORMAT_KEY, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DefaultThemeColor, 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_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, PlaitChildrenElement, PlaitContextService, PlaitElement, PlaitElementComponent, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitModule, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RIGHT_ARROW, RectangleClient, ResizeCursorClass, RetroThemeColor, 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, X, Y, Z, ZERO, addMovingElements, addSelectedElement, arrowPoints, cacheMovingElements, cacheSelectedElements, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, 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, getClipboardByKey, getClipboardDataByMedia, getDataFromClipboard, getElementById, getElementHostBBox, getHitElementOfRoot, getHitElements, getIsRecursionFunc, getMovingElements, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getRealScrollBarWidth, getRectangleByElements, getSelectedElements, getTemporaryElements, getTemporaryRef, getTextFromClipboard, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, handleTouchTarget, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isDOMElement, isDOMNode, isFromScrolling, isFromViewportChange, isHitElements, isInPlaitBoard, isLineHitLine, isMainPointer, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedElement, isSelectionMoving, isSetViewportOperation, normalizePoint, preventTouchMove, removeMovingElements, removeSelectedElement, rotate, scrollToRectangle, setClipboardData, setClipboardDataByMedia, setClipboardDataByText, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectionMoving, setStrokeLinecap, shouldClear, shouldMerge, shouldSave, throttleRAF, toImage, toPoint, transformPoint, transformPoints, updateForeignObject, updateForeignObjectWidth, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withMoving, withOptions, withSelection };
3477
3966
  //# sourceMappingURL=plait-core.mjs.map