@plait/core 0.24.0-next.5 → 0.24.0-next.7

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.
@@ -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
 
@@ -30,6 +30,16 @@ const BOARD_TO_MOVING_ELEMENT = new WeakMap();
30
30
  const IS_PREVENT_TOUCH_MOVE = new WeakMap();
31
31
  const PATH_REFS = new WeakMap();
32
32
 
33
+ var PlaitPointerType;
34
+ (function (PlaitPointerType) {
35
+ PlaitPointerType["hand"] = "hand";
36
+ PlaitPointerType["selection"] = "selection";
37
+ })(PlaitPointerType || (PlaitPointerType = {}));
38
+
39
+ /**
40
+ * Extendable Custom Types Interface
41
+ */
42
+
33
43
  function depthFirstRecursion(node, callback, recursion, isReverse) {
34
44
  if (!recursion || recursion(node)) {
35
45
  let children = [];
@@ -54,2073 +64,2080 @@ const getIsRecursionFunc = (board) => {
54
64
  };
55
65
  };
56
66
 
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));
67
+ const SELECTION_BORDER_COLOR = '#6698FF';
68
+ const SELECTION_FILL_COLOR = '#6698FF19'; // 主色 0.1 透明度
69
+ const Selection = {
70
+ isCollapsed(selection) {
71
+ if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
72
+ return true;
76
73
  }
77
74
  else {
78
- calcRectangleClient(element);
75
+ return false;
79
76
  }
80
- });
81
- if (boundaryBox.left === Number.MAX_VALUE) {
82
- return {
83
- x: 0,
84
- y: 0,
85
- width: 0,
86
- height: 0
87
- };
88
77
  }
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
- }
99
- function getElementById(board, id) {
100
- let element = null;
78
+ };
79
+
80
+ const getHitElements = (board, selection, match = () => true) => {
81
+ const realSelection = selection || board.selection;
82
+ const selectedElements = [];
83
+ const isCollapsed = realSelection && realSelection.ranges.length === 1 && Selection.isCollapsed(realSelection.ranges[0]);
101
84
  depthFirstRecursion(board, node => {
102
- if (id === node.id) {
103
- element = node;
85
+ if (selectedElements.length > 0 && isCollapsed) {
86
+ return;
87
+ }
88
+ if (!PlaitBoard.isBoard(node) &&
89
+ match(node) &&
90
+ realSelection &&
91
+ realSelection.ranges.some(range => {
92
+ return board.isHitSelection(node, range);
93
+ })) {
94
+ selectedElements.push(node);
104
95
  }
105
96
  }, getIsRecursionFunc(board), true);
106
- return element;
107
- }
108
-
109
- var ThemeColorMode;
110
- (function (ThemeColorMode) {
111
- ThemeColorMode["default"] = "default";
112
- ThemeColorMode["colorful"] = "colorful";
113
- ThemeColorMode["soft"] = "soft";
114
- ThemeColorMode["retro"] = "retro";
115
- ThemeColorMode["dark"] = "dark";
116
- ThemeColorMode["starry"] = "starry";
117
- })(ThemeColorMode || (ThemeColorMode = {}));
118
- const DefaultThemeColor = {
119
- mode: ThemeColorMode.default,
120
- boardBackground: '#ffffff',
121
- textColor: '#333333'
97
+ return selectedElements;
122
98
  };
123
- const ColorfulThemeColor = {
124
- mode: ThemeColorMode.colorful,
125
- boardBackground: '#ffffff',
126
- textColor: '#333333'
99
+ const getHitElementOfRoot = (board, rootElements, range) => {
100
+ const newRootElements = [...rootElements].reverse();
101
+ return newRootElements.find(item => {
102
+ return board.isHitSelection(item, range);
103
+ });
127
104
  };
128
- const SoftThemeColor = {
129
- mode: ThemeColorMode.soft,
130
- boardBackground: '#f5f5f5',
131
- textColor: '#333333'
105
+ const isHitElements = (board, elements, ranges) => {
106
+ let isIntersectionElements = false;
107
+ if (elements.length) {
108
+ elements.map(item => {
109
+ if (!isIntersectionElements) {
110
+ isIntersectionElements = ranges.some(range => {
111
+ return board.isHitSelection(item, range);
112
+ });
113
+ }
114
+ });
115
+ }
116
+ return isIntersectionElements;
132
117
  };
133
- const RetroThemeColor = {
134
- mode: ThemeColorMode.retro,
135
- boardBackground: '#f9f8ed',
136
- textColor: '#333333'
118
+ const cacheSelectedElements = (board, selectedElements) => {
119
+ BOARD_TO_SELECTED_ELEMENT.set(board, selectedElements);
137
120
  };
138
- const DarkThemeColor = {
139
- mode: ThemeColorMode.dark,
140
- boardBackground: '#141414',
141
- textColor: '#FFFFFF'
121
+ const getSelectedElements = (board) => {
122
+ return BOARD_TO_SELECTED_ELEMENT.get(board) || [];
142
123
  };
143
- const StarryThemeColor = {
144
- mode: ThemeColorMode.starry,
145
- boardBackground: '#0d2537',
146
- textColor: '#FFFFFF'
124
+ const addSelectedElement = (board, element) => {
125
+ const selectedElements = getSelectedElements(board);
126
+ cacheSelectedElements(board, [...selectedElements, element]);
147
127
  };
148
- const ThemeColors = [
149
- DefaultThemeColor,
150
- ColorfulThemeColor,
151
- SoftThemeColor,
152
- RetroThemeColor,
153
- DarkThemeColor,
154
- StarryThemeColor
155
- ];
156
-
157
- const RectangleClient = {
158
- isHit: (origin, target) => {
159
- const minX = origin.x < target.x ? origin.x : target.x;
160
- const maxX = origin.x + origin.width > target.x + target.width ? origin.x + origin.width : target.x + target.width;
161
- const minY = origin.y < target.y ? origin.y : target.y;
162
- const maxY = origin.y + origin.height > target.y + target.height ? origin.y + origin.height : target.y + target.height;
163
- // float calculate error( eg: 1.4210854715202004e-14 > 0)
164
- if (Math.floor(maxX - minX - origin.width - target.width) <= 0 && Math.floor(maxY - minY - origin.height - target.height) <= 0) {
165
- return true;
166
- }
167
- else {
168
- return false;
169
- }
170
- },
171
- toRectangleClient: (points) => {
172
- const xArray = points.map(ele => ele[0]);
173
- const yArray = points.map(ele => ele[1]);
174
- const xMin = Math.min(...xArray);
175
- const xMax = Math.max(...xArray);
176
- const yMin = Math.min(...yArray);
177
- const yMax = Math.max(...yArray);
178
- const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
179
- return rect;
180
- },
181
- getOutlineRectangle: (rectangle, offset) => {
182
- return {
183
- x: rectangle.x + offset,
184
- y: rectangle.y + offset,
185
- width: rectangle.width - offset * 2,
186
- height: rectangle.height - offset * 2
187
- };
188
- },
189
- isEqual: (rectangle, otherRectangle) => {
190
- return (rectangle.x === otherRectangle.x &&
191
- rectangle.y === otherRectangle.y &&
192
- rectangle.width === otherRectangle.width &&
193
- rectangle.height === otherRectangle.height);
194
- },
195
- getCornerPoints: (rectangle) => {
196
- return [
197
- [rectangle.x, rectangle.y],
198
- [rectangle.x + rectangle.width, rectangle.y],
199
- [rectangle.x + rectangle.width, rectangle.y + rectangle.height],
200
- [rectangle.x, rectangle.y + rectangle.height]
201
- ];
202
- },
203
- getEdgeCenterPoints: (rectangle) => {
204
- return [
205
- [rectangle.x + rectangle.width / 2, rectangle.y],
206
- [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
207
- [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
208
- [rectangle.x, rectangle.y + rectangle.height / 2]
209
- ];
210
- }
128
+ const removeSelectedElement = (board, element) => {
129
+ const selectedElements = getSelectedElements(board);
130
+ const newSelectedElements = selectedElements.filter(value => value !== element);
131
+ cacheSelectedElements(board, newSelectedElements);
132
+ };
133
+ const clearSelectedElement = (board) => {
134
+ cacheSelectedElements(board, []);
135
+ };
136
+ const isSelectedElement = (board, element) => {
137
+ const selectedElements = getSelectedElements(board);
138
+ return !!selectedElements.find(value => value === element);
211
139
  };
212
140
 
213
- // https://stackoverflow.com/a/6853926/232122
214
- function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
215
- const A = x - x1;
216
- const B = y - y1;
217
- const C = x2 - x1;
218
- const D = y2 - y1;
219
- const dot = A * C + B * D;
220
- const lenSquare = C * C + D * D;
221
- let param = -1;
222
- if (lenSquare !== 0) {
223
- // in case of 0 length line
224
- param = dot / lenSquare;
225
- }
226
- let xx, yy;
227
- if (param < 0) {
228
- xx = x1;
229
- yy = y1;
230
- }
231
- else if (param > 1) {
232
- xx = x2;
233
- yy = y2;
234
- }
235
- else {
236
- xx = x1 + param * C;
237
- yy = y1 + param * D;
238
- }
239
- const dx = x - xx;
240
- const dy = y - yy;
241
- return Math.hypot(dx, dy);
242
- }
243
- function getNearestPointBetweenPointAndSegment(point, linePoints) {
244
- const x = point[0], y = point[1], x1 = linePoints[0][0], y1 = linePoints[0][1], x2 = linePoints[1][0], y2 = linePoints[1][1];
245
- const A = x - x1;
246
- const B = y - y1;
247
- const C = x2 - x1;
248
- const D = y2 - y1;
249
- const dot = A * C + B * D;
250
- const lenSquare = C * C + D * D;
251
- let param = -1;
252
- if (lenSquare !== 0) {
253
- // in case of 0 length line
254
- param = dot / lenSquare;
255
- }
256
- let xx, yy;
257
- if (param < 0) {
258
- xx = x1;
259
- yy = y1;
260
- }
261
- else if (param > 1) {
262
- xx = x2;
263
- yy = y2;
264
- }
265
- else {
266
- xx = x1 + param * C;
267
- yy = y1 + param * D;
268
- }
269
- return [xx, yy];
141
+ /**
142
+ * @license
143
+ * Copyright Google LLC All Rights Reserved.
144
+ *
145
+ * Use of this source code is governed by an MIT-style license that can be
146
+ * found in the LICENSE file at https://angular.io/license
147
+ */
148
+ const MAC_ENTER = 3;
149
+ const BACKSPACE = 8;
150
+ const TAB = 9;
151
+ const NUM_CENTER = 12;
152
+ const ENTER = 13;
153
+ const SHIFT = 16;
154
+ const CONTROL = 17;
155
+ const ALT = 18;
156
+ const PAUSE = 19;
157
+ const CAPS_LOCK = 20;
158
+ const ESCAPE = 27;
159
+ const SPACE = 32;
160
+ const PAGE_UP = 33;
161
+ const PAGE_DOWN = 34;
162
+ const END = 35;
163
+ const HOME = 36;
164
+ const LEFT_ARROW = 37;
165
+ const UP_ARROW = 38;
166
+ const RIGHT_ARROW = 39;
167
+ const DOWN_ARROW = 40;
168
+ const PLUS_SIGN = 43;
169
+ const PRINT_SCREEN = 44;
170
+ const INSERT = 45;
171
+ const DELETE = 46;
172
+ const ZERO = 48;
173
+ const ONE = 49;
174
+ const TWO = 50;
175
+ const THREE = 51;
176
+ const FOUR = 52;
177
+ const FIVE = 53;
178
+ const SIX = 54;
179
+ const SEVEN = 55;
180
+ const EIGHT = 56;
181
+ const NINE = 57;
182
+ const FF_SEMICOLON = 59; // Firefox (Gecko) fires this for semicolon instead of 186
183
+ const FF_EQUALS = 61; // Firefox (Gecko) fires this for equals instead of 187
184
+ const QUESTION_MARK = 63;
185
+ const AT_SIGN = 64;
186
+ const A = 65;
187
+ const B = 66;
188
+ const C = 67;
189
+ const D = 68;
190
+ const E = 69;
191
+ const F = 70;
192
+ const G = 71;
193
+ const H = 72;
194
+ const I = 73;
195
+ const J = 74;
196
+ const K = 75;
197
+ const L = 76;
198
+ const M = 77;
199
+ const N = 78;
200
+ const O = 79;
201
+ const P = 80;
202
+ const Q = 81;
203
+ const R = 82;
204
+ const S = 83;
205
+ const T = 84;
206
+ const U = 85;
207
+ const V = 86;
208
+ const W = 87;
209
+ const X = 88;
210
+ const Y = 89;
211
+ const Z = 90;
212
+ const META = 91; // WIN_KEY_LEFT
213
+ const MAC_WK_CMD_LEFT = 91;
214
+ const MAC_WK_CMD_RIGHT = 93;
215
+ const CONTEXT_MENU = 93;
216
+ const NUMPAD_ZERO = 96;
217
+ const NUMPAD_ONE = 97;
218
+ const NUMPAD_TWO = 98;
219
+ const NUMPAD_THREE = 99;
220
+ const NUMPAD_FOUR = 100;
221
+ const NUMPAD_FIVE = 101;
222
+ const NUMPAD_SIX = 102;
223
+ const NUMPAD_SEVEN = 103;
224
+ const NUMPAD_EIGHT = 104;
225
+ const NUMPAD_NINE = 105;
226
+ const NUMPAD_MULTIPLY = 106;
227
+ const NUMPAD_PLUS = 107;
228
+ const NUMPAD_MINUS = 109;
229
+ const NUMPAD_PERIOD = 110;
230
+ const NUMPAD_DIVIDE = 111;
231
+ const F1 = 112;
232
+ const F2 = 113;
233
+ const F3 = 114;
234
+ const F4 = 115;
235
+ const F5 = 116;
236
+ const F6 = 117;
237
+ const F7 = 118;
238
+ const F8 = 119;
239
+ const F9 = 120;
240
+ const F10 = 121;
241
+ const F11 = 122;
242
+ const F12 = 123;
243
+ const NUM_LOCK = 144;
244
+ const SCROLL_LOCK = 145;
245
+ const FIRST_MEDIA = 166;
246
+ const FF_MINUS = 173;
247
+ const MUTE = 173; // Firefox (Gecko) fires 181 for MUTE
248
+ const VOLUME_DOWN = 174; // Firefox (Gecko) fires 182 for VOLUME_DOWN
249
+ const VOLUME_UP = 175; // Firefox (Gecko) fires 183 for VOLUME_UP
250
+ const FF_MUTE = 181;
251
+ const FF_VOLUME_DOWN = 182;
252
+ const LAST_MEDIA = 183;
253
+ const FF_VOLUME_UP = 183;
254
+ const SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON
255
+ const EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS
256
+ const COMMA = 188;
257
+ const DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS
258
+ const PERIOD = 190;
259
+ const SLASH = 191;
260
+ const APOSTROPHE = 192;
261
+ const TILDE = 192;
262
+ const OPEN_SQUARE_BRACKET = 219;
263
+ const BACKSLASH = 220;
264
+ const CLOSE_SQUARE_BRACKET = 221;
265
+ const SINGLE_QUOTE = 222;
266
+ const MAC_META = 224;
267
+
268
+ var ResizeCursorClass;
269
+ (function (ResizeCursorClass) {
270
+ ResizeCursorClass["ew-resize"] = "ew-resize";
271
+ })(ResizeCursorClass || (ResizeCursorClass = {}));
272
+
273
+ const ATTACHED_ELEMENT_CLASS_NAME = 'plait-board-attached';
274
+
275
+ const CLIP_BOARD_FORMAT_KEY = 'x-plait-fragment';
276
+ const HOST_CLASS_NAME = 'plait-board-container';
277
+ const SCROLL_BAR_WIDTH = 20;
278
+ const MAX_RADIUS = 16;
279
+ const POINTER_BUTTON = {
280
+ MAIN: 0,
281
+ WHEEL: 1,
282
+ SECONDARY: 2,
283
+ TOUCH: -1
284
+ };
285
+ const PRESS_AND_MOVE_BUFFER = 5;
286
+
287
+ const NS = 'http://www.w3.org/2000/svg';
288
+ function toPoint(x, y, container) {
289
+ const rect = container.getBoundingClientRect();
290
+ return [x - rect.x, y - rect.y];
270
291
  }
271
- function distanceBetweenPointAndSegments(points, point) {
272
- const len = points.length;
273
- let distance = Infinity;
274
- for (let i = 0; i < len - 1; i++) {
275
- const p = points[i];
276
- const p2 = points[i + 1];
277
- const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
278
- if (currentDistance < distance) {
279
- distance = currentDistance;
280
- }
281
- }
282
- return distance;
292
+ function createG() {
293
+ const newG = document.createElementNS(NS, 'g');
294
+ return newG;
283
295
  }
284
- function getNearestPointBetweenPointAndSegments(point, points) {
285
- const len = points.length;
286
- let distance = Infinity;
287
- let result = point;
288
- for (let i = 0; i < len; i++) {
289
- const p = points[i];
290
- const p2 = i === len - 1 ? points[0] : points[i + 1];
291
- const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
292
- if (currentDistance < distance) {
293
- distance = currentDistance;
294
- result = getNearestPointBetweenPointAndSegment(point, [p, p2]);
295
- }
296
+ function createPath() {
297
+ const newG = document.createElementNS(NS, 'path');
298
+ return newG;
299
+ }
300
+ function createRect(rectangle, options) {
301
+ const rect = document.createElementNS(NS, 'rect');
302
+ rect.setAttribute('x', `${rectangle.x}`);
303
+ rect.setAttribute('y', `${rectangle.y}`);
304
+ rect.setAttribute('width', `${rectangle.width}`);
305
+ rect.setAttribute('height', `${rectangle.height}`);
306
+ for (let key in options) {
307
+ const optionKey = key;
308
+ rect.setAttribute(key, `${options[optionKey]}`);
296
309
  }
297
- return result;
310
+ return rect;
298
311
  }
299
- function rotate(x1, y1, x2, y2, angle) {
300
- // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
301
- // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
302
- // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
303
- return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
312
+ const setStrokeLinecap = (g, value) => {
313
+ g.setAttribute('stroke-linecap', value);
314
+ };
315
+ const setPathStrokeLinecap = (g, value) => {
316
+ g.querySelectorAll('path').forEach(path => {
317
+ path.setAttribute('stroke-linecap', value);
318
+ });
319
+ };
320
+ function createMask() {
321
+ return document.createElementNS(NS, 'mask');
304
322
  }
305
- function distanceBetweenPointAndPoint(x1, y1, x2, y2) {
306
- const dx = x1 - x2;
307
- const dy = y1 - y2;
308
- return Math.hypot(dx, dy);
323
+ function createSVG() {
324
+ const svg = document.createElementNS(NS, 'svg');
325
+ return svg;
309
326
  }
310
- // https://stackoverflow.com/questions/5254838/calculating-distance-between-a-point-and-a-rectangular-box-nearest-point
311
- function distanceBetweenPointAndRectangle(x, y, rect) {
312
- var dx = Math.max(rect.x - x, 0, x - (rect.x + rect.width));
313
- var dy = Math.max(rect.y - y, 0, y - (rect.y + rect.height));
314
- return Math.sqrt(dx * dx + dy * dy);
327
+ function createText(x, y, fill, textContent) {
328
+ var text = document.createElementNS(NS, 'text');
329
+ text.setAttribute('x', `${x}`);
330
+ text.setAttribute('y', `${y}`);
331
+ text.setAttribute('fill', fill);
332
+ text.textContent = textContent;
333
+ return text;
315
334
  }
316
- const isLineHitLine = (a, b, c, d) => {
317
- const crossProduct = (v1, v2) => v1[0] * v2[1] - v1[1] * v2[0];
318
- const ab = [b[0] - a[0], b[1] - a[1]];
319
- const ac = [c[0] - a[0], c[1] - a[1]];
320
- const ad = [d[0] - a[0], d[1] - a[1]];
321
- const ca = [a[0] - c[0], a[1] - c[1]];
322
- const cb = [b[0] - c[0], b[1] - c[1]];
323
- const cd = [d[0] - c[0], d[1] - c[1]];
324
- return crossProduct(ab, ac) * crossProduct(ab, ad) <= 0 && crossProduct(cd, ca) * crossProduct(cd, cb) <= 0;
335
+ /**
336
+ * Check if a DOM node is an element node.
337
+ */
338
+ const isDOMElement = (value) => {
339
+ return isDOMNode(value) && value.nodeType === 1;
325
340
  };
326
- const isPolylineHitRectangle = (points, rectangle) => {
327
- const rectanglePoints = RectangleClient.getCornerPoints(rectangle);
328
- for (let i = 1; i < points.length; i++) {
329
- const isIntersect = isLineHitLine(points[i], points[i - 1], rectanglePoints[0], rectanglePoints[1]) ||
330
- isLineHitLine(points[i], points[i - 1], rectanglePoints[1], rectanglePoints[2]) ||
331
- isLineHitLine(points[i], points[i - 1], rectanglePoints[2], rectanglePoints[3]) ||
332
- isLineHitLine(points[i], points[i - 1], rectanglePoints[3], rectanglePoints[0]);
333
- if (isIntersect) {
341
+ /**
342
+ * Check if a value is a DOM node.
343
+ */
344
+ const isDOMNode = (value) => {
345
+ return value instanceof window.Node;
346
+ };
347
+ const hasInputOrTextareaTarget = (target) => {
348
+ if (isDOMElement(target)) {
349
+ if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
334
350
  return true;
335
351
  }
336
352
  }
337
353
  return false;
338
354
  };
339
- //https://stackoverflow.com/questions/22521982/check-if-point-is-inside-a-polygon
340
- const isPointInPolygon = (point, points) => {
341
- // ray-casting algorithm based on
342
- // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
343
- const x = point[0], y = point[1];
344
- let inside = false;
345
- for (var i = 0, j = points.length - 1; i < points.length; j = i++) {
346
- let xi = points[i][0], yi = points[i][1];
347
- let xj = points[j][0], yj = points[j][1];
348
- let intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
349
- if (intersect)
350
- inside = !inside;
351
- }
352
- return inside;
353
- };
354
- const isPointInEllipse = (point, center, rx, ry, rotation = 0) => {
355
- const cosAngle = Math.cos(rotation);
356
- const sinAngle = Math.sin(rotation);
357
- const x1 = (point[0] - center[0]) * cosAngle + (point[1] - center[1]) * sinAngle;
358
- const y1 = (point[1] - center[1]) * cosAngle - (point[0] - center[0]) * sinAngle;
359
- return (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry) <= 1;
355
+ const isSecondaryPointer = (event) => {
356
+ return event.button === POINTER_BUTTON.SECONDARY;
360
357
  };
361
- const isPointInRoundRectangle = (point, rectangle, radius) => {
362
- const { x: rectX, y: rectY, width, height } = rectangle;
363
- const isInRectangle = point[0] >= rectX && point[0] <= rectX + width && point[1] >= rectY && point[1] <= rectY + height;
364
- const handleLeftTop = point[0] >= rectX &&
365
- point[0] <= rectX + radius &&
366
- point[1] >= rectY &&
367
- point[1] <= rectY + radius &&
368
- Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + radius)) > radius;
369
- const handleLeftBottom = point[0] >= rectX &&
370
- point[0] <= rectX + radius &&
371
- point[1] >= rectY + height &&
372
- point[1] <= rectY + height - radius &&
373
- Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + height - radius)) > radius;
374
- const handleRightTop = point[0] >= rectX + width - radius &&
375
- point[0] <= rectX + width &&
376
- point[1] >= rectY &&
377
- point[1] <= rectY + radius &&
378
- Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + radius)) > radius;
379
- const handleRightBottom = point[0] >= rectX + width - radius &&
380
- point[0] <= rectX + width &&
381
- point[1] >= rectY + height - radius &&
382
- point[1] <= rectY + height &&
383
- Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + height - radius)) > radius;
384
- const isInCorner = handleLeftTop || handleLeftBottom || handleRightTop || handleRightBottom;
385
- return isInRectangle && !isInCorner;
358
+ const isMainPointer = (event) => {
359
+ return event.button === POINTER_BUTTON.MAIN;
386
360
  };
387
361
 
388
- const PlaitBoard = {
389
- isBoard(value) {
390
- const cachedIsBoard = IS_BOARD_CACHE.get(value);
391
- if (cachedIsBoard !== undefined) {
392
- return cachedIsBoard;
362
+ function hasBeforeContextChange(value) {
363
+ if (value.beforeContextChange) {
364
+ return true;
365
+ }
366
+ return false;
367
+ }
368
+ function hasOnContextChanged(value) {
369
+ if (value.onContextChanged) {
370
+ return true;
371
+ }
372
+ return false;
373
+ }
374
+
375
+ class PlaitPluginElementComponent {
376
+ set context(value) {
377
+ if (hasBeforeContextChange(this)) {
378
+ this.beforeContextChange(value);
393
379
  }
394
- const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
395
- IS_BOARD_CACHE.set(value, isBoard);
396
- return isBoard;
397
- },
398
- findPath(board, node) {
399
- const path = [];
400
- let child = node;
401
- while (true) {
402
- const parent = NODE_TO_PARENT.get(child);
403
- if (parent == null) {
404
- if (PlaitBoard.isBoard(child)) {
405
- return path;
406
- }
407
- else {
408
- break;
409
- }
380
+ const previousContext = this._context;
381
+ this._context = value;
382
+ if (this.element) {
383
+ ELEMENT_TO_COMPONENT.set(this.element, this);
384
+ }
385
+ if (this.initialized) {
386
+ this.cdr.markForCheck();
387
+ if (hasOnContextChanged(this)) {
388
+ this.onContextChanged(value, previousContext);
410
389
  }
411
- const i = NODE_TO_INDEX.get(child);
412
- if (i == null) {
413
- break;
390
+ }
391
+ else {
392
+ if (PlaitElement.isRootElement(this.element) && this.element.children) {
393
+ this.g = createG();
394
+ this.rootG = createG();
395
+ this.rootG.append(this.g);
396
+ }
397
+ else {
398
+ this.g = createG();
414
399
  }
415
- path.unshift(i);
416
- child = parent;
417
400
  }
418
- throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
419
- },
420
- getHost(board) {
421
- return BOARD_TO_HOST.get(board);
422
- },
423
- getElementHost(board) {
424
- return BOARD_TO_ELEMENT_HOST.get(board)?.host;
425
- },
426
- getElementUpperHost(board) {
427
- return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
428
- },
429
- getElementActiveHost(board) {
430
- return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
431
- },
432
- getRoughSVG(board) {
433
- return BOARD_TO_ROUGH_SVG.get(board);
434
- },
435
- getComponent(board) {
436
- return BOARD_TO_COMPONENT.get(board);
437
- },
438
- getBoardContainer(board) {
439
- return PlaitBoard.getComponent(board).nativeElement;
440
- },
441
- getRectangle(board) {
442
- return getRectangleByElements(board, board.children, true);
443
- },
444
- getViewportContainer(board) {
445
- return PlaitBoard.getHost(board).parentElement;
446
- },
447
- isFocus(board) {
448
- return !!board.selection;
449
- },
450
- isReadonly(board) {
451
- return board.options.readonly;
452
- },
453
- hasBeenTextEditing(board) {
454
- return !!IS_TEXT_EDITABLE.get(board);
455
- },
456
- getPointer(board) {
457
- return board.pointer;
458
- },
459
- isPointer(board, pointer) {
460
- return board.pointer === pointer;
461
- },
462
- isInPointer(board, pointers) {
463
- const point = board.pointer;
464
- return pointers.includes(point);
465
- },
466
- getMovingPointInBoard(board) {
467
- return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
468
- },
469
- isMovingPointInBoard(board) {
470
- const point = BOARD_TO_MOVING_POINT.get(board);
471
- const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
472
- if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
473
- return true;
401
+ }
402
+ get context() {
403
+ return this._context;
404
+ }
405
+ get element() {
406
+ return this.context && this.context.element;
407
+ }
408
+ get board() {
409
+ return this.context && this.context.board;
410
+ }
411
+ get selected() {
412
+ return this.context && this.context.selected;
413
+ }
414
+ get effect() {
415
+ return this.context && this.context.effect;
416
+ }
417
+ constructor(cdr) {
418
+ this.cdr = cdr;
419
+ this.initialized = false;
420
+ }
421
+ ngOnInit() {
422
+ if (this.element.type) {
423
+ (this.rootG || this.g).setAttribute(`plait-${this.element.type}`, 'true');
474
424
  }
475
- return false;
476
- },
477
- getThemeColors(board) {
478
- return (board.options.themeColors || ThemeColors);
425
+ this.initialized = true;
479
426
  }
480
- };
481
-
482
- var PlaitPointerType;
483
- (function (PlaitPointerType) {
484
- PlaitPointerType["hand"] = "hand";
485
- PlaitPointerType["selection"] = "selection";
486
- })(PlaitPointerType || (PlaitPointerType = {}));
487
-
488
- function isNullOrUndefined(value) {
489
- return value === null || value === undefined;
490
- }
491
- /**
492
- * 规范 point
493
- * @param point
494
- * @returns point
495
- */
496
- function normalizePoint(point) {
497
- return Array.isArray(point)
498
- ? {
499
- x: point[0],
500
- y: point[1]
427
+ ngOnDestroy() {
428
+ if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
429
+ ELEMENT_TO_COMPONENT.delete(this.element);
501
430
  }
502
- : point;
431
+ removeSelectedElement(this.board, this.element);
432
+ (this.rootG || this.g).remove();
433
+ }
434
+ 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 }); }
435
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.3", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
503
436
  }
437
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitPluginElementComponent, decorators: [{
438
+ type: Directive
439
+ }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { context: [{
440
+ type: Input
441
+ }] } });
442
+ const ELEMENT_TO_COMPONENT = new WeakMap();
504
443
 
505
- const Viewport = {
506
- isViewport: (value) => {
507
- return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
508
- },
509
- };
510
-
511
- const Path = {
512
- /**
513
- * Get a list of ancestor paths for a given path.
514
- *
515
- * The paths are sorted from shallowest to deepest ancestor. However, if the
516
- * `reverse: true` option is passed, they are reversed.
517
- */
518
- ancestors(path, options = {}) {
519
- const { reverse = false } = options;
520
- let paths = Path.levels(path, options);
521
- if (reverse) {
522
- paths = paths.slice(1);
444
+ const RectangleClient = {
445
+ isHit: (origin, target) => {
446
+ const minX = origin.x < target.x ? origin.x : target.x;
447
+ const maxX = origin.x + origin.width > target.x + target.width ? origin.x + origin.width : target.x + target.width;
448
+ const minY = origin.y < target.y ? origin.y : target.y;
449
+ const maxY = origin.y + origin.height > target.y + target.height ? origin.y + origin.height : target.y + target.height;
450
+ // float calculate error( eg: 1.4210854715202004e-14 > 0)
451
+ if (Math.floor(maxX - minX - origin.width - target.width) <= 0 && Math.floor(maxY - minY - origin.height - target.height) <= 0) {
452
+ return true;
523
453
  }
524
454
  else {
525
- paths = paths.slice(0, -1);
526
- }
527
- return paths;
528
- },
529
- /**
530
- * Get a list of paths at every level down to a path. Note: this is the same
531
- * as `Path.ancestors`, but including the path itself.
532
- *
533
- * The paths are sorted from shallowest to deepest. However, if the `reverse:
534
- * true` option is passed, they are reversed.
535
- */
536
- levels(path, options = {}) {
537
- const { reverse = false } = options;
538
- const list = [];
539
- for (let i = 0; i <= path.length; i++) {
540
- list.push(path.slice(0, i));
541
- }
542
- if (reverse) {
543
- list.reverse();
544
- }
545
- return list;
546
- },
547
- parent(path) {
548
- if (path.length === 0) {
549
- throw new Error(`Cannot get the parent path of the root path [${path}].`);
550
- }
551
- return path.slice(0, -1);
552
- },
553
- next(path) {
554
- if (path.length === 0) {
555
- throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
556
- }
557
- const last = path[path.length - 1];
558
- return path.slice(0, -1).concat(last + 1);
559
- },
560
- hasPrevious(path) {
561
- return path[path.length - 1] > 0;
562
- },
563
- previous(path) {
564
- if (path.length === 0) {
565
- throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
455
+ return false;
566
456
  }
567
- const last = path[path.length - 1];
568
- return path.slice(0, -1).concat(last - 1);
569
457
  },
570
- /**
571
- * Check if a path is an ancestor of another.
572
- */
573
- isAncestor(path, another) {
574
- return path.length < another.length && Path.compare(path, another) === 0;
575
- },
576
- /**
577
- * Compare a path to another, returning an integer indicating whether the path
578
- * was before, at, or after the other.
579
- *
580
- * Note: Two paths of unequal length can still receive a `0` result if one is
581
- * directly above or below the other. If you want exact matching, use
582
- * [[Path.equals]] instead.
583
- */
584
- compare(path, another) {
585
- const min = Math.min(path.length, another.length);
586
- for (let i = 0; i < min; i++) {
587
- if (path[i] < another[i])
588
- return -1;
589
- if (path[i] > another[i])
590
- return 1;
591
- }
592
- return 0;
458
+ toRectangleClient: (points) => {
459
+ const xArray = points.map(ele => ele[0]);
460
+ const yArray = points.map(ele => ele[1]);
461
+ const xMin = Math.min(...xArray);
462
+ const xMax = Math.max(...xArray);
463
+ const yMin = Math.min(...yArray);
464
+ const yMax = Math.max(...yArray);
465
+ const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
466
+ return rect;
593
467
  },
594
- /**
595
- * Check if a path is exactly equal to another.
596
- */
597
- equals(path, another) {
598
- return path.length === another.length && path.every((n, i) => n === another[i]);
468
+ getOutlineRectangle: (rectangle, offset) => {
469
+ return {
470
+ x: rectangle.x + offset,
471
+ y: rectangle.y + offset,
472
+ width: rectangle.width - offset * 2,
473
+ height: rectangle.height - offset * 2
474
+ };
599
475
  },
600
- /**
601
- * Check if a path ends before one of the indexes in another.
602
- */
603
- endsBefore(path, another) {
604
- const i = path.length - 1;
605
- const as = path.slice(0, i);
606
- const bs = another.slice(0, i);
607
- const av = path[i];
608
- const bv = another[i];
609
- return Path.equals(as, bs) && av < bv;
476
+ isEqual: (rectangle, otherRectangle) => {
477
+ return (rectangle.x === otherRectangle.x &&
478
+ rectangle.y === otherRectangle.y &&
479
+ rectangle.width === otherRectangle.width &&
480
+ rectangle.height === otherRectangle.height);
610
481
  },
611
- /**
612
- * Check if a path is a sibling of another.
613
- */
614
- isSibling(path, another) {
615
- if (path.length !== another.length) {
616
- return false;
617
- }
618
- const as = path.slice(0, -1);
619
- const bs = another.slice(0, -1);
620
- const al = path[path.length - 1];
621
- const bl = another[another.length - 1];
622
- return al !== bl && Path.equals(as, bs);
482
+ getCornerPoints: (rectangle) => {
483
+ return [
484
+ [rectangle.x, rectangle.y],
485
+ [rectangle.x + rectangle.width, rectangle.y],
486
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height],
487
+ [rectangle.x, rectangle.y + rectangle.height]
488
+ ];
623
489
  },
624
- transform(path, operation) {
625
- return produce(path, p => {
626
- // PERF: Exit early if the operation is guaranteed not to have an effect.
627
- if (!path || path?.length === 0) {
628
- return;
629
- }
630
- if (p === null) {
631
- return null;
632
- }
633
- switch (operation.type) {
634
- case 'insert_node': {
635
- const { path: op } = operation;
636
- if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
637
- p[op.length - 1] += 1;
638
- }
639
- break;
640
- }
641
- case 'remove_node': {
642
- const { path: op } = operation;
643
- if (Path.equals(op, p) || Path.isAncestor(op, p)) {
644
- return null;
645
- }
646
- else if (Path.endsBefore(op, p)) {
647
- p[op.length - 1] -= 1;
648
- }
649
- break;
650
- }
651
- case 'move_node': {
652
- const { path: op, newPath: onp } = operation;
653
- // If the old and new path are the same, it's a no-op.
654
- if (Path.equals(op, onp)) {
655
- return;
656
- }
657
- if (Path.isAncestor(op, p) || Path.equals(op, p)) {
658
- const copy = onp.slice();
659
- // op.length <= onp.length is different for slate
660
- // resolve drag from [0, 0] to [0, 3] issue
661
- if (Path.endsBefore(op, onp) && op.length <= onp.length) {
662
- copy[op.length - 1] -= 1;
663
- }
664
- return copy.concat(p.slice(op.length));
665
- }
666
- else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
667
- if (Path.endsBefore(op, p)) {
668
- p[op.length - 1] -= 1;
669
- }
670
- else {
671
- p[op.length - 1] += 1;
672
- }
673
- }
674
- else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
675
- if (Path.endsBefore(op, p)) {
676
- p[op.length - 1] -= 1;
677
- }
678
- p[onp.length - 1] += 1;
679
- }
680
- else if (Path.endsBefore(op, p)) {
681
- if (Path.equals(onp, p)) {
682
- p[onp.length - 1] += 1;
683
- }
684
- p[op.length - 1] -= 1;
685
- }
686
- break;
687
- }
688
- }
689
- return p;
690
- });
490
+ getEdgeCenterPoints: (rectangle) => {
491
+ return [
492
+ [rectangle.x + rectangle.width / 2, rectangle.y],
493
+ [rectangle.x + rectangle.width, rectangle.y + rectangle.height / 2],
494
+ [rectangle.x + rectangle.width / 2, rectangle.y + rectangle.height],
495
+ [rectangle.x, rectangle.y + rectangle.height / 2]
496
+ ];
691
497
  }
692
498
  };
693
499
 
694
- const PlaitNode = {
695
- parent: (board, path) => {
696
- const parentPath = Path.parent(path);
697
- const p = PlaitNode.get(board, parentPath);
698
- return p;
699
- },
700
- /**
701
- * Return a generator of all the ancestor nodes above a specific path.
702
- *
703
- * By default the order is top-down, from highest to lowest ancestor in
704
- * the tree, but you can pass the `reverse: true` option to go bottom-up.
705
- */
706
- *parents(root, path, options = {}) {
707
- for (const p of Path.ancestors(path, options)) {
708
- const n = PlaitNode.get(root, p);
709
- yield n;
710
- }
711
- },
712
- get(root, path) {
713
- let node = root;
714
- for (let i = 0; i < path.length; i++) {
715
- const p = path[i];
716
- if (!node || !node.children || !node.children[p]) {
717
- throw new Error(`Cannot find a descendant at path [${path}]`);
718
- }
719
- node = node.children[p];
720
- }
721
- return node;
722
- },
723
- last(board, path) {
724
- let n = PlaitNode.get(board, path);
725
- while (n && n.children && n.children.length > 0) {
726
- const i = n.children.length - 1;
727
- n = n.children[i];
728
- }
729
- return n;
500
+ // https://stackoverflow.com/a/6853926/232122
501
+ function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
502
+ const A = x - x1;
503
+ const B = y - y1;
504
+ const C = x2 - x1;
505
+ const D = y2 - y1;
506
+ const dot = A * C + B * D;
507
+ const lenSquare = C * C + D * D;
508
+ let param = -1;
509
+ if (lenSquare !== 0) {
510
+ // in case of 0 length line
511
+ param = dot / lenSquare;
730
512
  }
731
- };
732
-
733
- const applyToDraft = (board, selection, viewport, theme, op) => {
734
- switch (op.type) {
735
- case 'insert_node': {
736
- const { path, node } = op;
737
- const parent = PlaitNode.parent(board, path);
738
- const index = path[path.length - 1];
739
- if (!parent.children || index > parent.children.length) {
740
- throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
741
- }
742
- parent.children.splice(index, 0, node);
743
- break;
744
- }
745
- case 'remove_node': {
746
- const { path } = op;
747
- const parent = PlaitNode.parent(board, path);
748
- const index = path[path.length - 1];
749
- if (!parent.children || index > parent.children.length) {
750
- throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
751
- }
752
- parent.children.splice(index, 1);
753
- break;
754
- }
755
- case 'move_node': {
756
- const { path, newPath } = op;
757
- if (Path.isAncestor(path, newPath)) {
758
- throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
759
- }
760
- const node = PlaitNode.get(board, path);
761
- const parent = PlaitNode.parent(board, path);
762
- const index = path[path.length - 1];
763
- // This is tricky, but since the `path` and `newPath` both refer to
764
- // the same snapshot in time, there's a mismatch. After either
765
- // removing the original position, the second step's path can be out
766
- // of date. So instead of using the `op.newPath` directly, we
767
- // transform `op.path` to ascertain what the `newPath` would be after
768
- // the operation was applied.
769
- parent.children?.splice(index, 1);
770
- const truePath = Path.transform(path, op);
771
- const newParent = PlaitNode.get(board, Path.parent(truePath));
772
- const newIndex = truePath[truePath.length - 1];
773
- newParent.children?.splice(newIndex, 0, node);
774
- break;
775
- }
776
- case 'set_node': {
777
- const { path, properties, newProperties } = op;
778
- if (path.length === 0) {
779
- throw new Error(`Cannot set properties on the root node!`);
780
- }
781
- const node = PlaitNode.get(board, path);
782
- for (const key in newProperties) {
783
- const value = newProperties[key];
784
- if (value == null) {
785
- delete node[key];
786
- }
787
- else {
788
- node[key] = value;
789
- }
790
- }
791
- // properties that were previously defined, but are now missing, must be deleted
792
- for (const key in properties) {
793
- if (!newProperties.hasOwnProperty(key)) {
794
- delete node[key];
795
- }
796
- }
797
- break;
798
- }
799
- case 'set_viewport': {
800
- const { newProperties } = op;
801
- if (newProperties == null) {
802
- viewport = newProperties;
803
- }
804
- else {
805
- if (viewport == null) {
806
- if (!Viewport.isViewport(newProperties)) {
807
- throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
808
- }
809
- viewport = { ...newProperties };
810
- }
811
- for (const key in newProperties) {
812
- const value = newProperties[key];
813
- if (value == null) {
814
- delete viewport[key];
815
- }
816
- else {
817
- viewport[key] = value;
818
- }
819
- }
820
- }
821
- break;
822
- }
823
- case 'set_selection': {
824
- const { newProperties } = op;
825
- if (newProperties == null) {
826
- selection = newProperties;
827
- }
828
- else {
829
- if (selection === null) {
830
- selection = op.newProperties;
831
- }
832
- else {
833
- selection.ranges = newProperties.ranges;
834
- }
835
- }
836
- break;
837
- }
838
- case 'set_theme': {
839
- const { newProperties } = op;
840
- theme = newProperties;
841
- break;
842
- }
513
+ let xx, yy;
514
+ if (param < 0) {
515
+ xx = x1;
516
+ yy = y1;
843
517
  }
844
- return { selection, viewport, theme };
845
- };
846
- const GeneralTransforms = {
847
- /**
848
- * Transform the board by an operation.
849
- */
850
- transform(board, op) {
851
- board.children = createDraft(board.children);
852
- let viewport = board.viewport && createDraft(board.viewport);
853
- let selection = board.selection && createDraft(board.selection);
854
- let theme = board.theme && createDraft(board.theme);
855
- try {
856
- const state = applyToDraft(board, selection, viewport, theme, op);
857
- viewport = state.viewport;
858
- selection = state.selection;
859
- theme = state.theme;
860
- }
861
- finally {
862
- board.children = finishDraft(board.children);
863
- if (selection) {
864
- board.selection = isDraft(selection) ? finishDraft(selection) : selection;
865
- }
866
- else {
867
- board.selection = null;
868
- }
869
- board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
870
- board.theme = isDraft(theme) ? finishDraft(theme) : theme;
518
+ else if (param > 1) {
519
+ xx = x2;
520
+ yy = y2;
521
+ }
522
+ else {
523
+ xx = x1 + param * C;
524
+ yy = y1 + param * D;
525
+ }
526
+ const dx = x - xx;
527
+ const dy = y - yy;
528
+ return Math.hypot(dx, dy);
529
+ }
530
+ function getNearestPointBetweenPointAndSegment(point, linePoints) {
531
+ const x = point[0], y = point[1], x1 = linePoints[0][0], y1 = linePoints[0][1], x2 = linePoints[1][0], y2 = linePoints[1][1];
532
+ const A = x - x1;
533
+ const B = y - y1;
534
+ const C = x2 - x1;
535
+ const D = y2 - y1;
536
+ const dot = A * C + B * D;
537
+ const lenSquare = C * C + D * D;
538
+ let param = -1;
539
+ if (lenSquare !== 0) {
540
+ // in case of 0 length line
541
+ param = dot / lenSquare;
542
+ }
543
+ let xx, yy;
544
+ if (param < 0) {
545
+ xx = x1;
546
+ yy = y1;
547
+ }
548
+ else if (param > 1) {
549
+ xx = x2;
550
+ yy = y2;
551
+ }
552
+ else {
553
+ xx = x1 + param * C;
554
+ yy = y1 + param * D;
555
+ }
556
+ return [xx, yy];
557
+ }
558
+ function distanceBetweenPointAndSegments(points, point) {
559
+ const len = points.length;
560
+ let distance = Infinity;
561
+ for (let i = 0; i < len - 1; i++) {
562
+ const p = points[i];
563
+ const p2 = points[i + 1];
564
+ const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
565
+ if (currentDistance < distance) {
566
+ distance = currentDistance;
871
567
  }
872
568
  }
873
- };
874
-
875
- function insertNode(board, node, path) {
876
- const operation = { type: 'insert_node', node, path };
877
- board.apply(operation);
569
+ return distance;
878
570
  }
879
- function setNode(board, props, path) {
880
- const properties = {};
881
- const newProperties = {};
882
- const node = PlaitNode.get(board, path);
883
- for (const k in props) {
884
- if (node[k] !== props[k]) {
885
- if (node.hasOwnProperty(k)) {
886
- properties[k] = node[k];
887
- }
888
- if (props[k] != null)
889
- newProperties[k] = props[k];
571
+ function getNearestPointBetweenPointAndSegments(point, points) {
572
+ const len = points.length;
573
+ let distance = Infinity;
574
+ let result = point;
575
+ for (let i = 0; i < len; i++) {
576
+ const p = points[i];
577
+ const p2 = i === len - 1 ? points[0] : points[i + 1];
578
+ const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
579
+ if (currentDistance < distance) {
580
+ distance = currentDistance;
581
+ result = getNearestPointBetweenPointAndSegment(point, [p, p2]);
890
582
  }
891
583
  }
892
- const operation = { type: 'set_node', properties, newProperties, path };
893
- board.apply(operation);
584
+ return result;
894
585
  }
895
- function removeNode(board, path) {
896
- const node = PlaitNode.get(board, path);
897
- const operation = { type: 'remove_node', path, node };
898
- board.apply(operation);
586
+ function rotate(x1, y1, x2, y2, angle) {
587
+ // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
588
+ // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
589
+ // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
590
+ return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
899
591
  }
900
- function moveNode(board, path, newPath) {
901
- const operation = { type: 'move_node', path, newPath };
902
- board.apply(operation);
592
+ function distanceBetweenPointAndPoint(x1, y1, x2, y2) {
593
+ const dx = x1 - x2;
594
+ const dy = y1 - y2;
595
+ return Math.hypot(dx, dy);
903
596
  }
904
- const NodeTransforms = {
905
- insertNode,
906
- setNode,
907
- removeNode,
908
- moveNode
909
- };
910
-
911
- function setSelection(board, selection) {
912
- const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
913
- board.apply(operation);
597
+ // https://stackoverflow.com/questions/5254838/calculating-distance-between-a-point-and-a-rectangular-box-nearest-point
598
+ function distanceBetweenPointAndRectangle(x, y, rect) {
599
+ var dx = Math.max(rect.x - x, 0, x - (rect.x + rect.width));
600
+ var dy = Math.max(rect.y - y, 0, y - (rect.y + rect.height));
601
+ return Math.sqrt(dx * dx + dy * dy);
914
602
  }
915
- const SelectionTransforms = {
916
- setSelection,
917
- setSelectionWithTemporaryElements
603
+ const isLineHitLine = (a, b, c, d) => {
604
+ const crossProduct = (v1, v2) => v1[0] * v2[1] - v1[1] * v2[0];
605
+ const ab = [b[0] - a[0], b[1] - a[1]];
606
+ const ac = [c[0] - a[0], c[1] - a[1]];
607
+ const ad = [d[0] - a[0], d[1] - a[1]];
608
+ const ca = [a[0] - c[0], a[1] - c[1]];
609
+ const cb = [b[0] - c[0], b[1] - c[1]];
610
+ const cd = [d[0] - c[0], d[1] - c[1]];
611
+ return crossProduct(ab, ac) * crossProduct(ab, ad) <= 0 && crossProduct(cd, ca) * crossProduct(cd, cb) <= 0;
918
612
  };
919
- function setSelectionWithTemporaryElements(board, elements) {
920
- setTimeout(() => {
921
- BOARD_TO_TEMPORARY_ELEMENTS.set(board, elements);
922
- setSelection(board, { ranges: [] });
613
+ const isPolylineHitRectangle = (points, rectangle) => {
614
+ const rectanglePoints = RectangleClient.getCornerPoints(rectangle);
615
+ for (let i = 1; i < points.length; i++) {
616
+ const isIntersect = isLineHitLine(points[i], points[i - 1], rectanglePoints[0], rectanglePoints[1]) ||
617
+ isLineHitLine(points[i], points[i - 1], rectanglePoints[1], rectanglePoints[2]) ||
618
+ isLineHitLine(points[i], points[i - 1], rectanglePoints[2], rectanglePoints[3]) ||
619
+ isLineHitLine(points[i], points[i - 1], rectanglePoints[3], rectanglePoints[0]);
620
+ if (isIntersect) {
621
+ return true;
622
+ }
623
+ }
624
+ return false;
625
+ };
626
+ //https://stackoverflow.com/questions/22521982/check-if-point-is-inside-a-polygon
627
+ const isPointInPolygon = (point, points) => {
628
+ // ray-casting algorithm based on
629
+ // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
630
+ const x = point[0], y = point[1];
631
+ let inside = false;
632
+ for (var i = 0, j = points.length - 1; i < points.length; j = i++) {
633
+ let xi = points[i][0], yi = points[i][1];
634
+ let xj = points[j][0], yj = points[j][1];
635
+ let intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
636
+ if (intersect)
637
+ inside = !inside;
638
+ }
639
+ return inside;
640
+ };
641
+ const isPointInEllipse = (point, center, rx, ry, rotation = 0) => {
642
+ const cosAngle = Math.cos(rotation);
643
+ const sinAngle = Math.sin(rotation);
644
+ const x1 = (point[0] - center[0]) * cosAngle + (point[1] - center[1]) * sinAngle;
645
+ const y1 = (point[1] - center[1]) * cosAngle - (point[0] - center[0]) * sinAngle;
646
+ return (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry) <= 1;
647
+ };
648
+ const isPointInRoundRectangle = (point, rectangle, radius) => {
649
+ const { x: rectX, y: rectY, width, height } = rectangle;
650
+ const isInRectangle = point[0] >= rectX && point[0] <= rectX + width && point[1] >= rectY && point[1] <= rectY + height;
651
+ const handleLeftTop = point[0] >= rectX &&
652
+ point[0] <= rectX + radius &&
653
+ point[1] >= rectY &&
654
+ point[1] <= rectY + radius &&
655
+ Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + radius)) > radius;
656
+ const handleLeftBottom = point[0] >= rectX &&
657
+ point[0] <= rectX + radius &&
658
+ point[1] >= rectY + height &&
659
+ point[1] <= rectY + height - radius &&
660
+ Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + height - radius)) > radius;
661
+ const handleRightTop = point[0] >= rectX + width - radius &&
662
+ point[0] <= rectX + width &&
663
+ point[1] >= rectY &&
664
+ point[1] <= rectY + radius &&
665
+ Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + radius)) > radius;
666
+ const handleRightBottom = point[0] >= rectX + width - radius &&
667
+ point[0] <= rectX + width &&
668
+ point[1] >= rectY + height - radius &&
669
+ point[1] <= rectY + height &&
670
+ Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + height - radius)) > radius;
671
+ const isInCorner = handleLeftTop || handleLeftBottom || handleRightTop || handleRightBottom;
672
+ return isInRectangle && !isInCorner;
673
+ };
674
+
675
+ function transformPoints(board, points) {
676
+ const newPoints = points.map(point => {
677
+ return transformPoint(board, point);
923
678
  });
679
+ return newPoints;
680
+ }
681
+ function transformPoint(board, point) {
682
+ const { width, height } = PlaitBoard.getHost(board).getBoundingClientRect();
683
+ const viewBox = PlaitBoard.getHost(board).viewBox.baseVal;
684
+ const x = (point[0] / width) * viewBox.width + viewBox.x;
685
+ const y = (point[1] / height) * viewBox.height + viewBox.y;
686
+ const newPoint = [x, y];
687
+ return newPoint;
688
+ }
689
+ function isInPlaitBoard(board, x, y) {
690
+ const plaitBoardElement = PlaitBoard.getBoardContainer(board);
691
+ const plaitBoardRect = plaitBoardElement.getBoundingClientRect();
692
+ const distances = distanceBetweenPointAndRectangle(x, y, plaitBoardRect);
693
+ return distances === 0;
694
+ }
695
+ function getRealScrollBarWidth(board) {
696
+ const { hideScrollbar } = board.options;
697
+ let scrollBarWidth = 0;
698
+ if (!hideScrollbar) {
699
+ const viewportContainer = PlaitBoard.getViewportContainer(board);
700
+ scrollBarWidth = viewportContainer.offsetWidth - viewportContainer.clientWidth;
701
+ }
702
+ return scrollBarWidth;
924
703
  }
925
704
 
926
- function setViewport(board, viewport) {
927
- const operation = { type: 'set_viewport', properties: board.viewport, newProperties: viewport };
928
- board.apply(operation);
705
+ function createForeignObject(x, y, width, height) {
706
+ var newForeignObject = document.createElementNS(NS, 'foreignObject');
707
+ newForeignObject.setAttribute('x', `${x}`);
708
+ newForeignObject.setAttribute('y', `${y}`);
709
+ newForeignObject.setAttribute('width', `${width}`);
710
+ newForeignObject.setAttribute('height', `${height}`);
711
+ return newForeignObject;
929
712
  }
930
- const ViewportTransforms$1 = {
931
- setViewport
713
+ function updateForeignObject(target, width, height, x, y) {
714
+ const foreignObject = target instanceof SVGForeignObjectElement ? target : target.querySelector('foreignObject');
715
+ if (foreignObject) {
716
+ foreignObject.setAttribute('width', `${width}`);
717
+ foreignObject.setAttribute('height', `${height}`);
718
+ foreignObject.setAttribute('x', `${x}`);
719
+ foreignObject.setAttribute('y', `${y}`);
720
+ }
721
+ }
722
+ function updateForeignObjectWidth(target, width) {
723
+ const foreignObject = target instanceof SVGForeignObjectElement ? target : target.querySelector('foreignObject');
724
+ if (foreignObject) {
725
+ foreignObject.setAttribute('width', `${width}`);
726
+ }
727
+ }
728
+
729
+ const IS_MAC = typeof window != 'undefined' && /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
730
+
731
+ const IS_IOS = typeof navigator !== 'undefined' &&
732
+ typeof window !== 'undefined' &&
733
+ /iPad|iPhone|iPod/.test(navigator.userAgent) &&
734
+ !window.MSStream;
735
+ const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
736
+ const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
737
+ const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
738
+ // "modern" Edge was released at 79.x
739
+ const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
740
+ const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
741
+ // Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
742
+ const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
743
+
744
+ function isNullOrUndefined(value) {
745
+ return value === null || value === undefined;
746
+ }
747
+ /**
748
+ * 规范 point
749
+ * @param point
750
+ * @returns point
751
+ */
752
+ function normalizePoint(point) {
753
+ return Array.isArray(point)
754
+ ? {
755
+ x: point[0],
756
+ y: point[1]
757
+ }
758
+ : point;
759
+ }
760
+
761
+ /**
762
+ * Check whether to merge an operation into the previous operation.
763
+ */
764
+ const shouldMerge = (op, prev) => {
765
+ if (op.type === 'set_viewport' && op.type === prev?.type) {
766
+ return true;
767
+ }
768
+ return false;
769
+ };
770
+ /**
771
+ * Check whether an operation needs to be saved to the history.
772
+ */
773
+ const shouldSave = (op, prev) => {
774
+ if (op.type === 'set_selection' || op.type === 'set_viewport') {
775
+ return false;
776
+ }
777
+ return true;
778
+ };
779
+ /**
780
+ * Check whether an operation should clear the redos stack.
781
+ */
782
+ const shouldClear = (op) => {
783
+ if (op.type === 'set_selection') {
784
+ return false;
785
+ }
786
+ return true;
787
+ };
788
+ const PlaitHistoryBoard = {
789
+ /**
790
+ * Get the saving flag's current value.
791
+ */
792
+ isSaving(board) {
793
+ return SAVING.get(board);
794
+ },
795
+ /**
796
+ * Get the merge flag's current value.
797
+ */
798
+ isMerging(board) {
799
+ return MERGING.get(board);
800
+ },
801
+ /**
802
+ * Apply a series of changes inside a synchronous `fn`, without merging any of
803
+ * the new operations into previous save point in the history.
804
+ */
805
+ withoutMerging(board, fn) {
806
+ const prev = PlaitHistoryBoard.isMerging(board);
807
+ MERGING.set(board, false);
808
+ fn();
809
+ MERGING.set(board, prev);
810
+ },
811
+ /**
812
+ * Apply a series of changes inside a synchronous `fn`, without saving any of
813
+ * their operations into the history.
814
+ */
815
+ withoutSaving(board, fn) {
816
+ const prev = PlaitHistoryBoard.isSaving(board);
817
+ SAVING.set(board, false);
818
+ fn();
819
+ SAVING.set(board, prev);
820
+ }
932
821
  };
933
822
 
934
823
  /**
935
- * @license
936
- * Copyright Google LLC All Rights Reserved.
937
- *
938
- * Use of this source code is governed by an MIT-style license that can be
939
- * found in the LICENSE file at https://angular.io/license
824
+ * Hotkey mappings for each platform.
940
825
  */
941
- const MAC_ENTER = 3;
942
- const BACKSPACE = 8;
943
- const TAB = 9;
944
- const NUM_CENTER = 12;
945
- const ENTER = 13;
946
- const SHIFT = 16;
947
- const CONTROL = 17;
948
- const ALT = 18;
949
- const PAUSE = 19;
950
- const CAPS_LOCK = 20;
951
- const ESCAPE = 27;
952
- const SPACE = 32;
953
- const PAGE_UP = 33;
954
- const PAGE_DOWN = 34;
955
- const END = 35;
956
- const HOME = 36;
957
- const LEFT_ARROW = 37;
958
- const UP_ARROW = 38;
959
- const RIGHT_ARROW = 39;
960
- const DOWN_ARROW = 40;
961
- const PLUS_SIGN = 43;
962
- const PRINT_SCREEN = 44;
963
- const INSERT = 45;
964
- const DELETE = 46;
965
- const ZERO = 48;
966
- const ONE = 49;
967
- const TWO = 50;
968
- const THREE = 51;
969
- const FOUR = 52;
970
- const FIVE = 53;
971
- const SIX = 54;
972
- const SEVEN = 55;
973
- const EIGHT = 56;
974
- const NINE = 57;
975
- const FF_SEMICOLON = 59; // Firefox (Gecko) fires this for semicolon instead of 186
976
- const FF_EQUALS = 61; // Firefox (Gecko) fires this for equals instead of 187
977
- const QUESTION_MARK = 63;
978
- const AT_SIGN = 64;
979
- const A = 65;
980
- const B = 66;
981
- const C = 67;
982
- const D = 68;
983
- const E = 69;
984
- const F = 70;
985
- const G = 71;
986
- const H = 72;
987
- const I = 73;
988
- const J = 74;
989
- const K = 75;
990
- const L = 76;
991
- const M = 77;
992
- const N = 78;
993
- const O = 79;
994
- const P = 80;
995
- const Q = 81;
996
- const R = 82;
997
- const S = 83;
998
- const T = 84;
999
- const U = 85;
1000
- const V = 86;
1001
- const W = 87;
1002
- const X = 88;
1003
- const Y = 89;
1004
- const Z = 90;
1005
- const META = 91; // WIN_KEY_LEFT
1006
- const MAC_WK_CMD_LEFT = 91;
1007
- const MAC_WK_CMD_RIGHT = 93;
1008
- const CONTEXT_MENU = 93;
1009
- const NUMPAD_ZERO = 96;
1010
- const NUMPAD_ONE = 97;
1011
- const NUMPAD_TWO = 98;
1012
- const NUMPAD_THREE = 99;
1013
- const NUMPAD_FOUR = 100;
1014
- const NUMPAD_FIVE = 101;
1015
- const NUMPAD_SIX = 102;
1016
- const NUMPAD_SEVEN = 103;
1017
- const NUMPAD_EIGHT = 104;
1018
- const NUMPAD_NINE = 105;
1019
- const NUMPAD_MULTIPLY = 106;
1020
- const NUMPAD_PLUS = 107;
1021
- const NUMPAD_MINUS = 109;
1022
- const NUMPAD_PERIOD = 110;
1023
- const NUMPAD_DIVIDE = 111;
1024
- const F1 = 112;
1025
- const F2 = 113;
1026
- const F3 = 114;
1027
- const F4 = 115;
1028
- const F5 = 116;
1029
- const F6 = 117;
1030
- const F7 = 118;
1031
- const F8 = 119;
1032
- const F9 = 120;
1033
- const F10 = 121;
1034
- const F11 = 122;
1035
- const F12 = 123;
1036
- const NUM_LOCK = 144;
1037
- const SCROLL_LOCK = 145;
1038
- const FIRST_MEDIA = 166;
1039
- const FF_MINUS = 173;
1040
- const MUTE = 173; // Firefox (Gecko) fires 181 for MUTE
1041
- const VOLUME_DOWN = 174; // Firefox (Gecko) fires 182 for VOLUME_DOWN
1042
- const VOLUME_UP = 175; // Firefox (Gecko) fires 183 for VOLUME_UP
1043
- const FF_MUTE = 181;
1044
- const FF_VOLUME_DOWN = 182;
1045
- const LAST_MEDIA = 183;
1046
- const FF_VOLUME_UP = 183;
1047
- const SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON
1048
- const EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS
1049
- const COMMA = 188;
1050
- const DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS
1051
- const PERIOD = 190;
1052
- const SLASH = 191;
1053
- const APOSTROPHE = 192;
1054
- const TILDE = 192;
1055
- const OPEN_SQUARE_BRACKET = 219;
1056
- const BACKSLASH = 220;
1057
- const CLOSE_SQUARE_BRACKET = 221;
1058
- const SINGLE_QUOTE = 222;
1059
- const MAC_META = 224;
1060
-
1061
- var ResizeCursorClass;
1062
- (function (ResizeCursorClass) {
1063
- ResizeCursorClass["ew-resize"] = "ew-resize";
1064
- })(ResizeCursorClass || (ResizeCursorClass = {}));
826
+ const HOTKEYS = {
827
+ bold: 'mod+b',
828
+ compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
829
+ moveBackward: 'left',
830
+ moveForward: 'right',
831
+ moveUp: 'up',
832
+ moveDown: 'down',
833
+ moveWordBackward: 'ctrl+left',
834
+ moveWordForward: 'ctrl+right',
835
+ deleteBackward: 'shift?+backspace',
836
+ deleteForward: 'shift?+delete',
837
+ extendBackward: 'shift+left',
838
+ extendForward: 'shift+right',
839
+ italic: 'mod+i',
840
+ splitBlock: 'shift?+enter',
841
+ undo: 'mod+z'
842
+ };
843
+ const APPLE_HOTKEYS = {
844
+ moveLineBackward: 'opt+up',
845
+ moveLineForward: 'opt+down',
846
+ moveWordBackward: 'opt+left',
847
+ moveWordForward: 'opt+right',
848
+ deleteBackward: ['ctrl+backspace', 'ctrl+h'],
849
+ deleteForward: ['ctrl+delete', 'ctrl+d'],
850
+ deleteLineBackward: 'cmd+shift?+backspace',
851
+ deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
852
+ deleteWordBackward: 'opt+shift?+backspace',
853
+ deleteWordForward: 'opt+shift?+delete',
854
+ extendLineBackward: 'opt+shift+up',
855
+ extendLineForward: 'opt+shift+down',
856
+ redo: 'cmd+shift+z',
857
+ transposeCharacter: 'ctrl+t'
858
+ };
859
+ const WINDOWS_HOTKEYS = {
860
+ deleteWordBackward: 'ctrl+shift?+backspace',
861
+ deleteWordForward: 'ctrl+shift?+delete',
862
+ redo: ['ctrl+y', 'ctrl+shift+z']
863
+ };
864
+ /**
865
+ * Create a platform-aware hotkey checker.
866
+ */
867
+ const create = (key) => {
868
+ const generic = HOTKEYS[key];
869
+ const apple = APPLE_HOTKEYS[key];
870
+ const windows = WINDOWS_HOTKEYS[key];
871
+ const isGeneric = generic && isKeyHotkey(generic);
872
+ const isApple = apple && isKeyHotkey(apple);
873
+ const isWindows = windows && isKeyHotkey(windows);
874
+ return (event) => {
875
+ if (isGeneric && isGeneric(event)) {
876
+ return true;
877
+ }
878
+ if (IS_APPLE && isApple && isApple(event)) {
879
+ return true;
880
+ }
881
+ if (!IS_APPLE && isWindows && isWindows(event)) {
882
+ return true;
883
+ }
884
+ return false;
885
+ };
886
+ };
887
+ /**
888
+ * Hotkeys.
889
+ */
890
+ const hotkeys = {
891
+ isBold: create('bold'),
892
+ isCompose: create('compose'),
893
+ isMoveBackward: create('moveBackward'),
894
+ isMoveForward: create('moveForward'),
895
+ isMoveUp: create('moveUp'),
896
+ isMoveDown: create('moveDown'),
897
+ isDeleteBackward: create('deleteBackward'),
898
+ isDeleteForward: create('deleteForward'),
899
+ isDeleteLineBackward: create('deleteLineBackward'),
900
+ isDeleteLineForward: create('deleteLineForward'),
901
+ isDeleteWordBackward: create('deleteWordBackward'),
902
+ isDeleteWordForward: create('deleteWordForward'),
903
+ isExtendBackward: create('extendBackward'),
904
+ isExtendForward: create('extendForward'),
905
+ isExtendLineBackward: create('extendLineBackward'),
906
+ isExtendLineForward: create('extendLineForward'),
907
+ isItalic: create('italic'),
908
+ isMoveLineBackward: create('moveLineBackward'),
909
+ isMoveLineForward: create('moveLineForward'),
910
+ isMoveWordBackward: create('moveWordBackward'),
911
+ isMoveWordForward: create('moveWordForward'),
912
+ isRedo: create('redo'),
913
+ isSplitBlock: create('splitBlock'),
914
+ isTransposeCharacter: create('transposeCharacter'),
915
+ isUndo: create('undo')
916
+ };
1065
917
 
1066
- const ATTACHED_ELEMENT_CLASS_NAME = 'plait-board-attached';
918
+ function idCreator(length = 5) {
919
+ // remove numeral
920
+ const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
921
+ const maxPosition = $chars.length;
922
+ let key = '';
923
+ for (let i = 0; i < length; i++) {
924
+ key += $chars.charAt(Math.floor(Math.random() * maxPosition));
925
+ }
926
+ return key;
927
+ }
1067
928
 
1068
- const CLIP_BOARD_FORMAT_KEY = 'x-plait-fragment';
1069
- const HOST_CLASS_NAME = 'plait-board-container';
1070
- const SCROLL_BAR_WIDTH = 20;
1071
- const MAX_RADIUS = 16;
1072
- const POINTER_BUTTON = {
1073
- MAIN: 0,
1074
- WHEEL: 1,
1075
- SECONDARY: 2,
1076
- TOUCH: -1
929
+ /**
930
+ * drawRoundRectangle
931
+ */
932
+ function drawRoundRectangle(rs, x1, y1, x2, y2, options, outline = false, borderRadius) {
933
+ const width = Math.abs(x1 - x2);
934
+ const height = Math.abs(y1 - y2);
935
+ let radius = borderRadius || 0;
936
+ if (radius === 0) {
937
+ const defaultRadius = Math.min(width, height) / 8;
938
+ let radius = defaultRadius;
939
+ if (defaultRadius > MAX_RADIUS) {
940
+ radius = outline ? MAX_RADIUS + 2 : MAX_RADIUS;
941
+ }
942
+ }
943
+ const point1 = [x1 + radius, y1];
944
+ const point2 = [x2 - radius, y1];
945
+ const point3 = [x2, y1 + radius];
946
+ const point4 = [x2, y2 - radius];
947
+ const point5 = [x2 - radius, y2];
948
+ const point6 = [x1 + radius, y2];
949
+ const point7 = [x1, y2 - radius];
950
+ const point8 = [x1, y1 + radius];
951
+ 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);
952
+ }
953
+ const drawRectangle = (board, rectangle, options) => {
954
+ const roughSVG = PlaitBoard.getRoughSVG(board);
955
+ const rectangleG = roughSVG.rectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height, options);
956
+ setStrokeLinecap(rectangleG, 'round');
957
+ return rectangleG;
1077
958
  };
1078
- const PRESS_AND_MOVE_BUFFER = 5;
1079
959
 
1080
- const NS = 'http://www.w3.org/2000/svg';
1081
- function toPoint(x, y, container) {
1082
- const rect = container.getBoundingClientRect();
1083
- return [x - rect.x, y - rect.y];
960
+ function arrowPoints(start, end, maxHypotenuseLength = 10, degree = 40) {
961
+ const width = Math.abs(start[0] - end[0]);
962
+ const height = Math.abs(start[1] - end[1]);
963
+ let hypotenuse = Math.hypot(width, height); // 斜边
964
+ const realRotateLine = hypotenuse > maxHypotenuseLength * 2 ? maxHypotenuseLength : hypotenuse / 2;
965
+ const rotateWidth = (realRotateLine / hypotenuse) * width;
966
+ const rotateHeight = (realRotateLine / hypotenuse) * height;
967
+ const rotatePoint = [
968
+ end[0] > start[0] ? end[0] - rotateWidth : end[0] + rotateWidth,
969
+ end[1] > start[1] ? end[1] - rotateHeight : end[1] + rotateHeight
970
+ ];
971
+ const pointRight = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (degree * Math.PI) / 180);
972
+ const pointLeft = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (-degree * Math.PI) / 180);
973
+ return { pointLeft, pointRight };
1084
974
  }
1085
- function createG() {
1086
- const newG = document.createElementNS(NS, 'g');
1087
- return newG;
975
+ function drawArrow(rs, start, end, options, maxHypotenuseLength = 10, degree = 40) {
976
+ const { pointLeft, pointRight } = arrowPoints(start, end, maxHypotenuseLength, degree);
977
+ const arrowLineLeft = rs.linearPath([pointLeft, end], options);
978
+ const arrowLineRight = rs.linearPath([pointRight, end], options);
979
+ return [arrowLineLeft, arrowLineRight];
1088
980
  }
1089
- function createPath() {
1090
- const newG = document.createElementNS(NS, 'path');
1091
- return newG;
981
+
982
+ function drawCircle(roughSVG, point, diameter, options) {
983
+ return roughSVG.circle(point[0], point[1], diameter, options);
1092
984
  }
1093
- function createRect(rectangle, options) {
1094
- const rect = document.createElementNS(NS, 'rect');
1095
- rect.setAttribute('x', `${rectangle.x}`);
1096
- rect.setAttribute('y', `${rectangle.y}`);
1097
- rect.setAttribute('width', `${rectangle.width}`);
1098
- rect.setAttribute('height', `${rectangle.height}`);
1099
- for (let key in options) {
1100
- const optionKey = key;
1101
- rect.setAttribute(key, `${options[optionKey]}`);
985
+
986
+ function drawLine(rs, start, end, options) {
987
+ return rs.linearPath([start, end], options);
988
+ }
989
+ function drawLinearPath(points, options) {
990
+ const g = createG();
991
+ const path = createPath();
992
+ let polylinePath = '';
993
+ points.forEach((point, index) => {
994
+ if (index === 0) {
995
+ polylinePath += `M ${point[0]} ${point[1]} `;
996
+ }
997
+ else {
998
+ polylinePath += `L ${point[0]} ${point[1]} `;
999
+ }
1000
+ });
1001
+ path.setAttribute('d', polylinePath);
1002
+ path.setAttribute('stroke', `${options?.stroke}`);
1003
+ path.setAttribute('stroke-width', `${options?.strokeWidth}`);
1004
+ path.setAttribute('fill', `none`);
1005
+ g.appendChild(path);
1006
+ return g;
1007
+ }
1008
+ function drawBezierPath(points, options) {
1009
+ const g = createG();
1010
+ const path = createPath();
1011
+ let polylinePath = '';
1012
+ for (let i = 0; i < points.length - 3; i += 3) {
1013
+ if (i === 0) {
1014
+ polylinePath += `M ${points[0][0]} ${points[0][1]} `;
1015
+ }
1016
+ else {
1017
+ 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]}`;
1018
+ }
1019
+ }
1020
+ path.setAttribute('d', polylinePath);
1021
+ path.setAttribute('stroke', `${options?.stroke}`);
1022
+ path.setAttribute('stroke-width', `${options?.strokeWidth}`);
1023
+ path.setAttribute('fill', `none`);
1024
+ g.appendChild(path);
1025
+ return g;
1026
+ }
1027
+
1028
+ const IS_FROM_SCROLLING = new WeakMap();
1029
+ const IS_FROM_VIEWPORT_CHANGE = new WeakMap();
1030
+ function getViewportContainerRect(board) {
1031
+ const { hideScrollbar } = board.options;
1032
+ const scrollBarWidth = hideScrollbar ? SCROLL_BAR_WIDTH : 0;
1033
+ const viewportRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1034
+ return {
1035
+ width: viewportRect.width + scrollBarWidth,
1036
+ height: viewportRect.height + scrollBarWidth
1037
+ };
1038
+ }
1039
+ function getElementHostBBox(board, zoom) {
1040
+ const childrenRect = getRectangleByElements(board, board.children, true);
1041
+ const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1042
+ const containerWidth = viewportContainerRect.width / zoom;
1043
+ const containerHeight = viewportContainerRect.height / zoom;
1044
+ let left;
1045
+ let right;
1046
+ let top;
1047
+ let bottom;
1048
+ if (childrenRect.width < containerWidth) {
1049
+ const centerX = childrenRect.x + childrenRect.width / 2;
1050
+ const halfContainerWidth = containerWidth / 2;
1051
+ left = centerX - halfContainerWidth;
1052
+ right = centerX + halfContainerWidth;
1053
+ }
1054
+ else {
1055
+ left = childrenRect.x;
1056
+ right = childrenRect.x + childrenRect.width;
1057
+ }
1058
+ if (childrenRect.height < containerHeight) {
1059
+ const centerY = childrenRect.y + childrenRect.height / 2;
1060
+ const halfContainerHeight = containerHeight / 2;
1061
+ top = centerY - halfContainerHeight;
1062
+ bottom = centerY + halfContainerHeight;
1063
+ }
1064
+ else {
1065
+ top = childrenRect.y;
1066
+ bottom = childrenRect.y + childrenRect.height;
1067
+ }
1068
+ return {
1069
+ left,
1070
+ right,
1071
+ top,
1072
+ bottom
1073
+ };
1074
+ }
1075
+ /**
1076
+ * 验证缩放比是否符合限制,如果超出限制,则返回合适的缩放比
1077
+ * @param zoom 缩放比
1078
+ * @param minZoom 最小缩放比
1079
+ * @param maxZoom 最大缩放比
1080
+ * @returns 正确的缩放比
1081
+ */
1082
+ function clampZoomLevel(zoom, minZoom = 0.2, maxZoom = 4) {
1083
+ return zoom < minZoom ? minZoom : zoom > maxZoom ? maxZoom : zoom;
1084
+ }
1085
+ function getViewBox(board, zoom) {
1086
+ const boardContainerRectangle = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1087
+ const elementHostBBox = getElementHostBBox(board, zoom);
1088
+ const horizontalPadding = boardContainerRectangle.width / 2;
1089
+ const verticalPadding = boardContainerRectangle.height / 2;
1090
+ const viewBox = [
1091
+ elementHostBBox.left - horizontalPadding / zoom,
1092
+ elementHostBBox.top - verticalPadding / zoom,
1093
+ elementHostBBox.right - elementHostBBox.left + (horizontalPadding * 2) / zoom,
1094
+ elementHostBBox.bottom - elementHostBBox.top + (verticalPadding * 2) / zoom
1095
+ ];
1096
+ return viewBox;
1097
+ }
1098
+ function getViewBoxCenterPoint(board) {
1099
+ const childrenRectangle = getRectangleByElements(board, board.children, true);
1100
+ return [childrenRectangle.x + childrenRectangle.width / 2, childrenRectangle.y + childrenRectangle.height / 2];
1101
+ }
1102
+ function setSVGViewBox(board, viewBox) {
1103
+ const zoom = board.viewport.zoom;
1104
+ const hostElement = PlaitBoard.getHost(board);
1105
+ hostElement.style.display = 'block';
1106
+ hostElement.style.width = `${viewBox[2] * zoom}px`;
1107
+ hostElement.style.height = `${viewBox[3] * zoom}px`;
1108
+ if (viewBox && viewBox[2] > 0 && viewBox[3] > 0) {
1109
+ hostElement.setAttribute('viewBox', viewBox.join(' '));
1110
+ }
1111
+ }
1112
+ function updateViewportOffset(board) {
1113
+ const origination = getViewportOrigination(board);
1114
+ if (!origination)
1115
+ return;
1116
+ const { zoom } = board.viewport;
1117
+ const viewBox = getViewBox(board, zoom);
1118
+ const scrollLeft = (origination[0] - viewBox[0]) * zoom;
1119
+ const scrollTop = (origination[1] - viewBox[1]) * zoom;
1120
+ updateViewportContainerScroll(board, scrollLeft, scrollTop);
1121
+ }
1122
+ function updateViewportContainerScroll(board, left, top, isFromViewportChange = true) {
1123
+ const viewportContainer = PlaitBoard.getViewportContainer(board);
1124
+ if (viewportContainer.scrollLeft !== left || viewportContainer.scrollTop !== top) {
1125
+ viewportContainer.scrollLeft = left;
1126
+ viewportContainer.scrollTop = top;
1127
+ isFromViewportChange && setIsFromViewportChange(board, true);
1102
1128
  }
1103
- return rect;
1104
1129
  }
1105
- const setStrokeLinecap = (g, value) => {
1106
- g.setAttribute('stroke-linecap', value);
1107
- };
1108
- const setPathStrokeLinecap = (g, value) => {
1109
- g.querySelectorAll('path').forEach(path => {
1110
- path.setAttribute('stroke-linecap', value);
1111
- });
1112
- };
1113
- function createMask() {
1114
- return document.createElementNS(NS, 'mask');
1130
+ function initializeViewportContainer(board) {
1131
+ const { width, height } = getViewportContainerRect(board);
1132
+ const viewportContainer = PlaitBoard.getViewportContainer(board);
1133
+ viewportContainer.style.width = `${width}px`;
1134
+ viewportContainer.style.height = `${height}px`;
1115
1135
  }
1116
- function createSVG() {
1117
- const svg = document.createElementNS(NS, 'svg');
1118
- return svg;
1136
+ function initializeViewBox(board) {
1137
+ const zoom = board.viewport.zoom;
1138
+ const viewBox = getViewBox(board, zoom);
1139
+ setSVGViewBox(board, viewBox);
1119
1140
  }
1120
- function createText(x, y, fill, textContent) {
1121
- var text = document.createElementNS(NS, 'text');
1122
- text.setAttribute('x', `${x}`);
1123
- text.setAttribute('y', `${y}`);
1124
- text.setAttribute('fill', fill);
1125
- text.textContent = textContent;
1126
- return text;
1141
+ function initializeViewportOffset(board) {
1142
+ if (!board.viewport?.origination) {
1143
+ const zoom = board.viewport.zoom;
1144
+ const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1145
+ const viewBox = getViewBox(board, zoom);
1146
+ const centerX = viewBox[0] + viewBox[2] / 2;
1147
+ const centerY = viewBox[1] + viewBox[3] / 2;
1148
+ const origination = [centerX - viewportContainerRect.width / 2 / zoom, centerY - viewportContainerRect.height / 2 / zoom];
1149
+ updateViewportOrigination(board, origination);
1150
+ updateViewportOffset(board);
1151
+ return;
1152
+ }
1153
+ updateViewportOffset(board);
1127
1154
  }
1128
- /**
1129
- * Check if a DOM node is an element node.
1130
- */
1131
- const isDOMElement = (value) => {
1132
- return isDOMNode(value) && value.nodeType === 1;
1155
+ const updateViewportOrigination = (board, origination) => {
1156
+ BOARD_TO_VIEWPORT_ORIGINATION.set(board, origination);
1133
1157
  };
1134
- /**
1135
- * Check if a value is a DOM node.
1136
- */
1137
- const isDOMNode = (value) => {
1138
- return value instanceof window.Node;
1158
+ const clearViewportOrigination = (board) => {
1159
+ BOARD_TO_VIEWPORT_ORIGINATION.delete(board);
1139
1160
  };
1140
- const hasInputOrTextareaTarget = (target) => {
1141
- if (isDOMElement(target)) {
1142
- if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
1143
- return true;
1144
- }
1161
+ const getViewportOrigination = (board) => {
1162
+ const origination = BOARD_TO_VIEWPORT_ORIGINATION.get(board);
1163
+ if (origination) {
1164
+ return origination;
1165
+ }
1166
+ else {
1167
+ return board.viewport.origination;
1145
1168
  }
1146
- return false;
1147
- };
1148
- const isSecondaryPointer = (event) => {
1149
- return event.button === POINTER_BUTTON.SECONDARY;
1150
1169
  };
1151
- const isMainPointer = (event) => {
1152
- return event.button === POINTER_BUTTON.MAIN;
1170
+ const isFromScrolling = (board) => {
1171
+ return !!IS_FROM_SCROLLING.get(board);
1153
1172
  };
1154
-
1155
- /**
1156
- * Extendable Custom Types Interface
1157
- */
1158
-
1159
- const SELECTION_BORDER_COLOR = '#6698FF';
1160
- const SELECTION_FILL_COLOR = '#6698FF19'; // 主色 0.1 透明度
1161
- const Selection = {
1162
- isCollapsed(selection) {
1163
- if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
1164
- return true;
1165
- }
1166
- else {
1167
- return false;
1168
- }
1169
- }
1173
+ const setIsFromScrolling = (board, state) => {
1174
+ IS_FROM_SCROLLING.set(board, state);
1170
1175
  };
1171
-
1172
- const getHitElements = (board, selection, match = () => true) => {
1173
- const realSelection = selection || board.selection;
1174
- const selectedElements = [];
1175
- const isCollapsed = realSelection && realSelection.ranges.length === 1 && Selection.isCollapsed(realSelection.ranges[0]);
1176
- depthFirstRecursion(board, node => {
1177
- if (selectedElements.length > 0 && isCollapsed) {
1178
- return;
1179
- }
1180
- if (!PlaitBoard.isBoard(node) &&
1181
- match(node) &&
1182
- realSelection &&
1183
- realSelection.ranges.some(range => {
1184
- return board.isHitSelection(node, range);
1185
- })) {
1186
- selectedElements.push(node);
1187
- }
1188
- }, getIsRecursionFunc(board), true);
1189
- return selectedElements;
1176
+ const isFromViewportChange = (board) => {
1177
+ return !!IS_FROM_VIEWPORT_CHANGE.get(board);
1190
1178
  };
1191
- const getHitElementOfRoot = (board, rootElements, range) => {
1192
- const newRootElements = [...rootElements].reverse();
1193
- return newRootElements.find(item => {
1194
- return board.isHitSelection(item, range);
1195
- });
1179
+ const setIsFromViewportChange = (board, state) => {
1180
+ IS_FROM_VIEWPORT_CHANGE.set(board, state);
1196
1181
  };
1197
- const isHitElements = (board, elements, ranges) => {
1198
- let isIntersectionElements = false;
1199
- if (elements.length) {
1200
- elements.map(item => {
1201
- if (!isIntersectionElements) {
1202
- isIntersectionElements = ranges.some(range => {
1203
- return board.isHitSelection(item, range);
1204
- });
1205
- }
1182
+ function scrollToRectangle(board, client) { }
1183
+
1184
+ let timerId = null;
1185
+ const throttleRAF = (fn) => {
1186
+ const scheduleFunc = () => {
1187
+ timerId = requestAnimationFrame(() => {
1188
+ timerId = null;
1189
+ fn();
1206
1190
  });
1191
+ };
1192
+ if (timerId !== null) {
1193
+ cancelAnimationFrame(timerId);
1194
+ timerId = null;
1207
1195
  }
1208
- return isIntersectionElements;
1209
- };
1210
- const cacheSelectedElements = (board, selectedElements) => {
1211
- BOARD_TO_SELECTED_ELEMENT.set(board, selectedElements);
1196
+ scheduleFunc();
1212
1197
  };
1213
- const getSelectedElements = (board) => {
1214
- return BOARD_TO_SELECTED_ELEMENT.get(board) || [];
1198
+ const debounce = (func, wait, options) => {
1199
+ let timerSubscription = null;
1200
+ return () => {
1201
+ if (timerSubscription && !timerSubscription.closed) {
1202
+ timerSubscription.unsubscribe();
1203
+ timerSubscription = timer(wait).subscribe(() => {
1204
+ func();
1205
+ });
1206
+ }
1207
+ else {
1208
+ if (options?.leading) {
1209
+ func();
1210
+ }
1211
+ timerSubscription = timer(wait).subscribe();
1212
+ }
1213
+ };
1215
1214
  };
1216
- const addSelectedElement = (board, element) => {
1217
- const selectedElements = getSelectedElements(board);
1218
- cacheSelectedElements(board, [...selectedElements, element]);
1215
+
1216
+ const getMovingElements = (board) => {
1217
+ return BOARD_TO_MOVING_ELEMENT.get(board) || [];
1219
1218
  };
1220
- const removeSelectedElement = (board, element) => {
1221
- const selectedElements = getSelectedElements(board);
1222
- const newSelectedElements = selectedElements.filter(value => value !== element);
1223
- cacheSelectedElements(board, newSelectedElements);
1219
+ const addMovingElements = (board, elements) => {
1220
+ const movingElements = getMovingElements(board);
1221
+ const newElements = elements.filter(item => !movingElements.find(movingElement => movingElement.key === item.key));
1222
+ cacheMovingElements(board, [...movingElements, ...newElements]);
1224
1223
  };
1225
- const clearSelectedElement = (board) => {
1226
- cacheSelectedElements(board, []);
1224
+ const removeMovingElements = (board) => {
1225
+ BOARD_TO_MOVING_ELEMENT.delete(board);
1227
1226
  };
1228
- const isSelectedElement = (board, element) => {
1229
- const selectedElements = getSelectedElements(board);
1230
- return !!selectedElements.find(value => value === element);
1227
+ const cacheMovingElements = (board, elements) => {
1228
+ BOARD_TO_MOVING_ELEMENT.set(board, elements);
1231
1229
  };
1232
1230
 
1233
- function hasBeforeContextChange(value) {
1234
- if (value.beforeContextChange) {
1235
- return true;
1236
- }
1237
- return false;
1238
- }
1239
- function hasOnContextChanged(value) {
1240
- if (value.onContextChanged) {
1241
- return true;
1242
- }
1243
- return false;
1244
- }
1245
-
1246
- class PlaitPluginElementComponent {
1247
- set context(value) {
1248
- if (hasBeforeContextChange(this)) {
1249
- this.beforeContextChange(value);
1250
- }
1251
- const previousContext = this._context;
1252
- this._context = value;
1253
- if (this.element) {
1254
- ELEMENT_TO_COMPONENT.set(this.element, this);
1255
- }
1256
- if (this.initialized) {
1257
- this.cdr.markForCheck();
1258
- if (hasOnContextChanged(this)) {
1259
- this.onContextChanged(value, previousContext);
1260
- }
1261
- }
1262
- else {
1263
- if (PlaitElement.isRootElement(this.element) && this.element.children) {
1264
- this.g = createG();
1265
- this.rootG = createG();
1266
- this.rootG.append(this.g);
1267
- }
1268
- else {
1269
- this.g = createG();
1270
- }
1271
- }
1272
- }
1273
- get context() {
1274
- return this._context;
1275
- }
1276
- get element() {
1277
- return this.context && this.context.element;
1278
- }
1279
- get board() {
1280
- return this.context && this.context.board;
1281
- }
1282
- get selected() {
1283
- return this.context && this.context.selected;
1284
- }
1285
- get effect() {
1286
- return this.context && this.context.effect;
1287
- }
1288
- constructor(cdr) {
1289
- this.cdr = cdr;
1290
- this.initialized = false;
1231
+ function cloneCSSStyle(nativeNode, clonedNode) {
1232
+ const targetStyle = clonedNode.style;
1233
+ if (!targetStyle) {
1234
+ return;
1291
1235
  }
1292
- ngOnInit() {
1293
- if (this.element.type) {
1294
- (this.rootG || this.g).setAttribute(`plait-${this.element.type}`, 'true');
1295
- }
1296
- this.initialized = true;
1236
+ const sourceStyle = window.getComputedStyle(nativeNode);
1237
+ if (sourceStyle.cssText) {
1238
+ targetStyle.cssText = sourceStyle.cssText;
1239
+ targetStyle.transformOrigin = sourceStyle.transformOrigin;
1297
1240
  }
1298
- ngOnDestroy() {
1299
- if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
1300
- ELEMENT_TO_COMPONENT.delete(this.element);
1301
- }
1302
- removeSelectedElement(this.board, this.element);
1303
- (this.rootG || this.g).remove();
1241
+ else {
1242
+ Array.from(sourceStyle).forEach(name => {
1243
+ let value = sourceStyle.getPropertyValue(name);
1244
+ targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
1245
+ });
1304
1246
  }
1305
- 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 }); }
1306
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.3", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
1307
- }
1308
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.3", ngImport: i0, type: PlaitPluginElementComponent, decorators: [{
1309
- type: Directive
1310
- }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { context: [{
1311
- type: Input
1312
- }] } });
1313
- const ELEMENT_TO_COMPONENT = new WeakMap();
1314
-
1315
- function transformPoints(board, points) {
1316
- const newPoints = points.map(point => {
1317
- return transformPoint(board, point);
1318
- });
1319
- return newPoints;
1320
1247
  }
1321
- function transformPoint(board, point) {
1322
- const { width, height } = PlaitBoard.getHost(board).getBoundingClientRect();
1323
- const viewBox = PlaitBoard.getHost(board).viewBox.baseVal;
1324
- const x = (point[0] / width) * viewBox.width + viewBox.x;
1325
- const y = (point[1] / height) * viewBox.height + viewBox.y;
1326
- const newPoint = [x, y];
1327
- return newPoint;
1248
+ function createCanvas(width, height, fillStyle) {
1249
+ const canvas = document.createElement('canvas');
1250
+ const ctx = canvas.getContext('2d');
1251
+ canvas.width = width;
1252
+ canvas.height = height;
1253
+ canvas.style.width = `${width}px`;
1254
+ canvas.style.height = `${height}px`;
1255
+ ctx.strokeStyle = '#ffffff';
1256
+ ctx.fillStyle = fillStyle;
1257
+ ctx.fillRect(0, 0, width, height);
1258
+ return {
1259
+ canvas,
1260
+ ctx
1261
+ };
1328
1262
  }
1329
- function isInPlaitBoard(board, x, y) {
1330
- const plaitBoardElement = PlaitBoard.getBoardContainer(board);
1331
- const plaitBoardRect = plaitBoardElement.getBoundingClientRect();
1332
- const distances = distanceBetweenPointAndRectangle(x, y, plaitBoardRect);
1333
- return distances === 0;
1263
+ function isElementNode(node) {
1264
+ return node.nodeType === Node.ELEMENT_NODE;
1334
1265
  }
1335
- function getRealScrollBarWidth(board) {
1336
- const { hideScrollbar } = board.options;
1337
- let scrollBarWidth = 0;
1338
- if (!hideScrollbar) {
1339
- const viewportContainer = PlaitBoard.getViewportContainer(board);
1340
- scrollBarWidth = viewportContainer.offsetWidth - viewportContainer.clientWidth;
1266
+ function cloneSvg(board, options) {
1267
+ const elementHostBox = getRectangleByElements(board, board.children, true);
1268
+ const { width, height, x, y } = elementHostBox;
1269
+ const { padding = 4, inlineStyleClassNames } = options;
1270
+ const sourceSvg = PlaitBoard.getHost(board);
1271
+ const cloneSvgElement = sourceSvg.cloneNode(true);
1272
+ cloneSvgElement.style.width = `${width}px`;
1273
+ cloneSvgElement.style.height = `${height}px`;
1274
+ cloneSvgElement.style.backgroundColor = '';
1275
+ cloneSvgElement.setAttribute('width', `${width}`);
1276
+ cloneSvgElement.setAttribute('height', `${height}`);
1277
+ cloneSvgElement.setAttribute('viewBox', [x - padding, y - padding, width + 2 * padding, height + 2 * padding].join(','));
1278
+ if (inlineStyleClassNames) {
1279
+ const sourceNodes = Array.from(sourceSvg.querySelectorAll(inlineStyleClassNames));
1280
+ const cloneNodes = Array.from(cloneSvgElement.querySelectorAll(inlineStyleClassNames));
1281
+ sourceNodes.forEach((node, index) => {
1282
+ const cloneNode = cloneNodes[index];
1283
+ const childElements = Array.from(node.querySelectorAll('*')).filter(isElementNode);
1284
+ const cloneChildElements = Array.from(cloneNode.querySelectorAll('*')).filter(isElementNode);
1285
+ sourceNodes.push(...childElements);
1286
+ cloneNodes.push(...cloneChildElements);
1287
+ });
1288
+ sourceNodes.forEach((node, index) => {
1289
+ const cloneNode = cloneNodes[index];
1290
+ cloneCSSStyle(node, cloneNode);
1291
+ });
1341
1292
  }
1342
- return scrollBarWidth;
1293
+ return cloneSvgElement;
1343
1294
  }
1344
-
1345
- function createForeignObject(x, y, width, height) {
1346
- var newForeignObject = document.createElementNS(NS, 'foreignObject');
1347
- newForeignObject.setAttribute('x', `${x}`);
1348
- newForeignObject.setAttribute('y', `${y}`);
1349
- newForeignObject.setAttribute('width', `${width}`);
1350
- newForeignObject.setAttribute('height', `${height}`);
1351
- return newForeignObject;
1295
+ function loadImage(src) {
1296
+ return new Promise((resolve, reject) => {
1297
+ const img = new Image();
1298
+ img.onload = () => resolve(img);
1299
+ img.onerror = () => reject(new Error('Failed to load image'));
1300
+ img.src = src;
1301
+ });
1352
1302
  }
1353
- function updateForeignObject(target, width, height, x, y) {
1354
- const foreignObject = target instanceof SVGForeignObjectElement ? target : target.querySelector('foreignObject');
1355
- if (foreignObject) {
1356
- foreignObject.setAttribute('width', `${width}`);
1357
- foreignObject.setAttribute('height', `${height}`);
1358
- foreignObject.setAttribute('x', `${x}`);
1359
- foreignObject.setAttribute('y', `${y}`);
1303
+ async function toImage(board, options) {
1304
+ if (!board) {
1305
+ return undefined;
1360
1306
  }
1361
- }
1362
- function updateForeignObjectWidth(target, width) {
1363
- const foreignObject = target instanceof SVGForeignObjectElement ? target : target.querySelector('foreignObject');
1364
- if (foreignObject) {
1365
- foreignObject.setAttribute('width', `${width}`);
1307
+ const elementHostBox = getRectangleByElements(board, board.children, true);
1308
+ const { ratio = 2, fillStyle = 'transparent' } = options;
1309
+ const { width, height } = elementHostBox;
1310
+ const ratioWidth = width * ratio;
1311
+ const ratioHeight = height * ratio;
1312
+ const cloneSvgElement = cloneSvg(board, options);
1313
+ const { canvas, ctx } = createCanvas(ratioWidth, ratioHeight, fillStyle);
1314
+ const svgStr = new XMLSerializer().serializeToString(cloneSvgElement);
1315
+ const imgSrc = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;
1316
+ try {
1317
+ const img = await loadImage(imgSrc);
1318
+ ctx.drawImage(img, 0, 0, ratioWidth, ratioHeight);
1319
+ const url = canvas.toDataURL('image/png');
1320
+ return url;
1321
+ }
1322
+ catch (error) {
1323
+ console.error('Error converting SVG to image:', error);
1324
+ return undefined;
1366
1325
  }
1367
1326
  }
1327
+ function downloadImage(url, name) {
1328
+ const a = document.createElement('a');
1329
+ a.href = url;
1330
+ a.download = name;
1331
+ a.click();
1332
+ a.remove();
1333
+ }
1368
1334
 
1369
- const IS_MAC = typeof window != 'undefined' && /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
1370
-
1371
- const IS_IOS = typeof navigator !== 'undefined' &&
1372
- typeof window !== 'undefined' &&
1373
- /iPad|iPhone|iPod/.test(navigator.userAgent) &&
1374
- !window.MSStream;
1375
- const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
1376
- const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
1377
- const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
1378
- // "modern" Edge was released at 79.x
1379
- const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
1380
- const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
1381
- // Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
1382
- const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
1383
-
1384
- /**
1385
- * Check whether to merge an operation into the previous operation.
1386
- */
1387
- const shouldMerge = (op, prev) => {
1388
- if (op.type === 'set_viewport' && op.type === prev?.type) {
1389
- return true;
1335
+ const getClipboardByKey = (key) => {
1336
+ return `application/x-plait-${key}-fragment`;
1337
+ };
1338
+ const setClipboardData = (data, elements) => {
1339
+ const result = [...elements];
1340
+ const pluginContextResult = getDataFromClipboard(data);
1341
+ if (pluginContextResult) {
1342
+ result.push(...pluginContextResult);
1390
1343
  }
1391
- return false;
1344
+ const stringObj = JSON.stringify(result);
1345
+ const encoded = window.btoa(encodeURIComponent(stringObj));
1346
+ data?.setData(`application/${CLIP_BOARD_FORMAT_KEY}`, encoded);
1392
1347
  };
1393
- /**
1394
- * Check whether an operation needs to be saved to the history.
1395
- */
1396
- const shouldSave = (op, prev) => {
1397
- if (op.type === 'set_selection' || op.type === 'set_viewport') {
1398
- return false;
1348
+ const setClipboardDataByText = (data, text) => {
1349
+ const pluginContextResult = getTextFromClipboard(data);
1350
+ data?.setData(`text/plain`, text + '\n' + pluginContextResult);
1351
+ };
1352
+ const setClipboardDataByMedia = (data, media, key) => {
1353
+ const stringObj = JSON.stringify(media);
1354
+ const encoded = window.btoa(encodeURIComponent(stringObj));
1355
+ data?.setData(getClipboardByKey(key), encoded);
1356
+ };
1357
+ const getDataFromClipboard = (data) => {
1358
+ const encoded = data?.getData(`application/${CLIP_BOARD_FORMAT_KEY}`);
1359
+ let nodesData = [];
1360
+ if (encoded) {
1361
+ const decoded = decodeURIComponent(window.atob(encoded));
1362
+ nodesData = JSON.parse(decoded);
1399
1363
  }
1400
- return true;
1364
+ return nodesData;
1401
1365
  };
1402
- /**
1403
- * Check whether an operation should clear the redos stack.
1404
- */
1405
- const shouldClear = (op) => {
1406
- if (op.type === 'set_selection') {
1407
- return false;
1366
+ const getTextFromClipboard = (data) => {
1367
+ return (data ? data.getData(`text/plain`) : '');
1368
+ };
1369
+ const getClipboardDataByMedia = (data, key) => {
1370
+ const encoded = data?.getData(getClipboardByKey(key));
1371
+ let imageItem = null;
1372
+ if (encoded) {
1373
+ const decoded = decodeURIComponent(window.atob(encoded));
1374
+ imageItem = JSON.parse(decoded);
1408
1375
  }
1409
- return true;
1376
+ return imageItem;
1410
1377
  };
1411
- const PlaitHistoryBoard = {
1378
+
1379
+ const isPreventTouchMove = (board) => {
1380
+ return !!IS_PREVENT_TOUCH_MOVE.get(board);
1381
+ };
1382
+ const preventTouchMove = (board, state) => {
1383
+ IS_PREVENT_TOUCH_MOVE.set(board, state);
1384
+ };
1385
+
1386
+ const PlaitElement = {
1387
+ isRootElement(value) {
1388
+ const parent = NODE_TO_PARENT.get(value);
1389
+ if (parent && PlaitBoard.isBoard(parent)) {
1390
+ return true;
1391
+ }
1392
+ else {
1393
+ return false;
1394
+ }
1395
+ },
1396
+ getComponent(value) {
1397
+ return ELEMENT_TO_COMPONENT.get(value);
1398
+ }
1399
+ };
1400
+
1401
+ const Path = {
1412
1402
  /**
1413
- * Get the saving flag's current value.
1403
+ * Get a list of ancestor paths for a given path.
1404
+ *
1405
+ * The paths are sorted from shallowest to deepest ancestor. However, if the
1406
+ * `reverse: true` option is passed, they are reversed.
1414
1407
  */
1415
- isSaving(board) {
1416
- return SAVING.get(board);
1408
+ ancestors(path, options = {}) {
1409
+ const { reverse = false } = options;
1410
+ let paths = Path.levels(path, options);
1411
+ if (reverse) {
1412
+ paths = paths.slice(1);
1413
+ }
1414
+ else {
1415
+ paths = paths.slice(0, -1);
1416
+ }
1417
+ return paths;
1417
1418
  },
1418
1419
  /**
1419
- * Get the merge flag's current value.
1420
+ * Get a list of paths at every level down to a path. Note: this is the same
1421
+ * as `Path.ancestors`, but including the path itself.
1422
+ *
1423
+ * The paths are sorted from shallowest to deepest. However, if the `reverse:
1424
+ * true` option is passed, they are reversed.
1420
1425
  */
1421
- isMerging(board) {
1422
- return MERGING.get(board);
1426
+ levels(path, options = {}) {
1427
+ const { reverse = false } = options;
1428
+ const list = [];
1429
+ for (let i = 0; i <= path.length; i++) {
1430
+ list.push(path.slice(0, i));
1431
+ }
1432
+ if (reverse) {
1433
+ list.reverse();
1434
+ }
1435
+ return list;
1436
+ },
1437
+ parent(path) {
1438
+ if (path.length === 0) {
1439
+ throw new Error(`Cannot get the parent path of the root path [${path}].`);
1440
+ }
1441
+ return path.slice(0, -1);
1442
+ },
1443
+ next(path) {
1444
+ if (path.length === 0) {
1445
+ throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
1446
+ }
1447
+ const last = path[path.length - 1];
1448
+ return path.slice(0, -1).concat(last + 1);
1449
+ },
1450
+ hasPrevious(path) {
1451
+ return path[path.length - 1] > 0;
1452
+ },
1453
+ previous(path) {
1454
+ if (path.length === 0) {
1455
+ throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
1456
+ }
1457
+ const last = path[path.length - 1];
1458
+ return path.slice(0, -1).concat(last - 1);
1423
1459
  },
1424
1460
  /**
1425
- * Apply a series of changes inside a synchronous `fn`, without merging any of
1426
- * the new operations into previous save point in the history.
1461
+ * Check if a path is an ancestor of another.
1427
1462
  */
1428
- withoutMerging(board, fn) {
1429
- const prev = PlaitHistoryBoard.isMerging(board);
1430
- MERGING.set(board, false);
1431
- fn();
1432
- MERGING.set(board, prev);
1463
+ isAncestor(path, another) {
1464
+ return path.length < another.length && Path.compare(path, another) === 0;
1433
1465
  },
1434
1466
  /**
1435
- * Apply a series of changes inside a synchronous `fn`, without saving any of
1436
- * their operations into the history.
1467
+ * Compare a path to another, returning an integer indicating whether the path
1468
+ * was before, at, or after the other.
1469
+ *
1470
+ * Note: Two paths of unequal length can still receive a `0` result if one is
1471
+ * directly above or below the other. If you want exact matching, use
1472
+ * [[Path.equals]] instead.
1437
1473
  */
1438
- withoutSaving(board, fn) {
1439
- const prev = PlaitHistoryBoard.isSaving(board);
1440
- SAVING.set(board, false);
1441
- fn();
1442
- SAVING.set(board, prev);
1443
- }
1444
- };
1445
-
1446
- /**
1447
- * Hotkey mappings for each platform.
1448
- */
1449
- const HOTKEYS = {
1450
- bold: 'mod+b',
1451
- compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
1452
- moveBackward: 'left',
1453
- moveForward: 'right',
1454
- moveUp: 'up',
1455
- moveDown: 'down',
1456
- moveWordBackward: 'ctrl+left',
1457
- moveWordForward: 'ctrl+right',
1458
- deleteBackward: 'shift?+backspace',
1459
- deleteForward: 'shift?+delete',
1460
- extendBackward: 'shift+left',
1461
- extendForward: 'shift+right',
1462
- italic: 'mod+i',
1463
- splitBlock: 'shift?+enter',
1464
- undo: 'mod+z'
1465
- };
1466
- const APPLE_HOTKEYS = {
1467
- moveLineBackward: 'opt+up',
1468
- moveLineForward: 'opt+down',
1469
- moveWordBackward: 'opt+left',
1470
- moveWordForward: 'opt+right',
1471
- deleteBackward: ['ctrl+backspace', 'ctrl+h'],
1472
- deleteForward: ['ctrl+delete', 'ctrl+d'],
1473
- deleteLineBackward: 'cmd+shift?+backspace',
1474
- deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
1475
- deleteWordBackward: 'opt+shift?+backspace',
1476
- deleteWordForward: 'opt+shift?+delete',
1477
- extendLineBackward: 'opt+shift+up',
1478
- extendLineForward: 'opt+shift+down',
1479
- redo: 'cmd+shift+z',
1480
- transposeCharacter: 'ctrl+t'
1481
- };
1482
- const WINDOWS_HOTKEYS = {
1483
- deleteWordBackward: 'ctrl+shift?+backspace',
1484
- deleteWordForward: 'ctrl+shift?+delete',
1485
- redo: ['ctrl+y', 'ctrl+shift+z']
1486
- };
1487
- /**
1488
- * Create a platform-aware hotkey checker.
1489
- */
1490
- const create = (key) => {
1491
- const generic = HOTKEYS[key];
1492
- const apple = APPLE_HOTKEYS[key];
1493
- const windows = WINDOWS_HOTKEYS[key];
1494
- const isGeneric = generic && isKeyHotkey(generic);
1495
- const isApple = apple && isKeyHotkey(apple);
1496
- const isWindows = windows && isKeyHotkey(windows);
1497
- return (event) => {
1498
- if (isGeneric && isGeneric(event)) {
1499
- return true;
1500
- }
1501
- if (IS_APPLE && isApple && isApple(event)) {
1502
- return true;
1474
+ compare(path, another) {
1475
+ const min = Math.min(path.length, another.length);
1476
+ for (let i = 0; i < min; i++) {
1477
+ if (path[i] < another[i])
1478
+ return -1;
1479
+ if (path[i] > another[i])
1480
+ return 1;
1503
1481
  }
1504
- if (!IS_APPLE && isWindows && isWindows(event)) {
1505
- return true;
1482
+ return 0;
1483
+ },
1484
+ /**
1485
+ * Check if a path is exactly equal to another.
1486
+ */
1487
+ equals(path, another) {
1488
+ return path.length === another.length && path.every((n, i) => n === another[i]);
1489
+ },
1490
+ /**
1491
+ * Check if a path ends before one of the indexes in another.
1492
+ */
1493
+ endsBefore(path, another) {
1494
+ const i = path.length - 1;
1495
+ const as = path.slice(0, i);
1496
+ const bs = another.slice(0, i);
1497
+ const av = path[i];
1498
+ const bv = another[i];
1499
+ return Path.equals(as, bs) && av < bv;
1500
+ },
1501
+ /**
1502
+ * Check if a path is a sibling of another.
1503
+ */
1504
+ isSibling(path, another) {
1505
+ if (path.length !== another.length) {
1506
+ return false;
1506
1507
  }
1507
- return false;
1508
- };
1509
- };
1510
- /**
1511
- * Hotkeys.
1512
- */
1513
- const hotkeys = {
1514
- isBold: create('bold'),
1515
- isCompose: create('compose'),
1516
- isMoveBackward: create('moveBackward'),
1517
- isMoveForward: create('moveForward'),
1518
- isMoveUp: create('moveUp'),
1519
- isMoveDown: create('moveDown'),
1520
- isDeleteBackward: create('deleteBackward'),
1521
- isDeleteForward: create('deleteForward'),
1522
- isDeleteLineBackward: create('deleteLineBackward'),
1523
- isDeleteLineForward: create('deleteLineForward'),
1524
- isDeleteWordBackward: create('deleteWordBackward'),
1525
- isDeleteWordForward: create('deleteWordForward'),
1526
- isExtendBackward: create('extendBackward'),
1527
- isExtendForward: create('extendForward'),
1528
- isExtendLineBackward: create('extendLineBackward'),
1529
- isExtendLineForward: create('extendLineForward'),
1530
- isItalic: create('italic'),
1531
- isMoveLineBackward: create('moveLineBackward'),
1532
- isMoveLineForward: create('moveLineForward'),
1533
- isMoveWordBackward: create('moveWordBackward'),
1534
- isMoveWordForward: create('moveWordForward'),
1535
- isRedo: create('redo'),
1536
- isSplitBlock: create('splitBlock'),
1537
- isTransposeCharacter: create('transposeCharacter'),
1538
- isUndo: create('undo')
1539
- };
1540
-
1541
- function idCreator(length = 5) {
1542
- // remove numeral
1543
- const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
1544
- const maxPosition = $chars.length;
1545
- let key = '';
1546
- for (let i = 0; i < length; i++) {
1547
- key += $chars.charAt(Math.floor(Math.random() * maxPosition));
1508
+ const as = path.slice(0, -1);
1509
+ const bs = another.slice(0, -1);
1510
+ const al = path[path.length - 1];
1511
+ const bl = another[another.length - 1];
1512
+ return al !== bl && Path.equals(as, bs);
1513
+ },
1514
+ transform(path, operation) {
1515
+ return produce(path, p => {
1516
+ // PERF: Exit early if the operation is guaranteed not to have an effect.
1517
+ if (!path || path?.length === 0) {
1518
+ return;
1519
+ }
1520
+ if (p === null) {
1521
+ return null;
1522
+ }
1523
+ switch (operation.type) {
1524
+ case 'insert_node': {
1525
+ const { path: op } = operation;
1526
+ if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
1527
+ p[op.length - 1] += 1;
1528
+ }
1529
+ break;
1530
+ }
1531
+ case 'remove_node': {
1532
+ const { path: op } = operation;
1533
+ if (Path.equals(op, p) || Path.isAncestor(op, p)) {
1534
+ return null;
1535
+ }
1536
+ else if (Path.endsBefore(op, p)) {
1537
+ p[op.length - 1] -= 1;
1538
+ }
1539
+ break;
1540
+ }
1541
+ case 'move_node': {
1542
+ const { path: op, newPath: onp } = operation;
1543
+ // If the old and new path are the same, it's a no-op.
1544
+ if (Path.equals(op, onp)) {
1545
+ return;
1546
+ }
1547
+ if (Path.isAncestor(op, p) || Path.equals(op, p)) {
1548
+ const copy = onp.slice();
1549
+ // op.length <= onp.length is different for slate
1550
+ // resolve drag from [0, 0] to [0, 3] issue
1551
+ if (Path.endsBefore(op, onp) && op.length <= onp.length) {
1552
+ copy[op.length - 1] -= 1;
1553
+ }
1554
+ return copy.concat(p.slice(op.length));
1555
+ }
1556
+ else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
1557
+ if (Path.endsBefore(op, p)) {
1558
+ p[op.length - 1] -= 1;
1559
+ }
1560
+ else {
1561
+ p[op.length - 1] += 1;
1562
+ }
1563
+ }
1564
+ else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
1565
+ if (Path.endsBefore(op, p)) {
1566
+ p[op.length - 1] -= 1;
1567
+ }
1568
+ p[onp.length - 1] += 1;
1569
+ }
1570
+ else if (Path.endsBefore(op, p)) {
1571
+ if (Path.equals(onp, p)) {
1572
+ p[onp.length - 1] += 1;
1573
+ }
1574
+ p[op.length - 1] -= 1;
1575
+ }
1576
+ break;
1577
+ }
1578
+ }
1579
+ return p;
1580
+ });
1548
1581
  }
1549
- return key;
1550
- }
1582
+ };
1551
1583
 
1552
- /**
1553
- * drawRoundRectangle
1554
- */
1555
- function drawRoundRectangle(rs, x1, y1, x2, y2, options, outline = false, borderRadius) {
1556
- const width = Math.abs(x1 - x2);
1557
- const height = Math.abs(y1 - y2);
1558
- let radius = borderRadius || 0;
1559
- if (radius === 0) {
1560
- const defaultRadius = Math.min(width, height) / 8;
1561
- let radius = defaultRadius;
1562
- if (defaultRadius > MAX_RADIUS) {
1563
- radius = outline ? MAX_RADIUS + 2 : MAX_RADIUS;
1584
+ const PlaitNode = {
1585
+ parent: (board, path) => {
1586
+ const parentPath = Path.parent(path);
1587
+ const p = PlaitNode.get(board, parentPath);
1588
+ return p;
1589
+ },
1590
+ /**
1591
+ * Return a generator of all the ancestor nodes above a specific path.
1592
+ *
1593
+ * By default the order is top-down, from highest to lowest ancestor in
1594
+ * the tree, but you can pass the `reverse: true` option to go bottom-up.
1595
+ */
1596
+ *parents(root, path, options = {}) {
1597
+ for (const p of Path.ancestors(path, options)) {
1598
+ const n = PlaitNode.get(root, p);
1599
+ yield n;
1600
+ }
1601
+ },
1602
+ get(root, path) {
1603
+ let node = root;
1604
+ for (let i = 0; i < path.length; i++) {
1605
+ const p = path[i];
1606
+ if (!node || !node.children || !node.children[p]) {
1607
+ throw new Error(`Cannot find a descendant at path [${path}]`);
1608
+ }
1609
+ node = node.children[p];
1610
+ }
1611
+ return node;
1612
+ },
1613
+ last(board, path) {
1614
+ let n = PlaitNode.get(board, path);
1615
+ while (n && n.children && n.children.length > 0) {
1616
+ const i = n.children.length - 1;
1617
+ n = n.children[i];
1564
1618
  }
1619
+ return n;
1565
1620
  }
1566
- const point1 = [x1 + radius, y1];
1567
- const point2 = [x2 - radius, y1];
1568
- const point3 = [x2, y1 + radius];
1569
- const point4 = [x2, y2 - radius];
1570
- const point5 = [x2 - radius, y2];
1571
- const point6 = [x1 + radius, y2];
1572
- const point7 = [x1, y2 - radius];
1573
- const point8 = [x1, y1 + radius];
1574
- 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);
1575
- }
1576
- const drawRectangle = (board, rectangle, options) => {
1577
- const roughSVG = PlaitBoard.getRoughSVG(board);
1578
- const rectangleG = roughSVG.rectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height, options);
1579
- setStrokeLinecap(rectangleG, 'round');
1580
- return rectangleG;
1581
1621
  };
1582
1622
 
1583
- function arrowPoints(start, end, maxHypotenuseLength = 10, degree = 40) {
1584
- const width = Math.abs(start[0] - end[0]);
1585
- const height = Math.abs(start[1] - end[1]);
1586
- let hypotenuse = Math.hypot(width, height); // 斜边
1587
- const realRotateLine = hypotenuse > maxHypotenuseLength * 2 ? maxHypotenuseLength : hypotenuse / 2;
1588
- const rotateWidth = (realRotateLine / hypotenuse) * width;
1589
- const rotateHeight = (realRotateLine / hypotenuse) * height;
1590
- const rotatePoint = [
1591
- end[0] > start[0] ? end[0] - rotateWidth : end[0] + rotateWidth,
1592
- end[1] > start[1] ? end[1] - rotateHeight : end[1] + rotateHeight
1593
- ];
1594
- const pointRight = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (degree * Math.PI) / 180);
1595
- const pointLeft = rotate(rotatePoint[0], rotatePoint[1], end[0], end[1], (-degree * Math.PI) / 180);
1596
- return { pointLeft, pointRight };
1597
- }
1598
- function drawArrow(rs, start, end, options, maxHypotenuseLength = 10, degree = 40) {
1599
- const { pointLeft, pointRight } = arrowPoints(start, end, maxHypotenuseLength, degree);
1600
- const arrowLineLeft = rs.linearPath([pointLeft, end], options);
1601
- const arrowLineRight = rs.linearPath([pointRight, end], options);
1602
- return [arrowLineLeft, arrowLineRight];
1603
- }
1604
-
1605
- function drawCircle(roughSVG, point, diameter, options) {
1606
- return roughSVG.circle(point[0], point[1], diameter, options);
1607
- }
1608
-
1609
- function drawLine(rs, start, end, options) {
1610
- return rs.linearPath([start, end], options);
1611
- }
1612
- function drawLinearPath(points, options) {
1613
- const g = createG();
1614
- const path = createPath();
1615
- let polylinePath = '';
1616
- points.forEach((point, index) => {
1617
- if (index === 0) {
1618
- polylinePath += `M ${point[0]} ${point[1]} `;
1623
+ const isSetViewportOperation = (value) => {
1624
+ return value.type === 'set_viewport';
1625
+ };
1626
+ const inverse = (op) => {
1627
+ switch (op.type) {
1628
+ case 'insert_node': {
1629
+ return { ...op, type: 'remove_node' };
1619
1630
  }
1620
- else {
1621
- polylinePath += `L ${point[0]} ${point[1]} `;
1631
+ case 'remove_node': {
1632
+ return { ...op, type: 'insert_node' };
1622
1633
  }
1623
- });
1624
- path.setAttribute('d', polylinePath);
1625
- path.setAttribute('stroke', `${options?.stroke}`);
1626
- path.setAttribute('stroke-width', `${options?.strokeWidth}`);
1627
- path.setAttribute('fill', `none`);
1628
- g.appendChild(path);
1629
- return g;
1630
- }
1631
- function drawBezierPath(points, options) {
1632
- const g = createG();
1633
- const path = createPath();
1634
- let polylinePath = '';
1635
- for (let i = 0; i < points.length - 3; i += 3) {
1636
- if (i === 0) {
1637
- polylinePath += `M ${points[0][0]} ${points[0][1]} `;
1634
+ case 'move_node': {
1635
+ const { newPath, path } = op;
1636
+ // PERF: in this case the move operation is a no-op anyways.
1637
+ if (Path.equals(newPath, path)) {
1638
+ return op;
1639
+ }
1640
+ // when operation path is [0,0] -> [0,2], should exec Path.transform to get [0,1] -> [0,0]
1641
+ // shoud not return [0,2] -> [0,0] #WIK-8981
1642
+ // if (Path.isSibling(path, newPath)) {
1643
+ // return { ...op, path: newPath, newPath: path };
1644
+ // }
1645
+ // If the move does not happen within a single parent it is possible
1646
+ // for the move to impact the true path to the location where the node
1647
+ // was removed from and where it was inserted. We have to adjust for this
1648
+ // and find the original path. We can accomplish this (only in non-sibling)
1649
+ // moves by looking at the impact of the move operation on the node
1650
+ // after the original move path.
1651
+ const inversePath = Path.transform(path, op);
1652
+ const inverseNewPath = Path.transform(Path.next(path), op);
1653
+ return { ...op, path: inversePath, newPath: inverseNewPath };
1638
1654
  }
1639
- else {
1640
- 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]}`;
1655
+ case 'set_node': {
1656
+ const { properties, newProperties } = op;
1657
+ return { ...op, properties: newProperties, newProperties: properties };
1641
1658
  }
1642
- }
1643
- path.setAttribute('d', polylinePath);
1644
- path.setAttribute('stroke', `${options?.stroke}`);
1645
- path.setAttribute('stroke-width', `${options?.strokeWidth}`);
1646
- path.setAttribute('fill', `none`);
1647
- g.appendChild(path);
1648
- return g;
1649
- }
1650
-
1651
- let timerId = null;
1652
- const throttleRAF = (fn) => {
1653
- const scheduleFunc = () => {
1654
- timerId = requestAnimationFrame(() => {
1655
- timerId = null;
1656
- fn();
1657
- });
1658
- };
1659
- if (timerId !== null) {
1660
- cancelAnimationFrame(timerId);
1661
- timerId = null;
1662
- }
1663
- scheduleFunc();
1664
- };
1665
- const debounce = (func, wait, options) => {
1666
- let timerSubscription = null;
1667
- return () => {
1668
- if (timerSubscription && !timerSubscription.closed) {
1669
- timerSubscription.unsubscribe();
1670
- timerSubscription = timer(wait).subscribe(() => {
1671
- func();
1672
- });
1659
+ case 'set_selection': {
1660
+ const { properties, newProperties } = op;
1661
+ if (properties == null) {
1662
+ return {
1663
+ ...op,
1664
+ properties: newProperties,
1665
+ newProperties: null
1666
+ };
1667
+ }
1668
+ else if (newProperties == null) {
1669
+ return {
1670
+ ...op,
1671
+ properties: null,
1672
+ newProperties: properties
1673
+ };
1674
+ }
1675
+ else {
1676
+ return { ...op, properties: newProperties, newProperties: properties };
1677
+ }
1673
1678
  }
1674
- else {
1675
- if (options?.leading) {
1676
- func();
1679
+ case 'set_viewport': {
1680
+ const { properties, newProperties } = op;
1681
+ if (properties == null) {
1682
+ return {
1683
+ ...op,
1684
+ properties: newProperties,
1685
+ newProperties: newProperties
1686
+ };
1677
1687
  }
1678
- timerSubscription = timer(wait).subscribe();
1688
+ else if (newProperties == null) {
1689
+ return {
1690
+ ...op,
1691
+ properties: properties,
1692
+ newProperties: properties
1693
+ };
1694
+ }
1695
+ else {
1696
+ return { ...op, properties: newProperties, newProperties: properties };
1697
+ }
1698
+ }
1699
+ case 'set_theme': {
1700
+ const { properties, newProperties } = op;
1701
+ return { ...op, properties: newProperties, newProperties: properties };
1679
1702
  }
1680
- };
1681
- };
1682
-
1683
- const getMovingElements = (board) => {
1684
- return BOARD_TO_MOVING_ELEMENT.get(board) || [];
1703
+ }
1685
1704
  };
1686
- const addMovingElements = (board, elements) => {
1687
- const movingElements = getMovingElements(board);
1688
- const newElements = elements.filter(item => !movingElements.find(movingElement => movingElement.key === item.key));
1689
- cacheMovingElements(board, [...movingElements, ...newElements]);
1705
+ const PlaitOperation = {
1706
+ isSetViewportOperation,
1707
+ inverse
1690
1708
  };
1691
- const removeMovingElements = (board) => {
1692
- BOARD_TO_MOVING_ELEMENT.delete(board);
1709
+
1710
+ const Point = {
1711
+ isEquals(point, otherPoint) {
1712
+ return point && otherPoint && point[0] === otherPoint[0] && point[1] === otherPoint[1];
1713
+ }
1693
1714
  };
1694
- const cacheMovingElements = (board, elements) => {
1695
- BOARD_TO_MOVING_ELEMENT.set(board, elements);
1715
+
1716
+ const Viewport = {
1717
+ isViewport: (value) => {
1718
+ return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
1719
+ },
1696
1720
  };
1697
1721
 
1698
- function cloneCSSStyle(nativeNode, clonedNode) {
1699
- const targetStyle = clonedNode.style;
1700
- if (!targetStyle) {
1701
- return;
1702
- }
1703
- const sourceStyle = window.getComputedStyle(nativeNode);
1704
- if (sourceStyle.cssText) {
1705
- targetStyle.cssText = sourceStyle.cssText;
1706
- targetStyle.transformOrigin = sourceStyle.transformOrigin;
1707
- }
1708
- else {
1709
- Array.from(sourceStyle).forEach(name => {
1710
- let value = sourceStyle.getPropertyValue(name);
1711
- targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
1712
- });
1713
- }
1714
- }
1715
- function createCanvas(width, height, fillStyle) {
1716
- const canvas = document.createElement('canvas');
1717
- const ctx = canvas.getContext('2d');
1718
- canvas.width = width;
1719
- canvas.height = height;
1720
- canvas.style.width = `${width}px`;
1721
- canvas.style.height = `${height}px`;
1722
- ctx.strokeStyle = '#ffffff';
1723
- ctx.fillStyle = fillStyle;
1724
- ctx.fillRect(0, 0, width, height);
1725
- return {
1726
- canvas,
1727
- ctx
1728
- };
1729
- }
1730
- function isElementNode(node) {
1731
- return node.nodeType === Node.ELEMENT_NODE;
1732
- }
1733
- function cloneSvg(board, options) {
1734
- const elementHostBox = getRectangleByElements(board, board.children, true);
1735
- const { width, height, x, y } = elementHostBox;
1736
- const { padding = 4, inlineStyleClassNames } = options;
1737
- const sourceSvg = PlaitBoard.getHost(board);
1738
- const cloneSvgElement = sourceSvg.cloneNode(true);
1739
- cloneSvgElement.style.width = `${width}px`;
1740
- cloneSvgElement.style.height = `${height}px`;
1741
- cloneSvgElement.style.backgroundColor = '';
1742
- cloneSvgElement.setAttribute('width', `${width}`);
1743
- cloneSvgElement.setAttribute('height', `${height}`);
1744
- cloneSvgElement.setAttribute('viewBox', [x - padding, y - padding, width + 2 * padding, height + 2 * padding].join(','));
1745
- if (inlineStyleClassNames) {
1746
- const sourceNodes = Array.from(sourceSvg.querySelectorAll(inlineStyleClassNames));
1747
- const cloneNodes = Array.from(cloneSvgElement.querySelectorAll(inlineStyleClassNames));
1748
- sourceNodes.forEach((node, index) => {
1749
- const cloneNode = cloneNodes[index];
1750
- const childElements = Array.from(node.querySelectorAll('*')).filter(isElementNode);
1751
- const cloneChildElements = Array.from(cloneNode.querySelectorAll('*')).filter(isElementNode);
1752
- sourceNodes.push(...childElements);
1753
- cloneNodes.push(...cloneChildElements);
1754
- });
1755
- sourceNodes.forEach((node, index) => {
1756
- const cloneNode = cloneNodes[index];
1757
- cloneCSSStyle(node, cloneNode);
1758
- });
1759
- }
1760
- return cloneSvgElement;
1761
- }
1762
- function loadImage(src) {
1763
- return new Promise((resolve, reject) => {
1764
- const img = new Image();
1765
- img.onload = () => resolve(img);
1766
- img.onerror = () => reject(new Error('Failed to load image'));
1767
- img.src = src;
1768
- });
1769
- }
1770
- async function toImage(board, options) {
1771
- if (!board) {
1772
- return undefined;
1773
- }
1774
- const elementHostBox = getRectangleByElements(board, board.children, true);
1775
- const { ratio = 2, fillStyle = 'transparent' } = options;
1776
- const { width, height } = elementHostBox;
1777
- const ratioWidth = width * ratio;
1778
- const ratioHeight = height * ratio;
1779
- const cloneSvgElement = cloneSvg(board, options);
1780
- const { canvas, ctx } = createCanvas(ratioWidth, ratioHeight, fillStyle);
1781
- const svgStr = new XMLSerializer().serializeToString(cloneSvgElement);
1782
- const imgSrc = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgStr)}`;
1783
- try {
1784
- const img = await loadImage(imgSrc);
1785
- ctx.drawImage(img, 0, 0, ratioWidth, ratioHeight);
1786
- const url = canvas.toDataURL('image/png');
1787
- return url;
1788
- }
1789
- catch (error) {
1790
- console.error('Error converting SVG to image:', error);
1791
- return undefined;
1792
- }
1793
- }
1794
- function downloadImage(url, name) {
1795
- const a = document.createElement('a');
1796
- a.href = url;
1797
- a.download = name;
1798
- a.click();
1799
- a.remove();
1800
- }
1722
+ const SAVING = new WeakMap();
1723
+ const MERGING = new WeakMap();
1801
1724
 
1802
- const getClipboardByKey = (key) => {
1803
- return `application/x-plait-${key}-fragment`;
1725
+ var PlaitPluginKey;
1726
+ (function (PlaitPluginKey) {
1727
+ PlaitPluginKey["withSelection"] = "withSelection";
1728
+ })(PlaitPluginKey || (PlaitPluginKey = {}));
1729
+
1730
+ var ThemeColorMode;
1731
+ (function (ThemeColorMode) {
1732
+ ThemeColorMode["default"] = "default";
1733
+ ThemeColorMode["colorful"] = "colorful";
1734
+ ThemeColorMode["soft"] = "soft";
1735
+ ThemeColorMode["retro"] = "retro";
1736
+ ThemeColorMode["dark"] = "dark";
1737
+ ThemeColorMode["starry"] = "starry";
1738
+ })(ThemeColorMode || (ThemeColorMode = {}));
1739
+ const DefaultThemeColor = {
1740
+ mode: ThemeColorMode.default,
1741
+ boardBackground: '#ffffff',
1742
+ textColor: '#333333'
1804
1743
  };
1805
- const setClipboardData = (data, elements) => {
1806
- const result = [...elements];
1807
- const pluginContextResult = getDataFromClipboard(data);
1808
- if (pluginContextResult) {
1809
- result.push(...pluginContextResult);
1810
- }
1811
- const stringObj = JSON.stringify(result);
1812
- const encoded = window.btoa(encodeURIComponent(stringObj));
1813
- data?.setData(`application/${CLIP_BOARD_FORMAT_KEY}`, encoded);
1744
+ const ColorfulThemeColor = {
1745
+ mode: ThemeColorMode.colorful,
1746
+ boardBackground: '#ffffff',
1747
+ textColor: '#333333'
1814
1748
  };
1815
- const setClipboardDataByText = (data, text) => {
1816
- const pluginContextResult = getTextFromClipboard(data);
1817
- data?.setData(`text/plain`, text + '\n' + pluginContextResult);
1749
+ const SoftThemeColor = {
1750
+ mode: ThemeColorMode.soft,
1751
+ boardBackground: '#f5f5f5',
1752
+ textColor: '#333333'
1818
1753
  };
1819
- const setClipboardDataByMedia = (data, media, key) => {
1820
- const stringObj = JSON.stringify(media);
1821
- const encoded = window.btoa(encodeURIComponent(stringObj));
1822
- data?.setData(getClipboardByKey(key), encoded);
1754
+ const RetroThemeColor = {
1755
+ mode: ThemeColorMode.retro,
1756
+ boardBackground: '#f9f8ed',
1757
+ textColor: '#333333'
1823
1758
  };
1824
- const getDataFromClipboard = (data) => {
1825
- const encoded = data?.getData(`application/${CLIP_BOARD_FORMAT_KEY}`);
1826
- let nodesData = [];
1827
- if (encoded) {
1828
- const decoded = decodeURIComponent(window.atob(encoded));
1829
- nodesData = JSON.parse(decoded);
1830
- }
1831
- return nodesData;
1759
+ const DarkThemeColor = {
1760
+ mode: ThemeColorMode.dark,
1761
+ boardBackground: '#141414',
1762
+ textColor: '#FFFFFF'
1832
1763
  };
1833
- const getTextFromClipboard = (data) => {
1834
- return (data ? data.getData(`text/plain`) : '');
1764
+ const StarryThemeColor = {
1765
+ mode: ThemeColorMode.starry,
1766
+ boardBackground: '#0d2537',
1767
+ textColor: '#FFFFFF'
1835
1768
  };
1836
- const getClipboardDataByMedia = (data, key) => {
1837
- const encoded = data?.getData(getClipboardByKey(key));
1838
- let imageItem = null;
1839
- if (encoded) {
1840
- const decoded = decodeURIComponent(window.atob(encoded));
1841
- imageItem = JSON.parse(decoded);
1769
+ const ThemeColors = [
1770
+ DefaultThemeColor,
1771
+ ColorfulThemeColor,
1772
+ SoftThemeColor,
1773
+ RetroThemeColor,
1774
+ DarkThemeColor,
1775
+ StarryThemeColor
1776
+ ];
1777
+
1778
+ function getRectangleByElements(board, elements, recursion) {
1779
+ const boundaryBox = {
1780
+ left: Number.MAX_VALUE,
1781
+ top: Number.MAX_VALUE,
1782
+ right: Number.NEGATIVE_INFINITY,
1783
+ bottom: Number.NEGATIVE_INFINITY
1784
+ };
1785
+ const calcRectangleClient = (node) => {
1786
+ const nodeRectangle = board.getRectangle(node);
1787
+ if (nodeRectangle) {
1788
+ boundaryBox.left = Math.min(boundaryBox.left, nodeRectangle.x);
1789
+ boundaryBox.top = Math.min(boundaryBox.top, nodeRectangle.y);
1790
+ boundaryBox.right = Math.max(boundaryBox.right, nodeRectangle.x + nodeRectangle.width);
1791
+ boundaryBox.bottom = Math.max(boundaryBox.bottom, nodeRectangle.y + nodeRectangle.height);
1792
+ }
1793
+ else {
1794
+ console.error(`can not get rectangle of element:`, node);
1795
+ }
1796
+ };
1797
+ elements.forEach(element => {
1798
+ if (recursion) {
1799
+ depthFirstRecursion(element, node => calcRectangleClient(node), node => board.isRecursion(node));
1800
+ }
1801
+ else {
1802
+ calcRectangleClient(element);
1803
+ }
1804
+ });
1805
+ if (boundaryBox.left === Number.MAX_VALUE) {
1806
+ return {
1807
+ x: 0,
1808
+ y: 0,
1809
+ width: 0,
1810
+ height: 0
1811
+ };
1812
+ }
1813
+ return {
1814
+ x: boundaryBox.left,
1815
+ y: boundaryBox.top,
1816
+ width: boundaryBox.right - boundaryBox.left,
1817
+ height: boundaryBox.bottom - boundaryBox.top
1818
+ };
1819
+ }
1820
+ function getBoardRectangle(board) {
1821
+ return getRectangleByElements(board, board.children, true);
1822
+ }
1823
+ function getElementById(board, id, dataSource) {
1824
+ if (!dataSource) {
1825
+ dataSource = findElements(board, { match: (element) => true, recursion: (element) => true });
1842
1826
  }
1843
- return imageItem;
1844
- };
1845
-
1846
- const isPreventTouchMove = (board) => {
1847
- return !!IS_PREVENT_TOUCH_MOVE.get(board);
1848
- };
1849
- const preventTouchMove = (board, state) => {
1850
- IS_PREVENT_TOUCH_MOVE.set(board, state);
1851
- };
1852
-
1853
- const PlaitElement = {
1854
- isRootElement(value) {
1855
- const parent = NODE_TO_PARENT.get(value);
1856
- if (parent && PlaitBoard.isBoard(parent)) {
1827
+ let element = dataSource.find((element) => element.id === id);
1828
+ return element;
1829
+ }
1830
+ function findElements(board, options) {
1831
+ let elements = [];
1832
+ depthFirstRecursion(board, node => {
1833
+ if (!PlaitBoard.isBoard(node) && options.match(node)) {
1834
+ elements.push(node);
1835
+ }
1836
+ }, (value) => {
1837
+ if (PlaitBoard.isBoard(value)) {
1857
1838
  return true;
1858
1839
  }
1859
1840
  else {
1860
- return false;
1841
+ return getIsRecursionFunc(board)(value) && options.recursion(value);
1842
+ }
1843
+ }, true);
1844
+ return elements;
1845
+ }
1846
+
1847
+ const PlaitBoard = {
1848
+ isBoard(value) {
1849
+ const cachedIsBoard = IS_BOARD_CACHE.get(value);
1850
+ if (cachedIsBoard !== undefined) {
1851
+ return cachedIsBoard;
1861
1852
  }
1853
+ const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
1854
+ IS_BOARD_CACHE.set(value, isBoard);
1855
+ return isBoard;
1862
1856
  },
1863
- getComponent(value) {
1864
- return ELEMENT_TO_COMPONENT.get(value);
1857
+ findPath(board, node) {
1858
+ const path = [];
1859
+ let child = node;
1860
+ while (true) {
1861
+ const parent = NODE_TO_PARENT.get(child);
1862
+ if (parent == null) {
1863
+ if (PlaitBoard.isBoard(child)) {
1864
+ return path;
1865
+ }
1866
+ else {
1867
+ break;
1868
+ }
1869
+ }
1870
+ const i = NODE_TO_INDEX.get(child);
1871
+ if (i == null) {
1872
+ break;
1873
+ }
1874
+ path.unshift(i);
1875
+ child = parent;
1876
+ }
1877
+ throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
1878
+ },
1879
+ getHost(board) {
1880
+ return BOARD_TO_HOST.get(board);
1881
+ },
1882
+ getElementHost(board) {
1883
+ return BOARD_TO_ELEMENT_HOST.get(board)?.host;
1884
+ },
1885
+ getElementUpperHost(board) {
1886
+ return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
1887
+ },
1888
+ getElementActiveHost(board) {
1889
+ return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
1890
+ },
1891
+ getRoughSVG(board) {
1892
+ return BOARD_TO_ROUGH_SVG.get(board);
1893
+ },
1894
+ getComponent(board) {
1895
+ return BOARD_TO_COMPONENT.get(board);
1896
+ },
1897
+ getBoardContainer(board) {
1898
+ return PlaitBoard.getComponent(board).nativeElement;
1899
+ },
1900
+ getRectangle(board) {
1901
+ return getRectangleByElements(board, board.children, true);
1902
+ },
1903
+ getViewportContainer(board) {
1904
+ return PlaitBoard.getHost(board).parentElement;
1905
+ },
1906
+ isFocus(board) {
1907
+ return !!board.selection;
1908
+ },
1909
+ isReadonly(board) {
1910
+ return board.options.readonly;
1911
+ },
1912
+ hasBeenTextEditing(board) {
1913
+ return !!IS_TEXT_EDITABLE.get(board);
1914
+ },
1915
+ getPointer(board) {
1916
+ return board.pointer;
1917
+ },
1918
+ isPointer(board, pointer) {
1919
+ return board.pointer === pointer;
1920
+ },
1921
+ isInPointer(board, pointers) {
1922
+ const point = board.pointer;
1923
+ return pointers.includes(point);
1924
+ },
1925
+ getMovingPointInBoard(board) {
1926
+ return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
1927
+ },
1928
+ isMovingPointInBoard(board) {
1929
+ const point = BOARD_TO_MOVING_POINT.get(board);
1930
+ const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1931
+ if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
1932
+ return true;
1933
+ }
1934
+ return false;
1935
+ },
1936
+ getThemeColors(board) {
1937
+ return (board.options.themeColors || ThemeColors);
1865
1938
  }
1866
1939
  };
1867
1940
 
1868
- const isSetViewportOperation = (value) => {
1869
- return value.type === 'set_viewport';
1870
- };
1871
- const inverse = (op) => {
1941
+ const applyToDraft = (board, selection, viewport, theme, op) => {
1872
1942
  switch (op.type) {
1873
1943
  case 'insert_node': {
1874
- return { ...op, type: 'remove_node' };
1944
+ const { path, node } = op;
1945
+ const parent = PlaitNode.parent(board, path);
1946
+ const index = path[path.length - 1];
1947
+ if (!parent.children || index > parent.children.length) {
1948
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
1949
+ }
1950
+ parent.children.splice(index, 0, node);
1951
+ break;
1875
1952
  }
1876
1953
  case 'remove_node': {
1877
- return { ...op, type: 'insert_node' };
1954
+ const { path } = op;
1955
+ const parent = PlaitNode.parent(board, path);
1956
+ const index = path[path.length - 1];
1957
+ if (!parent.children || index > parent.children.length) {
1958
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
1959
+ }
1960
+ parent.children.splice(index, 1);
1961
+ break;
1878
1962
  }
1879
1963
  case 'move_node': {
1880
- const { newPath, path } = op;
1881
- // PERF: in this case the move operation is a no-op anyways.
1882
- if (Path.equals(newPath, path)) {
1883
- return op;
1964
+ const { path, newPath } = op;
1965
+ if (Path.isAncestor(path, newPath)) {
1966
+ throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
1884
1967
  }
1885
- // when operation path is [0,0] -> [0,2], should exec Path.transform to get [0,1] -> [0,0]
1886
- // shoud not return [0,2] -> [0,0] #WIK-8981
1887
- // if (Path.isSibling(path, newPath)) {
1888
- // return { ...op, path: newPath, newPath: path };
1889
- // }
1890
- // If the move does not happen within a single parent it is possible
1891
- // for the move to impact the true path to the location where the node
1892
- // was removed from and where it was inserted. We have to adjust for this
1893
- // and find the original path. We can accomplish this (only in non-sibling)
1894
- // moves by looking at the impact of the move operation on the node
1895
- // after the original move path.
1896
- const inversePath = Path.transform(path, op);
1897
- const inverseNewPath = Path.transform(Path.next(path), op);
1898
- return { ...op, path: inversePath, newPath: inverseNewPath };
1968
+ const node = PlaitNode.get(board, path);
1969
+ const parent = PlaitNode.parent(board, path);
1970
+ const index = path[path.length - 1];
1971
+ // This is tricky, but since the `path` and `newPath` both refer to
1972
+ // the same snapshot in time, there's a mismatch. After either
1973
+ // removing the original position, the second step's path can be out
1974
+ // of date. So instead of using the `op.newPath` directly, we
1975
+ // transform `op.path` to ascertain what the `newPath` would be after
1976
+ // the operation was applied.
1977
+ parent.children?.splice(index, 1);
1978
+ const truePath = Path.transform(path, op);
1979
+ const newParent = PlaitNode.get(board, Path.parent(truePath));
1980
+ const newIndex = truePath[truePath.length - 1];
1981
+ newParent.children?.splice(newIndex, 0, node);
1982
+ break;
1899
1983
  }
1900
1984
  case 'set_node': {
1901
- const { properties, newProperties } = op;
1902
- return { ...op, properties: newProperties, newProperties: properties };
1903
- }
1904
- case 'set_selection': {
1905
- const { properties, newProperties } = op;
1906
- if (properties == null) {
1907
- return {
1908
- ...op,
1909
- properties: newProperties,
1910
- newProperties: null
1911
- };
1985
+ const { path, properties, newProperties } = op;
1986
+ if (path.length === 0) {
1987
+ throw new Error(`Cannot set properties on the root node!`);
1912
1988
  }
1913
- else if (newProperties == null) {
1914
- return {
1915
- ...op,
1916
- properties: null,
1917
- newProperties: properties
1918
- };
1989
+ const node = PlaitNode.get(board, path);
1990
+ for (const key in newProperties) {
1991
+ const value = newProperties[key];
1992
+ if (value == null) {
1993
+ delete node[key];
1994
+ }
1995
+ else {
1996
+ node[key] = value;
1997
+ }
1919
1998
  }
1920
- else {
1921
- return { ...op, properties: newProperties, newProperties: properties };
1999
+ // properties that were previously defined, but are now missing, must be deleted
2000
+ for (const key in properties) {
2001
+ if (!newProperties.hasOwnProperty(key)) {
2002
+ delete node[key];
2003
+ }
1922
2004
  }
2005
+ break;
1923
2006
  }
1924
2007
  case 'set_viewport': {
1925
- const { properties, newProperties } = op;
1926
- if (properties == null) {
1927
- return {
1928
- ...op,
1929
- properties: newProperties,
1930
- newProperties: newProperties
1931
- };
2008
+ const { newProperties } = op;
2009
+ if (newProperties == null) {
2010
+ viewport = newProperties;
1932
2011
  }
1933
- else if (newProperties == null) {
1934
- return {
1935
- ...op,
1936
- properties: properties,
1937
- newProperties: properties
1938
- };
2012
+ else {
2013
+ if (viewport == null) {
2014
+ if (!Viewport.isViewport(newProperties)) {
2015
+ throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
2016
+ }
2017
+ viewport = { ...newProperties };
2018
+ }
2019
+ for (const key in newProperties) {
2020
+ const value = newProperties[key];
2021
+ if (value == null) {
2022
+ delete viewport[key];
2023
+ }
2024
+ else {
2025
+ viewport[key] = value;
2026
+ }
2027
+ }
2028
+ }
2029
+ break;
2030
+ }
2031
+ case 'set_selection': {
2032
+ const { newProperties } = op;
2033
+ if (newProperties == null) {
2034
+ selection = newProperties;
1939
2035
  }
1940
2036
  else {
1941
- return { ...op, properties: newProperties, newProperties: properties };
2037
+ if (selection === null) {
2038
+ selection = op.newProperties;
2039
+ }
2040
+ else {
2041
+ selection.ranges = newProperties.ranges;
2042
+ }
1942
2043
  }
2044
+ break;
1943
2045
  }
1944
2046
  case 'set_theme': {
1945
- const { properties, newProperties } = op;
1946
- return { ...op, properties: newProperties, newProperties: properties };
2047
+ const { newProperties } = op;
2048
+ theme = newProperties;
2049
+ break;
1947
2050
  }
1948
2051
  }
2052
+ return { selection, viewport, theme };
1949
2053
  };
1950
- const PlaitOperation = {
1951
- isSetViewportOperation,
1952
- inverse
1953
- };
1954
-
1955
- const Point = {
1956
- isEquals(point, otherPoint) {
1957
- return point && otherPoint && point[0] === otherPoint[0] && point[1] === otherPoint[1];
2054
+ const GeneralTransforms = {
2055
+ /**
2056
+ * Transform the board by an operation.
2057
+ */
2058
+ transform(board, op) {
2059
+ board.children = createDraft(board.children);
2060
+ let viewport = board.viewport && createDraft(board.viewport);
2061
+ let selection = board.selection && createDraft(board.selection);
2062
+ let theme = board.theme && createDraft(board.theme);
2063
+ try {
2064
+ const state = applyToDraft(board, selection, viewport, theme, op);
2065
+ viewport = state.viewport;
2066
+ selection = state.selection;
2067
+ theme = state.theme;
2068
+ }
2069
+ finally {
2070
+ board.children = finishDraft(board.children);
2071
+ if (selection) {
2072
+ board.selection = isDraft(selection) ? finishDraft(selection) : selection;
2073
+ }
2074
+ else {
2075
+ board.selection = null;
2076
+ }
2077
+ board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
2078
+ board.theme = isDraft(theme) ? finishDraft(theme) : theme;
2079
+ }
1958
2080
  }
1959
2081
  };
1960
2082
 
1961
- const SAVING = new WeakMap();
1962
- const MERGING = new WeakMap();
1963
-
1964
- var PlaitPluginKey;
1965
- (function (PlaitPluginKey) {
1966
- PlaitPluginKey["withSelection"] = "withSelection";
1967
- })(PlaitPluginKey || (PlaitPluginKey = {}));
1968
-
1969
- const IS_FROM_SCROLLING = new WeakMap();
1970
- const IS_FROM_VIEWPORT_CHANGE = new WeakMap();
1971
- function getViewportContainerRect(board) {
1972
- const { hideScrollbar } = board.options;
1973
- const scrollBarWidth = hideScrollbar ? SCROLL_BAR_WIDTH : 0;
1974
- const viewportRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1975
- return {
1976
- width: viewportRect.width + scrollBarWidth,
1977
- height: viewportRect.height + scrollBarWidth
1978
- };
1979
- }
1980
- function getElementHostBBox(board, zoom) {
1981
- const childrenRect = getRectangleByElements(board, board.children, true);
1982
- const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
1983
- const containerWidth = viewportContainerRect.width / zoom;
1984
- const containerHeight = viewportContainerRect.height / zoom;
1985
- let left;
1986
- let right;
1987
- let top;
1988
- let bottom;
1989
- if (childrenRect.width < containerWidth) {
1990
- const centerX = childrenRect.x + childrenRect.width / 2;
1991
- const halfContainerWidth = containerWidth / 2;
1992
- left = centerX - halfContainerWidth;
1993
- right = centerX + halfContainerWidth;
1994
- }
1995
- else {
1996
- left = childrenRect.x;
1997
- right = childrenRect.x + childrenRect.width;
1998
- }
1999
- if (childrenRect.height < containerHeight) {
2000
- const centerY = childrenRect.y + childrenRect.height / 2;
2001
- const halfContainerHeight = containerHeight / 2;
2002
- top = centerY - halfContainerHeight;
2003
- bottom = centerY + halfContainerHeight;
2004
- }
2005
- else {
2006
- top = childrenRect.y;
2007
- bottom = childrenRect.y + childrenRect.height;
2008
- }
2009
- return {
2010
- left,
2011
- right,
2012
- top,
2013
- bottom
2014
- };
2015
- }
2016
- /**
2017
- * 验证缩放比是否符合限制,如果超出限制,则返回合适的缩放比
2018
- * @param zoom 缩放比
2019
- * @param minZoom 最小缩放比
2020
- * @param maxZoom 最大缩放比
2021
- * @returns 正确的缩放比
2022
- */
2023
- function clampZoomLevel(zoom, minZoom = 0.2, maxZoom = 4) {
2024
- return zoom < minZoom ? minZoom : zoom > maxZoom ? maxZoom : zoom;
2025
- }
2026
- function getViewBox(board, zoom) {
2027
- const boardContainerRectangle = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
2028
- const elementHostBBox = getElementHostBBox(board, zoom);
2029
- const horizontalPadding = boardContainerRectangle.width / 2;
2030
- const verticalPadding = boardContainerRectangle.height / 2;
2031
- const viewBox = [
2032
- elementHostBBox.left - horizontalPadding / zoom,
2033
- elementHostBBox.top - verticalPadding / zoom,
2034
- elementHostBBox.right - elementHostBBox.left + (horizontalPadding * 2) / zoom,
2035
- elementHostBBox.bottom - elementHostBBox.top + (verticalPadding * 2) / zoom
2036
- ];
2037
- return viewBox;
2038
- }
2039
- function getViewBoxCenterPoint(board) {
2040
- const childrenRectangle = getRectangleByElements(board, board.children, true);
2041
- return [childrenRectangle.x + childrenRectangle.width / 2, childrenRectangle.y + childrenRectangle.height / 2];
2083
+ function insertNode(board, node, path) {
2084
+ const operation = { type: 'insert_node', node, path };
2085
+ board.apply(operation);
2042
2086
  }
2043
- function setSVGViewBox(board, viewBox) {
2044
- const zoom = board.viewport.zoom;
2045
- const hostElement = PlaitBoard.getHost(board);
2046
- hostElement.style.display = 'block';
2047
- hostElement.style.width = `${viewBox[2] * zoom}px`;
2048
- hostElement.style.height = `${viewBox[3] * zoom}px`;
2049
- if (viewBox && viewBox[2] > 0 && viewBox[3] > 0) {
2050
- hostElement.setAttribute('viewBox', viewBox.join(' '));
2087
+ function setNode(board, props, path) {
2088
+ const properties = {};
2089
+ const newProperties = {};
2090
+ const node = PlaitNode.get(board, path);
2091
+ for (const k in props) {
2092
+ if (node[k] !== props[k]) {
2093
+ if (node.hasOwnProperty(k)) {
2094
+ properties[k] = node[k];
2095
+ }
2096
+ if (props[k] != null)
2097
+ newProperties[k] = props[k];
2098
+ }
2051
2099
  }
2100
+ const operation = { type: 'set_node', properties, newProperties, path };
2101
+ board.apply(operation);
2052
2102
  }
2053
- function updateViewportOffset(board) {
2054
- const origination = getViewportOrigination(board);
2055
- if (!origination)
2056
- return;
2057
- const { zoom } = board.viewport;
2058
- const viewBox = getViewBox(board, zoom);
2059
- const scrollLeft = (origination[0] - viewBox[0]) * zoom;
2060
- const scrollTop = (origination[1] - viewBox[1]) * zoom;
2061
- updateViewportContainerScroll(board, scrollLeft, scrollTop);
2103
+ function removeNode(board, path) {
2104
+ const node = PlaitNode.get(board, path);
2105
+ const operation = { type: 'remove_node', path, node };
2106
+ board.apply(operation);
2062
2107
  }
2063
- function updateViewportContainerScroll(board, left, top, isFromViewportChange = true) {
2064
- const viewportContainer = PlaitBoard.getViewportContainer(board);
2065
- if (viewportContainer.scrollLeft !== left || viewportContainer.scrollTop !== top) {
2066
- viewportContainer.scrollLeft = left;
2067
- viewportContainer.scrollTop = top;
2068
- isFromViewportChange && setIsFromViewportChange(board, true);
2069
- }
2108
+ function moveNode(board, path, newPath) {
2109
+ const operation = { type: 'move_node', path, newPath };
2110
+ board.apply(operation);
2070
2111
  }
2071
- function initializeViewportContainer(board) {
2072
- const { width, height } = getViewportContainerRect(board);
2073
- const viewportContainer = PlaitBoard.getViewportContainer(board);
2074
- viewportContainer.style.width = `${width}px`;
2075
- viewportContainer.style.height = `${height}px`;
2112
+ const NodeTransforms = {
2113
+ insertNode,
2114
+ setNode,
2115
+ removeNode,
2116
+ moveNode
2117
+ };
2118
+
2119
+ function setSelection(board, selection) {
2120
+ const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
2121
+ board.apply(operation);
2076
2122
  }
2077
- function initializeViewBox(board) {
2078
- const zoom = board.viewport.zoom;
2079
- const viewBox = getViewBox(board, zoom);
2080
- setSVGViewBox(board, viewBox);
2123
+ const SelectionTransforms = {
2124
+ setSelection,
2125
+ setSelectionWithTemporaryElements
2126
+ };
2127
+ function setSelectionWithTemporaryElements(board, elements) {
2128
+ setTimeout(() => {
2129
+ BOARD_TO_TEMPORARY_ELEMENTS.set(board, elements);
2130
+ setSelection(board, { ranges: [] });
2131
+ });
2081
2132
  }
2082
- function initializeViewportOffset(board) {
2083
- if (!board.viewport?.origination) {
2084
- const zoom = board.viewport.zoom;
2085
- const viewportContainerRect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
2086
- const viewBox = getViewBox(board, zoom);
2087
- const centerX = viewBox[0] + viewBox[2] / 2;
2088
- const centerY = viewBox[1] + viewBox[3] / 2;
2089
- const origination = [centerX - viewportContainerRect.width / 2 / zoom, centerY - viewportContainerRect.height / 2 / zoom];
2090
- updateViewportOrigination(board, origination);
2091
- updateViewportOffset(board);
2092
- return;
2093
- }
2094
- updateViewportOffset(board);
2133
+
2134
+ function setViewport(board, viewport) {
2135
+ const operation = { type: 'set_viewport', properties: board.viewport, newProperties: viewport };
2136
+ board.apply(operation);
2095
2137
  }
2096
- const updateViewportOrigination = (board, origination) => {
2097
- BOARD_TO_VIEWPORT_ORIGINATION.set(board, origination);
2098
- };
2099
- const clearViewportOrigination = (board) => {
2100
- BOARD_TO_VIEWPORT_ORIGINATION.delete(board);
2101
- };
2102
- const getViewportOrigination = (board) => {
2103
- const origination = BOARD_TO_VIEWPORT_ORIGINATION.get(board);
2104
- if (origination) {
2105
- return origination;
2106
- }
2107
- else {
2108
- return board.viewport.origination;
2109
- }
2110
- };
2111
- const isFromScrolling = (board) => {
2112
- return !!IS_FROM_SCROLLING.get(board);
2113
- };
2114
- const setIsFromScrolling = (board, state) => {
2115
- IS_FROM_SCROLLING.set(board, state);
2116
- };
2117
- const isFromViewportChange = (board) => {
2118
- return !!IS_FROM_VIEWPORT_CHANGE.get(board);
2119
- };
2120
- const setIsFromViewportChange = (board, state) => {
2121
- IS_FROM_VIEWPORT_CHANGE.set(board, state);
2138
+ const ViewportTransforms$1 = {
2139
+ setViewport
2122
2140
  };
2123
- function scrollToRectangle(board, client) { }
2124
2141
 
2125
2142
  function setTheme(board, themeColorMode) {
2126
2143
  const operation = { type: 'set_theme', properties: board.theme, newProperties: themeColorMode };
@@ -2240,6 +2257,25 @@ const BoardTransforms = {
2240
2257
  fitViewportWidth
2241
2258
  };
2242
2259
 
2260
+ const removeElements = (board, elements) => {
2261
+ elements
2262
+ .map(element => {
2263
+ const path = PlaitBoard.findPath(board, element);
2264
+ const ref = board.pathRef(path);
2265
+ return () => {
2266
+ removeNode(board, ref.current);
2267
+ ref.unref();
2268
+ removeSelectedElement(board, element);
2269
+ };
2270
+ })
2271
+ .forEach(action => {
2272
+ action();
2273
+ });
2274
+ };
2275
+ const CoreTransforms = {
2276
+ removeElements
2277
+ };
2278
+
2243
2279
  const Transforms = {
2244
2280
  ...GeneralTransforms,
2245
2281
  ...ViewportTransforms$1,
@@ -2335,7 +2371,11 @@ function createBoard(children, options) {
2335
2371
  dblclick: (event) => { },
2336
2372
  setFragment: (data) => { },
2337
2373
  insertFragment: (data) => { },
2338
- deleteFragment: (data) => { },
2374
+ deleteFragment: (data) => {
2375
+ const elements = board.getDeletedFragment([]);
2376
+ CoreTransforms.removeElements(board, elements);
2377
+ },
2378
+ getDeletedFragment: (data) => data,
2339
2379
  drawElement: (context) => [],
2340
2380
  redrawElement: (context, previousContext) => { },
2341
2381
  destroyElement: (context) => { },
@@ -2894,6 +2934,11 @@ const withHotkey = (board) => {
2894
2934
  Transforms.setSelectionWithTemporaryElements(board, elements);
2895
2935
  return;
2896
2936
  }
2937
+ const selectedElements = getSelectedElements(board);
2938
+ if (!PlaitBoard.isReadonly(board) && selectedElements.length > 0 && (hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event))) {
2939
+ event.preventDefault();
2940
+ board.deleteFragment(null);
2941
+ }
2897
2942
  keydown(event);
2898
2943
  };
2899
2944
  board.globalKeydown = (event) => {
@@ -3301,10 +3346,6 @@ class PlaitBoardComponent {
3301
3346
  this.board.globalKeydown(event);
3302
3347
  }), filter(event => this.isFocused && !PlaitBoard.hasBeenTextEditing(this.board) && !hasInputOrTextareaTarget(event.target)))
3303
3348
  .subscribe((event) => {
3304
- const selectedElements = getSelectedElements(this.board);
3305
- if (selectedElements.length > 0 && (hotkeys.isDeleteBackward(event) || hotkeys.isDeleteForward(event))) {
3306
- this.board.deleteFragment(null);
3307
- }
3308
3349
  this.board.keydown(event);
3309
3350
  });
3310
3351
  fromEvent(document, 'keyup')
@@ -3658,5 +3699,5 @@ function createModModifierKeys() {
3658
3699
  * Generated bundle index. Do not edit.
3659
3700
  */
3660
3701
 
3661
- 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, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createSelectionOuterG, createTestingBoard, createText, createTouchEvent, debounce, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawLine, drawLinearPath, drawRectangle, drawRoundRectangle, fakeNodeWeakMap, getBoardRectangle, getClipboardByKey, getClipboardDataByMedia, getDataFromClipboard, getElementById, getElementHostBBox, getHitElementOfRoot, getHitElements, getIsRecursionFunc, getMovingElements, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, 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, 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 };
3702
+ 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, 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_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, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createSelectionOuterG, 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, getTextFromClipboard, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, 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 };
3662
3703
  //# sourceMappingURL=plait-core.mjs.map