@elabs-ai/components-process 4.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/core/index.d.ts +801 -3
- package/dist/core/index.js +1334 -0
- package/dist/core/index.js.map +1 -1
- package/dist/index.d.ts +1889 -34
- package/dist/index.js +5512 -196
- package/dist/index.js.map +1 -1
- package/dist/test/index.d.ts +223 -5
- package/dist/test/index.js +346 -191
- package/dist/test/index.js.map +1 -1
- package/package.json +14 -13
- package/src/__contract__/case-table.contract.test.tsx +49 -0
- package/src/__contract__/compare-kpi-strip.contract.test.tsx +49 -0
- package/src/__contract__/conformance-overlay.contract.test.tsx +49 -0
- package/src/__contract__/happy-path-editor.contract.test.tsx +49 -0
- package/src/__contract__/process-kpi-strip.contract.test.tsx +49 -0
- package/src/__contract__/violation-list.contract.test.tsx +49 -0
- package/src/abstraction-controls/abstraction-controls-per-type.test.tsx +80 -0
- package/src/abstraction-controls/abstraction-controls.stories.tsx +43 -1
- package/src/abstraction-controls/abstraction-controls.tsx +198 -5
- package/src/case-table/case-table.stories.tsx +89 -0
- package/src/case-table/case-table.test.tsx +148 -0
- package/src/case-table/case-table.tsx +144 -0
- package/src/case-table/columns.ts +116 -0
- package/src/case-table/index.ts +11 -0
- package/src/case-timeline/case-timeline-model.test.ts +72 -0
- package/src/case-timeline/case-timeline-model.ts +112 -0
- package/src/case-timeline/case-timeline.stories.tsx +94 -0
- package/src/case-timeline/case-timeline.test.tsx +51 -0
- package/src/case-timeline/case-timeline.tsx +109 -0
- package/src/case-timeline/index.ts +9 -0
- package/src/conformance-overlay/conformance-fixture.ts +59 -0
- package/src/conformance-overlay/conformance-legend.tsx +109 -0
- package/src/conformance-overlay/conformance-overlay.stories.tsx +116 -0
- package/src/conformance-overlay/conformance-overlay.test.tsx +88 -0
- package/src/conformance-overlay/conformance-overlay.tsx +107 -0
- package/src/conformance-overlay/conformance-state.test.ts +79 -0
- package/src/conformance-overlay/conformance-state.ts +220 -0
- package/src/conformance-overlay/index.ts +4 -0
- package/src/core/activity-color-scale.test.ts +107 -0
- package/src/core/activity-color-scale.ts +133 -0
- package/src/core/adapters/ocel.test.ts +112 -0
- package/src/core/adapters/ocel.ts +359 -0
- package/src/core/adapters/xes.test.ts +293 -0
- package/src/core/adapters/xes.ts +384 -0
- package/src/core/cases-from-log.test.ts +72 -0
- package/src/core/cases-from-log.ts +85 -0
- package/src/core/conformance.test.ts +80 -0
- package/src/core/conformance.ts +91 -0
- package/src/core/diff-graphs.test.ts +151 -0
- package/src/core/diff-graphs.ts +118 -0
- package/src/core/discover-object-centric-graph.test.ts +94 -0
- package/src/core/discover-object-centric-graph.ts +296 -0
- package/src/core/fixtures/ocel-sample.ts +82 -0
- package/src/core/fixtures/sample.xes +68 -0
- package/src/core/index.ts +113 -0
- package/src/core/reference-model.test.ts +43 -0
- package/src/core/reference-model.ts +116 -0
- package/src/core/replay-timeline.test.ts +161 -0
- package/src/core/replay-timeline.ts +260 -0
- package/src/core/segments.test.ts +185 -0
- package/src/core/segments.ts +153 -0
- package/src/core/token-replay.test.ts +218 -0
- package/src/core/token-replay.ts +456 -0
- package/src/core/types.ts +2 -2
- package/src/dotted-chart/compute-dots.test.ts +176 -0
- package/src/dotted-chart/compute-dots.ts +241 -0
- package/src/dotted-chart/dotted-chart-labels.ts +93 -0
- package/src/dotted-chart/dotted-chart.stories.tsx +182 -0
- package/src/dotted-chart/dotted-chart.test.tsx +135 -0
- package/src/dotted-chart/dotted-chart.tsx +841 -0
- package/src/dotted-chart/index.ts +23 -0
- package/src/dotted-chart/use-element-size.ts +33 -0
- package/src/happy-path-editor/happy-path-editor-context.ts +81 -0
- package/src/happy-path-editor/happy-path-editor.stories.tsx +116 -0
- package/src/happy-path-editor/happy-path-editor.test.tsx +142 -0
- package/src/happy-path-editor/happy-path-editor.tsx +239 -0
- package/src/happy-path-editor/happy-path-step-node.tsx +175 -0
- package/src/happy-path-editor/index.ts +4 -0
- package/src/index.ts +51 -1
- package/src/performance-spectrum/aggregate-segments.test.ts +107 -0
- package/src/performance-spectrum/aggregate-segments.ts +174 -0
- package/src/performance-spectrum/index.ts +25 -0
- package/src/performance-spectrum/performance-spectrum-context.tsx +116 -0
- package/src/performance-spectrum/performance-spectrum.stories.tsx +128 -0
- package/src/performance-spectrum/performance-spectrum.test.tsx +190 -0
- package/src/performance-spectrum/performance-spectrum.tsx +870 -0
- package/src/process-compare/compare-kpi-strip.stories.tsx +48 -0
- package/src/process-compare/compare-kpi-strip.tsx +94 -0
- package/src/process-compare/compare-model.ts +83 -0
- package/src/process-compare/compare-side.tsx +42 -0
- package/src/process-compare/diff-to-graph.ts +104 -0
- package/src/process-compare/index.ts +23 -0
- package/src/process-compare/process-compare.stories.tsx +184 -0
- package/src/process-compare/process-compare.test.tsx +224 -0
- package/src/process-compare/process-compare.tsx +251 -0
- package/src/process-explorer.stories.tsx +1 -1
- package/src/process-filter-bar/index.ts +2 -0
- package/src/process-filter-bar/process-filter-bar.stories.tsx +156 -0
- package/src/process-filter-bar/process-filter-bar.test.tsx +201 -0
- package/src/process-filter-bar/process-filter-bar.tsx +167 -0
- package/src/process-kpi-strip/process-kpi-strip.stories.tsx +47 -0
- package/src/process-kpi-strip/process-kpi-strip.test.tsx +67 -0
- package/src/process-kpi-strip/process-kpi-strip.tsx +148 -8
- package/src/process-map/activity-accent.ts +25 -0
- package/src/process-map/index.ts +1 -0
- package/src/process-map/map-model.test.ts +16 -0
- package/src/process-map/map-model.ts +323 -1
- package/src/process-map/object-centric-map.test.tsx +132 -0
- package/src/process-map/process-activity-node.tsx +152 -14
- package/src/process-map/process-map-object-centric.stories.tsx +219 -0
- package/src/process-map/process-map.stories.tsx +64 -0
- package/src/process-map/process-map.tsx +240 -16
- package/src/process-map/process-transition-edge.test.tsx +47 -0
- package/src/process-map/process-transition-edge.tsx +134 -7
- package/src/process-map/use-process-layout.ts +30 -9
- package/src/process-replay/congestion-heat.tsx +107 -0
- package/src/process-replay/index.ts +14 -0
- package/src/process-replay/process-replay.stories.tsx +168 -0
- package/src/process-replay/process-replay.test.tsx +170 -0
- package/src/process-replay/process-replay.tsx +285 -0
- package/src/process-replay/replay-controls.tsx +147 -0
- package/src/process-replay/replay-format.ts +83 -0
- package/src/process-replay/replay-tokens-context.ts +30 -0
- package/src/process-replay/use-controllable-value.ts +30 -0
- package/src/templates-process-explorer.stories.tsx +1304 -0
- package/src/test/contract.test.ts +66 -0
- package/src/test/contract.ts +107 -6
- package/src/test/doubles.test.tsx +87 -1
- package/src/test/doubles.tsx +174 -3
- package/src/test/index.ts +25 -1
- package/src/use-process-explorer/use-process-explorer.test.ts +44 -0
- package/src/use-process-explorer/use-process-explorer.ts +34 -2
- package/src/variant-explorer/coverage-bar.tsx +36 -0
- package/src/variant-explorer/index.ts +16 -0
- package/src/variant-explorer/sequence-chips.tsx +103 -0
- package/src/variant-explorer/variant-explorer-model.ts +42 -0
- package/src/variant-explorer/variant-explorer.stories.tsx +226 -0
- package/src/variant-explorer/variant-explorer.test.tsx +302 -0
- package/src/variant-explorer/variant-explorer.tsx +567 -0
- package/src/variant-explorer/variant-row.tsx +137 -0
- package/src/violation-list/index.ts +2 -0
- package/src/violation-list/violation-list.stories.tsx +73 -0
- package/src/violation-list/violation-list.test.tsx +84 -0
- package/src/violation-list/violation-list.tsx +259 -0
|
@@ -89,7 +89,29 @@ import {
|
|
|
89
89
|
import { abstractGraph, type AbstractionOptions } from "../core/abstract-graph";
|
|
90
90
|
import { detectRework, type ReworkStats } from "../core/detect-rework";
|
|
91
91
|
import { discoverGraph } from "../core/discover-graph";
|
|
92
|
+
import type { ActivityColorScale } from "../core/activity-color-scale";
|
|
92
93
|
import type { EventLog, ProcessGraph } from "../core/types";
|
|
94
|
+
import type { ConformanceResult } from "../core/conformance";
|
|
95
|
+
import {
|
|
96
|
+
activityConformance,
|
|
97
|
+
CONFORMANCE_STATE_DEFAULT_LABELS,
|
|
98
|
+
resolveConformanceStates,
|
|
99
|
+
transitionConformance,
|
|
100
|
+
withActivityConformance,
|
|
101
|
+
withTransitionConformance,
|
|
102
|
+
} from "../conformance-overlay/conformance-state";
|
|
103
|
+
import { ConformanceStateMark } from "../conformance-overlay/conformance-legend";
|
|
104
|
+
import {
|
|
105
|
+
abstractObjectCentricGraph,
|
|
106
|
+
objectCentricProcessGraph,
|
|
107
|
+
objectTypeColorScale,
|
|
108
|
+
type ObjectCentricGraph,
|
|
109
|
+
} from "../core/discover-object-centric-graph";
|
|
110
|
+
import {
|
|
111
|
+
buildObjectCentricMapModel,
|
|
112
|
+
OBJECT_CENTRIC_MAP_DEFAULT_LABELS,
|
|
113
|
+
type ObjectCentricMapLabels,
|
|
114
|
+
} from "./map-model";
|
|
93
115
|
import {
|
|
94
116
|
buildProcessMapModel,
|
|
95
117
|
processGraphStructureKey,
|
|
@@ -209,11 +231,67 @@ export interface ProcessMapProps extends Omit<HTMLAttributes<HTMLDivElement>, "o
|
|
|
209
231
|
tableView?: boolean;
|
|
210
232
|
/** No graph yet. Renders the loading panel rather than an empty canvas. */
|
|
211
233
|
loading?: boolean;
|
|
234
|
+
/**
|
|
235
|
+
* A shared activity colour scale (RM-054, `/core`'s `activityColorScale`). When given,
|
|
236
|
+
* each activity node shows its identity swatch — the same colour `VariantExplorer`
|
|
237
|
+
* paints that activity with. Build it once from the FULL graph and hand the same
|
|
238
|
+
* instance to both views. Omit for today's uncoloured nodes.
|
|
239
|
+
*/
|
|
240
|
+
colorScale?: ActivityColorScale;
|
|
241
|
+
/**
|
|
242
|
+
* A replay result (RM-061's `tokenReplay`) to overlay (RM-062). When given, every node
|
|
243
|
+
* and edge carries `data-conformance="both" | "logOnly" | "modelOnly"` — additive to
|
|
244
|
+
* `data-selection` — painted as a status tone PLUS a glyph and a line style, with the
|
|
245
|
+
* state's word in the accessible name and a Conformance column in the table twin. Omit
|
|
246
|
+
* for today's map, unchanged. Usually reached through `ConformanceOverlay`.
|
|
247
|
+
*/
|
|
248
|
+
conformance?: ConformanceResult;
|
|
249
|
+
/**
|
|
250
|
+
* Object-centric — RM-066. An object-centric graph (`discoverObjectCentricGraph`, e.g.
|
|
251
|
+
* over `fromOcel(…).logs`) to draw instead of `graph`/`log` — mutually exclusive with
|
|
252
|
+
* both, and it wins when given together (with a dev-mode console warning). Shared
|
|
253
|
+
* activities merge into one node carrying a chip per object type; edges are drawn once
|
|
254
|
+
* per object type in that type's chart colour, side by side. `abstraction` applies to
|
|
255
|
+
* every type alike; for per-type abstraction pass `abstractObjectCentricGraph(…)`'s
|
|
256
|
+
* result here instead.
|
|
257
|
+
*/
|
|
258
|
+
objectCentric?: ObjectCentricGraph;
|
|
259
|
+
/** Object-centric — RM-066. Strings the object-centric mode composes. */
|
|
260
|
+
objectCentricLabels?: ObjectCentricMapLabels;
|
|
212
261
|
/**
|
|
213
262
|
* Accessible name for the canvas region. Defaults to the localized
|
|
214
263
|
* `process.map.label` message.
|
|
215
264
|
*/
|
|
216
265
|
label?: string;
|
|
266
|
+
/**
|
|
267
|
+
* The smallest zoom the OPENING fit may use. The default keeps activity names legible
|
|
268
|
+
* and, when the process does not fit at that size, opens on its start. A workspace that
|
|
269
|
+
* would rather open on the whole picture — a wide canvas, a reader who zooms in on what
|
|
270
|
+
* matters — lowers it. The reader can always zoom out further by hand.
|
|
271
|
+
* @default PROCESS_MAP_LEGIBLE_ZOOM
|
|
272
|
+
*/
|
|
273
|
+
fitMinZoom?: number;
|
|
274
|
+
/**
|
|
275
|
+
* The fraction of the pane the opening fit keeps clear on every side. The default is
|
|
276
|
+
* generous because transition pills are drawn outside the fitted bounds; a pane that is
|
|
277
|
+
* short on the axis the process runs along can trade some of it for a larger picture.
|
|
278
|
+
* @default 0.15
|
|
279
|
+
*/
|
|
280
|
+
fitPadding?: number;
|
|
281
|
+
/**
|
|
282
|
+
* Re-frames the map whenever this value changes. The map already re-fits when its own
|
|
283
|
+
* structure or direction changes; it cannot know that the pane around it just changed
|
|
284
|
+
* shape — a dock opened, a side panel closed. Pass anything that changes when that
|
|
285
|
+
* happens. Unset: no extra re-fits, the reader's viewport is left alone.
|
|
286
|
+
*/
|
|
287
|
+
refitKey?: string | number;
|
|
288
|
+
// elkjs adapter — RM-067
|
|
289
|
+
/**
|
|
290
|
+
* The layout engine. `"elk"` lays the map out with `@elabs-ai/components-flow`'s
|
|
291
|
+
* `layoutFlowElk` — elkjs, an optional peer, loaded lazily on first use; when it is not
|
|
292
|
+
* installed the map falls back to dagre with a development-only warning. @default "dagre"
|
|
293
|
+
*/
|
|
294
|
+
layoutEngine?: "dagre" | "elk";
|
|
217
295
|
}
|
|
218
296
|
|
|
219
297
|
/** Resolve the graph the map paints, running `/core` only when its own inputs change. */
|
|
@@ -236,6 +314,31 @@ function useResolvedGraph(
|
|
|
236
314
|
}, [base, activities, paths]);
|
|
237
315
|
}
|
|
238
316
|
|
|
317
|
+
/** Object-centric — RM-066. The abstracted graph, its flattened twin and the type scale. */
|
|
318
|
+
function useObjectCentricView(
|
|
319
|
+
objectCentric: ObjectCentricGraph | undefined,
|
|
320
|
+
abstraction: Pick<AbstractionOptions, "activities" | "paths"> | undefined,
|
|
321
|
+
) {
|
|
322
|
+
const scale = useMemo(
|
|
323
|
+
() => (objectCentric ? objectTypeColorScale(objectCentric) : undefined),
|
|
324
|
+
[objectCentric],
|
|
325
|
+
);
|
|
326
|
+
const activities = abstraction?.activities;
|
|
327
|
+
const paths = abstraction?.paths;
|
|
328
|
+
return useMemo(() => {
|
|
329
|
+
if (!objectCentric || !scale) return undefined;
|
|
330
|
+
const graph =
|
|
331
|
+
activities === undefined && paths === undefined
|
|
332
|
+
? objectCentric
|
|
333
|
+
: abstractObjectCentricGraph(
|
|
334
|
+
objectCentric,
|
|
335
|
+
{},
|
|
336
|
+
{ activities: activities ?? 1, paths: paths ?? 1 },
|
|
337
|
+
);
|
|
338
|
+
return { graph, flat: objectCentricProcessGraph(graph), scale };
|
|
339
|
+
}, [objectCentric, scale, activities, paths]);
|
|
340
|
+
}
|
|
341
|
+
|
|
239
342
|
/**
|
|
240
343
|
* The process map.
|
|
241
344
|
*
|
|
@@ -259,7 +362,15 @@ export function ProcessMap({
|
|
|
259
362
|
showLegend = true,
|
|
260
363
|
tableView = false,
|
|
261
364
|
loading = false,
|
|
365
|
+
colorScale,
|
|
366
|
+
conformance,
|
|
367
|
+
objectCentric,
|
|
368
|
+
objectCentricLabels = OBJECT_CENTRIC_MAP_DEFAULT_LABELS,
|
|
262
369
|
label,
|
|
370
|
+
layoutEngine = "dagre",
|
|
371
|
+
fitMinZoom = PROCESS_MAP_LEGIBLE_ZOOM,
|
|
372
|
+
fitPadding = FIT_VIEW_OPTIONS.padding,
|
|
373
|
+
refitKey,
|
|
263
374
|
className,
|
|
264
375
|
...props
|
|
265
376
|
}: ProcessMapProps) {
|
|
@@ -280,7 +391,22 @@ export function ProcessMap({
|
|
|
280
391
|
},
|
|
281
392
|
[t],
|
|
282
393
|
);
|
|
283
|
-
|
|
394
|
+
// Object-centric — RM-066: abstract per type, flatten for layout, colour from the FULL
|
|
395
|
+
// graph so a type keeps its swatch while the reader abstracts.
|
|
396
|
+
const objectCentricView = useObjectCentricView(objectCentric, abstraction);
|
|
397
|
+
useEffect(() => {
|
|
398
|
+
if (process.env.NODE_ENV === "production") return;
|
|
399
|
+
if (objectCentric && (graph || log)) {
|
|
400
|
+
console.warn(
|
|
401
|
+
"ProcessMap: `objectCentric` is mutually exclusive with `graph`/`log`; drawing `objectCentric`.",
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}, [objectCentric, graph, log]);
|
|
405
|
+
const resolved = useResolvedGraph(
|
|
406
|
+
objectCentricView ? objectCentricView.flat : graph,
|
|
407
|
+
objectCentricView ? undefined : log,
|
|
408
|
+
objectCentricView ? undefined : abstraction,
|
|
409
|
+
);
|
|
284
410
|
const derivedRework = useMemo(
|
|
285
411
|
() => (rework ? undefined : log ? detectRework(log) : undefined),
|
|
286
412
|
[rework, log],
|
|
@@ -315,9 +441,10 @@ export function ProcessMap({
|
|
|
315
441
|
rework: activeRework,
|
|
316
442
|
selection: activeSelection,
|
|
317
443
|
selectionStates,
|
|
444
|
+
colorScale,
|
|
318
445
|
})
|
|
319
446
|
: null,
|
|
320
|
-
[resolved, metric, activeRework, activeSelection, selectionStates],
|
|
447
|
+
[resolved, metric, activeRework, activeSelection, selectionStates, colorScale],
|
|
321
448
|
);
|
|
322
449
|
|
|
323
450
|
// ── Measured node sizes ───────────────────────────────────────────────────
|
|
@@ -387,6 +514,7 @@ export function ProcessMap({
|
|
|
387
514
|
edges: firstPass?.edges ?? EMPTY_EDGES,
|
|
388
515
|
structureKey: layoutKey,
|
|
389
516
|
direction,
|
|
517
|
+
layoutEngine,
|
|
390
518
|
});
|
|
391
519
|
|
|
392
520
|
// PASS 2 — the same model, now told which edges run against the layout direction, so
|
|
@@ -395,16 +523,30 @@ export function ProcessMap({
|
|
|
395
523
|
const model: ProcessMapModel | null = useMemo(
|
|
396
524
|
() =>
|
|
397
525
|
resolved
|
|
398
|
-
?
|
|
526
|
+
? buildObjectCentricMapModel({
|
|
399
527
|
graph: resolved,
|
|
400
528
|
metric,
|
|
401
529
|
rework: activeRework,
|
|
402
530
|
selection: activeSelection,
|
|
403
531
|
selectionStates,
|
|
404
532
|
backEdgeIds: layout.backEdgeIds,
|
|
533
|
+
colorScale,
|
|
534
|
+
objectCentric: objectCentricView?.graph,
|
|
535
|
+
objectTypeScale: objectCentricView?.scale,
|
|
536
|
+
objectCentricLabels,
|
|
405
537
|
})
|
|
406
538
|
: null,
|
|
407
|
-
[
|
|
539
|
+
[
|
|
540
|
+
objectCentricView,
|
|
541
|
+
objectCentricLabels,
|
|
542
|
+
resolved,
|
|
543
|
+
metric,
|
|
544
|
+
activeRework,
|
|
545
|
+
activeSelection,
|
|
546
|
+
selectionStates,
|
|
547
|
+
layout.backEdgeIds,
|
|
548
|
+
colorScale,
|
|
549
|
+
],
|
|
408
550
|
);
|
|
409
551
|
|
|
410
552
|
const positionedNodes = useMemo(
|
|
@@ -412,6 +554,38 @@ export function ProcessMap({
|
|
|
412
554
|
[model, layout, direction],
|
|
413
555
|
);
|
|
414
556
|
|
|
557
|
+
// ── Conformance (RM-062) ──────────────────────────────────────────────────
|
|
558
|
+
// A decoration AFTER the model and the layout, never an input to either: switching the
|
|
559
|
+
// reference model re-inks nodes and edges without re-deriving a metric or moving a node.
|
|
560
|
+
const conformanceStates = useMemo(
|
|
561
|
+
() => (conformance ? resolveConformanceStates(conformance) : null),
|
|
562
|
+
[conformance],
|
|
563
|
+
);
|
|
564
|
+
const canvasNodes = useMemo(
|
|
565
|
+
() =>
|
|
566
|
+
conformanceStates
|
|
567
|
+
? positionedNodes.map((node) => withActivityConformance(node, conformanceStates))
|
|
568
|
+
: positionedNodes,
|
|
569
|
+
[positionedNodes, conformanceStates],
|
|
570
|
+
);
|
|
571
|
+
// Where the process begins: a clamped opening fit keeps these in view on BOTH axes — in a
|
|
572
|
+
// left-to-right layout the first rank sits at mid-height, not in the top-left corner.
|
|
573
|
+
const startNodeIds = useMemo(
|
|
574
|
+
() => positionedNodes.filter((node) => node.data.isStart).map((node) => node.id),
|
|
575
|
+
[positionedNodes],
|
|
576
|
+
);
|
|
577
|
+
const fitViewOptions = useMemo(
|
|
578
|
+
() => ({ ...FIT_VIEW_OPTIONS, minZoom: fitMinZoom, padding: fitPadding }),
|
|
579
|
+
[fitMinZoom, fitPadding],
|
|
580
|
+
);
|
|
581
|
+
const canvasEdges = useMemo(
|
|
582
|
+
() =>
|
|
583
|
+
model && conformanceStates
|
|
584
|
+
? model.edges.map((edge) => withTransitionConformance(edge, conformanceStates))
|
|
585
|
+
: (model?.edges ?? EMPTY_EDGES),
|
|
586
|
+
[model, conformanceStates],
|
|
587
|
+
);
|
|
588
|
+
|
|
415
589
|
// ── Hover ─────────────────────────────────────────────────────────────────
|
|
416
590
|
const [hover, setHover] = useState<ProcessMapHoverState>(EMPTY_PROCESS_MAP_HOVER);
|
|
417
591
|
const edgesRef = useRef<ProcessMapEdge[]>([]);
|
|
@@ -723,6 +897,12 @@ export function ProcessMap({
|
|
|
723
897
|
<TableHead scope="col">{model.nodeMetricLabel}</TableHead>
|
|
724
898
|
<TableHead scope="col">{t("process.map.columnRework")}</TableHead>
|
|
725
899
|
<TableHead scope="col">{t("process.map.columnState")}</TableHead>
|
|
900
|
+
{conformanceStates ? (
|
|
901
|
+
<TableHead scope="col">{CONFORMANCE_STATE_DEFAULT_LABELS.column}</TableHead>
|
|
902
|
+
) : null}
|
|
903
|
+
{objectCentricView ? (
|
|
904
|
+
<TableHead scope="col">{objectCentricLabels.columnObjectTypes}</TableHead>
|
|
905
|
+
) : null}
|
|
726
906
|
</TableRow>
|
|
727
907
|
</TableHeader>
|
|
728
908
|
<TableBody>
|
|
@@ -730,6 +910,9 @@ export function ProcessMap({
|
|
|
730
910
|
<TableRow
|
|
731
911
|
key={row.id}
|
|
732
912
|
data-selection={row.selectionState}
|
|
913
|
+
data-conformance={
|
|
914
|
+
conformanceStates ? activityConformance(conformanceStates, row.id) : undefined
|
|
915
|
+
}
|
|
733
916
|
// Complementary, colour-only styling hook (step 4): the real channel is the
|
|
734
917
|
// State cell's text below, which fires for `selected` AND `excluded`; this
|
|
735
918
|
// only lights up `TableRow`'s existing `data-[state=selected]:bg-accent`.
|
|
@@ -744,6 +927,12 @@ export function ProcessMap({
|
|
|
744
927
|
</TableCell>
|
|
745
928
|
<TableCell className="tabular-nums">{row.reworkCount ?? 0}</TableCell>
|
|
746
929
|
<TableCell>{selectionStateText(row.selectionState)}</TableCell>
|
|
930
|
+
{conformanceStates ? (
|
|
931
|
+
<TableCell>
|
|
932
|
+
<ConformanceStateMark state={activityConformance(conformanceStates, row.id)} />
|
|
933
|
+
</TableCell>
|
|
934
|
+
) : null}
|
|
935
|
+
{objectCentricView ? <TableCell>{row.objectTypes}</TableCell> : null}
|
|
747
936
|
</TableRow>
|
|
748
937
|
))}
|
|
749
938
|
</TableBody>
|
|
@@ -756,11 +945,17 @@ export function ProcessMap({
|
|
|
756
945
|
</TableCaption>
|
|
757
946
|
<TableHeader>
|
|
758
947
|
<TableRow>
|
|
948
|
+
{objectCentricView ? (
|
|
949
|
+
<TableHead scope="col">{objectCentricLabels.columnObjectType}</TableHead>
|
|
950
|
+
) : null}
|
|
759
951
|
<TableHead scope="col">{t("process.map.columnFrom")}</TableHead>
|
|
760
952
|
<TableHead scope="col">{t("process.map.columnTo")}</TableHead>
|
|
761
953
|
<TableHead scope="col">{t("process.map.columnShape")}</TableHead>
|
|
762
954
|
<TableHead scope="col">{model.edgeMetricLabel}</TableHead>
|
|
763
955
|
<TableHead scope="col">{t("process.map.columnState")}</TableHead>
|
|
956
|
+
{conformanceStates ? (
|
|
957
|
+
<TableHead scope="col">{CONFORMANCE_STATE_DEFAULT_LABELS.column}</TableHead>
|
|
958
|
+
) : null}
|
|
764
959
|
</TableRow>
|
|
765
960
|
</TableHeader>
|
|
766
961
|
<TableBody>
|
|
@@ -768,8 +963,14 @@ export function ProcessMap({
|
|
|
768
963
|
<TableRow
|
|
769
964
|
key={row.id}
|
|
770
965
|
data-selection={row.selectionState}
|
|
966
|
+
data-conformance={
|
|
967
|
+
conformanceStates
|
|
968
|
+
? transitionConformance(conformanceStates, row.source, row.target)
|
|
969
|
+
: undefined
|
|
970
|
+
}
|
|
771
971
|
data-state={row.selectionState === "selected" ? "selected" : undefined}
|
|
772
972
|
>
|
|
973
|
+
{objectCentricView ? <TableCell>{row.objectType}</TableCell> : null}
|
|
773
974
|
<TableCell>{row.source}</TableCell>
|
|
774
975
|
<TableCell>{row.target}</TableCell>
|
|
775
976
|
<TableCell>{row.shape}</TableCell>
|
|
@@ -779,6 +980,13 @@ export function ProcessMap({
|
|
|
779
980
|
: row.primaryLabel}
|
|
780
981
|
</TableCell>
|
|
781
982
|
<TableCell>{selectionStateText(row.selectionState)}</TableCell>
|
|
983
|
+
{conformanceStates ? (
|
|
984
|
+
<TableCell>
|
|
985
|
+
<ConformanceStateMark
|
|
986
|
+
state={transitionConformance(conformanceStates, row.source, row.target)}
|
|
987
|
+
/>
|
|
988
|
+
</TableCell>
|
|
989
|
+
) : null}
|
|
782
990
|
</TableRow>
|
|
783
991
|
))}
|
|
784
992
|
</TableBody>
|
|
@@ -807,8 +1015,8 @@ export function ProcessMap({
|
|
|
807
1015
|
<ProcessMapHoverContext value={hover}>
|
|
808
1016
|
<ProcessMapEdgeKeyContext value={handleEdgeKey}>
|
|
809
1017
|
<CanvasShell
|
|
810
|
-
nodes={
|
|
811
|
-
edges={
|
|
1018
|
+
nodes={canvasNodes}
|
|
1019
|
+
edges={canvasEdges}
|
|
812
1020
|
nodeTypes={NODE_TYPES}
|
|
813
1021
|
edgeTypes={EDGE_TYPES}
|
|
814
1022
|
// dagre runs in an EFFECT, so the first paint has every node stacked at the
|
|
@@ -819,8 +1027,9 @@ export function ProcessMap({
|
|
|
819
1027
|
// change (or a direction flip) genuinely moves the picture and has to be
|
|
820
1028
|
// re-framed; a metric switch is a cache hit, leaves this key alone, and must
|
|
821
1029
|
// NOT yank the viewport out from under the reader.
|
|
822
|
-
fitViewKey={`${layoutKey}::${direction}::${layout.layoutRuns}`}
|
|
823
|
-
fitViewKeyOptions={
|
|
1030
|
+
fitViewKey={`${layoutKey}::${direction}::${layout.layoutRuns}::${refitKey ?? ""}`}
|
|
1031
|
+
fitViewKeyOptions={fitViewOptions}
|
|
1032
|
+
fitViewAnchorNodeIds={startNodeIds}
|
|
824
1033
|
minZoom={MIN_ZOOM}
|
|
825
1034
|
nodesDraggable={false}
|
|
826
1035
|
nodesConnectable={false}
|
|
@@ -873,14 +1082,29 @@ export function ProcessMap({
|
|
|
873
1082
|
className="pointer-events-none absolute inset-x-3 top-3 flex items-start justify-between gap-3"
|
|
874
1083
|
>
|
|
875
1084
|
{showLegend ? (
|
|
876
|
-
<
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1085
|
+
<div className="flex items-start gap-3">
|
|
1086
|
+
<Legend
|
|
1087
|
+
variant="scale"
|
|
1088
|
+
kind="width"
|
|
1089
|
+
domain={model.edgeDomain}
|
|
1090
|
+
format={model.formatEdgeValue}
|
|
1091
|
+
title={model.edgeMetricLabel}
|
|
1092
|
+
className="pointer-events-auto"
|
|
1093
|
+
/>
|
|
1094
|
+
{/* Object-centric — RM-066: each type's colour beside its printed code and name. */}
|
|
1095
|
+
{objectCentricView ? (
|
|
1096
|
+
<Legend
|
|
1097
|
+
title={objectCentricLabels.legendTitle}
|
|
1098
|
+
items={objectCentricView.scale.legend
|
|
1099
|
+
.filter((entry) => objectCentricView.graph.objectTypes.includes(entry.activityId))
|
|
1100
|
+
.map((entry) => ({
|
|
1101
|
+
label: `${entry.code} · ${entry.label}`,
|
|
1102
|
+
color: `var(${entry.token})`,
|
|
1103
|
+
}))}
|
|
1104
|
+
className="pointer-events-auto"
|
|
1105
|
+
/>
|
|
1106
|
+
) : null}
|
|
1107
|
+
</div>
|
|
884
1108
|
) : (
|
|
885
1109
|
<span />
|
|
886
1110
|
)}
|
|
@@ -158,3 +158,50 @@ describe("ProcessTransitionEdge — reaches an excluded edge's label pill by dat
|
|
|
158
158
|
expect(pill).not.toHaveAttribute("data-selection");
|
|
159
159
|
});
|
|
160
160
|
});
|
|
161
|
+
|
|
162
|
+
// #354 — the focused element (this pill) and the named element (React Flow's own edge
|
|
163
|
+
// `<g>`, which `data.ariaLabel` also reaches via the top-level `edge.ariaLabel` React Flow
|
|
164
|
+
// itself reads — see `map-model.ts`) must be the SAME element. `data.ariaLabel` is the
|
|
165
|
+
// channel; this locks that it actually lands on the rendered pill's accessible name,
|
|
166
|
+
// merged with (never replacing) the excluded-state fields #351 already locks above.
|
|
167
|
+
describe("ProcessTransitionEdge — folds data.ariaLabel onto the pill's accessible name (#354)", () => {
|
|
168
|
+
it("uses data.ariaLabel as the pill's accessible name instead of the bare printed label", () => {
|
|
169
|
+
render(
|
|
170
|
+
<ProcessTransitionEdge
|
|
171
|
+
{...makeEdgeProps({
|
|
172
|
+
data: {
|
|
173
|
+
...BASE_EDGE_DATA,
|
|
174
|
+
ariaLabel: "Transition from a to b, Transitions 12",
|
|
175
|
+
},
|
|
176
|
+
})}
|
|
177
|
+
/>,
|
|
178
|
+
);
|
|
179
|
+
expect(
|
|
180
|
+
screen.getByRole("button", { name: "Transition from a to b, Transitions 12" }),
|
|
181
|
+
).toBeInTheDocument();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("merges data.ariaLabel with the excluded-state ghost frame rather than replacing it", () => {
|
|
185
|
+
render(
|
|
186
|
+
<ProcessTransitionEdge
|
|
187
|
+
{...makeEdgeProps({
|
|
188
|
+
data: {
|
|
189
|
+
...BASE_EDGE_DATA,
|
|
190
|
+
selectionState: "excluded",
|
|
191
|
+
ariaLabel: "Transition from a to b, Transitions 12, excluded",
|
|
192
|
+
},
|
|
193
|
+
})}
|
|
194
|
+
/>,
|
|
195
|
+
);
|
|
196
|
+
const pill = screen.getByRole("button", {
|
|
197
|
+
name: "Transition from a to b, Transitions 12, excluded",
|
|
198
|
+
});
|
|
199
|
+
expect(pill.className).toMatch(/\bborder-dashed\b/);
|
|
200
|
+
expect(pill).toHaveAttribute("data-selection", "excluded");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("falls back to the pill's own computed name when data carries no ariaLabel", () => {
|
|
204
|
+
render(<ProcessTransitionEdge {...makeEdgeProps()} />);
|
|
205
|
+
expect(screen.getByRole("button", { name: "12×" })).toBeInTheDocument();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -47,17 +47,67 @@
|
|
|
47
47
|
* attribute straight onto the pill's root button — visible, non-text, reachable by a
|
|
48
48
|
* `[data-selection="excluded"]` selector same as the node and the edge, and never lower
|
|
49
49
|
* than 4.5:1 because the label text itself is untouched.
|
|
50
|
+
*
|
|
51
|
+
* ## The label pill is also the only reachable place for the transition's name (#354)
|
|
52
|
+
*
|
|
53
|
+
* The same portal-out-of-the-`<g>` fact applies to `aria-label`: React Flow's `EdgeWrapper`
|
|
54
|
+
* puts `edge.ariaLabel` on the outer `<g>`, but that `<g>` is never a tab stop
|
|
55
|
+
* (`process-map.tsx` sets `edgesFocusable={false}` so the pill is the arrow's one stop —
|
|
56
|
+
* see that file's own docblock). `labelProps` carries `data.ariaLabel` (computed once, in
|
|
57
|
+
* `map-model.ts`'s `transitionAriaLabel`, and read here rather than recomputed, so the
|
|
58
|
+
* canvas and the `TableView` twin cannot drift) onto the pill's `aria-label`, merged with
|
|
59
|
+
* the excluded-state fields above rather than replacing them.
|
|
50
60
|
*/
|
|
51
|
-
import { useMemo } from "react";
|
|
61
|
+
import { useMemo, type CSSProperties } from "react";
|
|
52
62
|
import type { EdgeProps } from "@xyflow/react";
|
|
63
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
53
64
|
import {
|
|
54
65
|
FlowSelfLoopEdge,
|
|
55
66
|
FlowWeightedEdge,
|
|
67
|
+
Position,
|
|
56
68
|
type FlowSelfLoopEdgeData,
|
|
57
69
|
type FlowWeightedEdgeData,
|
|
58
70
|
} from "@elabs-ai/components-flow";
|
|
59
71
|
import { useProcessMapEdgeKeys, useProcessMapHover } from "./process-map-context";
|
|
60
72
|
import { GHOST_OPACITY, type ProcessMapEdge } from "./map-model";
|
|
73
|
+
import {
|
|
74
|
+
CONFORMANCE_STATE_ENCODING,
|
|
75
|
+
type ConformanceState,
|
|
76
|
+
} from "../conformance-overlay/conformance-state";
|
|
77
|
+
import { useProcessReplayEdgeTokens } from "../process-replay/replay-tokens-context";
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The label pill under a conformance state (RM-062): the state's glyph as a leading
|
|
81
|
+
* `::before` mark (circle / triangle / square — the same shapes as the node marker and the
|
|
82
|
+
* legend) and the tone's fill rung on the pill border. The pill's own `aria-label` already
|
|
83
|
+
* carries the state's word (`ProcessMap` folds it into `data.ariaLabel`), so the glyph is
|
|
84
|
+
* a visible, non-colour channel only. The line style lives on the STROKE, not the pill, so
|
|
85
|
+
* it never fights the excluded state's dashed pill border.
|
|
86
|
+
*/
|
|
87
|
+
const CONFORMANCE_LABEL_CLASS: Record<ConformanceState, string> = {
|
|
88
|
+
both: "border-success before:text-success before:content-['●']",
|
|
89
|
+
logOnly: "border-warning before:text-warning before:content-['△']",
|
|
90
|
+
modelOnly: "border-destructive before:text-destructive before:content-['⬚']",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The stroke under a conformance state: the tone as paint plus the state's dash pattern.
|
|
95
|
+
* Omitted while the edge is selected, so React Flow's selection stroke still wins; a
|
|
96
|
+
* `"both"` edge keeps its own dash (a back edge stays dashed).
|
|
97
|
+
*/
|
|
98
|
+
function conformanceStrokeStyle(
|
|
99
|
+
state: ConformanceState | undefined,
|
|
100
|
+
selected: boolean | undefined,
|
|
101
|
+
): CSSProperties | undefined {
|
|
102
|
+
if (!state || selected) return undefined;
|
|
103
|
+
const encoding = CONFORMANCE_STATE_ENCODING[state];
|
|
104
|
+
return {
|
|
105
|
+
stroke: encoding.colorVar,
|
|
106
|
+
...(encoding.strokeDasharray
|
|
107
|
+
? { strokeDasharray: encoding.strokeDasharray, strokeLinecap: "round" }
|
|
108
|
+
: {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
61
111
|
|
|
62
112
|
/**
|
|
63
113
|
* The `scaleGroup` every process-map edge shares, so `computeEdgeWeightScale` min-maxes
|
|
@@ -75,6 +125,21 @@ const UNRELATED_OPACITY = 0.25;
|
|
|
75
125
|
* opacity (see this file's own docblock, and {@link GHOST_OPACITY}'s, for why). */
|
|
76
126
|
const EXCLUDED_LABEL_PROPS = { className: "border-dashed", "data-selection": "excluded" } as const;
|
|
77
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Object-centric — RM-066. Per-type edges joining the same two activities are drawn side
|
|
130
|
+
* by side: each one's endpoints shift across the flow axis by its slot, so the strokes and
|
|
131
|
+
* their label pills separate instead of stacking. The spread is capped so every endpoint
|
|
132
|
+
* stays on the card's own face.
|
|
133
|
+
*/
|
|
134
|
+
const PARALLEL_EDGE_GAP = 40;
|
|
135
|
+
const PARALLEL_EDGE_MAX_SPREAD = 120;
|
|
136
|
+
|
|
137
|
+
function parallelShift(index: number | undefined, count: number | undefined): number {
|
|
138
|
+
if (index === undefined || count === undefined || count < 2) return 0;
|
|
139
|
+
const gap = Math.min(PARALLEL_EDGE_GAP, PARALLEL_EDGE_MAX_SPREAD / (count - 1));
|
|
140
|
+
return (index - (count - 1) / 2) * gap;
|
|
141
|
+
}
|
|
142
|
+
|
|
78
143
|
/**
|
|
79
144
|
* Branded process-map transition edge. Register it in
|
|
80
145
|
* `edgeTypes={{ "process-transition": ProcessTransitionEdge }}`; build edges with
|
|
@@ -85,20 +150,65 @@ export function ProcessTransitionEdge(props: EdgeProps<ProcessMapEdge>) {
|
|
|
85
150
|
const hover = useProcessMapHover();
|
|
86
151
|
const onEdgeKey = useProcessMapEdgeKeys();
|
|
87
152
|
const isExcluded = data?.selectionState === "excluded";
|
|
88
|
-
|
|
153
|
+
// The focused element (the label pill's `<button>`) and the named element (React Flow's
|
|
154
|
+
// own edge `<g>`, unreachable — see this file's own docblock) are two different DOM
|
|
155
|
+
// nodes; `data.ariaLabel` (`transitionAriaLabel`, computed once in `map-model.ts`) is the
|
|
156
|
+
// channel that gets the same accessible name onto the one a screen-reader user actually
|
|
157
|
+
// lands on (#354). Merged with, never replacing, the excluded-state fields.
|
|
158
|
+
const conformance = data?.conformance;
|
|
159
|
+
const selected = props.selected;
|
|
160
|
+
const labelProps = useMemo(() => {
|
|
161
|
+
if (!isExcluded && !data?.ariaLabel && !conformance) return undefined;
|
|
162
|
+
return {
|
|
163
|
+
...(isExcluded ? EXCLUDED_LABEL_PROPS : undefined),
|
|
164
|
+
...(data?.ariaLabel ? { "aria-label": data.ariaLabel } : undefined),
|
|
165
|
+
// Additive to the excluded fields (RM-062): the dashed excluded border stays, the
|
|
166
|
+
// conformance glyph and tone join it. The tone border yields to the selection ring.
|
|
167
|
+
...(conformance
|
|
168
|
+
? {
|
|
169
|
+
"data-conformance": conformance,
|
|
170
|
+
className: cn(
|
|
171
|
+
isExcluded && EXCLUDED_LABEL_PROPS.className,
|
|
172
|
+
CONFORMANCE_LABEL_CLASS[conformance],
|
|
173
|
+
selected && "border-ring",
|
|
174
|
+
),
|
|
175
|
+
}
|
|
176
|
+
: undefined),
|
|
177
|
+
};
|
|
178
|
+
}, [isExcluded, data?.ariaLabel, conformance, selected]);
|
|
179
|
+
const strokeStyle = conformanceStrokeStyle(conformance, selected);
|
|
180
|
+
const edgeStyle = strokeStyle ? { ...props.style, ...strokeStyle } : props.style;
|
|
181
|
+
// RM-065: tokens from an enclosing `ProcessReplay`. `undefined` outside one, and then the
|
|
182
|
+
// key is not added at all, so a map with no replay builds exactly the data it did before.
|
|
183
|
+
const replayTokens = useProcessReplayEdgeTokens(props.id);
|
|
184
|
+
// Object-centric — RM-066: a per-type edge takes its type's chart stroke (on the edge
|
|
185
|
+
// object's `style`), never the value ramp, and sits in its own parallel slot.
|
|
186
|
+
const isObjectTyped = data?.objectType !== undefined;
|
|
187
|
+
const shift = parallelShift(data?.parallelIndex, data?.parallelCount);
|
|
188
|
+
const acrossX = props.targetPosition === Position.Top || props.targetPosition === Position.Bottom;
|
|
189
|
+
const placed = shift
|
|
190
|
+
? {
|
|
191
|
+
sourceX: props.sourceX + (acrossX ? shift : 0),
|
|
192
|
+
targetX: props.targetX + (acrossX ? shift : 0),
|
|
193
|
+
sourceY: props.sourceY + (acrossX ? 0 : shift),
|
|
194
|
+
targetY: props.targetY + (acrossX ? 0 : shift),
|
|
195
|
+
}
|
|
196
|
+
: undefined;
|
|
89
197
|
|
|
90
198
|
const weightedData = useMemo<FlowWeightedEdgeData>(
|
|
91
199
|
() => ({
|
|
92
200
|
weight: data?.weight,
|
|
93
201
|
scaleGroup: PROCESS_MAP_EDGE_SCALE_GROUP,
|
|
94
|
-
value: data?.value,
|
|
95
|
-
valueDomain: data?.valueDomain,
|
|
202
|
+
value: isObjectTyped ? undefined : data?.value,
|
|
203
|
+
valueDomain: isObjectTyped ? undefined : data?.valueDomain,
|
|
96
204
|
label: data?.label,
|
|
97
205
|
secondaryLabel: data?.secondaryLabel,
|
|
98
206
|
variant: data?.isBackEdge ? "back" : "forward",
|
|
99
207
|
labelProps,
|
|
208
|
+
...(replayTokens ? { tokens: replayTokens } : undefined),
|
|
100
209
|
}),
|
|
101
210
|
[
|
|
211
|
+
isObjectTyped,
|
|
102
212
|
data?.weight,
|
|
103
213
|
data?.value,
|
|
104
214
|
data?.valueDomain,
|
|
@@ -106,6 +216,7 @@ export function ProcessTransitionEdge(props: EdgeProps<ProcessMapEdge>) {
|
|
|
106
216
|
data?.secondaryLabel,
|
|
107
217
|
data?.isBackEdge,
|
|
108
218
|
labelProps,
|
|
219
|
+
replayTokens,
|
|
109
220
|
],
|
|
110
221
|
);
|
|
111
222
|
|
|
@@ -116,8 +227,9 @@ export function ProcessTransitionEdge(props: EdgeProps<ProcessMapEdge>) {
|
|
|
116
227
|
label: data?.label,
|
|
117
228
|
secondaryLabel: data?.secondaryLabel,
|
|
118
229
|
labelProps,
|
|
230
|
+
...(replayTokens ? { tokens: replayTokens } : undefined),
|
|
119
231
|
}),
|
|
120
|
-
[data?.weight, data?.label, data?.secondaryLabel, labelProps],
|
|
232
|
+
[data?.weight, data?.label, data?.secondaryLabel, labelProps, replayTokens],
|
|
121
233
|
);
|
|
122
234
|
|
|
123
235
|
const opacity = isExcluded
|
|
@@ -132,6 +244,9 @@ export function ProcessTransitionEdge(props: EdgeProps<ProcessMapEdge>) {
|
|
|
132
244
|
data-shape={data?.isSelfLoop ? "self-loop" : data?.isBackEdge ? "back" : "forward"}
|
|
133
245
|
data-selection={data?.selectionState}
|
|
134
246
|
data-incident={hover.incidentEdgeIds.has(props.id) ? "true" : undefined}
|
|
247
|
+
data-conformance={conformance}
|
|
248
|
+
data-dash={conformance ? CONFORMANCE_STATE_ENCODING[conformance].dash : undefined}
|
|
249
|
+
data-object-type={data?.objectType?.type}
|
|
135
250
|
className="transition-opacity duration-fast ease-standard motion-reduce:transition-none"
|
|
136
251
|
style={{ opacity }}
|
|
137
252
|
// The label pill is portalled out of this `<g>` by `EdgeLabelRenderer`, so it has no
|
|
@@ -142,9 +257,21 @@ export function ProcessTransitionEdge(props: EdgeProps<ProcessMapEdge>) {
|
|
|
142
257
|
onKeyDown={(event) => onEdgeKey(props.id, event)}
|
|
143
258
|
>
|
|
144
259
|
{data?.isSelfLoop ? (
|
|
145
|
-
<FlowSelfLoopEdge
|
|
260
|
+
<FlowSelfLoopEdge
|
|
261
|
+
{...props}
|
|
262
|
+
{...placed}
|
|
263
|
+
style={edgeStyle}
|
|
264
|
+
type="self-loop"
|
|
265
|
+
data={selfLoopData}
|
|
266
|
+
/>
|
|
146
267
|
) : (
|
|
147
|
-
<FlowWeightedEdge
|
|
268
|
+
<FlowWeightedEdge
|
|
269
|
+
{...props}
|
|
270
|
+
{...placed}
|
|
271
|
+
style={edgeStyle}
|
|
272
|
+
type="weighted"
|
|
273
|
+
data={weightedData}
|
|
274
|
+
/>
|
|
148
275
|
)}
|
|
149
276
|
</g>
|
|
150
277
|
);
|