@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,969 @@
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 === null || path === void 0 ? void 0 : 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
+ var _a, _b;
189
+ switch (op.type) {
190
+ case 'insert_node': {
191
+ const { path, node } = op;
192
+ const parent = PlaitNode.parent(board, path);
193
+ const index = path[path.length - 1];
194
+ if (!parent.children || index > parent.children.length) {
195
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
196
+ }
197
+ parent.children.splice(index, 0, node);
198
+ break;
199
+ }
200
+ case 'remove_node': {
201
+ const { path } = op;
202
+ const parent = PlaitNode.parent(board, path);
203
+ const index = path[path.length - 1];
204
+ if (!parent.children || index > parent.children.length) {
205
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
206
+ }
207
+ parent.children.splice(index, 1);
208
+ break;
209
+ }
210
+ case 'move_node': {
211
+ const { path, newPath } = op;
212
+ if (Path.isAncestor(path, newPath)) {
213
+ throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
214
+ }
215
+ const node = PlaitNode.get(board, path);
216
+ const parent = PlaitNode.parent(board, path);
217
+ const index = path[path.length - 1];
218
+ // This is tricky, but since the `path` and `newPath` both refer to
219
+ // the same snapshot in time, there's a mismatch. After either
220
+ // removing the original position, the second step's path can be out
221
+ // of date. So instead of using the `op.newPath` directly, we
222
+ // transform `op.path` to ascertain what the `newPath` would be after
223
+ // the operation was applied.
224
+ (_a = parent.children) === null || _a === void 0 ? void 0 : _a.splice(index, 1);
225
+ const truePath = Path.transform(path, op);
226
+ const newParent = PlaitNode.get(board, Path.parent(truePath));
227
+ const newIndex = truePath[truePath.length - 1];
228
+ (_b = newParent.children) === null || _b === void 0 ? void 0 : _b.splice(newIndex, 0, node);
229
+ break;
230
+ }
231
+ case 'set_node': {
232
+ const { path, properties, newProperties } = op;
233
+ if (path.length === 0) {
234
+ throw new Error(`Cannot set properties on the root node!`);
235
+ }
236
+ const node = PlaitNode.get(board, path);
237
+ for (const key in newProperties) {
238
+ const value = newProperties[key];
239
+ if (value == null) {
240
+ delete node[key];
241
+ }
242
+ else {
243
+ node[key] = value;
244
+ }
245
+ }
246
+ // properties that were previously defined, but are now missing, must be deleted
247
+ for (const key in properties) {
248
+ if (!newProperties.hasOwnProperty(key)) {
249
+ delete node[key];
250
+ }
251
+ }
252
+ break;
253
+ }
254
+ case 'set_viewport': {
255
+ const { newProperties } = op;
256
+ if (newProperties == null) {
257
+ viewport = newProperties;
258
+ }
259
+ else {
260
+ if (viewport == null) {
261
+ if (!Viewport.isViewport(newProperties)) {
262
+ throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
263
+ }
264
+ viewport = Object.assign({}, newProperties);
265
+ }
266
+ for (const key in newProperties) {
267
+ const value = newProperties[key];
268
+ if (value == null) {
269
+ delete viewport[key];
270
+ }
271
+ else {
272
+ viewport[key] = value;
273
+ }
274
+ }
275
+ }
276
+ break;
277
+ }
278
+ case 'set_selection': {
279
+ const { newProperties } = op;
280
+ if (newProperties == null) {
281
+ selection = newProperties;
282
+ }
283
+ else {
284
+ if (selection === null) {
285
+ selection = op.newProperties;
286
+ }
287
+ else {
288
+ selection.anchor = newProperties.anchor;
289
+ selection.focus = newProperties.focus;
290
+ }
291
+ }
292
+ break;
293
+ }
294
+ }
295
+ return { selection, viewport };
296
+ };
297
+ const GeneralTransforms = {
298
+ /**
299
+ * Transform the board by an operation.
300
+ */
301
+ transform(board, op) {
302
+ board.children = createDraft(board.children);
303
+ let viewport = board.viewport && createDraft(board.viewport);
304
+ let selection = board.selection && createDraft(board.selection);
305
+ try {
306
+ const state = applyToDraft(board, selection, viewport, op);
307
+ viewport = state.viewport;
308
+ selection = state.selection;
309
+ }
310
+ finally {
311
+ board.children = finishDraft(board.children);
312
+ if (selection) {
313
+ board.selection = isDraft(selection) ? finishDraft(selection) : selection;
314
+ }
315
+ else {
316
+ board.selection = null;
317
+ }
318
+ board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
319
+ }
320
+ }
321
+ };
322
+
323
+ function insertNode(board, node, path) {
324
+ const operation = { type: 'insert_node', node, path };
325
+ board.apply(operation);
326
+ }
327
+ function setNode(board, props, path) {
328
+ const properties = {};
329
+ const newProperties = {};
330
+ const node = PlaitNode.get(board, path);
331
+ for (const k in props) {
332
+ if (node[k] !== props[k]) {
333
+ if (node.hasOwnProperty(k)) {
334
+ properties[k] = node[k];
335
+ }
336
+ if (props[k] != null)
337
+ newProperties[k] = props[k];
338
+ }
339
+ }
340
+ const operation = { type: 'set_node', properties, newProperties, path };
341
+ board.apply(operation);
342
+ }
343
+ function removeNode(board, path) {
344
+ const operation = { type: 'remove_node', path };
345
+ board.apply(operation);
346
+ }
347
+ function moveNode(board, path, newPath) {
348
+ const operation = { type: 'move_node', path, newPath };
349
+ board.apply(operation);
350
+ }
351
+ const NodeTransforms = {
352
+ insertNode,
353
+ setNode,
354
+ removeNode,
355
+ moveNode
356
+ };
357
+
358
+ function setSelection(board, selection) {
359
+ const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
360
+ board.apply(operation);
361
+ }
362
+ const SelectionTransforms = {
363
+ setSelection
364
+ };
365
+
366
+ function setViewport(board, viewport) {
367
+ const operation = { type: 'set_viewport', properties: board.viewport, newProperties: viewport };
368
+ board.apply(operation);
369
+ }
370
+ const ViewportTransforms = {
371
+ setViewport
372
+ };
373
+
374
+ const Transforms = Object.assign(Object.assign(Object.assign(Object.assign({}, GeneralTransforms), ViewportTransforms), SelectionTransforms), NodeTransforms);
375
+
376
+ var BaseCursorStatus;
377
+ (function (BaseCursorStatus) {
378
+ BaseCursorStatus["move"] = "move";
379
+ BaseCursorStatus["select"] = "select";
380
+ })(BaseCursorStatus || (BaseCursorStatus = {}));
381
+
382
+ function createBoard(host, children) {
383
+ const board = {
384
+ host,
385
+ viewport: {
386
+ offsetX: 0,
387
+ offsetY: 0,
388
+ zoom: 1,
389
+ viewBackgroundColor: '#000'
390
+ },
391
+ children,
392
+ operations: [],
393
+ selection: { anchor: [0, -1], focus: [-1, -1] },
394
+ cursor: BaseCursorStatus.select,
395
+ apply: (operation) => {
396
+ board.operations.push(operation);
397
+ Transforms.transform(board, operation);
398
+ if (!FLUSHING.get(board)) {
399
+ FLUSHING.set(board, true);
400
+ Promise.resolve().then(() => {
401
+ FLUSHING.set(board, false);
402
+ board.onChange();
403
+ board.operations = [];
404
+ });
405
+ }
406
+ },
407
+ onChange: () => { },
408
+ mousedown: (event) => { },
409
+ mouseup: (event) => { },
410
+ mousemove: (event) => { },
411
+ keydown: (event) => { },
412
+ keyup: (event) => { },
413
+ dblclick: (event) => { },
414
+ drawElement: (context) => [],
415
+ redrawElement: (context, changes) => [],
416
+ destroyElement: () => { }
417
+ };
418
+ return board;
419
+ }
420
+
421
+ function withBoard(board) {
422
+ const { onChange, mouseup } = board;
423
+ board.onChange = () => {
424
+ const onContextChange = BOARD_TO_ON_CHANGE.get(board);
425
+ if (onContextChange) {
426
+ onContextChange();
427
+ }
428
+ onChange();
429
+ };
430
+ return board;
431
+ }
432
+
433
+ const NS = 'http://www.w3.org/2000/svg';
434
+ function toPoint(x, y, container) {
435
+ const rect = container.getBoundingClientRect();
436
+ return [x - rect.x, y - rect.y];
437
+ }
438
+ function createG() {
439
+ const newG = document.createElementNS(NS, 'g');
440
+ return newG;
441
+ }
442
+ function createSVG() {
443
+ const svg = document.createElementNS(NS, 'svg');
444
+ return svg;
445
+ }
446
+ function createText(x, y, fill, textContent) {
447
+ var text = document.createElementNS(NS, 'text');
448
+ text.setAttribute('x', `${x}`);
449
+ text.setAttribute('y', `${y}`);
450
+ text.setAttribute('fill', fill);
451
+ text.textContent = textContent;
452
+ return text;
453
+ }
454
+
455
+ function toRectangleClient(points) {
456
+ const xArray = points.map(ele => ele[0]);
457
+ const yArray = points.map(ele => ele[1]);
458
+ const xMin = Math.min(...xArray);
459
+ const xMax = Math.max(...xArray);
460
+ const yMin = Math.min(...yArray);
461
+ const yMax = Math.max(...yArray);
462
+ const rect = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
463
+ return rect;
464
+ }
465
+
466
+ function withSelection(board) {
467
+ const { mousedown, mousemove, mouseup } = board;
468
+ let start = null;
469
+ let end = null;
470
+ board.mousedown = (event) => {
471
+ // avoid select text when double click svg
472
+ if (!(event.target instanceof HTMLElement && event.target.closest('.richtext'))) {
473
+ event.preventDefault();
474
+ }
475
+ if (board.cursor === BaseCursorStatus.select) {
476
+ start = toPoint(event.x, event.y, board.host);
477
+ }
478
+ mousedown(event);
479
+ };
480
+ board.mousemove = (event) => {
481
+ const movedTarget = toPoint(event.x, event.y, board.host);
482
+ if (start) {
483
+ const rectangleClient = toRectangleClient([start, movedTarget]);
484
+ if (start && Math.hypot(rectangleClient.width, rectangleClient.height) > 5) {
485
+ end = movedTarget;
486
+ }
487
+ }
488
+ mousemove(event);
489
+ };
490
+ board.mouseup = (event) => {
491
+ if (start) {
492
+ Transforms.setSelection(board, { anchor: start, focus: start });
493
+ }
494
+ start = null;
495
+ end = null;
496
+ mouseup(event);
497
+ };
498
+ return board;
499
+ }
500
+
501
+ const isSetViewportOperation = (value) => {
502
+ return value.type === 'set_viewport';
503
+ };
504
+ const PlaitOperation = {
505
+ isSetViewportOperation
506
+ };
507
+
508
+ function transformPoints(board, points) {
509
+ const newPoints = points.map(point => {
510
+ return transformPoint(board, point);
511
+ });
512
+ return newPoints;
513
+ }
514
+ function transformPoint(board, point) {
515
+ const { width, height } = board.host.getBoundingClientRect();
516
+ const viewBox = getViewBox(board);
517
+ let x = (point[0] / width) * viewBox.width + viewBox.minX;
518
+ let y = (point[1] / height) * viewBox.height + viewBox.minY;
519
+ const newPoint = [x - board.viewport.offsetX, y - board.viewport.offsetY];
520
+ return newPoint;
521
+ }
522
+ function getViewBox(board) {
523
+ const { width, height } = board.host.getBoundingClientRect();
524
+ const scaleWidth = (board.viewport.zoom - 1) * width;
525
+ const scaleHeight = (board.viewport.zoom - 1) * height;
526
+ const viewBoxWidth = width - scaleWidth;
527
+ const viewBoxHeight = height - scaleHeight;
528
+ const minX = scaleWidth / 2;
529
+ const minY = scaleHeight / 2;
530
+ return { minX, minY: minY, width: viewBoxWidth, height: viewBoxHeight };
531
+ }
532
+
533
+ class PlaitElementComponent {
534
+ constructor(renderer2, viewContainerRef) {
535
+ this.renderer2 = renderer2;
536
+ this.viewContainerRef = viewContainerRef;
537
+ this.initialized = false;
538
+ this.selection = null;
539
+ }
540
+ ngOnInit() {
541
+ this.initialize();
542
+ this.transform(true);
543
+ this.drawElement();
544
+ }
545
+ initialize() {
546
+ this.initialized = true;
547
+ this.groupG = createG();
548
+ this.renderer2.setAttribute(this.groupG, 'plait-element-group', this.index.toString());
549
+ this.host.append(this.groupG);
550
+ }
551
+ transform(first = false) {
552
+ if (first && this.viewport.offsetX === 0 && this.viewport.offsetY === 0) {
553
+ return;
554
+ }
555
+ this.renderer2.setAttribute(this.groupG, 'transform', `translate(${this.viewport.offsetX} ${this.viewport.offsetY})`);
556
+ }
557
+ drawElement() {
558
+ const gArray = this.board.drawElement({ elementInstance: this });
559
+ gArray.forEach(g => {
560
+ this.groupG.appendChild(g);
561
+ });
562
+ }
563
+ ngOnChanges(changes) {
564
+ const viewport = changes['viewport'];
565
+ if (this.initialized && viewport) {
566
+ this.transform();
567
+ }
568
+ if (this.initialized) {
569
+ this.board.redrawElement({ elementInstance: this }, changes);
570
+ }
571
+ }
572
+ ngOnDestroy() {
573
+ this.board.destroyElement();
574
+ this.groupG.remove();
575
+ }
576
+ }
577
+ 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 });
578
+ 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 });
579
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitElementComponent, decorators: [{
580
+ type: Component,
581
+ args: [{
582
+ selector: 'plait-element',
583
+ template: '',
584
+ changeDetection: ChangeDetectionStrategy.OnPush
585
+ }]
586
+ }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ViewContainerRef }]; }, propDecorators: { index: [{
587
+ type: Input
588
+ }], element: [{
589
+ type: Input
590
+ }], board: [{
591
+ type: Input
592
+ }], viewport: [{
593
+ type: Input
594
+ }], selection: [{
595
+ type: Input
596
+ }], host: [{
597
+ type: Input
598
+ }] } });
599
+
600
+ class PlaitBoardComponent {
601
+ constructor(cdr, renderer2) {
602
+ this.cdr = cdr;
603
+ this.renderer2 = renderer2;
604
+ this.zoom = 100;
605
+ this.hostClass = `plait-board-container`;
606
+ this.destroy$ = new Subject();
607
+ this.plaitValue = [];
608
+ this.plaitPlugins = [];
609
+ this.plaitChange = new EventEmitter();
610
+ this.plaitBoardInitialized = new EventEmitter();
611
+ this.trackBy = (index, element) => {
612
+ return index;
613
+ };
614
+ }
615
+ get host() {
616
+ return this.svg.nativeElement;
617
+ }
618
+ ngOnInit() {
619
+ const roughSVG = rough.svg(this.host, { options: { roughness: 0, strokeWidth: 1 } });
620
+ HOST_TO_ROUGH_SVG.set(this.host, roughSVG);
621
+ this.initializePlugins();
622
+ this.initializeEvents();
623
+ this.updateViewport();
624
+ BOARD_TO_ON_CHANGE.set(this.board, () => {
625
+ this.cdr.detectChanges();
626
+ const changeEvent = {
627
+ children: this.board.children,
628
+ operations: this.board.operations,
629
+ viewport: this.board.viewport,
630
+ selection: this.board.selection
631
+ };
632
+ this.plaitChange.emit(changeEvent);
633
+ // update viewBox
634
+ if (this.board.operations.some(op => PlaitOperation.isSetViewportOperation(op))) {
635
+ this.updateViewport();
636
+ }
637
+ });
638
+ }
639
+ ngAfterViewInit() {
640
+ this.plaitBoardInitialized.emit(this.board);
641
+ }
642
+ initializePlugins() {
643
+ let board = withSelection(withBoard(createBoard(this.host, this.plaitValue)));
644
+ this.plaitPlugins.forEach(plugin => {
645
+ board = plugin(board);
646
+ });
647
+ this.board = board;
648
+ if (this.plaitViewport) {
649
+ this.board.viewport = this.plaitViewport;
650
+ }
651
+ }
652
+ initializeEvents() {
653
+ fromEvent(this.host, 'mousedown')
654
+ .pipe(takeUntil(this.destroy$))
655
+ .subscribe((event) => {
656
+ this.board.mousedown(event);
657
+ });
658
+ fromEvent(this.host, 'mousemove')
659
+ .pipe(takeUntil(this.destroy$))
660
+ .subscribe((event) => {
661
+ this.board.mousemove(event);
662
+ });
663
+ fromEvent(document, 'mouseup')
664
+ .pipe(takeUntil(this.destroy$))
665
+ .subscribe((event) => {
666
+ this.board.mouseup(event);
667
+ });
668
+ fromEvent(this.host, 'dblclick')
669
+ .pipe(takeUntil(this.destroy$))
670
+ .subscribe((event) => {
671
+ this.board.dblclick(event);
672
+ });
673
+ fromEvent(this.host, 'wheel')
674
+ .pipe(takeUntil(this.destroy$))
675
+ .subscribe((event) => {
676
+ event.preventDefault();
677
+ const viewport = this.board.viewport;
678
+ Transforms.setViewport(this.board, Object.assign(Object.assign({}, viewport), { offsetX: (viewport === null || viewport === void 0 ? void 0 : viewport.offsetX) - event.deltaX, offsetY: (viewport === null || viewport === void 0 ? void 0 : viewport.offsetY) - event.deltaY }));
679
+ });
680
+ fromEvent(document, 'keydown')
681
+ .pipe(takeUntil(this.destroy$), filter(() => {
682
+ return !IS_TEXT_EDITABLE.get(this.board);
683
+ }))
684
+ .subscribe((event) => {
685
+ var _a;
686
+ (_a = this.board) === null || _a === void 0 ? void 0 : _a.keydown(event);
687
+ });
688
+ fromEvent(document, 'keyup')
689
+ .pipe(takeUntil(this.destroy$))
690
+ .subscribe((event) => {
691
+ var _a;
692
+ (_a = this.board) === null || _a === void 0 ? void 0 : _a.keyup(event);
693
+ });
694
+ window.onresize = () => {
695
+ var _a;
696
+ const viewBoxModel = getViewBox(this.board);
697
+ const viewBoxValues = (_a = this.host.getAttribute('viewBox')) === null || _a === void 0 ? void 0 : _a.split(',');
698
+ this.renderer2.setAttribute(this.host, 'viewBox', `${viewBoxValues[0].trim()}, ${viewBoxValues[1].trim()}, ${viewBoxModel.width}, ${viewBoxModel.height}`);
699
+ };
700
+ }
701
+ updateViewport() {
702
+ this.zoom = Math.floor(this.board.viewport.zoom * 100);
703
+ const viewBox = getViewBox(this.board);
704
+ this.renderer2.setAttribute(this.host, 'viewBox', `${viewBox.minX}, ${viewBox.minY}, ${viewBox.width}, ${viewBox.height}`);
705
+ }
706
+ // 放大
707
+ zoomIn(event) {
708
+ var _a;
709
+ const viewport = (_a = this.board) === null || _a === void 0 ? void 0 : _a.viewport;
710
+ Transforms.setViewport(this.board, Object.assign(Object.assign({}, viewport), { zoom: viewport.zoom + 0.1 }));
711
+ }
712
+ // 缩小
713
+ zoomOut(event) {
714
+ var _a;
715
+ const viewport = (_a = this.board) === null || _a === void 0 ? void 0 : _a.viewport;
716
+ Transforms.setViewport(this.board, Object.assign(Object.assign({}, viewport), { zoom: viewport.zoom - 0.1 }));
717
+ }
718
+ resetZoom(event) {
719
+ var _a;
720
+ const viewport = (_a = this.board) === null || _a === void 0 ? void 0 : _a.viewport;
721
+ Transforms.setViewport(this.board, Object.assign(Object.assign({}, viewport), { zoom: 1 }));
722
+ }
723
+ ngOnDestroy() {
724
+ this.destroy$.next();
725
+ this.destroy$.complete();
726
+ HOST_TO_ROUGH_SVG.delete(this.host);
727
+ }
728
+ }
729
+ 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 });
730
+ 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: `
731
+ <svg #svg width="100%" height="100%"></svg>
732
+ <div class="plait-toolbar island zoom-toolbar">
733
+ <button class="item" (mousedown)="zoomOut($event)">-</button>
734
+ <button class="item zoom-value" (mousedown)="resetZoom($event)">{{ zoom }}%</button>
735
+ <button class="item" (mousedown)="zoomIn($event)">+</button>
736
+ </div>
737
+ <plait-element
738
+ *ngFor="let item of board.children; let index = index; trackBy: trackBy"
739
+ [index]="index"
740
+ [element]="item"
741
+ [board]="board"
742
+ [viewport]="board.viewport"
743
+ [selection]="board.selection"
744
+ [host]="host"
745
+ ></plait-element>
746
+ <ng-content></ng-content>
747
+ `, 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 });
748
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitBoardComponent, decorators: [{
749
+ type: Component,
750
+ args: [{
751
+ selector: 'plait-board',
752
+ template: `
753
+ <svg #svg width="100%" height="100%"></svg>
754
+ <div class="plait-toolbar island zoom-toolbar">
755
+ <button class="item" (mousedown)="zoomOut($event)">-</button>
756
+ <button class="item zoom-value" (mousedown)="resetZoom($event)">{{ zoom }}%</button>
757
+ <button class="item" (mousedown)="zoomIn($event)">+</button>
758
+ </div>
759
+ <plait-element
760
+ *ngFor="let item of board.children; let index = index; trackBy: trackBy"
761
+ [index]="index"
762
+ [element]="item"
763
+ [board]="board"
764
+ [viewport]="board.viewport"
765
+ [selection]="board.selection"
766
+ [host]="host"
767
+ ></plait-element>
768
+ <ng-content></ng-content>
769
+ `,
770
+ changeDetection: ChangeDetectionStrategy.OnPush
771
+ }]
772
+ }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }]; }, propDecorators: { hostClass: [{
773
+ type: HostBinding,
774
+ args: ['class']
775
+ }], svg: [{
776
+ type: ViewChild,
777
+ args: ['svg', { static: true }]
778
+ }], plaitValue: [{
779
+ type: Input
780
+ }], plaitViewport: [{
781
+ type: Input
782
+ }], plaitPlugins: [{
783
+ type: Input
784
+ }], plaitChange: [{
785
+ type: Output
786
+ }], plaitBoardInitialized: [{
787
+ type: Output
788
+ }] } });
789
+
790
+ class PlaitModule {
791
+ }
792
+ PlaitModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
793
+ PlaitModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, declarations: [PlaitBoardComponent, PlaitElementComponent], imports: [BrowserModule], exports: [PlaitBoardComponent, PlaitElementComponent] });
794
+ PlaitModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, imports: [[BrowserModule]] });
795
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: PlaitModule, decorators: [{
796
+ type: NgModule,
797
+ args: [{
798
+ declarations: [PlaitBoardComponent, PlaitElementComponent],
799
+ imports: [BrowserModule],
800
+ exports: [PlaitBoardComponent, PlaitElementComponent]
801
+ }]
802
+ }] });
803
+
804
+ const IS_IOS = typeof navigator !== 'undefined' &&
805
+ typeof window !== 'undefined' &&
806
+ /iPad|iPhone|iPod/.test(navigator.userAgent) &&
807
+ !window.MSStream;
808
+ const IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent);
809
+ const IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);
810
+ const IS_SAFARI = typeof navigator !== 'undefined' && /Version\/[\d\.]+.*Safari/.test(navigator.userAgent);
811
+ // "modern" Edge was released at 79.x
812
+ const IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])/i.test(navigator.userAgent);
813
+ const IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent);
814
+ // Native beforeInput events don't work well with react on Chrome 75 and older, Chrome 76+ can use beforeInput
815
+ const IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])/i.test(navigator.userAgent);
816
+
817
+ /**
818
+ * Hotkey mappings for each platform.
819
+ */
820
+ const HOTKEYS = {
821
+ bold: 'mod+b',
822
+ compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'],
823
+ moveBackward: 'left',
824
+ moveForward: 'right',
825
+ moveUp: 'up',
826
+ moveDown: 'down',
827
+ moveWordBackward: 'ctrl+left',
828
+ moveWordForward: 'ctrl+right',
829
+ deleteBackward: 'shift?+backspace',
830
+ deleteForward: 'shift?+delete',
831
+ extendBackward: 'shift+left',
832
+ extendForward: 'shift+right',
833
+ italic: 'mod+i',
834
+ splitBlock: 'shift?+enter',
835
+ undo: 'mod+z'
836
+ };
837
+ const APPLE_HOTKEYS = {
838
+ moveLineBackward: 'opt+up',
839
+ moveLineForward: 'opt+down',
840
+ moveWordBackward: 'opt+left',
841
+ moveWordForward: 'opt+right',
842
+ deleteBackward: ['ctrl+backspace', 'ctrl+h'],
843
+ deleteForward: ['ctrl+delete', 'ctrl+d'],
844
+ deleteLineBackward: 'cmd+shift?+backspace',
845
+ deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'],
846
+ deleteWordBackward: 'opt+shift?+backspace',
847
+ deleteWordForward: 'opt+shift?+delete',
848
+ extendLineBackward: 'opt+shift+up',
849
+ extendLineForward: 'opt+shift+down',
850
+ redo: 'cmd+shift+z',
851
+ transposeCharacter: 'ctrl+t'
852
+ };
853
+ const WINDOWS_HOTKEYS = {
854
+ deleteWordBackward: 'ctrl+shift?+backspace',
855
+ deleteWordForward: 'ctrl+shift?+delete',
856
+ redo: ['ctrl+y', 'ctrl+shift+z']
857
+ };
858
+ /**
859
+ * Create a platform-aware hotkey checker.
860
+ */
861
+ const create = (key) => {
862
+ const generic = HOTKEYS[key];
863
+ const apple = APPLE_HOTKEYS[key];
864
+ const windows = WINDOWS_HOTKEYS[key];
865
+ const isGeneric = generic && isKeyHotkey(generic);
866
+ const isApple = apple && isKeyHotkey(apple);
867
+ const isWindows = windows && isKeyHotkey(windows);
868
+ return (event) => {
869
+ if (isGeneric && isGeneric(event)) {
870
+ return true;
871
+ }
872
+ if (IS_APPLE && isApple && isApple(event)) {
873
+ return true;
874
+ }
875
+ if (!IS_APPLE && isWindows && isWindows(event)) {
876
+ return true;
877
+ }
878
+ return false;
879
+ };
880
+ };
881
+ /**
882
+ * Hotkeys.
883
+ */
884
+ const hotkeys = {
885
+ isBold: create('bold'),
886
+ isCompose: create('compose'),
887
+ isMoveBackward: create('moveBackward'),
888
+ isMoveForward: create('moveForward'),
889
+ isMoveUp: create('moveUp'),
890
+ isMoveDown: create('moveDown'),
891
+ isDeleteBackward: create('deleteBackward'),
892
+ isDeleteForward: create('deleteForward'),
893
+ isDeleteLineBackward: create('deleteLineBackward'),
894
+ isDeleteLineForward: create('deleteLineForward'),
895
+ isDeleteWordBackward: create('deleteWordBackward'),
896
+ isDeleteWordForward: create('deleteWordForward'),
897
+ isExtendBackward: create('extendBackward'),
898
+ isExtendForward: create('extendForward'),
899
+ isExtendLineBackward: create('extendLineBackward'),
900
+ isExtendLineForward: create('extendLineForward'),
901
+ isItalic: create('italic'),
902
+ isMoveLineBackward: create('moveLineBackward'),
903
+ isMoveLineForward: create('moveLineForward'),
904
+ isMoveWordBackward: create('moveWordBackward'),
905
+ isMoveWordForward: create('moveWordForward'),
906
+ isRedo: create('redo'),
907
+ isSplitBlock: create('splitBlock'),
908
+ isTransposeCharacter: create('transposeCharacter'),
909
+ isUndo: create('undo')
910
+ };
911
+
912
+ function idCreator(length = 5) {
913
+ // remove numeral
914
+ const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
915
+ const maxPosition = $chars.length;
916
+ let key = '';
917
+ for (let i = 0; i < length; i++) {
918
+ key += $chars.charAt(Math.floor(Math.random() * maxPosition));
919
+ }
920
+ return key;
921
+ }
922
+
923
+ // https://stackoverflow.com/a/6853926/232122
924
+ function distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) {
925
+ const A = x - x1;
926
+ const B = y - y1;
927
+ const C = x2 - x1;
928
+ const D = y2 - y1;
929
+ const dot = A * C + B * D;
930
+ const lenSquare = C * C + D * D;
931
+ let param = -1;
932
+ if (lenSquare !== 0) {
933
+ // in case of 0 length line
934
+ param = dot / lenSquare;
935
+ }
936
+ let xx, yy;
937
+ if (param < 0) {
938
+ xx = x1;
939
+ yy = y1;
940
+ }
941
+ else if (param > 1) {
942
+ xx = x2;
943
+ yy = y2;
944
+ }
945
+ else {
946
+ xx = x1 + param * C;
947
+ yy = y1 + param * D;
948
+ }
949
+ const dx = x - xx;
950
+ const dy = y - yy;
951
+ return Math.hypot(dx, dy);
952
+ }
953
+ function rotate(x1, y1, x2, y2, angle) {
954
+ // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
955
+ // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
956
+ // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
957
+ return [(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2, (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2];
958
+ }
959
+
960
+ /*
961
+ * Public API Surface of plait
962
+ */
963
+
964
+ /**
965
+ * Generated bundle index. Do not edit.
966
+ */
967
+
968
+ 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 };
969
+ //# sourceMappingURL=plait-core.mjs.map