@plait/core 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +1 -0
  2. package/board/board.component.d.ts +37 -0
  3. package/core/base/detector.d.ts +7 -0
  4. package/core/element/element.component.d.ts +27 -0
  5. package/esm2020/board/board.component.mjs +211 -0
  6. package/esm2020/core/base/detector.mjs +2 -0
  7. package/esm2020/core/element/element.component.mjs +70 -0
  8. package/esm2020/interfaces/board.mjs +2 -0
  9. package/esm2020/interfaces/cursor.mjs +6 -0
  10. package/esm2020/interfaces/custom-types.mjs +5 -0
  11. package/esm2020/interfaces/element-context.mjs +2 -0
  12. package/esm2020/interfaces/element.mjs +2 -0
  13. package/esm2020/interfaces/graph.mjs +2 -0
  14. package/esm2020/interfaces/index.mjs +14 -0
  15. package/esm2020/interfaces/node.mjs +20 -0
  16. package/esm2020/interfaces/operation.mjs +7 -0
  17. package/esm2020/interfaces/path.mjs +139 -0
  18. package/esm2020/interfaces/plugin.mjs +2 -0
  19. package/esm2020/interfaces/point.mjs +2 -0
  20. package/esm2020/interfaces/selection.mjs +2 -0
  21. package/esm2020/interfaces/viewport.mjs +10 -0
  22. package/esm2020/plait-core.mjs +5 -0
  23. package/esm2020/plait.module.mjs +19 -0
  24. package/esm2020/plugins/create-board.mjs +42 -0
  25. package/esm2020/plugins/with-board.mjs +13 -0
  26. package/esm2020/plugins/with-selection.mjs +39 -0
  27. package/esm2020/public-api.mjs +11 -0
  28. package/esm2020/transfroms/general.mjs +139 -0
  29. package/esm2020/transfroms/index.mjs +11 -0
  30. package/esm2020/transfroms/node.mjs +36 -0
  31. package/esm2020/transfroms/selection.mjs +8 -0
  32. package/esm2020/transfroms/viewport.mjs +8 -0
  33. package/esm2020/utils/board.mjs +25 -0
  34. package/esm2020/utils/dom.mjs +22 -0
  35. package/esm2020/utils/environment.mjs +13 -0
  36. package/esm2020/utils/graph.mjs +11 -0
  37. package/esm2020/utils/helper.mjs +4 -0
  38. package/esm2020/utils/hotkeys.mjs +99 -0
  39. package/esm2020/utils/id-creator.mjs +11 -0
  40. package/esm2020/utils/index.mjs +10 -0
  41. package/esm2020/utils/math.mjs +37 -0
  42. package/esm2020/utils/weak-maps.mjs +6 -0
  43. package/fesm2015/plait-core.mjs +969 -0
  44. package/fesm2015/plait-core.mjs.map +1 -0
  45. package/fesm2020/plait-core.mjs +984 -0
  46. package/fesm2020/plait-core.mjs.map +1 -0
  47. package/interfaces/board.d.ts +32 -0
  48. package/interfaces/cursor.d.ts +5 -0
  49. package/interfaces/custom-types.d.ts +9 -0
  50. package/interfaces/element-context.d.ts +4 -0
  51. package/interfaces/element.d.ts +6 -0
  52. package/interfaces/graph.d.ts +6 -0
  53. package/interfaces/index.d.ts +13 -0
  54. package/interfaces/node.d.ts +13 -0
  55. package/interfaces/operation.d.ts +39 -0
  56. package/interfaces/path.d.ts +32 -0
  57. package/interfaces/plugin.d.ts +2 -0
  58. package/interfaces/point.d.ts +1 -0
  59. package/interfaces/selection.d.ts +5 -0
  60. package/interfaces/viewport.d.ts +13 -0
  61. package/package.json +31 -0
  62. package/plait-core.d.ts +5 -0
  63. package/plait.module.d.ts +9 -0
  64. package/plugins/create-board.d.ts +3 -0
  65. package/plugins/with-board.d.ts +2 -0
  66. package/plugins/with-selection.d.ts +2 -0
  67. package/public-api.d.ts +7 -0
  68. package/transfroms/general.d.ts +6 -0
  69. package/transfroms/index.d.ts +5 -0
  70. package/transfroms/node.d.ts +14 -0
  71. package/transfroms/selection.d.ts +7 -0
  72. package/transfroms/viewport.d.ts +7 -0
  73. package/utils/board.d.ts +10 -0
  74. package/utils/dom.d.ts +6 -0
  75. package/utils/environment.d.ts +7 -0
  76. package/utils/graph.d.ts +3 -0
  77. package/utils/helper.d.ts +1 -0
  78. package/utils/hotkeys.d.ts +32 -0
  79. package/utils/id-creator.d.ts +1 -0
  80. package/utils/index.d.ts +9 -0
  81. package/utils/math.d.ts +2 -0
  82. package/utils/weak-maps.d.ts +6 -0
@@ -0,0 +1,984 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, ChangeDetectionStrategy, Input, EventEmitter, HostBinding, ViewChild, Output, NgModule } from '@angular/core';
3
+ import produce, { createDraft, finishDraft, isDraft } from 'immer';
4
+ import { Subject, fromEvent } from 'rxjs';
5
+ import { takeUntil, filter } from 'rxjs/operators';
6
+ import rough from 'roughjs/bin/rough';
7
+ import * as i2 from '@angular/common';
8
+ import { BrowserModule } from '@angular/platform-browser';
9
+ import { isKeyHotkey } from 'is-hotkey';
10
+
11
+ // record richtext type status
12
+ const FLUSHING = new WeakMap();
13
+ const IS_TEXT_EDITABLE = new WeakMap();
14
+ const BOARD_TO_ON_CHANGE = new WeakMap();
15
+ const HOST_TO_ROUGH_SVG = new WeakMap();
16
+
17
+ function isNullOrUndefined(value) {
18
+ return value === null || value === undefined;
19
+ }
20
+
21
+ const Viewport = {
22
+ isViewport: (value) => {
23
+ return (!isNullOrUndefined(value.offsetX) &&
24
+ !isNullOrUndefined(value.offsetY) &&
25
+ !isNullOrUndefined(value.zoom) &&
26
+ !isNullOrUndefined(value.viewBackgroundColor));
27
+ }
28
+ };
29
+
30
+ const Path = {
31
+ parent(path) {
32
+ if (path.length === 0) {
33
+ throw new Error(`Cannot get the parent path of the root path [${path}].`);
34
+ }
35
+ return path.slice(0, -1);
36
+ },
37
+ next(path) {
38
+ if (path.length === 0) {
39
+ throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
40
+ }
41
+ const last = path[path.length - 1];
42
+ return path.slice(0, -1).concat(last + 1);
43
+ },
44
+ /**
45
+ * Check if a path is an ancestor of another.
46
+ */
47
+ isAncestor(path, another) {
48
+ return path.length < another.length && Path.compare(path, another) === 0;
49
+ },
50
+ /**
51
+ * Compare a path to another, returning an integer indicating whether the path
52
+ * was before, at, or after the other.
53
+ *
54
+ * Note: Two paths of unequal length can still receive a `0` result if one is
55
+ * directly above or below the other. If you want exact matching, use
56
+ * [[Path.equals]] instead.
57
+ */
58
+ compare(path, another) {
59
+ const min = Math.min(path.length, another.length);
60
+ for (let i = 0; i < min; i++) {
61
+ if (path[i] < another[i])
62
+ return -1;
63
+ if (path[i] > another[i])
64
+ return 1;
65
+ }
66
+ return 0;
67
+ },
68
+ /**
69
+ * Check if a path is exactly equal to another.
70
+ */
71
+ equals(path, another) {
72
+ return path.length === another.length && path.every((n, i) => n === another[i]);
73
+ },
74
+ /**
75
+ * Check if a path ends before one of the indexes in another.
76
+ */
77
+ endsBefore(path, another) {
78
+ const i = path.length - 1;
79
+ const as = path.slice(0, i);
80
+ const bs = another.slice(0, i);
81
+ const av = path[i];
82
+ const bv = another[i];
83
+ return Path.equals(as, bs) && av < bv;
84
+ },
85
+ /**
86
+ * Check if a path is a sibling of another.
87
+ */
88
+ isSibling(path, another) {
89
+ if (path.length !== another.length) {
90
+ return false;
91
+ }
92
+ const as = path.slice(0, -1);
93
+ const bs = another.slice(0, -1);
94
+ const al = path[path.length - 1];
95
+ const bl = another[another.length - 1];
96
+ return al !== bl && Path.equals(as, bs);
97
+ },
98
+ transform(path, operation) {
99
+ return produce(path, p => {
100
+ // PERF: Exit early if the operation is guaranteed not to have an effect.
101
+ if (!path || path?.length === 0) {
102
+ return;
103
+ }
104
+ if (p === null) {
105
+ return null;
106
+ }
107
+ switch (operation.type) {
108
+ case 'insert_node': {
109
+ const { path: op } = operation;
110
+ if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
111
+ p[op.length - 1] += 1;
112
+ }
113
+ break;
114
+ }
115
+ case 'remove_node': {
116
+ const { path: op } = operation;
117
+ if (Path.equals(op, p) || Path.isAncestor(op, p)) {
118
+ return null;
119
+ }
120
+ else if (Path.endsBefore(op, p)) {
121
+ p[op.length - 1] -= 1;
122
+ }
123
+ break;
124
+ }
125
+ case 'move_node': {
126
+ const { path: op, newPath: onp } = operation;
127
+ // If the old and new path are the same, it's a no-op.
128
+ if (Path.equals(op, onp)) {
129
+ return;
130
+ }
131
+ if (Path.isAncestor(op, p) || Path.equals(op, p)) {
132
+ const copy = onp.slice();
133
+ // op.length <= onp.length is different for slate
134
+ // resolve drag from [0, 0] to [0, 3] issue
135
+ if (Path.endsBefore(op, onp) && op.length <= onp.length) {
136
+ copy[op.length - 1] -= 1;
137
+ }
138
+ return copy.concat(p.slice(op.length));
139
+ }
140
+ else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
141
+ if (Path.endsBefore(op, p)) {
142
+ p[op.length - 1] -= 1;
143
+ }
144
+ else {
145
+ p[op.length - 1] += 1;
146
+ }
147
+ }
148
+ else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
149
+ if (Path.endsBefore(op, p)) {
150
+ p[op.length - 1] -= 1;
151
+ }
152
+ p[onp.length - 1] += 1;
153
+ }
154
+ else if (Path.endsBefore(op, p)) {
155
+ if (Path.equals(onp, p)) {
156
+ p[onp.length - 1] += 1;
157
+ }
158
+ p[op.length - 1] -= 1;
159
+ }
160
+ break;
161
+ }
162
+ }
163
+ return null;
164
+ });
165
+ }
166
+ };
167
+
168
+ const PlaitNode = {
169
+ parent: (board, path) => {
170
+ const parentPath = Path.parent(path);
171
+ const p = PlaitNode.get(board, parentPath);
172
+ return p;
173
+ },
174
+ get(board, path) {
175
+ let node = board;
176
+ for (let i = 0; i < path.length; i++) {
177
+ const p = path[i];
178
+ if (!node || !node.children || !node.children[p]) {
179
+ throw new Error(`Cannot find a descendant at path [${path}]`);
180
+ }
181
+ node = node.children[p];
182
+ }
183
+ return node;
184
+ }
185
+ };
186
+
187
+ const applyToDraft = (board, selection, viewport, op) => {
188
+ switch (op.type) {
189
+ case 'insert_node': {
190
+ const { path, node } = op;
191
+ const parent = PlaitNode.parent(board, path);
192
+ const index = path[path.length - 1];
193
+ if (!parent.children || index > parent.children.length) {
194
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
195
+ }
196
+ parent.children.splice(index, 0, node);
197
+ break;
198
+ }
199
+ case 'remove_node': {
200
+ const { path } = op;
201
+ const parent = PlaitNode.parent(board, path);
202
+ const index = path[path.length - 1];
203
+ if (!parent.children || index > parent.children.length) {
204
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
205
+ }
206
+ parent.children.splice(index, 1);
207
+ break;
208
+ }
209
+ case 'move_node': {
210
+ const { path, newPath } = op;
211
+ if (Path.isAncestor(path, newPath)) {
212
+ throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
213
+ }
214
+ const node = PlaitNode.get(board, path);
215
+ const parent = PlaitNode.parent(board, path);
216
+ const index = path[path.length - 1];
217
+ // This is tricky, but since the `path` and `newPath` both refer to
218
+ // the same snapshot in time, there's a mismatch. After either
219
+ // removing the original position, the second step's path can be out
220
+ // of date. So instead of using the `op.newPath` directly, we
221
+ // transform `op.path` to ascertain what the `newPath` would be after
222
+ // the operation was applied.
223
+ parent.children?.splice(index, 1);
224
+ const truePath = Path.transform(path, op);
225
+ const newParent = PlaitNode.get(board, Path.parent(truePath));
226
+ const newIndex = truePath[truePath.length - 1];
227
+ newParent.children?.splice(newIndex, 0, node);
228
+ break;
229
+ }
230
+ case 'set_node': {
231
+ const { path, properties, newProperties } = op;
232
+ if (path.length === 0) {
233
+ throw new Error(`Cannot set properties on the root node!`);
234
+ }
235
+ const node = PlaitNode.get(board, path);
236
+ for (const key in newProperties) {
237
+ const value = newProperties[key];
238
+ if (value == null) {
239
+ delete node[key];
240
+ }
241
+ else {
242
+ node[key] = value;
243
+ }
244
+ }
245
+ // properties that were previously defined, but are now missing, must be deleted
246
+ for (const key in properties) {
247
+ if (!newProperties.hasOwnProperty(key)) {
248
+ delete node[key];
249
+ }
250
+ }
251
+ break;
252
+ }
253
+ case 'set_viewport': {
254
+ const { newProperties } = op;
255
+ if (newProperties == null) {
256
+ viewport = newProperties;
257
+ }
258
+ else {
259
+ if (viewport == null) {
260
+ if (!Viewport.isViewport(newProperties)) {
261
+ throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
262
+ }
263
+ viewport = { ...newProperties };
264
+ }
265
+ for (const key in newProperties) {
266
+ const value = newProperties[key];
267
+ if (value == null) {
268
+ delete viewport[key];
269
+ }
270
+ else {
271
+ viewport[key] = value;
272
+ }
273
+ }
274
+ }
275
+ break;
276
+ }
277
+ case 'set_selection': {
278
+ const { newProperties } = op;
279
+ if (newProperties == null) {
280
+ selection = newProperties;
281
+ }
282
+ else {
283
+ if (selection === null) {
284
+ selection = op.newProperties;
285
+ }
286
+ else {
287
+ selection.anchor = newProperties.anchor;
288
+ selection.focus = newProperties.focus;
289
+ }
290
+ }
291
+ break;
292
+ }
293
+ }
294
+ return { selection, viewport };
295
+ };
296
+ const GeneralTransforms = {
297
+ /**
298
+ * Transform the board by an operation.
299
+ */
300
+ transform(board, op) {
301
+ board.children = createDraft(board.children);
302
+ let viewport = board.viewport && createDraft(board.viewport);
303
+ let selection = board.selection && createDraft(board.selection);
304
+ try {
305
+ const state = applyToDraft(board, selection, viewport, op);
306
+ viewport = state.viewport;
307
+ selection = state.selection;
308
+ }
309
+ finally {
310
+ board.children = finishDraft(board.children);
311
+ if (selection) {
312
+ board.selection = isDraft(selection) ? finishDraft(selection) : selection;
313
+ }
314
+ else {
315
+ board.selection = null;
316
+ }
317
+ board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
318
+ }
319
+ }
320
+ };
321
+
322
+ function insertNode(board, node, path) {
323
+ const operation = { type: 'insert_node', node, path };
324
+ board.apply(operation);
325
+ }
326
+ function setNode(board, props, path) {
327
+ const properties = {};
328
+ const newProperties = {};
329
+ const node = PlaitNode.get(board, path);
330
+ for (const k in props) {
331
+ if (node[k] !== props[k]) {
332
+ if (node.hasOwnProperty(k)) {
333
+ properties[k] = node[k];
334
+ }
335
+ if (props[k] != null)
336
+ newProperties[k] = props[k];
337
+ }
338
+ }
339
+ const operation = { type: 'set_node', properties, newProperties, path };
340
+ board.apply(operation);
341
+ }
342
+ function removeNode(board, path) {
343
+ const operation = { type: 'remove_node', path };
344
+ board.apply(operation);
345
+ }
346
+ function moveNode(board, path, newPath) {
347
+ const operation = { type: 'move_node', path, newPath };
348
+ board.apply(operation);
349
+ }
350
+ const NodeTransforms = {
351
+ insertNode,
352
+ setNode,
353
+ removeNode,
354
+ moveNode
355
+ };
356
+
357
+ function setSelection(board, selection) {
358
+ const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
359
+ board.apply(operation);
360
+ }
361
+ const SelectionTransforms = {
362
+ setSelection
363
+ };
364
+
365
+ function setViewport(board, viewport) {
366
+ const operation = { type: 'set_viewport', properties: board.viewport, newProperties: viewport };
367
+ board.apply(operation);
368
+ }
369
+ const ViewportTransforms = {
370
+ setViewport
371
+ };
372
+
373
+ const Transforms = {
374
+ ...GeneralTransforms,
375
+ ...ViewportTransforms,
376
+ ...SelectionTransforms,
377
+ ...NodeTransforms
378
+ };
379
+
380
+ var BaseCursorStatus;
381
+ (function (BaseCursorStatus) {
382
+ BaseCursorStatus["move"] = "move";
383
+ BaseCursorStatus["select"] = "select";
384
+ })(BaseCursorStatus || (BaseCursorStatus = {}));
385
+
386
+ function createBoard(host, children) {
387
+ const board = {
388
+ host,
389
+ viewport: {
390
+ offsetX: 0,
391
+ offsetY: 0,
392
+ zoom: 1,
393
+ viewBackgroundColor: '#000'
394
+ },
395
+ children,
396
+ operations: [],
397
+ selection: { anchor: [0, -1], focus: [-1, -1] },
398
+ cursor: BaseCursorStatus.select,
399
+ apply: (operation) => {
400
+ board.operations.push(operation);
401
+ Transforms.transform(board, operation);
402
+ if (!FLUSHING.get(board)) {
403
+ FLUSHING.set(board, true);
404
+ Promise.resolve().then(() => {
405
+ FLUSHING.set(board, false);
406
+ board.onChange();
407
+ board.operations = [];
408
+ });
409
+ }
410
+ },
411
+ onChange: () => { },
412
+ mousedown: (event) => { },
413
+ mouseup: (event) => { },
414
+ mousemove: (event) => { },
415
+ keydown: (event) => { },
416
+ keyup: (event) => { },
417
+ dblclick: (event) => { },
418
+ drawElement: (context) => [],
419
+ redrawElement: (context, changes) => [],
420
+ destroyElement: () => { }
421
+ };
422
+ return board;
423
+ }
424
+
425
+ function withBoard(board) {
426
+ const { onChange, mouseup } = board;
427
+ board.onChange = () => {
428
+ const onContextChange = BOARD_TO_ON_CHANGE.get(board);
429
+ if (onContextChange) {
430
+ onContextChange();
431
+ }
432
+ onChange();
433
+ };
434
+ return board;
435
+ }
436
+
437
+ const NS = 'http://www.w3.org/2000/svg';
438
+ function toPoint(x, y, container) {
439
+ const rect = container.getBoundingClientRect();
440
+ return [x - rect.x, y - rect.y];
441
+ }
442
+ function createG() {
443
+ const newG = document.createElementNS(NS, 'g');
444
+ return newG;
445
+ }
446
+ function createSVG() {
447
+ const svg = document.createElementNS(NS, 'svg');
448
+ return svg;
449
+ }
450
+ function createText(x, y, fill, textContent) {
451
+ var text = document.createElementNS(NS, 'text');
452
+ text.setAttribute('x', `${x}`);
453
+ text.setAttribute('y', `${y}`);
454
+ text.setAttribute('fill', fill);
455
+ text.textContent = textContent;
456
+ return text;
457
+ }
458
+
459
+ function toRectangleClient(points) {
460
+ const xArray = points.map(ele => ele[0]);
461
+ const yArray = points.map(ele => ele[1]);
462
+ const xMin = Math.min(...xArray);
463
+ const xMax = Math.max(...xArray);
464
+ const yMin = Math.min(...yArray);
465
+ const yMax = Math.max(...yArray);
466
+ const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
467
+ return rect;
468
+ }
469
+
470
+ function withSelection(board) {
471
+ const { mousedown, mousemove, mouseup } = board;
472
+ let start = null;
473
+ let end = null;
474
+ board.mousedown = (event) => {
475
+ // avoid select text when double click svg
476
+ if (!(event.target instanceof HTMLElement && event.target.closest('.richtext'))) {
477
+ event.preventDefault();
478
+ }
479
+ if (board.cursor === BaseCursorStatus.select) {
480
+ start = toPoint(event.x, event.y, board.host);
481
+ }
482
+ mousedown(event);
483
+ };
484
+ board.mousemove = (event) => {
485
+ const movedTarget = toPoint(event.x, event.y, board.host);
486
+ if (start) {
487
+ const rectangleClient = toRectangleClient([start, movedTarget]);
488
+ if (start && Math.hypot(rectangleClient.width, rectangleClient.height) > 5) {
489
+ end = movedTarget;
490
+ }
491
+ }
492
+ mousemove(event);
493
+ };
494
+ board.mouseup = (event) => {
495
+ if (start) {
496
+ Transforms.setSelection(board, { anchor: start, focus: start });
497
+ }
498
+ start = null;
499
+ end = null;
500
+ mouseup(event);
501
+ };
502
+ return board;
503
+ }
504
+
505
+ const isSetViewportOperation = (value) => {
506
+ return value.type === 'set_viewport';
507
+ };
508
+ const PlaitOperation = {
509
+ isSetViewportOperation
510
+ };
511
+
512
+ function transformPoints(board, points) {
513
+ const newPoints = points.map(point => {
514
+ return transformPoint(board, point);
515
+ });
516
+ return newPoints;
517
+ }
518
+ function transformPoint(board, point) {
519
+ const { width, height } = board.host.getBoundingClientRect();
520
+ const viewBox = getViewBox(board);
521
+ let x = (point[0] / width) * viewBox.width + viewBox.minX;
522
+ let y = (point[1] / height) * viewBox.height + viewBox.minY;
523
+ const newPoint = [x - board.viewport.offsetX, y - board.viewport.offsetY];
524
+ return newPoint;
525
+ }
526
+ function getViewBox(board) {
527
+ const { width, height } = board.host.getBoundingClientRect();
528
+ const scaleWidth = (board.viewport.zoom - 1) * width;
529
+ const scaleHeight = (board.viewport.zoom - 1) * height;
530
+ const viewBoxWidth = width - scaleWidth;
531
+ const viewBoxHeight = height - scaleHeight;
532
+ const minX = scaleWidth / 2;
533
+ const minY = scaleHeight / 2;
534
+ return { minX, minY: minY, width: viewBoxWidth, height: viewBoxHeight };
535
+ }
536
+
537
+ class PlaitElementComponent {
538
+ constructor(renderer2, viewContainerRef) {
539
+ this.renderer2 = renderer2;
540
+ this.viewContainerRef = viewContainerRef;
541
+ this.initialized = false;
542
+ this.selection = null;
543
+ }
544
+ ngOnInit() {
545
+ this.initialize();
546
+ this.transform(true);
547
+ this.drawElement();
548
+ }
549
+ initialize() {
550
+ this.initialized = true;
551
+ this.groupG = createG();
552
+ this.renderer2.setAttribute(this.groupG, 'plait-element-group', this.index.toString());
553
+ this.host.append(this.groupG);
554
+ }
555
+ transform(first = false) {
556
+ if (first && this.viewport.offsetX === 0 && this.viewport.offsetY === 0) {
557
+ return;
558
+ }
559
+ this.renderer2.setAttribute(this.groupG, 'transform', `translate(${this.viewport.offsetX} ${this.viewport.offsetY})`);
560
+ }
561
+ drawElement() {
562
+ const gArray = this.board.drawElement({ elementInstance: this });
563
+ gArray.forEach(g => {
564
+ this.groupG.appendChild(g);
565
+ });
566
+ }
567
+ ngOnChanges(changes) {
568
+ const viewport = changes['viewport'];
569
+ if (this.initialized && viewport) {
570
+ this.transform();
571
+ }
572
+ if (this.initialized) {
573
+ this.board.redrawElement({ elementInstance: this }, changes);
574
+ }
575
+ }
576
+ ngOnDestroy() {
577
+ this.board.destroyElement();
578
+ this.groupG.remove();
579
+ }
580
+ }
581
+ PlaitElementComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitElementComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Component });
582
+ PlaitElementComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: PlaitElementComponent, selector: "plait-element", inputs: { index: "index", element: "element", board: "board", viewport: "viewport", selection: "selection", host: "host" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
583
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitElementComponent, decorators: [{
584
+ type: Component,
585
+ args: [{
586
+ selector: 'plait-element',
587
+ template: '',
588
+ changeDetection: ChangeDetectionStrategy.OnPush
589
+ }]
590
+ }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ViewContainerRef }]; }, propDecorators: { index: [{
591
+ type: Input
592
+ }], element: [{
593
+ type: Input
594
+ }], board: [{
595
+ type: Input
596
+ }], viewport: [{
597
+ type: Input
598
+ }], selection: [{
599
+ type: Input
600
+ }], host: [{
601
+ type: Input
602
+ }] } });
603
+
604
+ class PlaitBoardComponent {
605
+ constructor(cdr, renderer2) {
606
+ this.cdr = cdr;
607
+ this.renderer2 = renderer2;
608
+ this.zoom = 100;
609
+ this.hostClass = `plait-board-container`;
610
+ this.destroy$ = new Subject();
611
+ this.plaitValue = [];
612
+ this.plaitPlugins = [];
613
+ this.plaitChange = new EventEmitter();
614
+ this.plaitBoardInitialized = new EventEmitter();
615
+ this.trackBy = (index, element) => {
616
+ return index;
617
+ };
618
+ }
619
+ get host() {
620
+ return this.svg.nativeElement;
621
+ }
622
+ ngOnInit() {
623
+ const roughSVG = rough.svg(this.host, { options: { roughness: 0, strokeWidth: 1 } });
624
+ HOST_TO_ROUGH_SVG.set(this.host, roughSVG);
625
+ this.initializePlugins();
626
+ this.initializeEvents();
627
+ this.updateViewport();
628
+ BOARD_TO_ON_CHANGE.set(this.board, () => {
629
+ this.cdr.detectChanges();
630
+ const changeEvent = {
631
+ children: this.board.children,
632
+ operations: this.board.operations,
633
+ viewport: this.board.viewport,
634
+ selection: this.board.selection
635
+ };
636
+ this.plaitChange.emit(changeEvent);
637
+ // update viewBox
638
+ if (this.board.operations.some(op => PlaitOperation.isSetViewportOperation(op))) {
639
+ this.updateViewport();
640
+ }
641
+ });
642
+ }
643
+ ngAfterViewInit() {
644
+ this.plaitBoardInitialized.emit(this.board);
645
+ }
646
+ initializePlugins() {
647
+ let board = withSelection(withBoard(createBoard(this.host, this.plaitValue)));
648
+ this.plaitPlugins.forEach(plugin => {
649
+ board = plugin(board);
650
+ });
651
+ this.board = board;
652
+ if (this.plaitViewport) {
653
+ this.board.viewport = this.plaitViewport;
654
+ }
655
+ }
656
+ initializeEvents() {
657
+ fromEvent(this.host, 'mousedown')
658
+ .pipe(takeUntil(this.destroy$))
659
+ .subscribe((event) => {
660
+ this.board.mousedown(event);
661
+ });
662
+ fromEvent(this.host, 'mousemove')
663
+ .pipe(takeUntil(this.destroy$))
664
+ .subscribe((event) => {
665
+ this.board.mousemove(event);
666
+ });
667
+ fromEvent(document, 'mouseup')
668
+ .pipe(takeUntil(this.destroy$))
669
+ .subscribe((event) => {
670
+ this.board.mouseup(event);
671
+ });
672
+ fromEvent(this.host, 'dblclick')
673
+ .pipe(takeUntil(this.destroy$))
674
+ .subscribe((event) => {
675
+ this.board.dblclick(event);
676
+ });
677
+ fromEvent(this.host, 'wheel')
678
+ .pipe(takeUntil(this.destroy$))
679
+ .subscribe((event) => {
680
+ event.preventDefault();
681
+ const viewport = this.board.viewport;
682
+ Transforms.setViewport(this.board, {
683
+ ...viewport,
684
+ offsetX: viewport?.offsetX - event.deltaX,
685
+ offsetY: viewport?.offsetY - event.deltaY
686
+ });
687
+ });
688
+ fromEvent(document, 'keydown')
689
+ .pipe(takeUntil(this.destroy$), filter(() => {
690
+ return !IS_TEXT_EDITABLE.get(this.board);
691
+ }))
692
+ .subscribe((event) => {
693
+ this.board?.keydown(event);
694
+ });
695
+ fromEvent(document, 'keyup')
696
+ .pipe(takeUntil(this.destroy$))
697
+ .subscribe((event) => {
698
+ this.board?.keyup(event);
699
+ });
700
+ window.onresize = () => {
701
+ const viewBoxModel = getViewBox(this.board);
702
+ const viewBoxValues = this.host.getAttribute('viewBox')?.split(',');
703
+ this.renderer2.setAttribute(this.host, 'viewBox', `${viewBoxValues[0].trim()}, ${viewBoxValues[1].trim()}, ${viewBoxModel.width}, ${viewBoxModel.height}`);
704
+ };
705
+ }
706
+ updateViewport() {
707
+ this.zoom = Math.floor(this.board.viewport.zoom * 100);
708
+ const viewBox = getViewBox(this.board);
709
+ this.renderer2.setAttribute(this.host, 'viewBox', `${viewBox.minX}, ${viewBox.minY}, ${viewBox.width}, ${viewBox.height}`);
710
+ }
711
+ // 放大
712
+ zoomIn(event) {
713
+ const viewport = this.board?.viewport;
714
+ Transforms.setViewport(this.board, {
715
+ ...viewport,
716
+ zoom: viewport.zoom + 0.1
717
+ });
718
+ }
719
+ // 缩小
720
+ zoomOut(event) {
721
+ const viewport = this.board?.viewport;
722
+ Transforms.setViewport(this.board, {
723
+ ...viewport,
724
+ zoom: viewport.zoom - 0.1
725
+ });
726
+ }
727
+ resetZoom(event) {
728
+ const viewport = this.board?.viewport;
729
+ Transforms.setViewport(this.board, {
730
+ ...viewport,
731
+ zoom: 1
732
+ });
733
+ }
734
+ ngOnDestroy() {
735
+ this.destroy$.next();
736
+ this.destroy$.complete();
737
+ HOST_TO_ROUGH_SVG.delete(this.host);
738
+ }
739
+ }
740
+ PlaitBoardComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitBoardComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
741
+ PlaitBoardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: PlaitBoardComponent, selector: "plait-board", inputs: { plaitValue: "plaitValue", plaitViewport: "plaitViewport", plaitPlugins: "plaitPlugins" }, outputs: { plaitChange: "plaitChange", plaitBoardInitialized: "plaitBoardInitialized" }, host: { properties: { "class": "this.hostClass" } }, viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true, static: true }], ngImport: i0, template: `
742
+ <svg #svg width="100%" height="100%"></svg>
743
+ <div class="plait-toolbar island zoom-toolbar">
744
+ <button class="item" (mousedown)="zoomOut($event)">-</button>
745
+ <button class="item zoom-value" (mousedown)="resetZoom($event)">{{ zoom }}%</button>
746
+ <button class="item" (mousedown)="zoomIn($event)">+</button>
747
+ </div>
748
+ <plait-element
749
+ *ngFor="let item of board.children; let index = index; trackBy: trackBy"
750
+ [index]="index"
751
+ [element]="item"
752
+ [board]="board"
753
+ [viewport]="board.viewport"
754
+ [selection]="board.selection"
755
+ [host]="host"
756
+ ></plait-element>
757
+ <ng-content></ng-content>
758
+ `, isInline: true, components: [{ type: PlaitElementComponent, selector: "plait-element", inputs: ["index", "element", "board", "viewport", "selection", "host"] }], directives: [{ type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
759
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitBoardComponent, decorators: [{
760
+ type: Component,
761
+ args: [{
762
+ selector: 'plait-board',
763
+ template: `
764
+ <svg #svg width="100%" height="100%"></svg>
765
+ <div class="plait-toolbar island zoom-toolbar">
766
+ <button class="item" (mousedown)="zoomOut($event)">-</button>
767
+ <button class="item zoom-value" (mousedown)="resetZoom($event)">{{ zoom }}%</button>
768
+ <button class="item" (mousedown)="zoomIn($event)">+</button>
769
+ </div>
770
+ <plait-element
771
+ *ngFor="let item of board.children; let index = index; trackBy: trackBy"
772
+ [index]="index"
773
+ [element]="item"
774
+ [board]="board"
775
+ [viewport]="board.viewport"
776
+ [selection]="board.selection"
777
+ [host]="host"
778
+ ></plait-element>
779
+ <ng-content></ng-content>
780
+ `,
781
+ changeDetection: ChangeDetectionStrategy.OnPush
782
+ }]
783
+ }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }]; }, propDecorators: { hostClass: [{
784
+ type: HostBinding,
785
+ args: ['class']
786
+ }], svg: [{
787
+ type: ViewChild,
788
+ args: ['svg', { static: true }]
789
+ }], plaitValue: [{
790
+ type: Input
791
+ }], plaitViewport: [{
792
+ type: Input
793
+ }], plaitPlugins: [{
794
+ type: Input
795
+ }], plaitChange: [{
796
+ type: Output
797
+ }], plaitBoardInitialized: [{
798
+ type: Output
799
+ }] } });
800
+
801
+ class PlaitModule {
802
+ }
803
+ PlaitModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
804
+ PlaitModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, declarations: [PlaitBoardComponent, PlaitElementComponent], imports: [BrowserModule], exports: [PlaitBoardComponent, PlaitElementComponent] });
805
+ PlaitModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, imports: [[BrowserModule]] });
806
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, decorators: [{
807
+ type: NgModule,
808
+ args: [{
809
+ declarations: [PlaitBoardComponent, PlaitElementComponent],
810
+ imports: [BrowserModule],
811
+ exports: [PlaitBoardComponent, PlaitElementComponent]
812
+ }]
813
+ }] });
814
+
815
+ const IS_IOS = typeof navigator !== 'undefined' &&
816
+ typeof window !== 'undefined' &&
817
+ /iPad|iPhone|iPod/.test(navigator.userAgent) &&
818
+ !window.MSStream;
819
+ const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
820
+ const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
821
+ const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
822
+ // "modern" Edge was released at 79.x
823
+ const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
824
+ const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
825
+ // Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
826
+ const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
827
+
828
+ /**
829
+ * Hotkey mappings for each platform.
830
+ */
831
+ const HOTKEYS = {
832
+ bold: 'mod+b',
833
+ compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
834
+ moveBackward: 'left',
835
+ moveForward: 'right',
836
+ moveUp: 'up',
837
+ moveDown: 'down',
838
+ moveWordBackward: 'ctrl+left',
839
+ moveWordForward: 'ctrl+right',
840
+ deleteBackward: 'shift?+backspace',
841
+ deleteForward: 'shift?+delete',
842
+ extendBackward: 'shift+left',
843
+ extendForward: 'shift+right',
844
+ italic: 'mod+i',
845
+ splitBlock: 'shift?+enter',
846
+ undo: 'mod+z'
847
+ };
848
+ const APPLE_HOTKEYS = {
849
+ moveLineBackward: 'opt+up',
850
+ moveLineForward: 'opt+down',
851
+ moveWordBackward: 'opt+left',
852
+ moveWordForward: 'opt+right',
853
+ deleteBackward: ['ctrl+backspace', 'ctrl+h'],
854
+ deleteForward: ['ctrl+delete', 'ctrl+d'],
855
+ deleteLineBackward: 'cmd+shift?+backspace',
856
+ deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
857
+ deleteWordBackward: 'opt+shift?+backspace',
858
+ deleteWordForward: 'opt+shift?+delete',
859
+ extendLineBackward: 'opt+shift+up',
860
+ extendLineForward: 'opt+shift+down',
861
+ redo: 'cmd+shift+z',
862
+ transposeCharacter: 'ctrl+t'
863
+ };
864
+ const WINDOWS_HOTKEYS = {
865
+ deleteWordBackward: 'ctrl+shift?+backspace',
866
+ deleteWordForward: 'ctrl+shift?+delete',
867
+ redo: ['ctrl+y', 'ctrl+shift+z']
868
+ };
869
+ /**
870
+ * Create a platform-aware hotkey checker.
871
+ */
872
+ const create = (key) => {
873
+ const generic = HOTKEYS[key];
874
+ const apple = APPLE_HOTKEYS[key];
875
+ const windows = WINDOWS_HOTKEYS[key];
876
+ const isGeneric = generic && isKeyHotkey(generic);
877
+ const isApple = apple && isKeyHotkey(apple);
878
+ const isWindows = windows && isKeyHotkey(windows);
879
+ return (event) => {
880
+ if (isGeneric && isGeneric(event)) {
881
+ return true;
882
+ }
883
+ if (IS_APPLE && isApple && isApple(event)) {
884
+ return true;
885
+ }
886
+ if (!IS_APPLE && isWindows && isWindows(event)) {
887
+ return true;
888
+ }
889
+ return false;
890
+ };
891
+ };
892
+ /**
893
+ * Hotkeys.
894
+ */
895
+ const hotkeys = {
896
+ isBold: create('bold'),
897
+ isCompose: create('compose'),
898
+ isMoveBackward: create('moveBackward'),
899
+ isMoveForward: create('moveForward'),
900
+ isMoveUp: create('moveUp'),
901
+ isMoveDown: create('moveDown'),
902
+ isDeleteBackward: create('deleteBackward'),
903
+ isDeleteForward: create('deleteForward'),
904
+ isDeleteLineBackward: create('deleteLineBackward'),
905
+ isDeleteLineForward: create('deleteLineForward'),
906
+ isDeleteWordBackward: create('deleteWordBackward'),
907
+ isDeleteWordForward: create('deleteWordForward'),
908
+ isExtendBackward: create('extendBackward'),
909
+ isExtendForward: create('extendForward'),
910
+ isExtendLineBackward: create('extendLineBackward'),
911
+ isExtendLineForward: create('extendLineForward'),
912
+ isItalic: create('italic'),
913
+ isMoveLineBackward: create('moveLineBackward'),
914
+ isMoveLineForward: create('moveLineForward'),
915
+ isMoveWordBackward: create('moveWordBackward'),
916
+ isMoveWordForward: create('moveWordForward'),
917
+ isRedo: create('redo'),
918
+ isSplitBlock: create('splitBlock'),
919
+ isTransposeCharacter: create('transposeCharacter'),
920
+ isUndo: create('undo')
921
+ };
922
+
923
+ function idCreator(length = 5) {
924
+ // remove numeral
925
+ const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
926
+ const maxPosition = $chars.length;
927
+ let key = '';
928
+ for (let i = 0; i < length; i++) {
929
+ key += $chars.charAt(Math.floor(Math.random() * maxPosition));
930
+ }
931
+ return key;
932
+ }
933
+
934
+ // https://stackoverflow.com/a/6853926/232122
935
+ function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
936
+ const A = x - x1;
937
+ const B = y - y1;
938
+ const C = x2 - x1;
939
+ const D = y2 - y1;
940
+ const dot = A * C + B * D;
941
+ const lenSquare = C * C + D * D;
942
+ let param = -1;
943
+ if (lenSquare !== 0) {
944
+ // in case of 0 length line
945
+ param = dot / lenSquare;
946
+ }
947
+ let xx, yy;
948
+ if (param < 0) {
949
+ xx = x1;
950
+ yy = y1;
951
+ }
952
+ else if (param > 1) {
953
+ xx = x2;
954
+ yy = y2;
955
+ }
956
+ else {
957
+ xx = x1 + param * C;
958
+ yy = y1 + param * D;
959
+ }
960
+ const dx = x - xx;
961
+ const dy = y - yy;
962
+ return Math.hypot(dx, dy);
963
+ }
964
+ function rotate(x1, y1, x2, y2, angle) {
965
+ // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
966
+ // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
967
+ // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
968
+ return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
969
+ }
970
+
971
+ /**
972
+ * Extendable Custom Types Interface
973
+ */
974
+
975
+ /*
976
+ * Public API Surface of plait
977
+ */
978
+
979
+ /**
980
+ * Generated bundle index. Do not edit.
981
+ */
982
+
983
+ export { BOARD_TO_ON_CHANGE, BaseCursorStatus, FLUSHING, HOST_TO_ROUGH_SVG, IS_APPLE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_SAFARI, IS_TEXT_EDITABLE, NS, Path, PlaitBoardComponent, PlaitElementComponent, PlaitModule, PlaitNode, PlaitOperation, Transforms, Viewport, createG, createSVG, createText, distanceBetweenPointAndSegment, getViewBox, hotkeys, idCreator, isNullOrUndefined, isSetViewportOperation, rotate, toPoint, toRectangleClient, transformPoint, transformPoints };
984
+ //# sourceMappingURL=plait-core.mjs.map