@modern-ant/model-diagram 0.2.5

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +396 -0
  3. package/THIRD_PARTY_NOTICES.md +32 -0
  4. package/dist/events/emitter.d.ts +8 -0
  5. package/dist/events/emitter.d.ts.map +1 -0
  6. package/dist/events/emitter.js +30 -0
  7. package/dist/events/emitter.js.map +1 -0
  8. package/dist/events/types.d.ts +94 -0
  9. package/dist/events/types.d.ts.map +1 -0
  10. package/dist/events/types.js +2 -0
  11. package/dist/events/types.js.map +1 -0
  12. package/dist/index.d.ts +8 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +4 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/jointjs/adapter.d.ts +75 -0
  17. package/dist/jointjs/adapter.d.ts.map +1 -0
  18. package/dist/jointjs/adapter.js +905 -0
  19. package/dist/jointjs/adapter.js.map +1 -0
  20. package/dist/layout/elk-layout.d.ts +15 -0
  21. package/dist/layout/elk-layout.d.ts.map +1 -0
  22. package/dist/layout/elk-layout.js +112 -0
  23. package/dist/layout/elk-layout.js.map +1 -0
  24. package/dist/model/types.d.ts +45 -0
  25. package/dist/model/types.d.ts.map +1 -0
  26. package/dist/model/types.js +2 -0
  27. package/dist/model/types.js.map +1 -0
  28. package/dist/model/validation.d.ts +8 -0
  29. package/dist/model/validation.d.ts.map +1 -0
  30. package/dist/model/validation.js +95 -0
  31. package/dist/model/validation.js.map +1 -0
  32. package/dist/rendering/diagram.d.ts +96 -0
  33. package/dist/rendering/diagram.d.ts.map +1 -0
  34. package/dist/rendering/diagram.js +733 -0
  35. package/dist/rendering/diagram.js.map +1 -0
  36. package/dist/rendering/geometry.d.ts +20 -0
  37. package/dist/rendering/geometry.d.ts.map +1 -0
  38. package/dist/rendering/geometry.js +42 -0
  39. package/dist/rendering/geometry.js.map +1 -0
  40. package/dist/rendering/store.d.ts +30 -0
  41. package/dist/rendering/store.d.ts.map +1 -0
  42. package/dist/rendering/store.js +231 -0
  43. package/dist/rendering/store.js.map +1 -0
  44. package/dist/routing/relationship-style.d.ts +15 -0
  45. package/dist/routing/relationship-style.d.ts.map +1 -0
  46. package/dist/routing/relationship-style.js +37 -0
  47. package/dist/routing/relationship-style.js.map +1 -0
  48. package/package.json +58 -0
@@ -0,0 +1,733 @@
1
+ import { TypedEventEmitter } from "../events/emitter.js";
2
+ import { JointJsAdapter } from "../jointjs/adapter.js";
3
+ import { ElkLayoutEngine, } from "../layout/elk-layout.js";
4
+ import { positionsEqual } from "./geometry.js";
5
+ import { DiagramStore } from "./store.js";
6
+ function copyPosition(position) {
7
+ return { x: position.x, y: position.y };
8
+ }
9
+ function copyWaypoints(waypoints) {
10
+ return waypoints.map(copyPosition);
11
+ }
12
+ function positionsRecord(layout) {
13
+ return Object.fromEntries(Object.entries(layout.classes ?? {}).map(([id, position]) => [
14
+ id,
15
+ copyPosition(position),
16
+ ]));
17
+ }
18
+ function positionRecordsEqual(first, second) {
19
+ const firstIds = Object.keys(first);
20
+ const secondIds = Object.keys(second);
21
+ return (firstIds.length === secondIds.length &&
22
+ firstIds.every((id) => {
23
+ const firstPosition = first[id];
24
+ const secondPosition = second[id];
25
+ return Boolean(firstPosition &&
26
+ secondPosition &&
27
+ positionsEqual(firstPosition, secondPosition));
28
+ }));
29
+ }
30
+ function waypointListsEqual(first, second) {
31
+ return (first.length === second.length &&
32
+ first.every((position, index) => {
33
+ const other = second[index];
34
+ return Boolean(other && positionsEqual(position, other));
35
+ }));
36
+ }
37
+ function changedWaypointIndex(before, after) {
38
+ const commonLength = Math.min(before.length, after.length);
39
+ for (let index = 0; index < commonLength; index += 1) {
40
+ const previous = before[index];
41
+ const next = after[index];
42
+ if (previous && next && !positionsEqual(previous, next))
43
+ return index;
44
+ }
45
+ return commonLength;
46
+ }
47
+ function selectionsEqual(first, second) {
48
+ if (first === null || second === null)
49
+ return first === second;
50
+ if (first.kind !== second.kind)
51
+ return false;
52
+ if (first.kind === "relationship-waypoint") {
53
+ return (second.kind === "relationship-waypoint" &&
54
+ first.relationshipId === second.relationshipId &&
55
+ first.index === second.index);
56
+ }
57
+ return second.kind !== "relationship-waypoint" && first.id === second.id;
58
+ }
59
+ export class ModelDiagram {
60
+ events = new TypedEventEmitter();
61
+ store;
62
+ renderer;
63
+ layoutEngine;
64
+ editable;
65
+ selection = null;
66
+ readyPromise = Promise.resolve();
67
+ destroyed = false;
68
+ layoutGeneration = 0;
69
+ undoStack = [];
70
+ redoStack = [];
71
+ elementDragState;
72
+ constructor(container, options) {
73
+ if (!(container instanceof HTMLElement)) {
74
+ throw new TypeError("createDiagram requires an HTMLElement container");
75
+ }
76
+ this.editable = options.editable ?? false;
77
+ this.store = new DiagramStore(options.model, options.layout);
78
+ this.layoutEngine = options.layoutEngine ?? new ElkLayoutEngine();
79
+ this.renderer = new JointJsAdapter(container, this.editable, {
80
+ onSelectionChanged: (selection) => this.changeSelection(selection),
81
+ onPositionDragStarted: (kind, id) => this.beginElementDrag(kind, id),
82
+ onPositionPreview: (kind, id, position) => this.previewElementPosition(kind, id, position),
83
+ onPositionCommitted: (kind, id, before, after) => this.commitElementPosition(kind, id, before, after),
84
+ onWaypointsPreview: (id, waypoints) => this.previewWaypoints(id, waypoints),
85
+ onWaypointsCommitted: (id, before, after) => this.commitWaypoints(id, before, after),
86
+ onWaypointAddRequested: (id, position, index) => this.addRelationshipWaypoint(id, position, index),
87
+ onDeleteSelectedWaypoint: () => this.deleteSelectedWaypoint(),
88
+ });
89
+ this.renderer.render(this.store);
90
+ const missingStoredPosition = options.model.classes.some(({ id }) => !options.layout?.classes?.[id]);
91
+ if ((options.autoLayout ?? true) && missingStoredPosition) {
92
+ this.readyPromise = this.performAutoLayout(options.layout ?? {}, {
93
+ preserveStoredPositions: true,
94
+ }, false).then(() => undefined);
95
+ }
96
+ }
97
+ on(type, listener) {
98
+ this.assertAlive();
99
+ return this.events.on(type, listener);
100
+ }
101
+ getModel() {
102
+ this.assertAlive();
103
+ return this.store.model;
104
+ }
105
+ setModel(model) {
106
+ this.assertAlive();
107
+ const previousLayout = this.store.getLayout();
108
+ const previousIds = new Set(this.store.model.classes.map(({ id }) => id));
109
+ this.store.setModel(model);
110
+ this.layoutGeneration += 1;
111
+ this.renderer.render(this.store);
112
+ this.resetHistory();
113
+ if (model.classes.some(({ id }) => !previousIds.has(id))) {
114
+ this.readyPromise = this.performAutoLayout(previousLayout, {
115
+ preserveStoredPositions: true,
116
+ }, false).then(() => undefined);
117
+ }
118
+ }
119
+ setLayout(layout) {
120
+ this.assertAlive();
121
+ this.store.setLayout(layout);
122
+ this.layoutGeneration += 1;
123
+ this.renderer.syncAllPositions(this.store);
124
+ this.resetHistory();
125
+ if (this.store.model.classes.some(({ id }) => !layout.classes?.[id])) {
126
+ this.readyPromise = this.performAutoLayout(layout, {
127
+ preserveStoredPositions: true,
128
+ }, false).then(() => undefined);
129
+ }
130
+ }
131
+ setEditable(editable) {
132
+ this.assertAlive();
133
+ const previous = this.historyAvailability();
134
+ this.editable = editable;
135
+ this.renderer.setEditable(editable);
136
+ const next = this.historyAvailability();
137
+ if (previous.canUndo !== next.canUndo ||
138
+ previous.canRedo !== next.canRedo) {
139
+ this.emitHistoryChanged();
140
+ }
141
+ }
142
+ getLayout() {
143
+ this.assertAlive();
144
+ return this.store.getLayout();
145
+ }
146
+ canUndo() {
147
+ this.assertAlive();
148
+ return this.historyAvailability().canUndo;
149
+ }
150
+ canRedo() {
151
+ this.assertAlive();
152
+ return this.historyAvailability().canRedo;
153
+ }
154
+ undo() {
155
+ this.assertEditable();
156
+ const entry = this.undoStack.pop();
157
+ if (!entry)
158
+ return;
159
+ this.redoStack.push(entry);
160
+ this.layoutGeneration += 1;
161
+ this.applyHistoryEntry(entry, false);
162
+ this.emitHistoryChanged();
163
+ }
164
+ redo() {
165
+ this.assertEditable();
166
+ const entry = this.redoStack.pop();
167
+ if (!entry)
168
+ return;
169
+ this.undoStack.push(entry);
170
+ this.layoutGeneration += 1;
171
+ this.applyHistoryEntry(entry, true);
172
+ this.emitHistoryChanged();
173
+ }
174
+ clearHistory() {
175
+ this.assertAlive();
176
+ this.resetHistory();
177
+ }
178
+ autoLayout(options = {}) {
179
+ this.assertAlive();
180
+ const operation = this.performAutoLayout(this.store.getLayout(), {
181
+ ...options,
182
+ preserveStoredPositions: options.preserveStoredPositions ?? false,
183
+ }, true);
184
+ this.readyPromise = operation.then(() => undefined);
185
+ return operation;
186
+ }
187
+ whenReady() {
188
+ return this.readyPromise;
189
+ }
190
+ updateRelationship(relationshipId, changes) {
191
+ this.assertEditable();
192
+ const previous = this.store.getRelationship(relationshipId);
193
+ this.store.updateRelationship(relationshipId, changes);
194
+ const next = this.store.getRelationship(relationshipId);
195
+ const before = {};
196
+ const after = {};
197
+ for (const field of ["label"]) {
198
+ if (!Object.hasOwn(changes, field) || previous[field] === next[field]) {
199
+ continue;
200
+ }
201
+ before[field] = previous[field];
202
+ after[field] = next[field];
203
+ }
204
+ if (Object.keys(after).length === 0)
205
+ return;
206
+ this.recordHistory({
207
+ kind: "relationship-properties",
208
+ relationshipId,
209
+ before,
210
+ after,
211
+ });
212
+ this.renderer.syncRelationship(relationshipId, this.store);
213
+ this.emit("relationship-changed", {
214
+ type: "relationship-changed",
215
+ relationshipId,
216
+ changes: { ...after },
217
+ });
218
+ this.emitHistoryChanged();
219
+ }
220
+ addRelationshipWaypoint(relationshipId, position, index) {
221
+ this.assertEditable();
222
+ const before = this.store.getWaypoints(relationshipId);
223
+ const insertedIndex = this.store.addWaypoint(relationshipId, position, index);
224
+ const after = this.store.getWaypoints(relationshipId);
225
+ this.recordHistory({
226
+ kind: "relationship-waypoints",
227
+ operation: "add",
228
+ relationshipId,
229
+ index: insertedIndex,
230
+ before,
231
+ after,
232
+ });
233
+ this.renderer.syncWaypoints(relationshipId, this.store);
234
+ this.changeSelection({
235
+ kind: "relationship-waypoint",
236
+ relationshipId,
237
+ index: insertedIndex,
238
+ });
239
+ this.emit("relationship-waypoint-added", {
240
+ type: "relationship-waypoint-added",
241
+ relationshipId,
242
+ index: insertedIndex,
243
+ ...position,
244
+ });
245
+ this.emitHistoryChanged();
246
+ }
247
+ moveRelationshipWaypoint(relationshipId, index, position) {
248
+ this.assertEditable();
249
+ const before = this.store.getWaypoints(relationshipId);
250
+ this.store.moveWaypoint(relationshipId, index, position);
251
+ const after = this.store.getWaypoints(relationshipId);
252
+ const recorded = !waypointListsEqual(before, after);
253
+ if (recorded) {
254
+ this.recordHistory({
255
+ kind: "relationship-waypoints",
256
+ operation: "move",
257
+ relationshipId,
258
+ index,
259
+ before,
260
+ after,
261
+ });
262
+ }
263
+ this.renderer.syncWaypoints(relationshipId, this.store);
264
+ this.changeSelection({
265
+ kind: "relationship-waypoint",
266
+ relationshipId,
267
+ index,
268
+ });
269
+ this.emit("relationship-waypoint-changed", {
270
+ type: "relationship-waypoint-changed",
271
+ relationshipId,
272
+ index,
273
+ ...position,
274
+ });
275
+ if (recorded)
276
+ this.emitHistoryChanged();
277
+ }
278
+ removeRelationshipWaypoint(relationshipId, index) {
279
+ this.assertEditable();
280
+ const before = this.store.getWaypoints(relationshipId);
281
+ this.store.removeWaypoint(relationshipId, index);
282
+ const after = this.store.getWaypoints(relationshipId);
283
+ this.recordHistory({
284
+ kind: "relationship-waypoints",
285
+ operation: "remove",
286
+ relationshipId,
287
+ index,
288
+ before,
289
+ after,
290
+ });
291
+ this.renderer.syncWaypoints(relationshipId, this.store);
292
+ this.changeSelection({ kind: "relationship", id: relationshipId });
293
+ this.emit("relationship-waypoint-removed", {
294
+ type: "relationship-waypoint-removed",
295
+ relationshipId,
296
+ index,
297
+ });
298
+ this.emitHistoryChanged();
299
+ }
300
+ resetRelationshipRoute(relationshipId) {
301
+ this.assertEditable();
302
+ const before = this.store.getWaypoints(relationshipId);
303
+ this.store.resetRoute(relationshipId);
304
+ const after = this.store.getWaypoints(relationshipId);
305
+ const recorded = !waypointListsEqual(before, after);
306
+ if (recorded) {
307
+ this.recordHistory({
308
+ kind: "relationship-waypoints",
309
+ operation: "reset",
310
+ relationshipId,
311
+ before,
312
+ after,
313
+ });
314
+ }
315
+ this.renderer.syncWaypoints(relationshipId, this.store);
316
+ this.changeSelection({ kind: "relationship", id: relationshipId });
317
+ this.emit("relationship-route-reset", {
318
+ type: "relationship-route-reset",
319
+ relationshipId,
320
+ });
321
+ if (recorded)
322
+ this.emitHistoryChanged();
323
+ }
324
+ fitToContent(padding) {
325
+ this.assertAlive();
326
+ this.renderer.fitToContent(padding);
327
+ }
328
+ setZoom(scale) {
329
+ this.assertAlive();
330
+ this.renderer.setZoom(scale);
331
+ }
332
+ zoomIn() {
333
+ this.setZoom(this.renderer.getZoom() * 1.2);
334
+ }
335
+ zoomOut() {
336
+ this.setZoom(this.renderer.getZoom() / 1.2);
337
+ }
338
+ panBy(dx, dy) {
339
+ this.assertAlive();
340
+ this.renderer.panBy(dx, dy);
341
+ }
342
+ destroy() {
343
+ if (this.destroyed)
344
+ return;
345
+ this.destroyed = true;
346
+ this.renderer.destroy();
347
+ this.undoStack.length = 0;
348
+ this.redoStack.length = 0;
349
+ this.elementDragState = undefined;
350
+ this.events.clear();
351
+ this.selection = null;
352
+ }
353
+ async performAutoLayout(storedLayout, options, addToHistory) {
354
+ const generation = ++this.layoutGeneration;
355
+ const before = positionsRecord(this.store.getLayout());
356
+ const model = this.store.model;
357
+ const positions = await this.layoutEngine.layout(model, storedLayout, options);
358
+ if (this.destroyed || generation !== this.layoutGeneration) {
359
+ return this.store.getLayout();
360
+ }
361
+ this.store.applyClassPositions(positions);
362
+ this.renderer.syncAllPositions(this.store);
363
+ const layout = this.store.getLayout();
364
+ const after = positionsRecord(layout);
365
+ const recorded = addToHistory && !positionRecordsEqual(before, after);
366
+ if (recorded) {
367
+ this.recordHistory({
368
+ kind: "layout-transaction",
369
+ before,
370
+ after,
371
+ });
372
+ }
373
+ this.emit("auto-layout-completed", {
374
+ type: "auto-layout-completed",
375
+ layout,
376
+ });
377
+ if (recorded)
378
+ this.emitHistoryChanged();
379
+ return layout;
380
+ }
381
+ beginElementDrag(kind, id) {
382
+ this.elementDragState = {
383
+ kind,
384
+ id,
385
+ ...(kind === "note"
386
+ ? { noteWasManual: this.store.hasManualNotePosition(id) }
387
+ : {}),
388
+ };
389
+ }
390
+ previewElementPosition(kind, id, position) {
391
+ if (!this.editable)
392
+ return;
393
+ if (kind === "class") {
394
+ this.layoutGeneration += 1;
395
+ this.store.setClassPosition(id, position);
396
+ if (!this.store.hasManualNotePosition(id)) {
397
+ this.renderer.syncNotePosition(id, this.store);
398
+ }
399
+ this.emit("class-position-preview", {
400
+ type: "class-position-preview",
401
+ classId: id,
402
+ ...position,
403
+ });
404
+ }
405
+ else {
406
+ this.store.setNotePosition(id, position);
407
+ this.emit("note-position-preview", {
408
+ type: "note-position-preview",
409
+ classId: id,
410
+ ...position,
411
+ });
412
+ }
413
+ }
414
+ commitElementPosition(kind, id, before, position) {
415
+ if (!this.editable)
416
+ return;
417
+ const dragState = this.elementDragState;
418
+ this.elementDragState = undefined;
419
+ if (kind === "class") {
420
+ this.store.setClassPosition(id, position);
421
+ this.recordHistory({
422
+ kind: "class-position",
423
+ classId: id,
424
+ before: copyPosition(before),
425
+ after: copyPosition(position),
426
+ });
427
+ this.emit("class-position-changed", {
428
+ type: "class-position-changed",
429
+ classId: id,
430
+ ...position,
431
+ });
432
+ }
433
+ else {
434
+ this.store.setNotePosition(id, position);
435
+ this.recordHistory({
436
+ kind: "note-position",
437
+ classId: id,
438
+ before: copyPosition(before),
439
+ after: copyPosition(position),
440
+ beforeWasManual: dragState?.kind === "note" && dragState.id === id
441
+ ? Boolean(dragState.noteWasManual)
442
+ : true,
443
+ });
444
+ this.emit("note-position-changed", {
445
+ type: "note-position-changed",
446
+ classId: id,
447
+ ...position,
448
+ });
449
+ }
450
+ this.emitHistoryChanged();
451
+ }
452
+ previewWaypoints(relationshipId, waypoints) {
453
+ if (!this.editable)
454
+ return;
455
+ const before = this.store.getWaypoints(relationshipId);
456
+ const index = changedWaypointIndex(before, waypoints);
457
+ this.store.replaceWaypoints(relationshipId, waypoints);
458
+ if (waypoints.length < before.length) {
459
+ this.changeSelection({ kind: "relationship", id: relationshipId });
460
+ this.emit("relationship-waypoint-removed", {
461
+ type: "relationship-waypoint-removed",
462
+ relationshipId,
463
+ index,
464
+ });
465
+ return;
466
+ }
467
+ if (waypoints.length > before.length) {
468
+ const added = waypoints[index];
469
+ if (!added)
470
+ return;
471
+ this.changeSelection({
472
+ kind: "relationship-waypoint",
473
+ relationshipId,
474
+ index,
475
+ });
476
+ this.emit("relationship-waypoint-added", {
477
+ type: "relationship-waypoint-added",
478
+ relationshipId,
479
+ index,
480
+ ...added,
481
+ });
482
+ return;
483
+ }
484
+ const changed = waypoints[index];
485
+ if (!changed)
486
+ return;
487
+ this.changeSelection({
488
+ kind: "relationship-waypoint",
489
+ relationshipId,
490
+ index,
491
+ });
492
+ this.emit("relationship-waypoint-preview", {
493
+ type: "relationship-waypoint-preview",
494
+ relationshipId,
495
+ index,
496
+ ...changed,
497
+ });
498
+ }
499
+ commitWaypoints(relationshipId, before, after) {
500
+ if (!this.editable || before.length !== after.length)
501
+ return;
502
+ this.store.replaceWaypoints(relationshipId, after);
503
+ const recorded = !waypointListsEqual(before, after);
504
+ if (recorded) {
505
+ this.recordHistory({
506
+ kind: "relationship-waypoints",
507
+ operation: "move",
508
+ relationshipId,
509
+ index: changedWaypointIndex(before, after),
510
+ before: copyWaypoints(before),
511
+ after: copyWaypoints(after),
512
+ });
513
+ }
514
+ for (let index = 0; index < after.length; index += 1) {
515
+ const previous = before[index];
516
+ const position = after[index];
517
+ if (!previous || !position || positionsEqual(previous, position))
518
+ continue;
519
+ this.changeSelection({
520
+ kind: "relationship-waypoint",
521
+ relationshipId,
522
+ index,
523
+ });
524
+ this.emit("relationship-waypoint-changed", {
525
+ type: "relationship-waypoint-changed",
526
+ relationshipId,
527
+ index,
528
+ ...position,
529
+ });
530
+ }
531
+ if (recorded)
532
+ this.emitHistoryChanged();
533
+ }
534
+ recordHistory(entry) {
535
+ this.undoStack.push(entry);
536
+ this.redoStack.length = 0;
537
+ }
538
+ resetHistory() {
539
+ if (this.undoStack.length === 0 && this.redoStack.length === 0)
540
+ return;
541
+ this.undoStack.length = 0;
542
+ this.redoStack.length = 0;
543
+ this.emitHistoryChanged();
544
+ }
545
+ historyAvailability() {
546
+ return {
547
+ canUndo: this.editable && this.undoStack.length > 0,
548
+ canRedo: this.editable && this.redoStack.length > 0,
549
+ };
550
+ }
551
+ emitHistoryChanged() {
552
+ this.emit("history-changed", {
553
+ type: "history-changed",
554
+ ...this.historyAvailability(),
555
+ });
556
+ }
557
+ applyHistoryEntry(entry, forward) {
558
+ switch (entry.kind) {
559
+ case "class-position": {
560
+ const position = forward ? entry.after : entry.before;
561
+ this.store.setClassPosition(entry.classId, position);
562
+ this.renderer.syncClassPosition(entry.classId, this.store);
563
+ if (!this.store.hasManualNotePosition(entry.classId)) {
564
+ this.renderer.syncNotePosition(entry.classId, this.store);
565
+ }
566
+ this.emit("class-position-changed", {
567
+ type: "class-position-changed",
568
+ classId: entry.classId,
569
+ ...position,
570
+ });
571
+ return;
572
+ }
573
+ case "note-position": {
574
+ const position = forward ? entry.after : entry.before;
575
+ if (forward || entry.beforeWasManual) {
576
+ this.store.setNotePosition(entry.classId, position);
577
+ }
578
+ else {
579
+ this.store.clearNotePosition(entry.classId);
580
+ }
581
+ this.renderer.syncNotePosition(entry.classId, this.store);
582
+ this.emit("note-position-changed", {
583
+ type: "note-position-changed",
584
+ classId: entry.classId,
585
+ ...this.store.getNotePosition(entry.classId),
586
+ });
587
+ return;
588
+ }
589
+ case "relationship-waypoints": {
590
+ const waypoints = forward ? entry.after : entry.before;
591
+ this.store.replaceWaypoints(entry.relationshipId, waypoints);
592
+ this.renderer.syncWaypoints(entry.relationshipId, this.store);
593
+ this.ensureValidWaypointSelection(entry.relationshipId, waypoints.length);
594
+ this.emitWaypointTransition(entry, forward);
595
+ return;
596
+ }
597
+ case "relationship-properties": {
598
+ const changes = forward ? entry.after : entry.before;
599
+ this.store.updateRelationship(entry.relationshipId, changes);
600
+ this.renderer.syncRelationship(entry.relationshipId, this.store);
601
+ this.emit("relationship-changed", {
602
+ type: "relationship-changed",
603
+ relationshipId: entry.relationshipId,
604
+ changes: { ...changes },
605
+ });
606
+ return;
607
+ }
608
+ case "layout-transaction": {
609
+ const positions = forward ? entry.after : entry.before;
610
+ this.store.applyClassPositions(new Map(Object.entries(positions)));
611
+ this.renderer.syncAllPositions(this.store);
612
+ this.emit("auto-layout-completed", {
613
+ type: "auto-layout-completed",
614
+ layout: this.store.getLayout(),
615
+ });
616
+ }
617
+ }
618
+ }
619
+ emitWaypointTransition(entry, forward) {
620
+ const before = forward ? entry.before : entry.after;
621
+ const after = forward ? entry.after : entry.before;
622
+ const index = entry.index ?? changedWaypointIndex(before, after);
623
+ if (entry.operation === "add") {
624
+ if (forward) {
625
+ const position = after[index];
626
+ if (position) {
627
+ this.emit("relationship-waypoint-added", {
628
+ type: "relationship-waypoint-added",
629
+ relationshipId: entry.relationshipId,
630
+ index,
631
+ ...position,
632
+ });
633
+ }
634
+ }
635
+ else {
636
+ this.emit("relationship-waypoint-removed", {
637
+ type: "relationship-waypoint-removed",
638
+ relationshipId: entry.relationshipId,
639
+ index,
640
+ });
641
+ }
642
+ return;
643
+ }
644
+ if (entry.operation === "remove") {
645
+ if (forward) {
646
+ this.emit("relationship-waypoint-removed", {
647
+ type: "relationship-waypoint-removed",
648
+ relationshipId: entry.relationshipId,
649
+ index,
650
+ });
651
+ }
652
+ else {
653
+ const position = after[index];
654
+ if (position) {
655
+ this.emit("relationship-waypoint-added", {
656
+ type: "relationship-waypoint-added",
657
+ relationshipId: entry.relationshipId,
658
+ index,
659
+ ...position,
660
+ });
661
+ }
662
+ }
663
+ return;
664
+ }
665
+ if (entry.operation === "reset") {
666
+ if (forward) {
667
+ this.emit("relationship-route-reset", {
668
+ type: "relationship-route-reset",
669
+ relationshipId: entry.relationshipId,
670
+ });
671
+ }
672
+ else {
673
+ for (const [restoredIndex, position] of after.entries()) {
674
+ this.emit("relationship-waypoint-added", {
675
+ type: "relationship-waypoint-added",
676
+ relationshipId: entry.relationshipId,
677
+ index: restoredIndex,
678
+ ...position,
679
+ });
680
+ }
681
+ }
682
+ return;
683
+ }
684
+ for (let changedIndex = 0; changedIndex < after.length; changedIndex += 1) {
685
+ const previous = before[changedIndex];
686
+ const position = after[changedIndex];
687
+ if (!previous || !position || positionsEqual(previous, position))
688
+ continue;
689
+ this.emit("relationship-waypoint-changed", {
690
+ type: "relationship-waypoint-changed",
691
+ relationshipId: entry.relationshipId,
692
+ index: changedIndex,
693
+ ...position,
694
+ });
695
+ }
696
+ }
697
+ ensureValidWaypointSelection(relationshipId, waypointCount) {
698
+ if (this.selection?.kind === "relationship-waypoint" &&
699
+ this.selection.relationshipId === relationshipId &&
700
+ this.selection.index >= waypointCount) {
701
+ this.changeSelection({ kind: "relationship", id: relationshipId });
702
+ }
703
+ }
704
+ deleteSelectedWaypoint() {
705
+ if (this.selection?.kind !== "relationship-waypoint")
706
+ return;
707
+ this.removeRelationshipWaypoint(this.selection.relationshipId, this.selection.index);
708
+ }
709
+ changeSelection(selection) {
710
+ if (selectionsEqual(this.selection, selection))
711
+ return;
712
+ this.selection = selection;
713
+ this.renderer.setSelection(selection);
714
+ this.emit("selection-changed", { type: "selection-changed", selection });
715
+ }
716
+ emit(type, event) {
717
+ this.events.emit(type, event);
718
+ }
719
+ assertAlive() {
720
+ if (this.destroyed)
721
+ throw new Error("Diagram has been destroyed");
722
+ }
723
+ assertEditable() {
724
+ this.assertAlive();
725
+ if (!this.editable) {
726
+ throw new Error("Diagram is read-only");
727
+ }
728
+ }
729
+ }
730
+ export function createDiagram(container, options) {
731
+ return new ModelDiagram(container, options);
732
+ }
733
+ //# sourceMappingURL=diagram.js.map