@bpmn-nova/studio 0.3.3-preview → 0.3.5-preview
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.
- package/README.md +75 -6
- package/dist/config.js +285 -0
- package/dist/controller.js +19 -4
- package/dist/index.d.ts +101 -2
- package/dist/modules/export-svg/render.js +82 -62
- package/dist/modules/node-geometry/index.d.ts +19 -0
- package/dist/modules/node-geometry/index.js +147 -0
- package/dist/modules/renderer-svg/index.js +63 -25
- package/dist/modules/viewer/index.d.ts +1 -1
- package/dist/modules/viewer/index.js +86 -35
- package/dist/panel-selection.js +60 -0
- package/dist/shell.js +315 -87
- package/dist/sidebars.js +195 -0
- package/dist/styles.css +72 -12
- package/llms-full.txt +1614 -93
- package/llms.txt +76 -15
- package/package.json +1 -1
|
@@ -394,6 +394,8 @@ export class BpmnViewer {
|
|
|
394
394
|
this.projectedRuntime = null;
|
|
395
395
|
this.runtimePresentation = null;
|
|
396
396
|
this.selection = null;
|
|
397
|
+
this._sidebarTransitionActive = false;
|
|
398
|
+
this._sidebarViewport = null;
|
|
397
399
|
this._detailsCleanup = null;
|
|
398
400
|
this._detailsMotion = null;
|
|
399
401
|
this._detailsRoot = null;
|
|
@@ -424,11 +426,7 @@ export class BpmnViewer {
|
|
|
424
426
|
throw error;
|
|
425
427
|
}
|
|
426
428
|
this._resizeObserver = typeof ResizeObserver === 'function'
|
|
427
|
-
? new ResizeObserver(() =>
|
|
428
|
-
if (this.projection !== 'auto') return;
|
|
429
|
-
const next = this._resolveProjection();
|
|
430
|
-
if (next !== this.activeProjection) this.refresh();
|
|
431
|
-
})
|
|
429
|
+
? new ResizeObserver(() => this._refreshAutoProjection())
|
|
432
430
|
: null;
|
|
433
431
|
this._resizeObserver?.observe(options.container);
|
|
434
432
|
}
|
|
@@ -436,10 +434,29 @@ export class BpmnViewer {
|
|
|
436
434
|
_resolveProjection() {
|
|
437
435
|
if (this.projection !== 'auto') return this.projection;
|
|
438
436
|
if (!this.runtime) return 'standard';
|
|
437
|
+
if (this._sidebarTransitionActive) return this.activeProjection;
|
|
439
438
|
const width = this.container.clientWidth || this.container.getBoundingClientRect?.().width || 0;
|
|
440
439
|
return width > 0 && width < 720 ? 'compact' : 'approval';
|
|
441
440
|
}
|
|
442
441
|
|
|
442
|
+
// The Shell owns the transition. Do not replace the renderer at intermediate widths.
|
|
443
|
+
_setSidebarTransitionActive(active) {
|
|
444
|
+
if (this._sidebarTransitionActive === Boolean(active)) return;
|
|
445
|
+
this._sidebarTransitionActive = Boolean(active);
|
|
446
|
+
if (active) {
|
|
447
|
+
if (this.activeProjection !== 'compact') this._sidebarViewport = this.renderer?.getViewportState?.() || this._sidebarViewport;
|
|
448
|
+
} else this._refreshAutoProjection();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
_refreshAutoProjection() {
|
|
452
|
+
if (this._sidebarTransitionActive || this.projection !== 'auto') return;
|
|
453
|
+
const next = this._resolveProjection();
|
|
454
|
+
if (next === this.activeProjection) return;
|
|
455
|
+
if (this.activeProjection !== 'compact') this._sidebarViewport = this.renderer?.getViewportState?.() || this._sidebarViewport;
|
|
456
|
+
this.refresh();
|
|
457
|
+
if (this.activeProjection !== 'compact' && this._sidebarViewport) this.renderer?.setViewportState?.(this._sidebarViewport);
|
|
458
|
+
}
|
|
459
|
+
|
|
443
460
|
_rebuildProjection() {
|
|
444
461
|
const previousProjection = this.activeProjection;
|
|
445
462
|
this.activeProjection = this._resolveProjection();
|
|
@@ -494,33 +511,59 @@ export class BpmnViewer {
|
|
|
494
511
|
return [];
|
|
495
512
|
}
|
|
496
513
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
514
|
+
_resolveTraceClick(payload) {
|
|
515
|
+
if (!payload) return null;
|
|
516
|
+
const targetType = payload.targetType;
|
|
517
|
+
const transitionId = payload.transition?.id || payload.traceItem?.transitionId;
|
|
518
|
+
const transition = targetType === 'transition' && transitionId
|
|
519
|
+
? this.runtimePresentation.getTransition(transitionId)
|
|
520
|
+
: null;
|
|
521
|
+
if (targetType === 'transition' && !transition) return null;
|
|
522
|
+
const elementId = transition?.sourceElementId || payload.elementId || payload.element?.id || payload.traceItem?.elementId;
|
|
523
|
+
let selectedElement;
|
|
524
|
+
if (targetType === 'edge') {
|
|
525
|
+
const current = this.projectedModel.edges.find((edge) => edge.id === elementId)
|
|
526
|
+
|| this.model.edges.find((edge) => edge.id === elementId);
|
|
527
|
+
if (!current) return null;
|
|
528
|
+
const sourceIds = current.sourceEdgeIds?.length ? current.sourceEdgeIds : [current.id];
|
|
501
529
|
const sourceEdges = sourceIds.map((id) => this.model.edges.find((edge) => edge.id === id)).filter(Boolean);
|
|
502
|
-
|
|
503
|
-
} else
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
this.
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
530
|
+
selectedElement = sourceEdges.find((edge) => current.name && edge.name === current.name) || sourceEdges[0] || current;
|
|
531
|
+
} else selectedElement = this.model.nodes.find((node) => node.id === elementId) || null;
|
|
532
|
+
if (!selectedElement && !transition) return null;
|
|
533
|
+
const presentation = selectedElement?.id && targetType !== 'edge'
|
|
534
|
+
? this.runtimePresentation.getNode(selectedElement.id)
|
|
535
|
+
: null;
|
|
536
|
+
const visitId = payload.visitId ?? payload.traceItem?.visitId ?? null;
|
|
537
|
+
const traceItems = this.traceProjection?.items || [];
|
|
538
|
+
const traceItem = traceItems.find((item) => payload.traceItem?.id && item.id === payload.traceItem.id)
|
|
539
|
+
|| (targetType === 'transition' ? traceItems.find((item) => item.transitionId === transitionId) : null)
|
|
540
|
+
|| (visitId ? traceItems.find((item) => item.elementId === elementId && item.visitId === visitId) : null)
|
|
541
|
+
|| null;
|
|
542
|
+
const visit = visitId ? presentation?.visits?.find((item) => item.id === visitId) : null;
|
|
543
|
+
if (targetType === 'visit' && visitId && !traceItem && !visit) return null;
|
|
544
|
+
const records = traceItem?.records || visit?.records;
|
|
545
|
+
return {
|
|
546
|
+
targetType,
|
|
547
|
+
elementId: selectedElement?.id || elementId,
|
|
548
|
+
element: selectedElement,
|
|
549
|
+
visitId,
|
|
550
|
+
traceItem,
|
|
551
|
+
transition,
|
|
516
552
|
presentation,
|
|
517
|
-
activityInstances: this._activityInstancesFor({
|
|
518
|
-
actions:
|
|
553
|
+
activityInstances: records ? [...records] : this._activityInstancesFor({ presentation }),
|
|
554
|
+
actions: traceItem?.actions || visit?.actions || (transition?.action ? [transition.action] : presentation?.actions) || [],
|
|
519
555
|
runtime: this.runtime,
|
|
520
556
|
projection: this.activeProjection,
|
|
521
557
|
originalEvent: payload.originalEvent,
|
|
522
558
|
viewer: this,
|
|
523
|
-
}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
_emitTraceClick(payload) {
|
|
563
|
+
const resolved = this._resolveTraceClick(payload);
|
|
564
|
+
if (!resolved) return;
|
|
565
|
+
this.options._onPanelSelection?.(resolved);
|
|
566
|
+
this.onTraceClick(resolved);
|
|
524
567
|
}
|
|
525
568
|
|
|
526
569
|
_mountRenderer() {
|
|
@@ -554,7 +597,7 @@ export class BpmnViewer {
|
|
|
554
597
|
onDetailsRequest: (item, anchor, event) => {
|
|
555
598
|
const node = this.model.nodes.find((candidate) => candidate.id === item.elementId);
|
|
556
599
|
if (!node) return;
|
|
557
|
-
this._select('node', node, event, { emitTrace: false });
|
|
600
|
+
this._select('node', node, event, { emitTrace: false, traceItem: item });
|
|
558
601
|
const presentation = this.runtimePresentation.getNode(node.id);
|
|
559
602
|
if (presentation.hasDetails && this.runtimeDetailsOptions.autoOpen) {
|
|
560
603
|
this._openRuntimeDetails(node, presentation, anchor, {
|
|
@@ -564,6 +607,7 @@ export class BpmnViewer {
|
|
|
564
607
|
},
|
|
565
608
|
onTransitionRequest: (item, anchor, event) => {
|
|
566
609
|
const transition = this.runtimePresentation.getTransition(item.transitionId);
|
|
610
|
+
this.options._onPanelSelection?.(this._resolveTraceClick({ targetType: 'transition', transition, traceItem: item, originalEvent: event }));
|
|
567
611
|
if (this.runtimeDetailsOptions.autoOpen) {
|
|
568
612
|
this._openRuntimeTransitionDetails(transition, anchor, {
|
|
569
613
|
restoreFocusVisible: event ? event.detail === 0 : Boolean(anchor?.matches?.(':focus-visible')),
|
|
@@ -602,17 +646,19 @@ export class BpmnViewer {
|
|
|
602
646
|
});
|
|
603
647
|
}
|
|
604
648
|
|
|
605
|
-
_select(kind, element, originalEvent = undefined, { emitTrace = true } = {}) {
|
|
649
|
+
_select(kind, element, originalEvent = undefined, { emitTrace = true, traceItem = null } = {}) {
|
|
606
650
|
this.selection = { kind, id: element.id };
|
|
607
651
|
this.renderer.setSelection(this.selection);
|
|
608
652
|
const presentation = kind === 'node' ? this.runtimePresentation.getNode(element.id) : null;
|
|
609
653
|
this.onElementClick({ kind, element, runtime: this.runtime, presentation });
|
|
610
654
|
if (emitTrace) this._emitTraceClick({ targetType: kind, element, presentation, originalEvent });
|
|
655
|
+
else this.options._onPanelSelection?.(this._resolveTraceClick({ targetType: traceItem ? 'visit' : kind, element, presentation, traceItem, originalEvent }));
|
|
611
656
|
}
|
|
612
657
|
|
|
613
658
|
clearSelection() {
|
|
614
659
|
this.selection = null;
|
|
615
660
|
this.renderer.setSelection(null);
|
|
661
|
+
this.options._onPanelSelection?.(null);
|
|
616
662
|
this.onElementClick(null);
|
|
617
663
|
}
|
|
618
664
|
|
|
@@ -923,6 +969,7 @@ export class BpmnViewer {
|
|
|
923
969
|
this._rebuildProjection();
|
|
924
970
|
this._mountRenderer();
|
|
925
971
|
if (this.selection) this.renderer.setSelection?.(this.selection);
|
|
972
|
+
this.options._onPanelSelection?.();
|
|
926
973
|
}
|
|
927
974
|
|
|
928
975
|
setModel(model) { this.model = model; this._svgExportAssetCache.clear(); this.refresh(); }
|
|
@@ -938,23 +985,25 @@ export class BpmnViewer {
|
|
|
938
985
|
this.refresh();
|
|
939
986
|
requestAnimationFrame(() => this.renderer.fitView());
|
|
940
987
|
}
|
|
941
|
-
setDisplayOptions({ timeline, runtimeDetails, runtimeTraceOptions, runtimeAssetResolver } = {}) {
|
|
988
|
+
setDisplayOptions({ timeline, runtimeDetails, runtimeTraceOptions, runtimeAssetResolver, replace = false } = {}) {
|
|
942
989
|
let changed = false;
|
|
943
990
|
if (runtimeAssetResolver !== undefined) {
|
|
944
991
|
this.runtimeAssetResolver = runtimeAssetResolver || null;
|
|
945
992
|
this._svgExportAssetCache.clear();
|
|
946
993
|
changed = true;
|
|
947
994
|
}
|
|
948
|
-
if (runtimeTraceOptions !== undefined) {
|
|
949
|
-
this.runtimeTraceOptions =
|
|
995
|
+
if (replace || runtimeTraceOptions !== undefined) {
|
|
996
|
+
this.runtimeTraceOptions = replace
|
|
997
|
+
? { ...(runtimeTraceOptions || {}) }
|
|
998
|
+
: { ...this.runtimeTraceOptions, ...(runtimeTraceOptions || {}) };
|
|
950
999
|
changed = true;
|
|
951
1000
|
}
|
|
952
|
-
if (timeline !== undefined) {
|
|
953
|
-
this.timelineOptions = mergeTimelineOptions(this.timelineOptions, timeline || {});
|
|
1001
|
+
if (replace || timeline !== undefined) {
|
|
1002
|
+
this.timelineOptions = mergeTimelineOptions(replace ? {} : this.timelineOptions, timeline || {});
|
|
954
1003
|
changed = true;
|
|
955
1004
|
}
|
|
956
|
-
if (runtimeDetails !== undefined) {
|
|
957
|
-
this.runtimeDetailsOptions = mergeRuntimeDetailsOptions(this.runtimeDetailsOptions, runtimeDetails || {});
|
|
1005
|
+
if (replace || runtimeDetails !== undefined) {
|
|
1006
|
+
this.runtimeDetailsOptions = mergeRuntimeDetailsOptions(replace ? {} : this.runtimeDetailsOptions, runtimeDetails || {});
|
|
958
1007
|
changed = true;
|
|
959
1008
|
}
|
|
960
1009
|
if (changed) this.refresh();
|
|
@@ -1013,6 +1062,8 @@ export class BpmnViewer {
|
|
|
1013
1062
|
fitView() { this.renderer.fitView(); }
|
|
1014
1063
|
zoomBy(delta) { this.renderer.zoomBy(delta); }
|
|
1015
1064
|
destroy() {
|
|
1065
|
+
this._sidebarTransitionActive = false;
|
|
1066
|
+
this._sidebarViewport = null;
|
|
1016
1067
|
this._svgExportPreview?.close?.({ immediate: true });
|
|
1017
1068
|
this._svgExportPreview = null;
|
|
1018
1069
|
this._svgExportAssetCache.clear();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Snapshot only public data. Never freeze the Controller, Viewer, or their source models.
|
|
2
|
+
function immutableCopy(value, seen = new WeakMap()) {
|
|
3
|
+
if (!value || typeof value !== 'object') return value;
|
|
4
|
+
if (seen.has(value)) return seen.get(value);
|
|
5
|
+
const copy = Array.isArray(value) ? [] : {};
|
|
6
|
+
seen.set(value, copy);
|
|
7
|
+
for (const key of Object.keys(value)) {
|
|
8
|
+
Object.defineProperty(copy, key, {
|
|
9
|
+
value: immutableCopy(value[key], seen),
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return Object.freeze(copy);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const EMPTY_SELECTION = Object.freeze({ selection: null, selectedElement: null, trace: null });
|
|
19
|
+
|
|
20
|
+
/** Resolve current facts rather than retaining a clicked node or an old Runtime snapshot. */
|
|
21
|
+
export function createPanelSelection(studio, viewer, previousTrace = null) {
|
|
22
|
+
if (!viewer) {
|
|
23
|
+
const selection = studio.selection;
|
|
24
|
+
const selectedElement = studio.getSelectedElement();
|
|
25
|
+
if (!selection || !selectedElement) return EMPTY_SELECTION;
|
|
26
|
+
return immutableCopy({ selection, selectedElement, trace: null });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// An explicit clear must stay cleared across refreshes, even when the Viewer still
|
|
30
|
+
// has an older visual selection (for example after a selected visit disappears).
|
|
31
|
+
if (!previousTrace) return EMPTY_SELECTION;
|
|
32
|
+
const current = viewer._resolveTraceClick(previousTrace);
|
|
33
|
+
if (!current) return EMPTY_SELECTION;
|
|
34
|
+
const { viewer: liveViewer, originalEvent: _originalEvent, ...traceData } = current;
|
|
35
|
+
const selection = current.element
|
|
36
|
+
? { kind: current.targetType === 'edge' ? 'edge' : 'node', id: current.element.id }
|
|
37
|
+
: null;
|
|
38
|
+
const snapshot = immutableCopy({ selection, selectedElement: current.element || null, trace: traceData });
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
...snapshot,
|
|
41
|
+
trace: Object.freeze({ ...snapshot.trace, viewer: liveViewer }),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Ignore fresh object identity so refreshes and paired timeline callbacks notify only once. */
|
|
46
|
+
export function samePanelSelection(previous, next) {
|
|
47
|
+
const pairs = new WeakMap();
|
|
48
|
+
const same = (left, right) => {
|
|
49
|
+
if (Object.is(left, right)) return true;
|
|
50
|
+
if (!left || !right || typeof left !== 'object' || typeof right !== 'object') return false;
|
|
51
|
+
if (pairs.has(left)) return pairs.get(left) === right;
|
|
52
|
+
pairs.set(left, right);
|
|
53
|
+
const keys = Object.keys(left);
|
|
54
|
+
if (keys.length !== Object.keys(right).length) return false;
|
|
55
|
+
return keys.every((key) => Object.hasOwn(right, key) && (key === 'viewer'
|
|
56
|
+
? left[key] === right[key]
|
|
57
|
+
: same(left[key], right[key])));
|
|
58
|
+
};
|
|
59
|
+
return same(previous, next);
|
|
60
|
+
}
|