@plait/core 0.56.2 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { IterableDiffers, inject, ViewContainerRef, ChangeDetectorRef, Directive, Input, Injectable, EventEmitter, ElementRef, Component, ChangeDetectionStrategy, Output, HostBinding, ViewChild, ContentChildren } from '@angular/core';
2
+ import { Directive, Input, Injectable, IterableDiffers, EventEmitter, inject, ElementRef, Component, ChangeDetectionStrategy, Output, HostBinding, ViewChild, ContentChildren } from '@angular/core';
3
3
  import rough from 'roughjs/bin/rough';
4
4
  import { timer, Subject, fromEvent } from 'rxjs';
5
5
  import { takeUntil, filter, tap } from 'rxjs/operators';
@@ -42,51 +42,6 @@ var PlaitPointerType;
42
42
  * Extendable Custom Types Interface
43
43
  */
44
44
 
45
- function depthFirstRecursion(node, callback, recursion, isReverse) {
46
- if (!recursion || recursion(node)) {
47
- let children = [];
48
- if (node.children) {
49
- children = [...node.children];
50
- }
51
- children = isReverse ? children.reverse() : children;
52
- children.forEach(child => {
53
- depthFirstRecursion(child, callback, recursion);
54
- });
55
- }
56
- callback(node);
57
- }
58
- const getIsRecursionFunc = (board) => {
59
- return (element) => {
60
- if (PlaitBoard.isBoard(element) || board.isRecursion(element)) {
61
- return true;
62
- }
63
- else {
64
- return false;
65
- }
66
- };
67
- };
68
-
69
- const SELECTION_BORDER_COLOR = '#6698FF';
70
- const SELECTION_FILL_COLOR = '#6698FF25'; // opacity 0.25
71
- const Selection = {
72
- isCollapsed(selection) {
73
- if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
74
- return true;
75
- }
76
- else {
77
- return false;
78
- }
79
- }
80
- };
81
-
82
- const sortElements = (board, elements, ascendingOrder = true) => {
83
- return [...elements].sort((a, b) => {
84
- const pathA = PlaitBoard.findPath(board, a);
85
- const pathB = PlaitBoard.findPath(board, b);
86
- return ascendingOrder ? pathA[0] - pathB[0] : pathB[0] - pathA[0];
87
- });
88
- };
89
-
90
45
  const RectangleClient = {
91
46
  isHit: (origin, target) => {
92
47
  return RectangleClient.isHitX(origin, target) && RectangleClient.isHitY(origin, target);
@@ -244,122 +199,371 @@ const RectangleClient = {
244
199
  }
245
200
  };
246
201
 
247
- var PlaitPluginKey;
248
- (function (PlaitPluginKey) {
249
- PlaitPluginKey["withSelection"] = "withSelection";
250
- })(PlaitPluginKey || (PlaitPluginKey = {}));
251
-
252
- const getHitElementsBySelection = (board, selection, match = () => true) => {
253
- const newSelection = selection || board.selection;
254
- const rectangleHitElements = [];
255
- if (!newSelection) {
256
- return [];
202
+ // https://stackoverflow.com/a/6853926/232122
203
+ function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
204
+ const A = x - x1;
205
+ const B = y - y1;
206
+ const C = x2 - x1;
207
+ const D = y2 - y1;
208
+ const dot = A * C + B * D;
209
+ const lenSquare = C * C + D * D;
210
+ let param = -1;
211
+ if (lenSquare !== 0) {
212
+ // in case of 0 length line
213
+ param = dot / lenSquare;
257
214
  }
258
- const isCollapsed = Selection.isCollapsed(newSelection);
259
- if (isCollapsed) {
260
- const hitElement = getHitElementByPoint(board, newSelection.anchor, match);
261
- if (hitElement) {
262
- return [hitElement];
263
- }
264
- else {
265
- return [];
266
- }
215
+ let xx, yy;
216
+ if (param < 0) {
217
+ xx = x1;
218
+ yy = y1;
267
219
  }
268
- depthFirstRecursion(board, node => {
269
- if (!PlaitBoard.isBoard(node) && match(node) && board.isRectangleHit(node, newSelection)) {
270
- rectangleHitElements.push(node);
271
- }
272
- }, getIsRecursionFunc(board), true);
273
- return rectangleHitElements;
274
- };
275
- const getHitElementByPoint = (board, point, match = () => true) => {
276
- let hitElement = undefined;
277
- let hitInsideElement = undefined;
278
- depthFirstRecursion(board, node => {
279
- if (hitElement) {
280
- return;
281
- }
282
- if (PlaitBoard.isBoard(node) || !match(node) || !PlaitElement.hasMounted(node)) {
283
- return;
284
- }
285
- if (board.isHit(node, point)) {
286
- hitElement = node;
287
- return;
288
- }
289
- /**
290
- * 需要增加场景测试
291
- * hitInsideElement 存的是第一个符合 isInsidePoint 的元素
292
- * 当有元素符合 isHit 时结束遍历,并返回 hitElement
293
- * 当所有元素都不符合 isHit ,则返回第一个符合 isInsidePoint 的元素
294
- * 这样保证最上面的元素优先被探测到;
295
- */
296
- if (!hitInsideElement && board.isInsidePoint(node, point)) {
297
- hitInsideElement = node;
298
- }
299
- }, getIsRecursionFunc(board), true);
300
- return hitElement || hitInsideElement;
301
- };
302
- const getHitSelectedElements = (board, point) => {
303
- const selectedElements = getSelectedElements(board);
304
- const targetRectangle = selectedElements.length > 0 && getRectangleByElements(board, selectedElements, false);
305
- const isInTargetRectangle = targetRectangle && RectangleClient.isPointInRectangle(targetRectangle, point);
306
- if (isInTargetRectangle) {
307
- return selectedElements;
220
+ else if (param > 1) {
221
+ xx = x2;
222
+ yy = y2;
308
223
  }
309
224
  else {
310
- return [];
225
+ xx = x1 + param * C;
226
+ yy = y1 + param * D;
311
227
  }
312
- };
313
- const cacheSelectedElements = (board, selectedElements) => {
314
- const sortedElements = sortElements(board, selectedElements);
315
- BOARD_TO_SELECTED_ELEMENT.set(board, sortedElements);
316
- };
317
- const getSelectedElements = (board) => {
318
- return BOARD_TO_SELECTED_ELEMENT.get(board) || [];
319
- };
320
- const addSelectedElement = (board, element) => {
321
- let elements = [];
322
- if (Array.isArray(element)) {
323
- elements.push(...element);
228
+ const dx = x - xx;
229
+ const dy = y - yy;
230
+ return Math.hypot(dx, dy);
231
+ }
232
+ function getNearestPointBetweenPointAndSegment(point, linePoints) {
233
+ const x = point[0], y = point[1], x1 = linePoints[0][0], y1 = linePoints[0][1], x2 = linePoints[1][0], y2 = linePoints[1][1];
234
+ const A = x - x1;
235
+ const B = y - y1;
236
+ const C = x2 - x1;
237
+ const D = y2 - y1;
238
+ const dot = A * C + B * D;
239
+ const lenSquare = C * C + D * D;
240
+ let param = -1;
241
+ if (lenSquare !== 0) {
242
+ // in case of 0 length line
243
+ param = dot / lenSquare;
244
+ }
245
+ let xx, yy;
246
+ if (param < 0) {
247
+ xx = x1;
248
+ yy = y1;
249
+ }
250
+ else if (param > 1) {
251
+ xx = x2;
252
+ yy = y2;
324
253
  }
325
254
  else {
326
- elements.push(element);
255
+ xx = x1 + param * C;
256
+ yy = y1 + param * D;
327
257
  }
328
- const selectedElements = getSelectedElements(board);
329
- cacheSelectedElements(board, [...selectedElements, ...elements]);
330
- };
331
- const removeSelectedElement = (board, element, isRemoveChildren = false) => {
332
- const selectedElements = getSelectedElements(board);
333
- if (selectedElements.includes(element)) {
334
- const targetElements = [];
335
- if (board.isRecursion(element) && isRemoveChildren) {
336
- depthFirstRecursion(element, node => {
337
- targetElements.push(node);
338
- }, node => board.isRecursion(node));
258
+ return [xx, yy];
259
+ }
260
+ function distanceBetweenPointAndSegments(points, point) {
261
+ const len = points.length;
262
+ let distance = Infinity;
263
+ for (let i = 0; i < len - 1; i++) {
264
+ const p = points[i];
265
+ const p2 = points[i + 1];
266
+ const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
267
+ if (currentDistance < distance) {
268
+ distance = currentDistance;
339
269
  }
340
- else {
341
- targetElements.push(element);
270
+ }
271
+ return distance;
272
+ }
273
+ function getNearestPointBetweenPointAndSegments(point, points, isClose = true) {
274
+ const len = points.length;
275
+ let distance = Infinity;
276
+ let result = point;
277
+ for (let i = 0; i < len; i++) {
278
+ const p = points[i];
279
+ if (i === len - 1 && !isClose)
280
+ continue;
281
+ const p2 = i === len - 1 ? points[0] : points[i + 1];
282
+ const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
283
+ if (currentDistance < distance) {
284
+ distance = currentDistance;
285
+ result = getNearestPointBetweenPointAndSegment(point, [p, p2]);
342
286
  }
343
- const newSelectedElements = selectedElements.filter(value => !targetElements.includes(value));
344
- cacheSelectedElements(board, newSelectedElements);
345
287
  }
346
- };
347
- const clearSelectedElement = (board) => {
348
- cacheSelectedElements(board, []);
349
- };
350
- const isSelectedElement = (board, element) => {
351
- const selectedElements = getSelectedElements(board);
352
- return !!selectedElements.find(value => value === element);
353
- };
354
- const temporaryDisableSelection = (board) => {
355
- const currentOptions = board.getPluginOptions(PlaitPluginKey.withSelection);
356
- board.setPluginOptions(PlaitPluginKey.withSelection, {
357
- isDisabledSelect: true
288
+ return result;
289
+ }
290
+ function getNearestPointBetweenPointAndEllipse(point, center, rx, ry, rotation = 0) {
291
+ const rectangleClient = {
292
+ x: center[0] - rx,
293
+ y: center[1] - ry,
294
+ height: ry * 2,
295
+ width: rx * 2
296
+ };
297
+ // https://stackoverflow.com/a/46007540/232122
298
+ const px = Math.abs(point[0] - rectangleClient.x - rectangleClient.width / 2);
299
+ const py = Math.abs(point[1] - rectangleClient.y - rectangleClient.height / 2);
300
+ let tx = 0.707;
301
+ let ty = 0.707;
302
+ const a = Math.abs(rectangleClient.width) / 2;
303
+ const b = Math.abs(rectangleClient.height) / 2;
304
+ [0, 1, 2, 3].forEach(x => {
305
+ const xx = a * tx;
306
+ const yy = b * ty;
307
+ const ex = ((a * a - b * b) * tx ** 3) / a;
308
+ const ey = ((b * b - a * a) * ty ** 3) / b;
309
+ const rx = xx - ex;
310
+ const ry = yy - ey;
311
+ const qx = px - ex;
312
+ const qy = py - ey;
313
+ const r = Math.hypot(ry, rx);
314
+ const q = Math.hypot(qy, qx);
315
+ tx = Math.min(1, Math.max(0, ((qx * r) / q + ex) / a));
316
+ ty = Math.min(1, Math.max(0, ((qy * r) / q + ey) / b));
317
+ const t = Math.hypot(ty, tx);
318
+ tx /= t;
319
+ ty /= t;
358
320
  });
359
- setTimeout(() => {
360
- board.setPluginOptions(PlaitPluginKey.withSelection, { ...currentOptions });
361
- }, 0);
362
- };
321
+ const signX = point[0] > center[0] ? 1 : -1;
322
+ const signY = point[1] > center[1] ? 1 : -1;
323
+ return [center[0] + a * tx * signX, center[1] + b * ty * signY];
324
+ }
325
+ function rotate(x1, y1, x2, y2, angle) {
326
+ // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
327
+ // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
328
+ // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
329
+ return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
330
+ }
331
+ function distanceBetweenPointAndPoint(x1, y1, x2, y2) {
332
+ const dx = x1 - x2;
333
+ const dy = y1 - y2;
334
+ return Math.hypot(dx, dy);
335
+ }
336
+ // https://stackoverflow.com/questions/5254838/calculating-distance-between-a-point-and-a-rectangular-box-nearest-point
337
+ function distanceBetweenPointAndRectangle(x, y, rect) {
338
+ var dx = Math.max(rect.x - x, 0, x - (rect.x + rect.width));
339
+ var dy = Math.max(rect.y - y, 0, y - (rect.y + rect.height));
340
+ return Math.sqrt(dx * dx + dy * dy);
341
+ }
342
+ const isLineHitLine = (a, b, c, d) => {
343
+ const crossProduct = (v1, v2) => v1[0] * v2[1] - v1[1] * v2[0];
344
+ const ab = [b[0] - a[0], b[1] - a[1]];
345
+ const ac = [c[0] - a[0], c[1] - a[1]];
346
+ const ad = [d[0] - a[0], d[1] - a[1]];
347
+ const ca = [a[0] - c[0], a[1] - c[1]];
348
+ const cb = [b[0] - c[0], b[1] - c[1]];
349
+ const cd = [d[0] - c[0], d[1] - c[1]];
350
+ return crossProduct(ab, ac) * crossProduct(ab, ad) <= 0 && crossProduct(cd, ca) * crossProduct(cd, cb) <= 0;
351
+ };
352
+ const isPolylineHitRectangle = (points, rectangle, isClose = true) => {
353
+ const rectanglePoints = RectangleClient.getCornerPoints(rectangle);
354
+ const len = points.length;
355
+ for (let i = 0; i < len; i++) {
356
+ if (i === len - 1 && !isClose)
357
+ continue;
358
+ const p1 = points[i];
359
+ const p2 = points[(i + 1) % len];
360
+ const isHit = isLineHitLine(p1, p2, rectanglePoints[0], rectanglePoints[1]) ||
361
+ isLineHitLine(p1, p2, rectanglePoints[1], rectanglePoints[2]) ||
362
+ isLineHitLine(p1, p2, rectanglePoints[2], rectanglePoints[3]) ||
363
+ isLineHitLine(p1, p2, rectanglePoints[3], rectanglePoints[0]);
364
+ if (isHit || isPointInPolygon(p1, rectanglePoints) || isPointInPolygon(p2, rectanglePoints)) {
365
+ return true;
366
+ }
367
+ }
368
+ return false;
369
+ };
370
+ //https://stackoverflow.com/questions/22521982/check-if-point-is-inside-a-polygon
371
+ const isPointInPolygon = (point, points) => {
372
+ // ray-casting algorithm based on
373
+ // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
374
+ const x = point[0], y = point[1];
375
+ let inside = false;
376
+ for (var i = 0, j = points.length - 1; i < points.length; j = i++) {
377
+ let xi = points[i][0], yi = points[i][1];
378
+ let xj = points[j][0], yj = points[j][1];
379
+ let intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
380
+ if (intersect)
381
+ inside = !inside;
382
+ }
383
+ return inside;
384
+ };
385
+ const isPointInEllipse = (point, center, rx, ry, angle = 0) => {
386
+ const cosAngle = Math.cos(angle);
387
+ const sinAngle = Math.sin(angle);
388
+ const x1 = (point[0] - center[0]) * cosAngle + (point[1] - center[1]) * sinAngle;
389
+ const y1 = (point[1] - center[1]) * cosAngle - (point[0] - center[0]) * sinAngle;
390
+ return (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry) <= 1;
391
+ };
392
+ const isPointInRoundRectangle = (point, rectangle, radius, angle = 0) => {
393
+ const { x: rectX, y: rectY, width, height } = rectangle;
394
+ const isInRectangle = point[0] >= rectX && point[0] <= rectX + width && point[1] >= rectY && point[1] <= rectY + height;
395
+ const handleLeftTop = point[0] >= rectX &&
396
+ point[0] <= rectX + radius &&
397
+ point[1] >= rectY &&
398
+ point[1] <= rectY + radius &&
399
+ Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + radius)) > radius;
400
+ const handleLeftBottom = point[0] >= rectX &&
401
+ point[0] <= rectX + radius &&
402
+ point[1] >= rectY + height &&
403
+ point[1] <= rectY + height - radius &&
404
+ Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + height - radius)) > radius;
405
+ const handleRightTop = point[0] >= rectX + width - radius &&
406
+ point[0] <= rectX + width &&
407
+ point[1] >= rectY &&
408
+ point[1] <= rectY + radius &&
409
+ Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + radius)) > radius;
410
+ const handleRightBottom = point[0] >= rectX + width - radius &&
411
+ point[0] <= rectX + width &&
412
+ point[1] >= rectY + height - radius &&
413
+ point[1] <= rectY + height &&
414
+ Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + height - radius)) > radius;
415
+ const isInCorner = handleLeftTop || handleLeftBottom || handleRightTop || handleRightBottom;
416
+ return isInRectangle && !isInCorner;
417
+ };
418
+ // https://gist.github.com/nicholaswmin/c2661eb11cad5671d816
419
+ const catmullRomFitting = function (points) {
420
+ const alpha = 0.5;
421
+ let p0, p1, p2, p3, bp1, bp2, d1, d2, d3, A, B, N, M;
422
+ var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
423
+ const result = [];
424
+ result.push([Math.round(points[0][0]), Math.round(points[0][1])]);
425
+ var length = points.length;
426
+ for (var i = 0; i < length - 1; i++) {
427
+ p0 = i == 0 ? points[0] : points[i - 1];
428
+ p1 = points[i];
429
+ p2 = points[i + 1];
430
+ p3 = i + 2 < length ? points[i + 2] : p2;
431
+ d1 = Math.sqrt(Math.pow(p0[0] - p1[0], 2) + Math.pow(p0[1] - p1[1], 2));
432
+ d2 = Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
433
+ d3 = Math.sqrt(Math.pow(p2[0] - p3[0], 2) + Math.pow(p2[1] - p3[1], 2));
434
+ // Catmull-Rom to Cubic Bezier conversion matrix
435
+ // A = 2d1^2a + 3d1^a * d2^a + d3^2a
436
+ // B = 2d3^2a + 3d3^a * d2^a + d2^2a
437
+ // [ 0 1 0 0 ]
438
+ // [ -d2^2a /N A/N d1^2a /N 0 ]
439
+ // [ 0 d3^2a /M B/M -d2^2a /M ]
440
+ // [ 0 0 1 0 ]
441
+ d3powA = Math.pow(d3, alpha);
442
+ d3pow2A = Math.pow(d3, 2 * alpha);
443
+ d2powA = Math.pow(d2, alpha);
444
+ d2pow2A = Math.pow(d2, 2 * alpha);
445
+ d1powA = Math.pow(d1, alpha);
446
+ d1pow2A = Math.pow(d1, 2 * alpha);
447
+ A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A;
448
+ B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A;
449
+ N = 3 * d1powA * (d1powA + d2powA);
450
+ if (N > 0) {
451
+ N = 1 / N;
452
+ }
453
+ M = 3 * d3powA * (d3powA + d2powA);
454
+ if (M > 0) {
455
+ M = 1 / M;
456
+ }
457
+ bp1 = [(-d2pow2A * p0[0] + A * p1[0] + d1pow2A * p2[0]) * N, (-d2pow2A * p0[1] + A * p1[1] + d1pow2A * p2[1]) * N];
458
+ bp2 = [(d3pow2A * p1[0] + B * p2[0] - d2pow2A * p3[0]) * M, (d3pow2A * p1[1] + B * p2[1] - d2pow2A * p3[1]) * M];
459
+ if (bp1[0] == 0 && bp1[1] == 0) {
460
+ bp1 = p1;
461
+ }
462
+ if (bp2[0] == 0 && bp2[1] == 0) {
463
+ bp2 = p2;
464
+ }
465
+ result.push(bp1, bp2, p2);
466
+ }
467
+ return result;
468
+ };
469
+ /**
470
+ * the result of slope is based on Cartesian coordinate system
471
+ * x, y are based on the position in the Cartesian coordinate system
472
+ */
473
+ function getEllipseTangentSlope(x, y, a, b) {
474
+ if (Math.abs(y) === 0) {
475
+ return x > 0 ? -Infinity : Infinity;
476
+ }
477
+ const k = (-b * b * x) / (a * a * y);
478
+ return k;
479
+ }
480
+ /**
481
+ * x, y are based on the position in the Cartesian coordinate system
482
+ */
483
+ function getVectorFromPointAndSlope(x, y, slope) {
484
+ if (slope === Infinity) {
485
+ return [0, -1];
486
+ }
487
+ else if (slope === -Infinity) {
488
+ return [0, 1];
489
+ }
490
+ let vector = [1, -slope];
491
+ if (y < 0) {
492
+ vector = [-vector[0], -vector[1]];
493
+ }
494
+ return vector;
495
+ }
496
+ /**
497
+ * The DOM likes values to be fixed to 3 decimal places
498
+ */
499
+ function toDomPrecision(v) {
500
+ return +v.toFixed(4);
501
+ }
502
+ function toFixed(v) {
503
+ return +v.toFixed(2);
504
+ }
505
+ /**
506
+ * Whether two numbers numbers a and b are approximately equal.
507
+ *
508
+ * @param a - The first point.
509
+ * @param b - The second point.
510
+ * @public
511
+ */
512
+ function approximately(a, b, precision = 0.000001) {
513
+ return Math.abs(a - b) <= precision;
514
+ }
515
+ // https://medium.com/@steveruiz/find-the-points-where-a-line-segment-intercepts-an-angled-ellipse-in-javascript-typescript-e451524beece
516
+ function getCrossingPointsBetweenEllipseAndSegment(startPoint, endPoint, cx, cy, rx, ry, segment_only = true) {
517
+ // If the ellipse or line segment are empty, return no tValues.
518
+ if (rx === 0 || ry === 0 || (startPoint[0] === endPoint[0] && startPoint[1] === endPoint[1])) {
519
+ return [];
520
+ }
521
+ rx = rx < 0 ? rx : -rx;
522
+ ry = ry < 0 ? ry : -ry;
523
+ startPoint[0] -= cx;
524
+ startPoint[1] -= cy;
525
+ endPoint[0] -= cx;
526
+ endPoint[1] -= cy;
527
+ // Calculate the quadratic parameters.
528
+ var A = ((endPoint[0] - startPoint[0]) * (endPoint[0] - startPoint[0])) / rx / rx +
529
+ ((endPoint[1] - startPoint[1]) * (endPoint[1] - startPoint[1])) / ry / ry;
530
+ var B = (2 * startPoint[0] * (endPoint[0] - startPoint[0])) / rx / rx + (2 * startPoint[1] * (endPoint[1] - startPoint[1])) / ry / ry;
531
+ var C = (startPoint[0] * startPoint[0]) / rx / rx + (startPoint[1] * startPoint[1]) / ry / ry - 1;
532
+ // Make a list of t values (normalized points on the line where intersections occur).
533
+ var tValues = [];
534
+ // Calculate the discriminant.
535
+ var discriminant = B * B - 4 * A * C;
536
+ if (discriminant === 0) {
537
+ // One real solution.
538
+ tValues.push(-B / 2 / A);
539
+ }
540
+ else if (discriminant > 0) {
541
+ // Two real solutions.
542
+ tValues.push((-B + Math.sqrt(discriminant)) / 2 / A);
543
+ tValues.push((-B - Math.sqrt(discriminant)) / 2 / A);
544
+ }
545
+ return (tValues
546
+ // Filter to only points that are on the segment.
547
+ .filter(t => !segment_only || (t >= 0 && t <= 1))
548
+ // Solve for points.
549
+ .map(t => [startPoint[0] + (endPoint[0] - startPoint[0]) * t + cx, startPoint[1] + (endPoint[1] - startPoint[1]) * t + cy]));
550
+ }
551
+
552
+ function isInPlaitBoard(board, x, y) {
553
+ const plaitBoardElement = PlaitBoard.getBoardContainer(board);
554
+ const plaitBoardRect = plaitBoardElement.getBoundingClientRect();
555
+ const distances = distanceBetweenPointAndRectangle(x, y, plaitBoardRect);
556
+ return distances === 0;
557
+ }
558
+ function getRealScrollBarWidth(board) {
559
+ const { hideScrollbar } = board.options;
560
+ let scrollBarWidth = 0;
561
+ if (!hideScrollbar) {
562
+ const viewportContainer = PlaitBoard.getViewportContainer(board);
563
+ scrollBarWidth = viewportContainer.offsetWidth - viewportContainer.clientWidth;
564
+ }
565
+ return scrollBarWidth;
566
+ }
363
567
 
364
568
  /**
365
569
  * @license
@@ -540,785 +744,57 @@ function createRect(rectangle, options) {
540
744
  const optionKey = key;
541
745
  rect.setAttribute(key, `${options[optionKey]}`);
542
746
  }
543
- return rect;
544
- }
545
- const setStrokeLinecap = (g, value) => {
546
- g.setAttribute('stroke-linecap', value);
547
- };
548
- const setPathStrokeLinecap = (g, value) => {
549
- g.querySelectorAll('path').forEach(path => {
550
- path.setAttribute('stroke-linecap', value);
551
- });
552
- };
553
- function createMask() {
554
- return document.createElementNS(NS, 'mask');
555
- }
556
- function createSVG() {
557
- const svg = document.createElementNS(NS, 'svg');
558
- return svg;
559
- }
560
- function createText(x, y, fill, textContent) {
561
- var text = document.createElementNS(NS, 'text');
562
- text.setAttribute('x', `${x}`);
563
- text.setAttribute('y', `${y}`);
564
- text.setAttribute('fill', fill);
565
- text.textContent = textContent;
566
- return text;
567
- }
568
- /**
569
- * Check if a DOM node is an element node.
570
- */
571
- const isDOMElement = (value) => {
572
- return isDOMNode(value) && value.nodeType === 1;
573
- };
574
- /**
575
- * Check if a value is a DOM node.
576
- */
577
- const isDOMNode = (value) => {
578
- return value instanceof window.Node;
579
- };
580
- const hasInputOrTextareaTarget = (target) => {
581
- if (isDOMElement(target)) {
582
- if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
583
- return true;
584
- }
585
- }
586
- return false;
587
- };
588
- const isSecondaryPointer = (event) => {
589
- return event.button === POINTER_BUTTON.SECONDARY;
590
- };
591
- const isMainPointer = (event) => {
592
- return event.button === POINTER_BUTTON.MAIN;
593
- };
594
-
595
- function hasBeforeContextChange(value) {
596
- if (value.beforeContextChange) {
597
- return true;
598
- }
599
- return false;
600
- }
601
- function hasOnContextChanged(value) {
602
- if (value.onContextChanged) {
603
- return true;
604
- }
605
- return false;
606
- }
607
-
608
- class ListRender {
609
- constructor(board, viewContainerRef) {
610
- this.board = board;
611
- this.viewContainerRef = viewContainerRef;
612
- this.children = [];
613
- this.componentRefs = [];
614
- this.contexts = [];
615
- this.differ = null;
616
- this.initialized = false;
617
- }
618
- initialize(children, childrenContext) {
619
- this.initialized = true;
620
- this.children = children;
621
- children.forEach((descendant, index) => {
622
- NODE_TO_INDEX.set(descendant, index);
623
- NODE_TO_PARENT.set(descendant, childrenContext.parent);
624
- const context = getContext(this.board, descendant, index, childrenContext.parent);
625
- const componentType = getComponentType(this.board, context);
626
- const componentRef = createPluginComponent(componentType, context, this.viewContainerRef, childrenContext);
627
- this.componentRefs.push(componentRef);
628
- this.contexts.push(context);
629
- });
630
- const newDiffers = this.viewContainerRef.injector.get(IterableDiffers);
631
- this.differ = newDiffers.find(children).create(trackBy);
632
- this.differ.diff(children);
633
- }
634
- update(children, childrenContext) {
635
- if (!this.initialized) {
636
- this.initialize(children, childrenContext);
637
- return;
638
- }
639
- if (!this.differ) {
640
- throw new Error('Exception: Can not find differ ');
641
- }
642
- const { board, parent } = childrenContext;
643
- const diffResult = this.differ.diff(children);
644
- if (diffResult) {
645
- const newContexts = [];
646
- const newComponentRefs = [];
647
- let currentIndexForFirstElement = null;
648
- diffResult.forEachItem((record) => {
649
- NODE_TO_INDEX.set(record.item, record.currentIndex);
650
- NODE_TO_PARENT.set(record.item, childrenContext.parent);
651
- const previousContext = record.previousIndex === null ? undefined : this.contexts[record.previousIndex];
652
- const context = getContext(board, record.item, record.currentIndex, parent, previousContext);
653
- if (record.previousIndex === null) {
654
- const componentType = getComponentType(board, context);
655
- const componentRef = createPluginComponent(componentType, context, this.viewContainerRef, childrenContext);
656
- newContexts.push(context);
657
- newComponentRefs.push(componentRef);
658
- }
659
- else {
660
- const componentRef = this.componentRefs[record.previousIndex];
661
- componentRef.instance.context = context;
662
- newComponentRefs.push(componentRef);
663
- newContexts.push(context);
664
- }
665
- // item might has been changed, so need to compare the id
666
- if (record.item === this.children[0] || record.item.id === this.children[0]?.id) {
667
- currentIndexForFirstElement = record.currentIndex;
668
- }
669
- });
670
- diffResult.forEachOperation(record => {
671
- // removed
672
- if (record.currentIndex === null) {
673
- const componentRef = this.componentRefs[record.previousIndex];
674
- componentRef?.destroy();
675
- }
676
- // moved
677
- if (record.previousIndex !== null && record.currentIndex !== null) {
678
- mountOnItemMove(record.item, record.currentIndex, childrenContext, currentIndexForFirstElement);
679
- }
680
- });
681
- this.componentRefs = newComponentRefs;
682
- this.contexts = newContexts;
683
- this.children = children;
684
- }
685
- else {
686
- const newContexts = [];
687
- this.children.forEach((element, index) => {
688
- NODE_TO_INDEX.set(element, index);
689
- NODE_TO_PARENT.set(element, childrenContext.parent);
690
- const previousContext = this.contexts[index];
691
- const previousComponentRef = this.componentRefs[index];
692
- const context = getContext(board, element, index, parent, previousContext);
693
- previousComponentRef.instance.context = context;
694
- newContexts.push(context);
695
- });
696
- this.contexts = newContexts;
697
- }
698
- }
699
- destroy() {
700
- this.children.forEach((element, index) => {
701
- if (this.componentRefs[index]) {
702
- this.componentRefs[index].destroy();
703
- }
704
- });
705
- this.componentRefs = [];
706
- this.children = [];
707
- this.contexts = [];
708
- this.initialized = false;
709
- this.differ = null;
710
- }
711
- }
712
- const trackBy = (index, element) => {
713
- return element.id;
714
- };
715
- const createPluginComponent = (componentType, context, viewContainerRef, childrenContext) => {
716
- const componentRef = viewContainerRef.createComponent(componentType, { injector: viewContainerRef.injector });
717
- const instance = componentRef.instance;
718
- instance.context = context;
719
- componentRef.changeDetectorRef.detectChanges();
720
- const g = componentRef.instance.getContainerG();
721
- mountElementG(context.index, g, childrenContext);
722
- componentRef.instance.initializeListRender();
723
- return componentRef;
724
- };
725
- const getComponentType = (board, context) => {
726
- const result = board.drawElement(context);
727
- return result;
728
- };
729
- const getContext = (board, element, index, parent, previousContext) => {
730
- let isSelected = isSelectedElement(board, element);
731
- const previousElement = previousContext && previousContext.element;
732
- if (previousElement && previousElement !== element && isSelectedElement(board, previousElement)) {
733
- isSelected = true;
734
- removeSelectedElement(board, previousElement);
735
- addSelectedElement(board, element);
736
- }
737
- const context = {
738
- element: element,
739
- parent: parent,
740
- board: board,
741
- selected: isSelected,
742
- index
743
- };
744
- return context;
745
- };
746
- // the g depth of root element:[1]-[2]-[3]-[4]
747
- // the g depth of root element and children element(the [2] element has children):
748
- // [1]-
749
- // [2]([2-1-1][2-1-2][2-1][2-2][2-3-1][2-3-2][2-3][2])-
750
- // [3]-
751
- // [4]
752
- const mountElementG = (index, g, childrenContext,
753
- // for moving scene: the current index for first element before moving
754
- currentIndexForFirstElement = null) => {
755
- const { parent, parentG } = childrenContext;
756
- if (PlaitBoard.isBoard(parent)) {
757
- if (index > 0) {
758
- const previousElement = parent.children[index - 1];
759
- const previousContainerG = PlaitElement.getContainerG(previousElement, { suppressThrow: false });
760
- previousContainerG.insertAdjacentElement('afterend', g);
761
- }
762
- else {
763
- if (currentIndexForFirstElement !== null) {
764
- const firstElement = parent.children[currentIndexForFirstElement];
765
- const firstContainerG = firstElement && PlaitElement.getContainerG(firstElement, { suppressThrow: true });
766
- if (firstElement && firstContainerG) {
767
- parentG.insertBefore(g, firstContainerG);
768
- }
769
- else {
770
- throw new Error('fail to mount container on moving');
771
- }
772
- }
773
- else {
774
- parentG.append(g);
775
- }
776
- }
777
- }
778
- else {
779
- if (index > 0) {
780
- const previousElement = parent.children[index - 1];
781
- const previousElementG = PlaitElement.getElementG(previousElement);
782
- previousElementG.insertAdjacentElement('afterend', g);
783
- }
784
- else {
785
- if (currentIndexForFirstElement) {
786
- const nextElement = parent.children[currentIndexForFirstElement];
787
- const nextPath = nextElement && PlaitBoard.findPath(childrenContext.board, nextElement);
788
- const first = nextPath && PlaitNode.first(childrenContext.board, nextPath);
789
- const firstContainerG = first && PlaitElement.getContainerG(first, { suppressThrow: false });
790
- if (firstContainerG) {
791
- parentG.insertBefore(g, firstContainerG);
792
- }
793
- else {
794
- throw new Error('fail to mount container on moving');
795
- }
796
- }
797
- else {
798
- let parentElementG = PlaitElement.getElementG(parent);
799
- parentG.insertBefore(g, parentElementG);
800
- }
801
- }
802
- }
803
- };
804
- const mountOnItemMove = (element, index, childrenContext, currentIndexForFirstElement) => {
805
- const containerG = PlaitElement.getContainerG(element, { suppressThrow: false });
806
- mountElementG(index, containerG, childrenContext, currentIndexForFirstElement);
807
- if (element.children && !PlaitElement.isRootElement(element)) {
808
- element.children.forEach((child, index) => {
809
- mountOnItemMove(child, index, { ...childrenContext, parent: element }, null);
810
- });
811
- }
812
- };
813
-
814
- class PlaitPluginElementComponent {
815
- get hasChildren() {
816
- return !!this.element.children;
817
- }
818
- set context(value) {
819
- if (hasBeforeContextChange(this)) {
820
- this.beforeContextChange(value);
821
- }
822
- const previousContext = this._context;
823
- this._context = value;
824
- if (this.element) {
825
- ELEMENT_TO_COMPONENT.set(this.element, this);
826
- }
827
- if (this.initialized) {
828
- const elementG = this.getElementG();
829
- const containerG = this.getContainerG();
830
- NODE_TO_G.set(this.element, elementG);
831
- NODE_TO_CONTAINER_G.set(this.element, containerG);
832
- ELEMENT_TO_REF.set(this.element, this.ref);
833
- this.updateListRender();
834
- this.cdr.markForCheck();
835
- if (hasOnContextChanged(this)) {
836
- this.onContextChanged(value, previousContext);
837
- }
838
- }
839
- else {
840
- if (PlaitElement.isRootElement(this.element) && this.hasChildren) {
841
- this._g = createG();
842
- this._containerG = createG();
843
- this._containerG.append(this._g);
844
- }
845
- else {
846
- this._g = createG();
847
- this._containerG = this._g;
848
- }
849
- NODE_TO_G.set(this.element, this._g);
850
- NODE_TO_CONTAINER_G.set(this.element, this._containerG);
851
- ELEMENT_TO_REF.set(this.element, this.ref);
852
- }
853
- }
854
- get context() {
855
- return this._context;
856
- }
857
- get element() {
858
- return this.context && this.context.element;
859
- }
860
- get board() {
861
- return this.context && this.context.board;
862
- }
863
- get selected() {
864
- return this.context && this.context.selected;
865
- }
866
- getContainerG() {
867
- return this._containerG;
868
- }
869
- getElementG() {
870
- return this._g;
871
- }
872
- constructor(ref) {
873
- this.ref = ref;
874
- this.viewContainerRef = inject(ViewContainerRef);
875
- this.cdr = inject(ChangeDetectorRef);
876
- this.initialized = false;
877
- }
878
- ngOnInit() {
879
- if (this.element.type) {
880
- this.getContainerG().setAttribute(`plait-${this.element.type}`, 'true');
881
- }
882
- if (this.hasChildren) {
883
- if (PlaitElement.isRootElement(this.element)) {
884
- this._rootContainerG = this._containerG;
885
- }
886
- else {
887
- const path = PlaitBoard.findPath(this.board, this.element);
888
- const rootNode = PlaitNode.get(this.board, path.slice(0, 1));
889
- this._rootContainerG = PlaitElement.getContainerG(rootNode, { suppressThrow: false });
890
- }
891
- }
892
- this.getContainerG().setAttribute('plait-data-id', this.element.id);
893
- this.initialized = true;
894
- }
895
- initializeListRender() {
896
- if (this.hasChildren) {
897
- this.listRender = new ListRender(this.board, this.viewContainerRef);
898
- if (this.board.isExpanded(this.element)) {
899
- this.listRender.initialize(this.element.children, this.initializeChildrenContext());
900
- }
901
- }
902
- }
903
- getRef() {
904
- return this.ref;
905
- }
906
- updateListRender() {
907
- if (this.hasChildren) {
908
- if (!this.listRender) {
909
- throw new Error('incorrectly initialize list render');
910
- }
911
- if (this.board.isExpanded(this.element)) {
912
- this.listRender.update(this.element.children, this.initializeChildrenContext());
913
- }
914
- else {
915
- if (this.listRender.initialized) {
916
- this.listRender.destroy();
917
- }
918
- }
919
- }
920
- }
921
- initializeChildrenContext() {
922
- if (!this._rootContainerG) {
923
- throw new Error('can not resolve root container g');
924
- }
925
- return {
926
- board: this.board,
927
- parent: this.element,
928
- parentG: this._rootContainerG
929
- };
930
- }
931
- ngOnDestroy() {
932
- if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
933
- ELEMENT_TO_COMPONENT.delete(this.element);
934
- }
935
- if (NODE_TO_G.get(this.element) === this._g) {
936
- NODE_TO_G.delete(this.element);
937
- }
938
- if (NODE_TO_CONTAINER_G.get(this.element) === this._containerG) {
939
- NODE_TO_CONTAINER_G.delete(this.element);
940
- }
941
- if (ELEMENT_TO_REF.get(this.element) === this.ref) {
942
- ELEMENT_TO_REF.set(this.element, this.ref);
943
- }
944
- removeSelectedElement(this.board, this.element);
945
- this.getContainerG().remove();
946
- }
947
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitPluginElementComponent, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive }); }
948
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.4", type: PlaitPluginElementComponent, inputs: { context: "context" }, ngImport: i0 }); }
949
- }
950
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitPluginElementComponent, decorators: [{
951
- type: Directive
952
- }], ctorParameters: () => [{ type: undefined }], propDecorators: { context: [{
953
- type: Input
954
- }] } });
955
- const ELEMENT_TO_COMPONENT = new WeakMap();
956
-
957
- // https://stackoverflow.com/a/6853926/232122
958
- function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
959
- const A = x - x1;
960
- const B = y - y1;
961
- const C = x2 - x1;
962
- const D = y2 - y1;
963
- const dot = A * C + B * D;
964
- const lenSquare = C * C + D * D;
965
- let param = -1;
966
- if (lenSquare !== 0) {
967
- // in case of 0 length line
968
- param = dot / lenSquare;
969
- }
970
- let xx, yy;
971
- if (param < 0) {
972
- xx = x1;
973
- yy = y1;
974
- }
975
- else if (param > 1) {
976
- xx = x2;
977
- yy = y2;
978
- }
979
- else {
980
- xx = x1 + param * C;
981
- yy = y1 + param * D;
982
- }
983
- const dx = x - xx;
984
- const dy = y - yy;
985
- return Math.hypot(dx, dy);
986
- }
987
- function getNearestPointBetweenPointAndSegment(point, linePoints) {
988
- const x = point[0], y = point[1], x1 = linePoints[0][0], y1 = linePoints[0][1], x2 = linePoints[1][0], y2 = linePoints[1][1];
989
- const A = x - x1;
990
- const B = y - y1;
991
- const C = x2 - x1;
992
- const D = y2 - y1;
993
- const dot = A * C + B * D;
994
- const lenSquare = C * C + D * D;
995
- let param = -1;
996
- if (lenSquare !== 0) {
997
- // in case of 0 length line
998
- param = dot / lenSquare;
999
- }
1000
- let xx, yy;
1001
- if (param < 0) {
1002
- xx = x1;
1003
- yy = y1;
1004
- }
1005
- else if (param > 1) {
1006
- xx = x2;
1007
- yy = y2;
1008
- }
1009
- else {
1010
- xx = x1 + param * C;
1011
- yy = y1 + param * D;
1012
- }
1013
- return [xx, yy];
1014
- }
1015
- function distanceBetweenPointAndSegments(points, point) {
1016
- const len = points.length;
1017
- let distance = Infinity;
1018
- for (let i = 0; i < len - 1; i++) {
1019
- const p = points[i];
1020
- const p2 = points[i + 1];
1021
- const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
1022
- if (currentDistance < distance) {
1023
- distance = currentDistance;
1024
- }
1025
- }
1026
- return distance;
1027
- }
1028
- function getNearestPointBetweenPointAndSegments(point, points, isClose = true) {
1029
- const len = points.length;
1030
- let distance = Infinity;
1031
- let result = point;
1032
- for (let i = 0; i < len; i++) {
1033
- const p = points[i];
1034
- if (i === len - 1 && !isClose)
1035
- continue;
1036
- const p2 = i === len - 1 ? points[0] : points[i + 1];
1037
- const currentDistance = distanceBetweenPointAndSegment(point[0], point[1], p[0], p[1], p2[0], p2[1]);
1038
- if (currentDistance < distance) {
1039
- distance = currentDistance;
1040
- result = getNearestPointBetweenPointAndSegment(point, [p, p2]);
1041
- }
1042
- }
1043
- return result;
1044
- }
1045
- function getNearestPointBetweenPointAndEllipse(point, center, rx, ry, rotation = 0) {
1046
- const rectangleClient = {
1047
- x: center[0] - rx,
1048
- y: center[1] - ry,
1049
- height: ry * 2,
1050
- width: rx * 2
1051
- };
1052
- // https://stackoverflow.com/a/46007540/232122
1053
- const px = Math.abs(point[0] - rectangleClient.x - rectangleClient.width / 2);
1054
- const py = Math.abs(point[1] - rectangleClient.y - rectangleClient.height / 2);
1055
- let tx = 0.707;
1056
- let ty = 0.707;
1057
- const a = Math.abs(rectangleClient.width) / 2;
1058
- const b = Math.abs(rectangleClient.height) / 2;
1059
- [0, 1, 2, 3].forEach(x => {
1060
- const xx = a * tx;
1061
- const yy = b * ty;
1062
- const ex = ((a * a - b * b) * tx ** 3) / a;
1063
- const ey = ((b * b - a * a) * ty ** 3) / b;
1064
- const rx = xx - ex;
1065
- const ry = yy - ey;
1066
- const qx = px - ex;
1067
- const qy = py - ey;
1068
- const r = Math.hypot(ry, rx);
1069
- const q = Math.hypot(qy, qx);
1070
- tx = Math.min(1, Math.max(0, ((qx * r) / q + ex) / a));
1071
- ty = Math.min(1, Math.max(0, ((qy * r) / q + ey) / b));
1072
- const t = Math.hypot(ty, tx);
1073
- tx /= t;
1074
- ty /= t;
1075
- });
1076
- const signX = point[0] > center[0] ? 1 : -1;
1077
- const signY = point[1] > center[1] ? 1 : -1;
1078
- return [center[0] + a * tx * signX, center[1] + b * ty * signY];
1079
- }
1080
- function rotate(x1, y1, x2, y2, angle) {
1081
- // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
1082
- // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
1083
- // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
1084
- return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
1085
- }
1086
- function distanceBetweenPointAndPoint(x1, y1, x2, y2) {
1087
- const dx = x1 - x2;
1088
- const dy = y1 - y2;
1089
- return Math.hypot(dx, dy);
1090
- }
1091
- // https://stackoverflow.com/questions/5254838/calculating-distance-between-a-point-and-a-rectangular-box-nearest-point
1092
- function distanceBetweenPointAndRectangle(x, y, rect) {
1093
- var dx = Math.max(rect.x - x, 0, x - (rect.x + rect.width));
1094
- var dy = Math.max(rect.y - y, 0, y - (rect.y + rect.height));
1095
- return Math.sqrt(dx * dx + dy * dy);
1096
- }
1097
- const isLineHitLine = (a, b, c, d) => {
1098
- const crossProduct = (v1, v2) => v1[0] * v2[1] - v1[1] * v2[0];
1099
- const ab = [b[0] - a[0], b[1] - a[1]];
1100
- const ac = [c[0] - a[0], c[1] - a[1]];
1101
- const ad = [d[0] - a[0], d[1] - a[1]];
1102
- const ca = [a[0] - c[0], a[1] - c[1]];
1103
- const cb = [b[0] - c[0], b[1] - c[1]];
1104
- const cd = [d[0] - c[0], d[1] - c[1]];
1105
- return crossProduct(ab, ac) * crossProduct(ab, ad) <= 0 && crossProduct(cd, ca) * crossProduct(cd, cb) <= 0;
1106
- };
1107
- const isPolylineHitRectangle = (points, rectangle, isClose = true) => {
1108
- const rectanglePoints = RectangleClient.getCornerPoints(rectangle);
1109
- const len = points.length;
1110
- for (let i = 0; i < len; i++) {
1111
- if (i === len - 1 && !isClose)
1112
- continue;
1113
- const p1 = points[i];
1114
- const p2 = points[(i + 1) % len];
1115
- const isHit = isLineHitLine(p1, p2, rectanglePoints[0], rectanglePoints[1]) ||
1116
- isLineHitLine(p1, p2, rectanglePoints[1], rectanglePoints[2]) ||
1117
- isLineHitLine(p1, p2, rectanglePoints[2], rectanglePoints[3]) ||
1118
- isLineHitLine(p1, p2, rectanglePoints[3], rectanglePoints[0]);
1119
- if (isHit || isPointInPolygon(p1, rectanglePoints) || isPointInPolygon(p2, rectanglePoints)) {
1120
- return true;
1121
- }
1122
- }
1123
- return false;
1124
- };
1125
- //https://stackoverflow.com/questions/22521982/check-if-point-is-inside-a-polygon
1126
- const isPointInPolygon = (point, points) => {
1127
- // ray-casting algorithm based on
1128
- // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
1129
- const x = point[0], y = point[1];
1130
- let inside = false;
1131
- for (var i = 0, j = points.length - 1; i < points.length; j = i++) {
1132
- let xi = points[i][0], yi = points[i][1];
1133
- let xj = points[j][0], yj = points[j][1];
1134
- let intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
1135
- if (intersect)
1136
- inside = !inside;
1137
- }
1138
- return inside;
1139
- };
1140
- const isPointInEllipse = (point, center, rx, ry, angle = 0) => {
1141
- const cosAngle = Math.cos(angle);
1142
- const sinAngle = Math.sin(angle);
1143
- const x1 = (point[0] - center[0]) * cosAngle + (point[1] - center[1]) * sinAngle;
1144
- const y1 = (point[1] - center[1]) * cosAngle - (point[0] - center[0]) * sinAngle;
1145
- return (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry) <= 1;
1146
- };
1147
- const isPointInRoundRectangle = (point, rectangle, radius, angle = 0) => {
1148
- const { x: rectX, y: rectY, width, height } = rectangle;
1149
- const isInRectangle = point[0] >= rectX && point[0] <= rectX + width && point[1] >= rectY && point[1] <= rectY + height;
1150
- const handleLeftTop = point[0] >= rectX &&
1151
- point[0] <= rectX + radius &&
1152
- point[1] >= rectY &&
1153
- point[1] <= rectY + radius &&
1154
- Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + radius)) > radius;
1155
- const handleLeftBottom = point[0] >= rectX &&
1156
- point[0] <= rectX + radius &&
1157
- point[1] >= rectY + height &&
1158
- point[1] <= rectY + height - radius &&
1159
- Math.hypot(point[0] - (rectX + radius), point[1] - (rectY + height - radius)) > radius;
1160
- const handleRightTop = point[0] >= rectX + width - radius &&
1161
- point[0] <= rectX + width &&
1162
- point[1] >= rectY &&
1163
- point[1] <= rectY + radius &&
1164
- Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + radius)) > radius;
1165
- const handleRightBottom = point[0] >= rectX + width - radius &&
1166
- point[0] <= rectX + width &&
1167
- point[1] >= rectY + height - radius &&
1168
- point[1] <= rectY + height &&
1169
- Math.hypot(point[0] - (rectX + width - radius), point[1] - (rectY + height - radius)) > radius;
1170
- const isInCorner = handleLeftTop || handleLeftBottom || handleRightTop || handleRightBottom;
1171
- return isInRectangle && !isInCorner;
1172
- };
1173
- // https://gist.github.com/nicholaswmin/c2661eb11cad5671d816
1174
- const catmullRomFitting = function (points) {
1175
- const alpha = 0.5;
1176
- let p0, p1, p2, p3, bp1, bp2, d1, d2, d3, A, B, N, M;
1177
- var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
1178
- const result = [];
1179
- result.push([Math.round(points[0][0]), Math.round(points[0][1])]);
1180
- var length = points.length;
1181
- for (var i = 0; i < length - 1; i++) {
1182
- p0 = i == 0 ? points[0] : points[i - 1];
1183
- p1 = points[i];
1184
- p2 = points[i + 1];
1185
- p3 = i + 2 < length ? points[i + 2] : p2;
1186
- d1 = Math.sqrt(Math.pow(p0[0] - p1[0], 2) + Math.pow(p0[1] - p1[1], 2));
1187
- d2 = Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
1188
- d3 = Math.sqrt(Math.pow(p2[0] - p3[0], 2) + Math.pow(p2[1] - p3[1], 2));
1189
- // Catmull-Rom to Cubic Bezier conversion matrix
1190
- // A = 2d1^2a + 3d1^a * d2^a + d3^2a
1191
- // B = 2d3^2a + 3d3^a * d2^a + d2^2a
1192
- // [ 0 1 0 0 ]
1193
- // [ -d2^2a /N A/N d1^2a /N 0 ]
1194
- // [ 0 d3^2a /M B/M -d2^2a /M ]
1195
- // [ 0 0 1 0 ]
1196
- d3powA = Math.pow(d3, alpha);
1197
- d3pow2A = Math.pow(d3, 2 * alpha);
1198
- d2powA = Math.pow(d2, alpha);
1199
- d2pow2A = Math.pow(d2, 2 * alpha);
1200
- d1powA = Math.pow(d1, alpha);
1201
- d1pow2A = Math.pow(d1, 2 * alpha);
1202
- A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A;
1203
- B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A;
1204
- N = 3 * d1powA * (d1powA + d2powA);
1205
- if (N > 0) {
1206
- N = 1 / N;
1207
- }
1208
- M = 3 * d3powA * (d3powA + d2powA);
1209
- if (M > 0) {
1210
- M = 1 / M;
1211
- }
1212
- bp1 = [(-d2pow2A * p0[0] + A * p1[0] + d1pow2A * p2[0]) * N, (-d2pow2A * p0[1] + A * p1[1] + d1pow2A * p2[1]) * N];
1213
- bp2 = [(d3pow2A * p1[0] + B * p2[0] - d2pow2A * p3[0]) * M, (d3pow2A * p1[1] + B * p2[1] - d2pow2A * p3[1]) * M];
1214
- if (bp1[0] == 0 && bp1[1] == 0) {
1215
- bp1 = p1;
1216
- }
1217
- if (bp2[0] == 0 && bp2[1] == 0) {
1218
- bp2 = p2;
1219
- }
1220
- result.push(bp1, bp2, p2);
1221
- }
1222
- return result;
1223
- };
1224
- /**
1225
- * the result of slope is based on Cartesian coordinate system
1226
- * x, y are based on the position in the Cartesian coordinate system
1227
- */
1228
- function getEllipseTangentSlope(x, y, a, b) {
1229
- if (Math.abs(y) === 0) {
1230
- return x > 0 ? -Infinity : Infinity;
1231
- }
1232
- const k = (-b * b * x) / (a * a * y);
1233
- return k;
747
+ return rect;
1234
748
  }
1235
- /**
1236
- * x, y are based on the position in the Cartesian coordinate system
1237
- */
1238
- function getVectorFromPointAndSlope(x, y, slope) {
1239
- if (slope === Infinity) {
1240
- return [0, -1];
1241
- }
1242
- else if (slope === -Infinity) {
1243
- return [0, 1];
1244
- }
1245
- let vector = [1, -slope];
1246
- if (y < 0) {
1247
- vector = [-vector[0], -vector[1]];
1248
- }
1249
- return vector;
749
+ const setStrokeLinecap = (g, value) => {
750
+ g.setAttribute('stroke-linecap', value);
751
+ };
752
+ const setPathStrokeLinecap = (g, value) => {
753
+ g.querySelectorAll('path').forEach(path => {
754
+ path.setAttribute('stroke-linecap', value);
755
+ });
756
+ };
757
+ function createMask() {
758
+ return document.createElementNS(NS, 'mask');
1250
759
  }
1251
- /**
1252
- * The DOM likes values to be fixed to 3 decimal places
1253
- */
1254
- function toDomPrecision(v) {
1255
- return +v.toFixed(4);
760
+ function createSVG() {
761
+ const svg = document.createElementNS(NS, 'svg');
762
+ return svg;
1256
763
  }
1257
- function toFixed(v) {
1258
- return +v.toFixed(2);
764
+ function createText(x, y, fill, textContent) {
765
+ var text = document.createElementNS(NS, 'text');
766
+ text.setAttribute('x', `${x}`);
767
+ text.setAttribute('y', `${y}`);
768
+ text.setAttribute('fill', fill);
769
+ text.textContent = textContent;
770
+ return text;
1259
771
  }
1260
772
  /**
1261
- * Whether two numbers numbers a and b are approximately equal.
1262
- *
1263
- * @param a - The first point.
1264
- * @param b - The second point.
1265
- * @public
773
+ * Check if a DOM node is an element node.
1266
774
  */
1267
- function approximately(a, b, precision = 0.000001) {
1268
- return Math.abs(a - b) <= precision;
1269
- }
1270
- // https://medium.com/@steveruiz/find-the-points-where-a-line-segment-intercepts-an-angled-ellipse-in-javascript-typescript-e451524beece
1271
- function getCrossingPointsBetweenEllipseAndSegment(startPoint, endPoint, cx, cy, rx, ry, segment_only = true) {
1272
- // If the ellipse or line segment are empty, return no tValues.
1273
- if (rx === 0 || ry === 0 || (startPoint[0] === endPoint[0] && startPoint[1] === endPoint[1])) {
1274
- return [];
1275
- }
1276
- rx = rx < 0 ? rx : -rx;
1277
- ry = ry < 0 ? ry : -ry;
1278
- startPoint[0] -= cx;
1279
- startPoint[1] -= cy;
1280
- endPoint[0] -= cx;
1281
- endPoint[1] -= cy;
1282
- // Calculate the quadratic parameters.
1283
- var A = ((endPoint[0] - startPoint[0]) * (endPoint[0] - startPoint[0])) / rx / rx +
1284
- ((endPoint[1] - startPoint[1]) * (endPoint[1] - startPoint[1])) / ry / ry;
1285
- var B = (2 * startPoint[0] * (endPoint[0] - startPoint[0])) / rx / rx + (2 * startPoint[1] * (endPoint[1] - startPoint[1])) / ry / ry;
1286
- var C = (startPoint[0] * startPoint[0]) / rx / rx + (startPoint[1] * startPoint[1]) / ry / ry - 1;
1287
- // Make a list of t values (normalized points on the line where intersections occur).
1288
- var tValues = [];
1289
- // Calculate the discriminant.
1290
- var discriminant = B * B - 4 * A * C;
1291
- if (discriminant === 0) {
1292
- // One real solution.
1293
- tValues.push(-B / 2 / A);
1294
- }
1295
- else if (discriminant > 0) {
1296
- // Two real solutions.
1297
- tValues.push((-B + Math.sqrt(discriminant)) / 2 / A);
1298
- tValues.push((-B - Math.sqrt(discriminant)) / 2 / A);
1299
- }
1300
- return (tValues
1301
- // Filter to only points that are on the segment.
1302
- .filter(t => !segment_only || (t >= 0 && t <= 1))
1303
- // Solve for points.
1304
- .map(t => [startPoint[0] + (endPoint[0] - startPoint[0]) * t + cx, startPoint[1] + (endPoint[1] - startPoint[1]) * t + cy]));
1305
- }
1306
-
1307
- function isInPlaitBoard(board, x, y) {
1308
- const plaitBoardElement = PlaitBoard.getBoardContainer(board);
1309
- const plaitBoardRect = plaitBoardElement.getBoundingClientRect();
1310
- const distances = distanceBetweenPointAndRectangle(x, y, plaitBoardRect);
1311
- return distances === 0;
1312
- }
1313
- function getRealScrollBarWidth(board) {
1314
- const { hideScrollbar } = board.options;
1315
- let scrollBarWidth = 0;
1316
- if (!hideScrollbar) {
1317
- const viewportContainer = PlaitBoard.getViewportContainer(board);
1318
- scrollBarWidth = viewportContainer.offsetWidth - viewportContainer.clientWidth;
775
+ const isDOMElement = (value) => {
776
+ return isDOMNode(value) && value.nodeType === 1;
777
+ };
778
+ /**
779
+ * Check if a value is a DOM node.
780
+ */
781
+ const isDOMNode = (value) => {
782
+ return value instanceof window.Node;
783
+ };
784
+ const hasInputOrTextareaTarget = (target) => {
785
+ if (isDOMElement(target)) {
786
+ if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
787
+ return true;
788
+ }
1319
789
  }
1320
- return scrollBarWidth;
1321
- }
790
+ return false;
791
+ };
792
+ const isSecondaryPointer = (event) => {
793
+ return event.button === POINTER_BUTTON.SECONDARY;
794
+ };
795
+ const isMainPointer = (event) => {
796
+ return event.button === POINTER_BUTTON.MAIN;
797
+ };
1322
798
 
1323
799
  function createForeignObject(x, y, width, height) {
1324
800
  var newForeignObject = document.createElementNS(NS, 'foreignObject');
@@ -1602,8 +1078,178 @@ function idCreator(length = 5) {
1602
1078
  for (let i = 0; i < length; i++) {
1603
1079
  key += $chars.charAt(Math.floor(Math.random() * maxPosition));
1604
1080
  }
1605
- return key;
1606
- }
1081
+ return key;
1082
+ }
1083
+
1084
+ function depthFirstRecursion(node, callback, recursion, isReverse) {
1085
+ if (!recursion || recursion(node)) {
1086
+ let children = [];
1087
+ if (node.children) {
1088
+ children = [...node.children];
1089
+ }
1090
+ children = isReverse ? children.reverse() : children;
1091
+ children.forEach(child => {
1092
+ depthFirstRecursion(child, callback, recursion);
1093
+ });
1094
+ }
1095
+ callback(node);
1096
+ }
1097
+ const getIsRecursionFunc = (board) => {
1098
+ return (element) => {
1099
+ if (PlaitBoard.isBoard(element) || board.isRecursion(element)) {
1100
+ return true;
1101
+ }
1102
+ else {
1103
+ return false;
1104
+ }
1105
+ };
1106
+ };
1107
+
1108
+ const SELECTION_BORDER_COLOR = '#6698FF';
1109
+ const SELECTION_FILL_COLOR = '#6698FF25'; // opacity 0.25
1110
+ const Selection = {
1111
+ isCollapsed(selection) {
1112
+ if (selection.anchor[0] == selection.focus[0] && selection.anchor[1] === selection.focus[1]) {
1113
+ return true;
1114
+ }
1115
+ else {
1116
+ return false;
1117
+ }
1118
+ }
1119
+ };
1120
+
1121
+ const sortElements = (board, elements, ascendingOrder = true) => {
1122
+ return [...elements].sort((a, b) => {
1123
+ const pathA = PlaitBoard.findPath(board, a);
1124
+ const pathB = PlaitBoard.findPath(board, b);
1125
+ return ascendingOrder ? pathA[0] - pathB[0] : pathB[0] - pathA[0];
1126
+ });
1127
+ };
1128
+
1129
+ var PlaitPluginKey;
1130
+ (function (PlaitPluginKey) {
1131
+ PlaitPluginKey["withSelection"] = "withSelection";
1132
+ })(PlaitPluginKey || (PlaitPluginKey = {}));
1133
+
1134
+ const getHitElementsBySelection = (board, selection, match = () => true) => {
1135
+ const newSelection = selection || board.selection;
1136
+ const rectangleHitElements = [];
1137
+ if (!newSelection) {
1138
+ return [];
1139
+ }
1140
+ const isCollapsed = Selection.isCollapsed(newSelection);
1141
+ if (isCollapsed) {
1142
+ const hitElement = getHitElementByPoint(board, newSelection.anchor, match);
1143
+ if (hitElement) {
1144
+ return [hitElement];
1145
+ }
1146
+ else {
1147
+ return [];
1148
+ }
1149
+ }
1150
+ depthFirstRecursion(board, node => {
1151
+ if (!PlaitBoard.isBoard(node) && match(node) && board.isRectangleHit(node, newSelection)) {
1152
+ rectangleHitElements.push(node);
1153
+ }
1154
+ }, getIsRecursionFunc(board), true);
1155
+ return rectangleHitElements;
1156
+ };
1157
+ const getHitElementByPoint = (board, point, match = () => true) => {
1158
+ let hitElement = undefined;
1159
+ let hitInsideElement = undefined;
1160
+ depthFirstRecursion(board, node => {
1161
+ if (hitElement) {
1162
+ return;
1163
+ }
1164
+ if (PlaitBoard.isBoard(node) || !match(node) || !PlaitElement.hasMounted(node)) {
1165
+ return;
1166
+ }
1167
+ if (board.isHit(node, point)) {
1168
+ hitElement = node;
1169
+ return;
1170
+ }
1171
+ /**
1172
+ * 需要增加场景测试
1173
+ * hitInsideElement 存的是第一个符合 isInsidePoint 的元素
1174
+ * 当有元素符合 isHit 时结束遍历,并返回 hitElement
1175
+ * 当所有元素都不符合 isHit ,则返回第一个符合 isInsidePoint 的元素
1176
+ * 这样保证最上面的元素优先被探测到;
1177
+ */
1178
+ if (!hitInsideElement && board.isInsidePoint(node, point)) {
1179
+ hitInsideElement = node;
1180
+ }
1181
+ }, getIsRecursionFunc(board), true);
1182
+ return hitElement || hitInsideElement;
1183
+ };
1184
+ const getHitSelectedElements = (board, point) => {
1185
+ const selectedElements = getSelectedElements(board);
1186
+ const targetRectangle = selectedElements.length > 0 && getRectangleByElements(board, selectedElements, false);
1187
+ const isInTargetRectangle = targetRectangle && RectangleClient.isPointInRectangle(targetRectangle, point);
1188
+ if (isInTargetRectangle) {
1189
+ return selectedElements;
1190
+ }
1191
+ else {
1192
+ return [];
1193
+ }
1194
+ };
1195
+ const cacheSelectedElements = (board, selectedElements) => {
1196
+ const sortedElements = sortElements(board, selectedElements);
1197
+ BOARD_TO_SELECTED_ELEMENT.set(board, sortedElements);
1198
+ };
1199
+ const getSelectedElements = (board) => {
1200
+ return BOARD_TO_SELECTED_ELEMENT.get(board) || [];
1201
+ };
1202
+ const addSelectedElement = (board, element) => {
1203
+ let elements = [];
1204
+ if (Array.isArray(element)) {
1205
+ elements.push(...element);
1206
+ }
1207
+ else {
1208
+ elements.push(element);
1209
+ }
1210
+ const selectedElements = getSelectedElements(board);
1211
+ cacheSelectedElements(board, [...selectedElements, ...elements]);
1212
+ };
1213
+ const removeSelectedElement = (board, element, isRemoveChildren = false) => {
1214
+ const selectedElements = getSelectedElements(board);
1215
+ if (selectedElements.includes(element)) {
1216
+ const targetElements = [];
1217
+ if (board.isRecursion(element) && isRemoveChildren) {
1218
+ depthFirstRecursion(element, node => {
1219
+ targetElements.push(node);
1220
+ }, node => board.isRecursion(node));
1221
+ }
1222
+ else {
1223
+ targetElements.push(element);
1224
+ }
1225
+ const newSelectedElements = selectedElements.filter(value => !targetElements.includes(value));
1226
+ cacheSelectedElements(board, newSelectedElements);
1227
+ }
1228
+ };
1229
+ const clearSelectedElement = (board) => {
1230
+ cacheSelectedElements(board, []);
1231
+ };
1232
+ const isSelectedElement = (board, element) => {
1233
+ const selectedElements = getSelectedElements(board);
1234
+ return !!selectedElements.find(value => value === element);
1235
+ };
1236
+ const temporaryDisableSelection = (board) => {
1237
+ const currentOptions = board.getPluginOptions(PlaitPluginKey.withSelection);
1238
+ board.setPluginOptions(PlaitPluginKey.withSelection, {
1239
+ isDisabledSelect: true
1240
+ });
1241
+ setTimeout(() => {
1242
+ board.setPluginOptions(PlaitPluginKey.withSelection, { ...currentOptions });
1243
+ }, 0);
1244
+ };
1245
+ const isHitSelectedRectangle = (board, point) => {
1246
+ const hitSelectedElements = getHitSelectedElements(board, point);
1247
+ return hitSelectedElements.length > 0;
1248
+ };
1249
+ const isHitElement = (board, point) => {
1250
+ const hitElement = getHitElementByPoint(board, point);
1251
+ return !!hitElement || isHitSelectedRectangle(board, point);
1252
+ };
1607
1253
 
1608
1254
  /**
1609
1255
  * drawRoundRectangle
@@ -3344,14 +2990,14 @@ const rotatePoints = (points, centerPoint, angle) => {
3344
2990
  }
3345
2991
  else {
3346
2992
  return points.map(point => {
3347
- return rotate(point[0], point[1], centerPoint[0], centerPoint[1], angle);
2993
+ return rotate(point[0], point[1], centerPoint[0], centerPoint[1], angle || 0);
3348
2994
  });
3349
2995
  }
3350
2996
  };
3351
2997
  const getSelectionAngle = (elements) => {
3352
2998
  let angle = elements[0]?.angle || 0;
3353
2999
  elements.forEach(item => {
3354
- if (item.angle !== angle && !approximately((item.angle % (Math.PI / 2)) - (angle % (Math.PI / 2)), 0)) {
3000
+ if (item.angle !== angle && !approximately(((item.angle || 0) % (Math.PI / 2)) - (angle % (Math.PI / 2)), 0)) {
3355
3001
  angle = 0;
3356
3002
  }
3357
3003
  });
@@ -3408,7 +3054,7 @@ const rotateAntiPointsByElement = (points, element) => {
3408
3054
  if (hasValidAngle(element)) {
3409
3055
  let rectangle = RectangleClient.getRectangleByPoints(element.points);
3410
3056
  const centerPoint = RectangleClient.getCenterPoint(rectangle);
3411
- return rotatePoints(points, centerPoint, -element.angle);
3057
+ return rotatePoints(points, centerPoint, element.angle ? -element.angle : 0);
3412
3058
  }
3413
3059
  else {
3414
3060
  return null;
@@ -3420,9 +3066,7 @@ const getRectangleByAngle = (rectangle, angle) => {
3420
3066
  const centerPoint = RectangleClient.getCenterPoint(rectangle);
3421
3067
  return RectangleClient.getRectangleByPoints(rotatePoints(cornerPoints, centerPoint, angle));
3422
3068
  }
3423
- else {
3424
- return null;
3425
- }
3069
+ return rectangle;
3426
3070
  };
3427
3071
  const isAxisChangedByAngle = (angle) => {
3428
3072
  const unitAngle = Math.abs(angle) % Math.PI;
@@ -3441,7 +3085,7 @@ function rotateElements(board, elements, angle) {
3441
3085
  const originAngle = item.angle;
3442
3086
  const points = rotatedDataPoints(item.points, selectionCenterPoint, normalizeAngle(angle));
3443
3087
  const path = PlaitBoard.findPath(board, item);
3444
- Transforms.setNode(board, { points, angle: normalizeAngle(originAngle + angle) }, path);
3088
+ Transforms.setNode(board, { points, angle: normalizeAngle((originAngle || 0) + angle) }, path);
3445
3089
  });
3446
3090
  }
3447
3091
  const normalizeAngle = (angle) => {
@@ -3871,7 +3515,10 @@ function getSnapRectangles(board, activeElements) {
3871
3515
  recursion: () => true,
3872
3516
  isReverse: false
3873
3517
  });
3874
- return elements.map(item => getRectangleByAngle(board.getRectangle(item), item.angle) || board.getRectangle(item));
3518
+ return elements.map(item => {
3519
+ const rectangle = board.getRectangle(item);
3520
+ return getRectangleByAngle(rectangle, item.angle || 0);
3521
+ });
3875
3522
  }
3876
3523
  function getBarPoint(point, isHorizontal) {
3877
3524
  return isHorizontal
@@ -4077,9 +3724,6 @@ const PlaitElement = {
4077
3724
  return false;
4078
3725
  }
4079
3726
  },
4080
- getComponent(value) {
4081
- return ELEMENT_TO_COMPONENT.get(value);
4082
- },
4083
3727
  getElementRef(value) {
4084
3728
  return ELEMENT_TO_REF.get(value);
4085
3729
  },
@@ -4332,6 +3976,11 @@ function getRectangleByElements(board, elements, recursion) {
4332
3976
  };
4333
3977
  }
4334
3978
  }
3979
+ function getBoundingRectangleByElements(board, elements, recursion) {
3980
+ const rectangle = getRectangleByElements(board, elements, recursion);
3981
+ const angle = getSelectionAngle(elements);
3982
+ return getRectangleByAngle(rectangle, angle);
3983
+ }
4335
3984
  function getBoardRectangle(board) {
4336
3985
  return getRectangleByElements(board, board.children, true);
4337
3986
  }
@@ -4399,6 +4048,9 @@ const PlaitBoard = {
4399
4048
  getHost(board) {
4400
4049
  return BOARD_TO_HOST.get(board);
4401
4050
  },
4051
+ getElementLowerHost(board) {
4052
+ return BOARD_TO_ELEMENT_HOST.get(board)?.lowerHost;
4053
+ },
4402
4054
  getElementHost(board) {
4403
4055
  return BOARD_TO_ELEMENT_HOST.get(board)?.host;
4404
4056
  },
@@ -4414,6 +4066,9 @@ const PlaitBoard = {
4414
4066
  getComponent(board) {
4415
4067
  return BOARD_TO_COMPONENT.get(board);
4416
4068
  },
4069
+ getViewContainerRef(board) {
4070
+ return BOARD_TO_COMPONENT.get(board).viewContainerRef;
4071
+ },
4417
4072
  getBoardContainer(board) {
4418
4073
  return BOARD_TO_ELEMENT_HOST.get(board)?.container;
4419
4074
  },
@@ -4764,10 +4419,7 @@ function withSelection(board) {
4764
4419
  event.preventDefault();
4765
4420
  }
4766
4421
  const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4767
- const selectedElements = getSelectedElements(board);
4768
- const hitElement = getHitElementByPoint(board, point);
4769
- const hitSelectedElements = selectedElements.length > 1 ? getHitSelectedElements(board, point) : [];
4770
- const isHitTarget = hitElement || hitSelectedElements.length > 0;
4422
+ const isHitTarget = isHitElement(board, point);
4771
4423
  const options = board.getPluginOptions(PlaitPluginKey.withSelection);
4772
4424
  if (PlaitBoard.isPointer(board, PlaitPointerType.selection) && !isHitTarget && options.isMultiple && !options.isDisabledSelect) {
4773
4425
  preventTouchMove(board, event, true);
@@ -5272,7 +4924,7 @@ function withMoving(board) {
5272
4924
  x: activeElementsRectangle.x + offsetX,
5273
4925
  y: activeElementsRectangle.y + offsetY
5274
4926
  };
5275
- const activeRectangle = getRectangleByAngle(newRectangle, getSelectionAngle(activeElements)) || newRectangle;
4927
+ const activeRectangle = getRectangleByAngle(newRectangle, getSelectionAngle(activeElements));
5276
4928
  const ref = getSnapMovingRef(board, activeRectangle, activeElements);
5277
4929
  offsetX += ref.deltaX;
5278
4930
  offsetY += ref.deltaY;
@@ -5467,10 +5119,10 @@ class PlaitIslandBaseComponent {
5467
5119
  markForCheck() {
5468
5120
  this.cdr.markForCheck();
5469
5121
  }
5470
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitIslandBaseComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
5471
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.4", type: PlaitIslandBaseComponent, host: { classAttribute: "plait-island-container" }, ngImport: i0 }); }
5122
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: PlaitIslandBaseComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
5123
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.1", type: PlaitIslandBaseComponent, host: { classAttribute: "plait-island-container" }, ngImport: i0 }); }
5472
5124
  }
5473
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitIslandBaseComponent, decorators: [{
5125
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: PlaitIslandBaseComponent, decorators: [{
5474
5126
  type: Directive,
5475
5127
  args: [{
5476
5128
  host: {
@@ -5503,10 +5155,10 @@ class PlaitIslandPopoverBaseComponent {
5503
5155
  this.subscription?.unsubscribe();
5504
5156
  this.islandOnDestroy();
5505
5157
  }
5506
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitIslandPopoverBaseComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
5507
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.4", type: PlaitIslandPopoverBaseComponent, inputs: { board: "board" }, host: { classAttribute: "plait-island-popover-container" }, ngImport: i0 }); }
5158
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: PlaitIslandPopoverBaseComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
5159
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.1", type: PlaitIslandPopoverBaseComponent, inputs: { board: "board" }, host: { classAttribute: "plait-island-popover-container" }, ngImport: i0 }); }
5508
5160
  }
5509
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitIslandPopoverBaseComponent, decorators: [{
5161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: PlaitIslandPopoverBaseComponent, decorators: [{
5510
5162
  type: Directive,
5511
5163
  args: [{
5512
5164
  host: {
@@ -5584,92 +5236,299 @@ const withHotkey = (board) => {
5584
5236
  event.preventDefault();
5585
5237
  deleteFragment(board);
5586
5238
  }
5587
- keyDown(event);
5588
- };
5589
- board.keyUp = (event) => {
5590
- keyUp(event);
5591
- };
5592
- board.globalKeyDown = (event) => {
5593
- if (PlaitBoard.getMovingPointInBoard(board) || PlaitBoard.isMovingPointInBoard(board)) {
5594
- if (isHotkey(['mod+=', 'mod++'], { byKey: true })(event)) {
5595
- event.preventDefault();
5596
- BoardTransforms.updateZoom(board, board.viewport.zoom + 0.1, false);
5597
- return;
5598
- }
5599
- if (isHotkey(['mod+shift+=', 'mod+shift++'], { byKey: true })(event)) {
5600
- event.preventDefault();
5601
- BoardTransforms.fitViewport(board);
5602
- return;
5603
- }
5604
- if (isHotkey(['mod+-', 'mod+shift+-'])(event)) {
5605
- event.preventDefault();
5606
- BoardTransforms.updateZoom(board, board.viewport.zoom - 0.1);
5607
- return;
5239
+ keyDown(event);
5240
+ };
5241
+ board.keyUp = (event) => {
5242
+ keyUp(event);
5243
+ };
5244
+ board.globalKeyDown = (event) => {
5245
+ if (PlaitBoard.getMovingPointInBoard(board) || PlaitBoard.isMovingPointInBoard(board)) {
5246
+ if (isHotkey(['mod+=', 'mod++'], { byKey: true })(event)) {
5247
+ event.preventDefault();
5248
+ BoardTransforms.updateZoom(board, board.viewport.zoom + 0.1, false);
5249
+ return;
5250
+ }
5251
+ if (isHotkey(['mod+shift+=', 'mod+shift++'], { byKey: true })(event)) {
5252
+ event.preventDefault();
5253
+ BoardTransforms.fitViewport(board);
5254
+ return;
5255
+ }
5256
+ if (isHotkey(['mod+-', 'mod+shift+-'])(event)) {
5257
+ event.preventDefault();
5258
+ BoardTransforms.updateZoom(board, board.viewport.zoom - 0.1);
5259
+ return;
5260
+ }
5261
+ if (isHotkey(['mod+0', 'mod+shift+0'], { byKey: true })(event)) {
5262
+ event.preventDefault();
5263
+ BoardTransforms.updateZoom(board, 1);
5264
+ return;
5265
+ }
5266
+ }
5267
+ globalKeyDown(event);
5268
+ };
5269
+ return board;
5270
+ };
5271
+
5272
+ class PlaitContextService {
5273
+ constructor() {
5274
+ this._stable = new Subject();
5275
+ this.uploadingFiles = [];
5276
+ }
5277
+ getUploadingFile(url) {
5278
+ return this.uploadingFiles.find(file => file.url === url);
5279
+ }
5280
+ setUploadingFile(file) {
5281
+ return this.uploadingFiles.push(file);
5282
+ }
5283
+ removeUploadingFile(fileEntry) {
5284
+ this.uploadingFiles = this.uploadingFiles.filter(file => file.url !== fileEntry.url);
5285
+ }
5286
+ onStable() {
5287
+ return this._stable.asObservable();
5288
+ }
5289
+ nextStable() {
5290
+ this._stable.next('');
5291
+ }
5292
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: PlaitContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5293
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: PlaitContextService }); }
5294
+ }
5295
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: PlaitContextService, decorators: [{
5296
+ type: Injectable
5297
+ }] });
5298
+
5299
+ function withRelatedFragment(board) {
5300
+ const { buildFragment } = board;
5301
+ board.buildFragment = (clipboardContext, rectangle, operationType, originData) => {
5302
+ let relatedFragment = board.getRelatedFragment(originData || []);
5303
+ if (relatedFragment) {
5304
+ if (originData?.length) {
5305
+ relatedFragment = relatedFragment.filter(item => !originData.map(element => element.id).includes(item.id));
5306
+ }
5307
+ if (relatedFragment.length) {
5308
+ if (!clipboardContext) {
5309
+ clipboardContext = createClipboardContext(WritableClipboardType.elements, relatedFragment, '');
5310
+ }
5311
+ else {
5312
+ clipboardContext = addClipboardContext(clipboardContext, {
5313
+ text: '',
5314
+ type: WritableClipboardType.elements,
5315
+ elements: relatedFragment
5316
+ });
5317
+ }
5318
+ }
5319
+ }
5320
+ return buildFragment(clipboardContext, rectangle, operationType, originData);
5321
+ };
5322
+ return board;
5323
+ }
5324
+
5325
+ class ListRender {
5326
+ constructor(board) {
5327
+ this.board = board;
5328
+ this.children = [];
5329
+ this.instances = [];
5330
+ this.contexts = [];
5331
+ this.differ = null;
5332
+ this.initialized = false;
5333
+ }
5334
+ initialize(children, childrenContext) {
5335
+ this.initialized = true;
5336
+ this.children = children;
5337
+ children.forEach((descendant, index) => {
5338
+ NODE_TO_INDEX.set(descendant, index);
5339
+ NODE_TO_PARENT.set(descendant, childrenContext.parent);
5340
+ const context = getContext(this.board, descendant, index, childrenContext.parent);
5341
+ const componentType = getComponentType(this.board, context);
5342
+ const instance = createPluginComponent(this.board, componentType, context, childrenContext);
5343
+ this.instances.push(instance);
5344
+ this.contexts.push(context);
5345
+ });
5346
+ const newDiffers = PlaitBoard.getViewContainerRef(this.board).injector.get(IterableDiffers);
5347
+ this.differ = newDiffers.find(children).create(trackBy);
5348
+ this.differ.diff(children);
5349
+ }
5350
+ update(children, childrenContext) {
5351
+ if (!this.initialized) {
5352
+ this.initialize(children, childrenContext);
5353
+ return;
5354
+ }
5355
+ if (!this.differ) {
5356
+ throw new Error('Exception: Can not find differ ');
5357
+ }
5358
+ const { board, parent } = childrenContext;
5359
+ const diffResult = this.differ.diff(children);
5360
+ if (diffResult) {
5361
+ const newContexts = [];
5362
+ const newInstances = [];
5363
+ let currentIndexForFirstElement = null;
5364
+ diffResult.forEachItem((record) => {
5365
+ NODE_TO_INDEX.set(record.item, record.currentIndex);
5366
+ NODE_TO_PARENT.set(record.item, childrenContext.parent);
5367
+ const previousContext = record.previousIndex === null ? undefined : this.contexts[record.previousIndex];
5368
+ const context = getContext(board, record.item, record.currentIndex, parent, previousContext);
5369
+ if (record.previousIndex === null) {
5370
+ const componentType = getComponentType(board, context);
5371
+ const componentRef = createPluginComponent(board, componentType, context, childrenContext);
5372
+ newContexts.push(context);
5373
+ newInstances.push(componentRef);
5374
+ }
5375
+ else {
5376
+ const instance = this.instances[record.previousIndex];
5377
+ instance.context = context;
5378
+ newInstances.push(instance);
5379
+ newContexts.push(context);
5380
+ }
5381
+ // item might has been changed, so need to compare the id
5382
+ if (record.item === this.children[0] || record.item.id === this.children[0]?.id) {
5383
+ currentIndexForFirstElement = record.currentIndex;
5384
+ }
5385
+ });
5386
+ diffResult.forEachOperation(record => {
5387
+ // removed
5388
+ if (record.currentIndex === null) {
5389
+ const componentRef = this.instances[record.previousIndex];
5390
+ componentRef?.destroy();
5391
+ }
5392
+ // moved
5393
+ if (record.previousIndex !== null && record.currentIndex !== null) {
5394
+ mountOnItemMove(record.item, record.currentIndex, childrenContext, currentIndexForFirstElement);
5395
+ }
5396
+ });
5397
+ this.instances = newInstances;
5398
+ this.contexts = newContexts;
5399
+ this.children = children;
5400
+ }
5401
+ else {
5402
+ const newContexts = [];
5403
+ this.children.forEach((element, index) => {
5404
+ NODE_TO_INDEX.set(element, index);
5405
+ NODE_TO_PARENT.set(element, childrenContext.parent);
5406
+ const previousContext = this.contexts[index];
5407
+ const previousInstance = this.instances[index];
5408
+ const context = getContext(board, element, index, parent, previousContext);
5409
+ previousInstance.context = context;
5410
+ newContexts.push(context);
5411
+ });
5412
+ this.contexts = newContexts;
5413
+ }
5414
+ }
5415
+ destroy() {
5416
+ this.children.forEach((element, index) => {
5417
+ if (this.instances[index]) {
5418
+ this.instances[index].destroy();
5419
+ }
5420
+ });
5421
+ this.instances = [];
5422
+ this.children = [];
5423
+ this.contexts = [];
5424
+ this.initialized = false;
5425
+ this.differ = null;
5426
+ }
5427
+ }
5428
+ const trackBy = (index, element) => {
5429
+ return element.id;
5430
+ };
5431
+ const createPluginComponent = (board, componentType, context, childrenContext) => {
5432
+ // const componentRef = PlaitBoard.getViewContainerRef(board).createComponent(componentType, { injector: PlaitBoard.getViewContainerRef(board).injector });
5433
+ // const instance = componentRef.instance;
5434
+ const instance = new componentType();
5435
+ instance.context = context;
5436
+ instance.initialize();
5437
+ const g = instance.getContainerG();
5438
+ mountElementG(context.index, g, childrenContext);
5439
+ instance.initializeListRender();
5440
+ return instance;
5441
+ };
5442
+ const getComponentType = (board, context) => {
5443
+ const result = board.drawElement(context);
5444
+ return result;
5445
+ };
5446
+ const getContext = (board, element, index, parent, previousContext) => {
5447
+ let isSelected = isSelectedElement(board, element);
5448
+ const previousElement = previousContext && previousContext.element;
5449
+ if (previousElement && previousElement !== element && isSelectedElement(board, previousElement)) {
5450
+ isSelected = true;
5451
+ removeSelectedElement(board, previousElement);
5452
+ addSelectedElement(board, element);
5453
+ }
5454
+ const context = {
5455
+ element: element,
5456
+ parent: parent,
5457
+ board: board,
5458
+ selected: isSelected,
5459
+ index
5460
+ };
5461
+ return context;
5462
+ };
5463
+ // the g depth of root element:[1]-[2]-[3]-[4]
5464
+ // the g depth of root element and children element(the [2] element has children):
5465
+ // [1]-
5466
+ // [2]([2-1-1][2-1-2][2-1][2-2][2-3-1][2-3-2][2-3][2])-
5467
+ // [3]-
5468
+ // [4]
5469
+ const mountElementG = (index, g, childrenContext,
5470
+ // for moving scene: the current index for first element before moving
5471
+ currentIndexForFirstElement = null) => {
5472
+ const { parent, parentG } = childrenContext;
5473
+ if (PlaitBoard.isBoard(parent)) {
5474
+ if (index > 0) {
5475
+ const previousElement = parent.children[index - 1];
5476
+ const previousContainerG = PlaitElement.getContainerG(previousElement, { suppressThrow: false });
5477
+ previousContainerG.insertAdjacentElement('afterend', g);
5478
+ }
5479
+ else {
5480
+ if (currentIndexForFirstElement !== null) {
5481
+ const firstElement = parent.children[currentIndexForFirstElement];
5482
+ const firstContainerG = firstElement && PlaitElement.getContainerG(firstElement, { suppressThrow: true });
5483
+ if (firstElement && firstContainerG) {
5484
+ parentG.insertBefore(g, firstContainerG);
5485
+ }
5486
+ else {
5487
+ throw new Error('fail to mount container on moving');
5488
+ }
5608
5489
  }
5609
- if (isHotkey(['mod+0', 'mod+shift+0'], { byKey: true })(event)) {
5610
- event.preventDefault();
5611
- BoardTransforms.updateZoom(board, 1);
5612
- return;
5490
+ else {
5491
+ parentG.append(g);
5613
5492
  }
5614
5493
  }
5615
- globalKeyDown(event);
5616
- };
5617
- return board;
5618
- };
5619
-
5620
- class PlaitContextService {
5621
- constructor() {
5622
- this._stable = new Subject();
5623
- this.uploadingFiles = [];
5624
- }
5625
- getUploadingFile(url) {
5626
- return this.uploadingFiles.find(file => file.url === url);
5627
- }
5628
- setUploadingFile(file) {
5629
- return this.uploadingFiles.push(file);
5630
- }
5631
- removeUploadingFile(fileEntry) {
5632
- this.uploadingFiles = this.uploadingFiles.filter(file => file.url !== fileEntry.url);
5633
- }
5634
- onStable() {
5635
- return this._stable.asObservable();
5636
- }
5637
- nextStable() {
5638
- this._stable.next('');
5639
5494
  }
5640
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5641
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitContextService }); }
5642
- }
5643
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitContextService, decorators: [{
5644
- type: Injectable
5645
- }] });
5646
-
5647
- function withRelatedFragment(board) {
5648
- const { buildFragment } = board;
5649
- board.buildFragment = (clipboardContext, rectangle, operationType, originData) => {
5650
- let relatedFragment = board.getRelatedFragment(originData || []);
5651
- if (relatedFragment) {
5652
- if (originData?.length) {
5653
- relatedFragment = relatedFragment.filter(item => !originData.map(element => element.id).includes(item.id));
5654
- }
5655
- if (relatedFragment.length) {
5656
- if (!clipboardContext) {
5657
- clipboardContext = createClipboardContext(WritableClipboardType.elements, relatedFragment, '');
5495
+ else {
5496
+ if (index > 0) {
5497
+ const previousElement = parent.children[index - 1];
5498
+ const previousElementG = PlaitElement.getElementG(previousElement);
5499
+ previousElementG.insertAdjacentElement('afterend', g);
5500
+ }
5501
+ else {
5502
+ if (currentIndexForFirstElement) {
5503
+ const nextElement = parent.children[currentIndexForFirstElement];
5504
+ const nextPath = nextElement && PlaitBoard.findPath(childrenContext.board, nextElement);
5505
+ const first = nextPath && PlaitNode.first(childrenContext.board, nextPath);
5506
+ const firstContainerG = first && PlaitElement.getContainerG(first, { suppressThrow: false });
5507
+ if (firstContainerG) {
5508
+ parentG.insertBefore(g, firstContainerG);
5658
5509
  }
5659
5510
  else {
5660
- clipboardContext = addClipboardContext(clipboardContext, {
5661
- text: '',
5662
- type: WritableClipboardType.elements,
5663
- elements: relatedFragment
5664
- });
5511
+ throw new Error('fail to mount container on moving');
5665
5512
  }
5666
5513
  }
5514
+ else {
5515
+ let parentElementG = PlaitElement.getElementG(parent);
5516
+ parentG.insertBefore(g, parentElementG);
5517
+ }
5667
5518
  }
5668
- return buildFragment(clipboardContext, rectangle, operationType, originData);
5669
- };
5670
- return board;
5671
- }
5519
+ }
5520
+ };
5521
+ const mountOnItemMove = (element, index, childrenContext, currentIndexForFirstElement) => {
5522
+ const containerG = PlaitElement.getContainerG(element, { suppressThrow: false });
5523
+ mountElementG(index, containerG, childrenContext, currentIndexForFirstElement);
5524
+ if (element.children && !PlaitElement.isRootElement(element)) {
5525
+ element.children.forEach((child, index) => {
5526
+ mountOnItemMove(child, index, { ...childrenContext, parent: element }, null);
5527
+ });
5528
+ }
5529
+ };
5672
5530
 
5531
+ const ElementLowerHostClass = 'element-lower-host';
5673
5532
  const ElementHostClass = 'element-host';
5674
5533
  const ElementUpperHostClass = 'element-upper-host';
5675
5534
  const ElementActiveHostClass = 'element-active-host';
@@ -5721,6 +5580,7 @@ class PlaitBoardComponent {
5721
5580
  };
5722
5581
  }
5723
5582
  ngOnInit() {
5583
+ const elementLowerHost = this.host.querySelector(`.${ElementLowerHostClass}`);
5724
5584
  const elementHost = this.host.querySelector(`.${ElementHostClass}`);
5725
5585
  const elementUpperHost = this.host.querySelector(`.${ElementUpperHostClass}`);
5726
5586
  const elementActiveHost = this.host.querySelector(`.${ElementActiveHostClass}`);
@@ -5745,6 +5605,7 @@ class PlaitBoardComponent {
5745
5605
  BOARD_TO_HOST.set(this.board, this.host);
5746
5606
  IS_BOARD_ALIVE.set(this.board, true);
5747
5607
  BOARD_TO_ELEMENT_HOST.set(this.board, {
5608
+ lowerHost: elementLowerHost,
5748
5609
  host: elementHost,
5749
5610
  upperHost: elementUpperHost,
5750
5611
  activeHost: elementActiveHost,
@@ -5917,7 +5778,7 @@ class PlaitBoardComponent {
5917
5778
  });
5918
5779
  }
5919
5780
  initializeListRender() {
5920
- this.listRender = new ListRender(this.board, this.viewContainerRef);
5781
+ this.listRender = new ListRender(this.board);
5921
5782
  this.listRender.initialize(this.board.children, this.initializeChildrenContext());
5922
5783
  }
5923
5784
  updateListRender() {
@@ -6020,10 +5881,11 @@ class PlaitBoardComponent {
6020
5881
  this.updateIslands();
6021
5882
  });
6022
5883
  }
6023
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitBoardComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ViewContainerRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
6024
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.2.4", type: PlaitBoardComponent, isStandalone: true, selector: "plait-board", inputs: { plaitValue: "plaitValue", plaitViewport: "plaitViewport", plaitPlugins: "plaitPlugins", plaitOptions: "plaitOptions", plaitTheme: "plaitTheme" }, outputs: { plaitChange: "plaitChange", plaitBoardInitialized: "plaitBoardInitialized" }, host: { properties: { "class": "this.hostClass", "class.readonly": "this.readonly", "class.focused": "this.isFocused", "class.disabled-scroll": "this.disabledScrollOnNonFocus" } }, providers: [PlaitContextService], queries: [{ propertyName: "islands", predicate: PlaitIslandBaseComponent, descendants: true }], viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true, static: true }, { propertyName: "viewportContainer", first: true, predicate: ["viewportContainer"], descendants: true, read: ElementRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
5884
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: PlaitBoardComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ViewContainerRef }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
5885
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.1", type: PlaitBoardComponent, isStandalone: true, selector: "plait-board", inputs: { plaitValue: "plaitValue", plaitViewport: "plaitViewport", plaitPlugins: "plaitPlugins", plaitOptions: "plaitOptions", plaitTheme: "plaitTheme" }, outputs: { plaitChange: "plaitChange", plaitBoardInitialized: "plaitBoardInitialized" }, host: { properties: { "class": "this.hostClass", "class.readonly": "this.readonly", "class.focused": "this.isFocused", "class.disabled-scroll": "this.disabledScrollOnNonFocus" } }, providers: [PlaitContextService], queries: [{ propertyName: "islands", predicate: PlaitIslandBaseComponent, descendants: true }], viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true, static: true }, { propertyName: "viewportContainer", first: true, predicate: ["viewportContainer"], descendants: true, read: ElementRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
6025
5886
  <div class="viewport-container" #viewportContainer>
6026
5887
  <svg #svg width="100%" height="100%" style="position: relative;" class="board-host-svg">
5888
+ <g class="element-lower-host"></g>
6027
5889
  <g class="element-host"></g>
6028
5890
  <g class="element-upper-host"></g>
6029
5891
  <g class="element-active-host"></g>
@@ -6032,13 +5894,14 @@ class PlaitBoardComponent {
6032
5894
  <ng-content></ng-content>
6033
5895
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6034
5896
  }
6035
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImport: i0, type: PlaitBoardComponent, decorators: [{
5897
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.1", ngImport: i0, type: PlaitBoardComponent, decorators: [{
6036
5898
  type: Component,
6037
5899
  args: [{
6038
5900
  selector: 'plait-board',
6039
5901
  template: `
6040
5902
  <div class="viewport-container" #viewportContainer>
6041
5903
  <svg #svg width="100%" height="100%" style="position: relative;" class="board-host-svg">
5904
+ <g class="element-lower-host"></g>
6042
5905
  <g class="element-host"></g>
6043
5906
  <g class="element-upper-host"></g>
6044
5907
  <g class="element-active-host"></g>
@@ -6087,6 +5950,149 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
6087
5950
  args: [PlaitIslandBaseComponent, { descendants: true }]
6088
5951
  }] } });
6089
5952
 
5953
+ function hasBeforeContextChange(value) {
5954
+ if (value.beforeContextChange) {
5955
+ return true;
5956
+ }
5957
+ return false;
5958
+ }
5959
+ function hasOnContextChanged(value) {
5960
+ if (value.onContextChanged) {
5961
+ return true;
5962
+ }
5963
+ return false;
5964
+ }
5965
+
5966
+ /**
5967
+ * 基于 element-flavour 实现元素的绘制,取代 Angular 组件
5968
+ */
5969
+ class ElementFlavour {
5970
+ get hasChildren() {
5971
+ return !!this.element.children;
5972
+ }
5973
+ set context(value) {
5974
+ if (hasBeforeContextChange(this)) {
5975
+ this.beforeContextChange(value);
5976
+ }
5977
+ const previousContext = this._context;
5978
+ this._context = value;
5979
+ if (this.initialized) {
5980
+ const elementG = this.getElementG();
5981
+ const containerG = this.getContainerG();
5982
+ NODE_TO_G.set(this.element, elementG);
5983
+ NODE_TO_CONTAINER_G.set(this.element, containerG);
5984
+ ELEMENT_TO_REF.set(this.element, this.ref);
5985
+ this.updateListRender();
5986
+ if (hasOnContextChanged(this)) {
5987
+ this.onContextChanged(value, previousContext);
5988
+ }
5989
+ }
5990
+ else {
5991
+ if (PlaitElement.isRootElement(this.element) && this.hasChildren) {
5992
+ this._g = createG();
5993
+ this._containerG = createG();
5994
+ this._containerG.append(this._g);
5995
+ }
5996
+ else {
5997
+ this._g = createG();
5998
+ this._containerG = this._g;
5999
+ }
6000
+ NODE_TO_G.set(this.element, this._g);
6001
+ NODE_TO_CONTAINER_G.set(this.element, this._containerG);
6002
+ ELEMENT_TO_REF.set(this.element, this.ref);
6003
+ }
6004
+ }
6005
+ get context() {
6006
+ return this._context;
6007
+ }
6008
+ get element() {
6009
+ return this.context && this.context.element;
6010
+ }
6011
+ get board() {
6012
+ return this.context && this.context.board;
6013
+ }
6014
+ get selected() {
6015
+ return this.context && this.context.selected;
6016
+ }
6017
+ getContainerG() {
6018
+ return this._containerG;
6019
+ }
6020
+ getElementG() {
6021
+ return this._g;
6022
+ }
6023
+ constructor(ref) {
6024
+ this.ref = ref;
6025
+ this.initialized = false;
6026
+ }
6027
+ initialize() {
6028
+ if (this.element.type) {
6029
+ this.getContainerG().setAttribute(`plait-${this.element.type}`, 'true');
6030
+ }
6031
+ if (this.hasChildren) {
6032
+ if (PlaitElement.isRootElement(this.element)) {
6033
+ this._rootContainerG = this._containerG;
6034
+ }
6035
+ else {
6036
+ const path = PlaitBoard.findPath(this.board, this.element);
6037
+ const rootNode = PlaitNode.get(this.board, path.slice(0, 1));
6038
+ this._rootContainerG = PlaitElement.getContainerG(rootNode, { suppressThrow: false });
6039
+ }
6040
+ }
6041
+ this.getContainerG().setAttribute('plait-data-id', this.element.id);
6042
+ this.initialized = true;
6043
+ }
6044
+ initializeListRender() {
6045
+ if (this.hasChildren) {
6046
+ this.listRender = new ListRender(this.board);
6047
+ if (this.board.isExpanded(this.element)) {
6048
+ this.listRender.initialize(this.element.children, this.initializeChildrenContext());
6049
+ }
6050
+ }
6051
+ }
6052
+ getRef() {
6053
+ return this.ref;
6054
+ }
6055
+ updateListRender() {
6056
+ if (this.hasChildren) {
6057
+ if (!this.listRender) {
6058
+ throw new Error('incorrectly initialize list render');
6059
+ }
6060
+ if (this.board.isExpanded(this.element)) {
6061
+ this.listRender.update(this.element.children, this.initializeChildrenContext());
6062
+ }
6063
+ else {
6064
+ if (this.listRender.initialized) {
6065
+ this.listRender.destroy();
6066
+ }
6067
+ }
6068
+ }
6069
+ }
6070
+ initializeChildrenContext() {
6071
+ if (!this._rootContainerG) {
6072
+ throw new Error('can not resolve root container g');
6073
+ }
6074
+ return {
6075
+ board: this.board,
6076
+ parent: this.element,
6077
+ parentG: this._rootContainerG
6078
+ };
6079
+ }
6080
+ destroy() {
6081
+ if (NODE_TO_G.get(this.element) === this._g) {
6082
+ NODE_TO_G.delete(this.element);
6083
+ }
6084
+ if (NODE_TO_CONTAINER_G.get(this.element) === this._containerG) {
6085
+ NODE_TO_CONTAINER_G.delete(this.element);
6086
+ }
6087
+ if (ELEMENT_TO_REF.get(this.element) === this.ref) {
6088
+ ELEMENT_TO_REF.set(this.element, this.ref);
6089
+ }
6090
+ removeSelectedElement(this.board, this.element);
6091
+ this.getContainerG().remove();
6092
+ this.listRender?.destroy();
6093
+ }
6094
+ }
6095
+
6090
6096
  /**
6091
6097
  * 1.create board instance
6092
6098
  * 2.build fake node weak map
@@ -6345,5 +6351,5 @@ const isDebug = (key) => {
6345
6351
  * Generated bundle index. Do not edit.
6346
6352
  */
6347
6353
 
6348
- export { A, ACTIVE_MOVING_CLASS_NAME, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, BOARD_TO_AFTER_CHANGE, BOARD_TO_COMPONENT, BOARD_TO_ELEMENT_HOST, BOARD_TO_HOST, BOARD_TO_IS_SELECTION_MOVING, BOARD_TO_MOVING_ELEMENT, BOARD_TO_MOVING_POINT, BOARD_TO_MOVING_POINT_IN_BOARD, BOARD_TO_ON_CHANGE, BOARD_TO_ROUGH_SVG, BOARD_TO_SELECTED_ELEMENT, BOARD_TO_TEMPORARY_ELEMENTS, BOARD_TO_TOUCH_REF, BOARD_TO_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, CursorClass, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DebugGenerator, DefaultThemeColor, Direction, E, EIGHT, ELEMENT_TO_COMPONENT, ELEMENT_TO_REF, END, ENTER, EQUALS, ESCAPE, F, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, FF_EQUALS, FF_MINUS, FF_MUTE, FF_SEMICOLON, FF_VOLUME_DOWN, FF_VOLUME_UP, FIRST_MEDIA, FIVE, FLUSHING, FOUR, G, H, HIT_DISTANCE_BUFFER, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_ALIVE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_DRAGGING, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_SAFARI, IS_TEXT_EDITABLE, J, K, L, LAST_MEDIA, LEFT_ARROW, M, MAC_ENTER, MAC_META, MAC_WK_CMD_LEFT, MAC_WK_CMD_RIGHT, MAX_RADIUS, MERGING, META, MUTE, N, NINE, NODE_TO_CONTAINER_G, NODE_TO_G, NODE_TO_INDEX, NODE_TO_PARENT, NS, NUMPAD_DIVIDE, NUMPAD_EIGHT, NUMPAD_FIVE, NUMPAD_FOUR, NUMPAD_MINUS, NUMPAD_MULTIPLY, NUMPAD_NINE, NUMPAD_ONE, NUMPAD_PERIOD, NUMPAD_PLUS, NUMPAD_SEVEN, NUMPAD_SIX, NUMPAD_THREE, NUMPAD_TWO, NUMPAD_ZERO, NUM_CENTER, NUM_LOCK, O, ONE, OPEN_SQUARE_BRACKET, P, PAGE_DOWN, PAGE_UP, PATH_REFS, PAUSE, PERIOD, PLUS_SIGN, POINTER_BUTTON, PRESS_AND_MOVE_BUFFER, PRINT_SCREEN, Path, PlaitBoard, PlaitBoardComponent, PlaitContextService, PlaitElement, PlaitGroupElement, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RESIZE_CURSORS, RESIZE_HANDLE_CLASS_NAME, RIGHT_ARROW, ROTATE_HANDLE_CLASS_NAME, RectangleClient, ResizeCursorClass, RetroThemeColor, RgbaToHEX, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SELECTION_RECTANGLE_CLASS_NAME, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SNAPPING_STROKE_WIDTH, SNAP_TOLERANCE, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, WritableClipboardOperationType, WritableClipboardType, X, Y, Z, ZERO, addClipboardContext, addSelectedElement, approximately, arrowPoints, buildPlaitHtml, cacheMovingElements, cacheSelectedElements, cacheSelectedElementsWithGroup, cacheSelectedElementsWithGroupOnShift, calcNewViewBox, canAddGroup, canRemoveGroup, canSetZIndex, catmullRomFitting, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createClipboardContext, createDebugGenerator, createFakeEvent, createForeignObject, createG, createGroup, createGroupRectangleG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createTestingBoard, createText, createTouchEvent, debounce, degreesToRadians, deleteFragment, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawDashedLines, drawEntireActiveRectangleG, drawLine, drawLinearPath, drawPendingNodesG, drawPointSnapLines, drawRectangle, drawRoundRectangle, drawSolidLines, duplicateElements, fakeNodeWeakMap, filterSelectedGroups, findElements, findIndex, findLastIndex, getAllElementsInGroup, getAllMoveOptions, getAngleBetweenPoints, getAngleByElement, getBarPoint, getBoardRectangle, getClipboardData, getClipboardFromHtml, getCrossingPointsBetweenEllipseAndSegment, getDataTransferClipboard, getDataTransferClipboardText, getEditingGroup, getElementById, getElementHostBBox, getElementsInGroup, getElementsInGroupByElement, getElementsIndices, getEllipseTangentSlope, getGroupByElement, getHighestGroup, getHighestIndexOfElement, getHighestSelectedElements, getHighestSelectedGroup, getHighestSelectedGroups, getHitElementByPoint, getHitElementsBySelection, getHitSelectedElements, getIsRecursionFunc, getMinPointDelta, getMovingElements, getNearestDelta, getNearestPointBetweenPointAndEllipse, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getNearestPointRectangle, getOffsetAfterRotate, getOneMoveOptions, getProbablySupportsClipboardRead, getProbablySupportsClipboardWrite, getProbablySupportsClipboardWriteText, getRealScrollBarWidth, getRectangleByAngle, getRectangleByElements, getRectangleByGroup, getRotatedBoundingRectangle, getSelectedElements, getSelectedGroups, getSelectedIsolatedElements, getSelectedIsolatedElementsCanAddToGroup, getSelectedTargetElements, getSelectionAngle, getSnapRectangles, getTemporaryElements, getTemporaryRef, getTripleAxis, getValidElements, getVectorFromPointAndSlope, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, handleTouchTarget, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hasSameAngle, hasSelectedElementsInSameGroup, hasValidAngle, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isAxisChangedByAngle, isContextmenu, isDOMElement, isDOMNode, isDebug, isDragging, isFromScrolling, isFromViewportChange, isHandleSelection, isInPlaitBoard, isIndicesContinuous, isLineHitLine, isMainPointer, isMovingElements, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedAllElementsInGroup, isSelectedElement, isSelectedElementOrGroup, isSelectionMoving, isSetSelectionOperation, isSetViewportOperation, isSnapPoint, moveElementsToNewPath, moveElementsToNewPathAfterAddGroup, nonGroupInHighestSelectedElements, normalizeAngle, normalizePoint, preventTouchMove, radiansToDegrees, removeMovingElements, removeSelectedElement, rotate, rotateAntiPointsByElement, rotateElements, rotatePoints, rotatePointsByElement, rotatedDataPoints, scrollToRectangle, setAngleForG, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setDragging, setFragment, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectedElementsWithGroup, setSelectionMoving, setStrokeLinecap, shouldClear, shouldMerge, shouldSave, sortElements, stripHtml, temporaryDisableSelection, throttleRAF, toDomPrecision, toFixed, toHostPoint, toHostPointFromViewBoxPoint, toImage, toScreenPointFromHostPoint, toViewBoxPoint, toViewBoxPoints, uniqueById, updateForeignObject, updateForeignObjectWidth, updatePoints, updateViewportByScrolling, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withArrowMoving, withMoving, withOptions, withSelection };
6354
+ export { A, ACTIVE_MOVING_CLASS_NAME, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, BOARD_TO_AFTER_CHANGE, BOARD_TO_COMPONENT, BOARD_TO_ELEMENT_HOST, BOARD_TO_HOST, BOARD_TO_IS_SELECTION_MOVING, BOARD_TO_MOVING_ELEMENT, BOARD_TO_MOVING_POINT, BOARD_TO_MOVING_POINT_IN_BOARD, BOARD_TO_ON_CHANGE, BOARD_TO_ROUGH_SVG, BOARD_TO_SELECTED_ELEMENT, BOARD_TO_TEMPORARY_ELEMENTS, BOARD_TO_TOUCH_REF, BOARD_TO_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, CursorClass, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DebugGenerator, DefaultThemeColor, Direction, E, EIGHT, ELEMENT_TO_REF, END, ENTER, EQUALS, ESCAPE, ElementFlavour, F, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, FF_EQUALS, FF_MINUS, FF_MUTE, FF_SEMICOLON, FF_VOLUME_DOWN, FF_VOLUME_UP, FIRST_MEDIA, FIVE, FLUSHING, FOUR, G, H, HIT_DISTANCE_BUFFER, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_ALIVE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_DRAGGING, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_SAFARI, IS_TEXT_EDITABLE, J, K, L, LAST_MEDIA, LEFT_ARROW, M, MAC_ENTER, MAC_META, MAC_WK_CMD_LEFT, MAC_WK_CMD_RIGHT, MAX_RADIUS, MERGING, META, MUTE, N, NINE, NODE_TO_CONTAINER_G, NODE_TO_G, NODE_TO_INDEX, NODE_TO_PARENT, NS, NUMPAD_DIVIDE, NUMPAD_EIGHT, NUMPAD_FIVE, NUMPAD_FOUR, NUMPAD_MINUS, NUMPAD_MULTIPLY, NUMPAD_NINE, NUMPAD_ONE, NUMPAD_PERIOD, NUMPAD_PLUS, NUMPAD_SEVEN, NUMPAD_SIX, NUMPAD_THREE, NUMPAD_TWO, NUMPAD_ZERO, NUM_CENTER, NUM_LOCK, O, ONE, OPEN_SQUARE_BRACKET, P, PAGE_DOWN, PAGE_UP, PATH_REFS, PAUSE, PERIOD, PLUS_SIGN, POINTER_BUTTON, PRESS_AND_MOVE_BUFFER, PRINT_SCREEN, Path, PlaitBoard, PlaitBoardComponent, PlaitContextService, PlaitElement, PlaitGroupElement, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitNode, PlaitOperation, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RESIZE_CURSORS, RESIZE_HANDLE_CLASS_NAME, RIGHT_ARROW, ROTATE_HANDLE_CLASS_NAME, RectangleClient, ResizeCursorClass, RetroThemeColor, RgbaToHEX, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SELECTION_RECTANGLE_CLASS_NAME, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SNAPPING_STROKE_WIDTH, SNAP_TOLERANCE, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, WritableClipboardOperationType, WritableClipboardType, X, Y, Z, ZERO, addClipboardContext, addSelectedElement, approximately, arrowPoints, buildPlaitHtml, cacheMovingElements, cacheSelectedElements, cacheSelectedElementsWithGroup, cacheSelectedElementsWithGroupOnShift, calcNewViewBox, canAddGroup, canRemoveGroup, canSetZIndex, catmullRomFitting, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createClipboardContext, createDebugGenerator, createFakeEvent, createForeignObject, createG, createGroup, createGroupRectangleG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createTestingBoard, createText, createTouchEvent, debounce, degreesToRadians, deleteFragment, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawDashedLines, drawEntireActiveRectangleG, drawLine, drawLinearPath, drawPendingNodesG, drawPointSnapLines, drawRectangle, drawRoundRectangle, drawSolidLines, duplicateElements, fakeNodeWeakMap, filterSelectedGroups, findElements, findIndex, findLastIndex, getAllElementsInGroup, getAllMoveOptions, getAngleBetweenPoints, getAngleByElement, getBarPoint, getBoardRectangle, getBoundingRectangleByElements, getClipboardData, getClipboardFromHtml, getCrossingPointsBetweenEllipseAndSegment, getDataTransferClipboard, getDataTransferClipboardText, getEditingGroup, getElementById, getElementHostBBox, getElementsInGroup, getElementsInGroupByElement, getElementsIndices, getEllipseTangentSlope, getGroupByElement, getHighestGroup, getHighestIndexOfElement, getHighestSelectedElements, getHighestSelectedGroup, getHighestSelectedGroups, getHitElementByPoint, getHitElementsBySelection, getHitSelectedElements, getIsRecursionFunc, getMinPointDelta, getMovingElements, getNearestDelta, getNearestPointBetweenPointAndEllipse, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getNearestPointRectangle, getOffsetAfterRotate, getOneMoveOptions, getProbablySupportsClipboardRead, getProbablySupportsClipboardWrite, getProbablySupportsClipboardWriteText, getRealScrollBarWidth, getRectangleByAngle, getRectangleByElements, getRectangleByGroup, getRotatedBoundingRectangle, getSelectedElements, getSelectedGroups, getSelectedIsolatedElements, getSelectedIsolatedElementsCanAddToGroup, getSelectedTargetElements, getSelectionAngle, getSnapRectangles, getTemporaryElements, getTemporaryRef, getTripleAxis, getValidElements, getVectorFromPointAndSlope, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, handleTouchTarget, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hasSameAngle, hasSelectedElementsInSameGroup, hasValidAngle, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isAxisChangedByAngle, isContextmenu, isDOMElement, isDOMNode, isDebug, isDragging, isFromScrolling, isFromViewportChange, isHandleSelection, isHitElement, isHitSelectedRectangle, isInPlaitBoard, isIndicesContinuous, isLineHitLine, isMainPointer, isMovingElements, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedAllElementsInGroup, isSelectedElement, isSelectedElementOrGroup, isSelectionMoving, isSetSelectionOperation, isSetViewportOperation, isSnapPoint, moveElementsToNewPath, moveElementsToNewPathAfterAddGroup, nonGroupInHighestSelectedElements, normalizeAngle, normalizePoint, preventTouchMove, radiansToDegrees, removeMovingElements, removeSelectedElement, rotate, rotateAntiPointsByElement, rotateElements, rotatePoints, rotatePointsByElement, rotatedDataPoints, scrollToRectangle, setAngleForG, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setDragging, setFragment, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectedElementsWithGroup, setSelectionMoving, setStrokeLinecap, shouldClear, shouldMerge, shouldSave, sortElements, stripHtml, temporaryDisableSelection, throttleRAF, toDomPrecision, toFixed, toHostPoint, toHostPointFromViewBoxPoint, toImage, toScreenPointFromHostPoint, toViewBoxPoint, toViewBoxPoints, uniqueById, updateForeignObject, updateForeignObjectWidth, updatePoints, updateViewportByScrolling, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withArrowMoving, withMoving, withOptions, withSelection };
6349
6355
  //# sourceMappingURL=plait-core.mjs.map