@nanobpm/bojtos-react 0.1.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.
@@ -0,0 +1,177 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { createBojtosSession, dispatchRound, dispatchWorkers, } from "@nanobpm/bojtos-kit";
3
+ /**
4
+ * React binding over a headless {@link BojtosSession}: owns the engine's
5
+ * lifecycle and the reactive `snapshot` / `events` / `processIds` state, and
6
+ * exposes the engine commands. The consuming component owns its own form state
7
+ * (selected process, seed vars, per-job output) and drives the visual contract
8
+ * (`<BpmnRuntimeView>` + the variable payload) off `snapshot`.
9
+ *
10
+ * This is the reactive half of the Bojtos public API (ADR 0043 §2); the console
11
+ * test-run panel is its first consumer (§8 step 2 — dogfooding is the acceptance
12
+ * test).
13
+ */
14
+ export function useBojtos({ bpmn, wasm }) {
15
+ const sessionRef = useRef(null);
16
+ const [phase, setPhase] = useState("loading");
17
+ const [error, setError] = useState(null);
18
+ const [processIds, setProcessIds] = useState([]);
19
+ const [snapshot, setSnapshot] = useState(null);
20
+ const [events, setEvents] = useState([]);
21
+ // The wasm source is an init-time concern (the first `ensureWasm` wins), so
22
+ // keep it in a ref rather than the mount effect's deps — a fresh URL/bytes
23
+ // identity each render must not re-create the session.
24
+ const wasmRef = useRef(wasm);
25
+ wasmRef.current = wasm;
26
+ const deployInto = useCallback((session) => {
27
+ const res = session.deploy(bpmn);
28
+ setProcessIds(res.processIds);
29
+ setSnapshot(null);
30
+ setEvents([]);
31
+ setError(null);
32
+ }, [bpmn]);
33
+ useEffect(() => {
34
+ let cancelled = false;
35
+ // A new diagram means a fresh engine: drop back to `loading` and clear the
36
+ // previous session's state — including `processIds` — so consumers never
37
+ // see `ready` (or a stale process list) against a freed session while the
38
+ // new one is still loading.
39
+ setPhase("loading");
40
+ setProcessIds([]);
41
+ setSnapshot(null);
42
+ setEvents([]);
43
+ setError(null);
44
+ createBojtosSession({ wasm: wasmRef.current })
45
+ .then((session) => {
46
+ if (cancelled) {
47
+ session.free();
48
+ return;
49
+ }
50
+ try {
51
+ deployInto(session);
52
+ }
53
+ catch (e) {
54
+ // A failed deploy (e.g. invalid BPMN) must free the just-created
55
+ // engine rather than leak it until unmount, and must not be stored
56
+ // as the active session.
57
+ session.free();
58
+ setError(String(e));
59
+ setPhase("error");
60
+ return;
61
+ }
62
+ sessionRef.current = session;
63
+ setPhase("ready");
64
+ })
65
+ .catch((e) => {
66
+ if (cancelled)
67
+ return;
68
+ setError(String(e));
69
+ setPhase("error");
70
+ });
71
+ return () => {
72
+ cancelled = true;
73
+ sessionRef.current?.free();
74
+ sessionRef.current = null;
75
+ };
76
+ }, [deployInto]);
77
+ const run = useCallback((fn) => {
78
+ const session = sessionRef.current;
79
+ if (!session)
80
+ return null;
81
+ try {
82
+ const snap = fn(session);
83
+ setSnapshot(snap);
84
+ setEvents(session.events());
85
+ setError(null);
86
+ return snap;
87
+ }
88
+ catch (e) {
89
+ setError(String(e));
90
+ return null;
91
+ }
92
+ }, []);
93
+ const createInstance = useCallback((processId, variablesJson) => run((s) => s.createInstance(processId, variablesJson)), [run]);
94
+ const completeJob = useCallback((jobKey, variablesJson) => run((s) => s.completeJob(jobKey, variablesJson)), [run]);
95
+ const failJob = useCallback((jobKey, retries, message) => run((s) => s.failJob(jobKey, retries, message)), [run]);
96
+ const advanceTime = useCallback((byMs) => run((s) => s.advanceTime(byMs)), [run]);
97
+ const correlateMessage = useCallback((messageName, correlationKey, variablesJson) => run((s) => s.correlateMessage(messageName, correlationKey, variablesJson)), [run]);
98
+ const runWorkers = useCallback(async (workers, opts) => {
99
+ const session = sessionRef.current;
100
+ if (!session)
101
+ return null;
102
+ try {
103
+ const { snapshot: settled } = await dispatchWorkers(session, workers, opts);
104
+ // The session may have been torn down/replaced (bpmn change, unmount)
105
+ // while we awaited — don't publish stale state or read a freed session.
106
+ if (sessionRef.current !== session)
107
+ return null;
108
+ setSnapshot(settled);
109
+ setEvents(session.events());
110
+ setError(null);
111
+ return settled;
112
+ }
113
+ catch (e) {
114
+ if (sessionRef.current !== session)
115
+ return null;
116
+ // Reflect whatever state the engine reached before the drain aborted
117
+ // (e.g. the maxRounds guard) so the view isn't left stale.
118
+ setSnapshot(session.snapshot());
119
+ setEvents(session.events());
120
+ setError(String(e));
121
+ return null;
122
+ }
123
+ }, []);
124
+ const stepWorkers = useCallback(async (workers, opts) => {
125
+ const session = sessionRef.current;
126
+ if (!session)
127
+ return null;
128
+ try {
129
+ const round = await dispatchRound(session, workers, opts);
130
+ // Bail if the session was replaced/freed while we awaited the round.
131
+ if (sessionRef.current !== session)
132
+ return null;
133
+ setSnapshot(round.snapshot);
134
+ setEvents(session.events());
135
+ setError(null);
136
+ return round;
137
+ }
138
+ catch (e) {
139
+ if (sessionRef.current !== session)
140
+ return null;
141
+ setSnapshot(session.snapshot());
142
+ setEvents(session.events());
143
+ setError(String(e));
144
+ return null;
145
+ }
146
+ }, []);
147
+ const reset = useCallback(() => {
148
+ const session = sessionRef.current;
149
+ if (!session)
150
+ return;
151
+ try {
152
+ // Wipe the engine to its pristine state, then redeploy the diagram, so a
153
+ // re-run starts from zero instances/completions rather than accumulating
154
+ // across runs (a plain redeploy leaves prior instances resident).
155
+ session.reset();
156
+ deployInto(session);
157
+ }
158
+ catch (e) {
159
+ setError(String(e));
160
+ }
161
+ }, [deployInto]);
162
+ return {
163
+ phase,
164
+ error,
165
+ processIds,
166
+ snapshot,
167
+ events,
168
+ createInstance,
169
+ completeJob,
170
+ failJob,
171
+ advanceTime,
172
+ correlateMessage,
173
+ runWorkers,
174
+ stepWorkers,
175
+ reset,
176
+ };
177
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@nanobpm/bojtos-react",
3
+ "version": "0.1.0",
4
+ "description": "React binding for the Bojtos in-browser BPMN demo framework (ADR 0043): the useBojtos hook (owns the engine session + reactive snapshot/event state) and the <BpmnRuntimeView> live token/incident diagram. Built on @nanobpm/bojtos-kit; the console test-run panel is its first consumer.",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/Magikcraft/nano-bpm",
10
+ "directory": "bojtos-react"
11
+ },
12
+ "main": "./dist/index.js",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./source": "./src/index.ts"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src"
25
+ ],
26
+ "sideEffects": false,
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.json",
29
+ "typecheck": "tsc -p tsconfig.json --noEmit",
30
+ "prepack": "npm run build"
31
+ },
32
+ "dependencies": {
33
+ "@nanobpm/bojtos-kit": "^0.1.0"
34
+ },
35
+ "peerDependencies": {
36
+ "bpmn-js": ">=17",
37
+ "react": ">=18"
38
+ },
39
+ "devDependencies": {
40
+ "@types/react": "^18.3.11",
41
+ "react": "^18.3.1",
42
+ "typescript": "^5.6.3"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }
package/src/Bojtos.tsx ADDED
@@ -0,0 +1,263 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import type { JobHandler, WasmEvent } from "@nanobpm/bojtos-kit";
3
+ import { BpmnRuntimeView } from "./BpmnRuntimeView.js";
4
+ import { useBojtos } from "./useBojtos.js";
5
+
6
+ /** A single engine event handed to `onTrace` as the simulation runs. */
7
+ export type TraceEvent = WasmEvent;
8
+
9
+ export interface BojtosProps {
10
+ /** The BPMN diagram XML to run. */
11
+ bpmn: string;
12
+ /**
13
+ * The in-browser workers, keyed by the model's job type (task definition
14
+ * type). Each handler receives the activated job (with the instance's live
15
+ * variables) and returns the variables to merge on completion — or throws to
16
+ * fail the job. This is the code a demo author edits to shape the run.
17
+ */
18
+ workers: Record<string, JobHandler>;
19
+ /** Initial instance variables (the starting payload). Defaults to `{}`. */
20
+ seed?: Record<string, unknown>;
21
+ /** Start the instance and run the workers automatically once ready. */
22
+ autoplay?: boolean;
23
+ /**
24
+ * Milliseconds between dispatch rounds while playing (default 700). The pause
25
+ * is what makes the token visibly hop task-to-task instead of settling
26
+ * instantly.
27
+ */
28
+ stepDelayMs?: number;
29
+ /**
30
+ * Which deployed process to start. Defaults to the first process in the
31
+ * diagram — set this only for a multi-process `.bpmn`.
32
+ */
33
+ processId?: string;
34
+ /**
35
+ * Optional engine wasm URL for bundlers where the default `import.meta.url`
36
+ * loader can't resolve the binary (ADR 0043 §3).
37
+ */
38
+ wasmUrl?: string;
39
+ /** Called for every engine event as the simulation advances. */
40
+ onTrace?: (event: TraceEvent) => void;
41
+ /** Optional class for the outer container. */
42
+ className?: string;
43
+ }
44
+
45
+ const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
46
+
47
+ const MARKER_CSS = `
48
+ .bojtos-diagram .nano-active .djs-visual > :nth-child(1) {
49
+ stroke: #10b981 !important;
50
+ stroke-width: 3px !important;
51
+ }
52
+ .bojtos-diagram .nano-incident .djs-visual > :nth-child(1) {
53
+ stroke: #ef4444 !important;
54
+ stroke-width: 3px !important;
55
+ fill: #fee2e2 !important;
56
+ }
57
+ `;
58
+
59
+ /**
60
+ * The turnkey Bojtos demo component (ADR 0043 §2): drop in a `bpmn` diagram and
61
+ * a map of in-browser `workers`, and it renders the live token/incident diagram
62
+ * beside the running variable payload, driving the "activate → handler →
63
+ * complete/fail" loop so you watch the token advance and the payload mutate as
64
+ * each worker runs.
65
+ *
66
+ * The consuming app must load bpmn-js's diagram CSS once
67
+ * (`bpmn-js/dist/assets/diagram-js.css` and
68
+ * `.../bpmn-font/css/bpmn-embedded.css`); the token/incident marker styles are
69
+ * injected here.
70
+ */
71
+ export function Bojtos({
72
+ bpmn,
73
+ workers,
74
+ seed,
75
+ autoplay,
76
+ stepDelayMs = 700,
77
+ processId,
78
+ wasmUrl,
79
+ onTrace,
80
+ className,
81
+ }: BojtosProps) {
82
+ const {
83
+ phase,
84
+ error,
85
+ processIds,
86
+ snapshot,
87
+ events,
88
+ createInstance,
89
+ stepWorkers,
90
+ reset,
91
+ } = useBojtos({ bpmn, wasm: wasmUrl });
92
+
93
+ const [playing, setPlaying] = useState(false);
94
+ const playingRef = useRef(false);
95
+ const startedRef = useRef(false);
96
+ // Tracks whether the component is still mounted, so the async play loop (which
97
+ // can outlive an unmount while awaiting `stepWorkers()` / `delay()`) neither
98
+ // sets state on an unmounted component nor keeps driving a freed session.
99
+ const mountedRef = useRef(true);
100
+ useEffect(
101
+ () => () => {
102
+ mountedRef.current = false;
103
+ playingRef.current = false;
104
+ },
105
+ [],
106
+ );
107
+
108
+ // Keep the object/function props in refs so the play loop and the autoplay
109
+ // effect don't churn (or re-fire) when a parent re-renders with fresh
110
+ // identities for `workers` / `seed` / `onTrace`.
111
+ const workersRef = useRef(workers);
112
+ workersRef.current = workers;
113
+ const seedRef = useRef(seed);
114
+ seedRef.current = seed;
115
+ const onTraceRef = useRef(onTrace);
116
+ onTraceRef.current = onTrace;
117
+
118
+ // A fresh engine (bpmn change / reset drops back to `loading`) clears the
119
+ // "instance created" latch and stops any in-flight play loop.
120
+ useEffect(() => {
121
+ if (phase === "loading") {
122
+ startedRef.current = false;
123
+ playingRef.current = false;
124
+ setPlaying(false);
125
+ }
126
+ }, [phase]);
127
+
128
+ // Forward every newly-appended engine event to `onTrace`.
129
+ const emittedRef = useRef(0);
130
+ useEffect(() => {
131
+ const cb = onTraceRef.current;
132
+ if (cb) {
133
+ for (let i = emittedRef.current; i < events.length; i++) cb(events[i]);
134
+ }
135
+ emittedRef.current = events.length;
136
+ }, [events]);
137
+
138
+ const ensureStarted = useCallback((): boolean => {
139
+ if (startedRef.current) return true;
140
+ const target = processId ?? processIds[0];
141
+ if (!target) return false;
142
+ // Only latch once the instance actually started — a failed createInstance
143
+ // (returns null, e.g. an engine error) must stay retryable rather than
144
+ // wedging the demo in a non-started state.
145
+ if (!createInstance(target, JSON.stringify(seedRef.current ?? {}))) {
146
+ return false;
147
+ }
148
+ startedRef.current = true;
149
+ return true;
150
+ }, [createInstance, processId, processIds]);
151
+
152
+ const step = useCallback(async () => {
153
+ if (phase !== "ready") return;
154
+ if (!ensureStarted()) return;
155
+ await stepWorkers(workersRef.current);
156
+ }, [phase, ensureStarted, stepWorkers]);
157
+
158
+ const play = useCallback(async () => {
159
+ if (phase !== "ready" || playingRef.current) return;
160
+ if (!ensureStarted()) return;
161
+ playingRef.current = true;
162
+ setPlaying(true);
163
+ try {
164
+ while (playingRef.current) {
165
+ const round = await stepWorkers(workersRef.current);
166
+ if (!round || round.handled === 0) break;
167
+ await delay(stepDelayMs);
168
+ }
169
+ } finally {
170
+ playingRef.current = false;
171
+ if (mountedRef.current) setPlaying(false);
172
+ }
173
+ }, [phase, ensureStarted, stepWorkers, stepDelayMs]);
174
+
175
+ const pause = useCallback(() => {
176
+ playingRef.current = false;
177
+ setPlaying(false);
178
+ }, []);
179
+
180
+ const restart = useCallback(() => {
181
+ playingRef.current = false;
182
+ setPlaying(false);
183
+ startedRef.current = false;
184
+ reset();
185
+ }, [reset]);
186
+
187
+ // Autoplay once, when the engine first becomes ready.
188
+ const autoplayedRef = useRef(false);
189
+ useEffect(() => {
190
+ if (autoplay && phase === "ready" && !autoplayedRef.current) {
191
+ autoplayedRef.current = true;
192
+ void play();
193
+ }
194
+ if (phase === "loading") autoplayedRef.current = false;
195
+ }, [autoplay, phase, play]);
196
+
197
+ const ready = phase === "ready";
198
+ const instance = snapshot?.instances[0];
199
+ const variables = instance?.variables ?? {};
200
+
201
+ return (
202
+ <div
203
+ className={className}
204
+ style={{ display: "flex", flexDirection: "column", gap: 8, minHeight: 320 }}
205
+ >
206
+ <style>{MARKER_CSS}</style>
207
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
208
+ <button type="button" onClick={play} disabled={!ready || playing}>
209
+ ▶ Play
210
+ </button>
211
+ <button type="button" onClick={pause} disabled={!playing}>
212
+ ⏸ Pause
213
+ </button>
214
+ <button type="button" onClick={step} disabled={!ready || playing}>
215
+ ⏭ Step
216
+ </button>
217
+ <button type="button" onClick={restart} disabled={!ready}>
218
+ ↺ Reset
219
+ </button>
220
+ <span style={{ marginLeft: "auto", fontSize: 12, opacity: 0.7 }}>
221
+ {error
222
+ ? `error: ${error}`
223
+ : phase === "loading"
224
+ ? "loading engine…"
225
+ : instance
226
+ ? instance.completed
227
+ ? "completed"
228
+ : "running"
229
+ : "ready"}
230
+ </span>
231
+ </div>
232
+ <div style={{ display: "flex", gap: 8, flex: 1, minHeight: 280 }}>
233
+ <div
234
+ className="bojtos-diagram"
235
+ style={{ flex: 2, border: "1px solid #e5e7eb", borderRadius: 6 }}
236
+ >
237
+ <BpmnRuntimeView
238
+ xml={bpmn}
239
+ activeIds={snapshot?.activeElementIds ?? []}
240
+ incidentIds={snapshot?.incidentElementIds ?? []}
241
+ />
242
+ </div>
243
+ <div
244
+ style={{
245
+ flex: 1,
246
+ minWidth: 200,
247
+ border: "1px solid #e5e7eb",
248
+ borderRadius: 6,
249
+ padding: 8,
250
+ overflow: "auto",
251
+ font: "12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace",
252
+ background: "#f9fafb",
253
+ }}
254
+ >
255
+ <div style={{ fontWeight: 600, marginBottom: 4 }}>Variables</div>
256
+ <pre style={{ margin: 0, whiteSpace: "pre-wrap" }}>
257
+ {JSON.stringify(variables, null, 2)}
258
+ </pre>
259
+ </div>
260
+ </div>
261
+ </div>
262
+ );
263
+ }
@@ -0,0 +1,148 @@
1
+ import { useEffect, useRef } from "react";
2
+ import NavigatedViewer from "bpmn-js/lib/NavigatedViewer";
3
+
4
+ interface Canvas {
5
+ zoom(mode: string): void;
6
+ addMarker(elementId: string, marker: string): void;
7
+ removeMarker(elementId: string, marker: string): void;
8
+ }
9
+
10
+ interface Overlays {
11
+ add(
12
+ elementId: string,
13
+ overlay: {
14
+ position: Record<string, number>;
15
+ html: string | HTMLElement;
16
+ },
17
+ ): string;
18
+ remove(id: string): void;
19
+ }
20
+
21
+ export interface BpmnRuntimeViewProps {
22
+ /** The diagram XML to render. */
23
+ xml: string;
24
+ /** Element ids to highlight as active (token) — marker class `nano-active`. */
25
+ activeIds: string[];
26
+ /** Element ids to highlight as incidents — marker class `nano-incident`. */
27
+ incidentIds: string[];
28
+ /** Optional class for the container element (it always fills its parent). */
29
+ className?: string;
30
+ }
31
+
32
+ /**
33
+ * Read-only diagram that imports the XML once and updates token/incident markers
34
+ * in place (no re-import, so the zoom/scroll position is preserved while
35
+ * stepping through the simulation). This is the token-movement half of the
36
+ * Bojtos visual contract (ADR 0043 §4): drive `activeIds` / `incidentIds` from a
37
+ * session snapshot's `activeElementIds` / `incidentElementIds`.
38
+ *
39
+ * The consumer must load bpmn-js's diagram CSS (`bpmn-js/dist/assets/
40
+ * diagram-js.css` and `.../bpmn-font/css/bpmn-embedded.css`) once in the app,
41
+ * and provide the `.nano-active` / `.nano-incident` marker styles plus a
42
+ * `.nano-token` style for the token badge overlaid on each active element.
43
+ */
44
+ export function BpmnRuntimeView({
45
+ xml,
46
+ activeIds,
47
+ incidentIds,
48
+ className,
49
+ }: BpmnRuntimeViewProps) {
50
+ const containerRef = useRef<HTMLDivElement>(null);
51
+ const viewerRef = useRef<NavigatedViewer | null>(null);
52
+ const importedRef = useRef(false);
53
+ const markedRef = useRef<{ id: string; cls: string }[]>([]);
54
+ const tokenOverlaysRef = useRef<string[]>([]);
55
+ // Track the latest ids in a ref so the post-import `applyMarkers()` (fired from
56
+ // the `[xml]` effect's async `.then`) uses current values, not the ids that
57
+ // were current when the import started — otherwise ids changing mid-import
58
+ // would leave the diagram unmarked until the next change.
59
+ const idsRef = useRef({ activeIds, incidentIds });
60
+ idsRef.current = { activeIds, incidentIds };
61
+
62
+ useEffect(() => {
63
+ if (!containerRef.current) return;
64
+ const viewer = new NavigatedViewer({ container: containerRef.current });
65
+ viewerRef.current = viewer;
66
+ importedRef.current = false;
67
+ viewer
68
+ .importXML(xml)
69
+ .then(() => {
70
+ viewer.get<Canvas>("canvas").zoom("fit-viewport");
71
+ importedRef.current = true;
72
+ applyMarkers();
73
+ })
74
+ .catch(() => {
75
+ /* malformed XML — leave blank */
76
+ });
77
+ return () => {
78
+ viewer.destroy();
79
+ viewerRef.current = null;
80
+ };
81
+ // eslint-disable-next-line react-hooks/exhaustive-deps
82
+ }, [xml]);
83
+
84
+ function applyMarkers() {
85
+ const viewer = viewerRef.current;
86
+ if (!viewer || !importedRef.current) return;
87
+ const canvas = viewer.get<Canvas>("canvas");
88
+ for (const { id, cls } of markedRef.current) {
89
+ try {
90
+ canvas.removeMarker(id, cls);
91
+ } catch {
92
+ /* ignore */
93
+ }
94
+ }
95
+ const next: { id: string; cls: string }[] = [];
96
+ for (const id of idsRef.current.activeIds) next.push({ id, cls: "nano-active" });
97
+ for (const id of idsRef.current.incidentIds)
98
+ next.push({ id, cls: "nano-incident" });
99
+ for (const { id, cls } of next) {
100
+ try {
101
+ canvas.addMarker(id, cls);
102
+ } catch {
103
+ /* element not in this diagram */
104
+ }
105
+ }
106
+ markedRef.current = next;
107
+
108
+ // A visible token badge on each active element: an explicit "token is here"
109
+ // marker so movement reads clearly even when a class-only highlight is too
110
+ // subtle. Overlays are removed/re-added each update so the token hops with
111
+ // the frontier.
112
+ const overlays = viewer.get<Overlays>("overlays");
113
+ for (const id of tokenOverlaysRef.current) {
114
+ try {
115
+ overlays.remove(id);
116
+ } catch {
117
+ /* ignore */
118
+ }
119
+ }
120
+ const nextOverlays: string[] = [];
121
+ for (const id of idsRef.current.activeIds) {
122
+ try {
123
+ nextOverlays.push(
124
+ overlays.add(id, {
125
+ position: { top: -12, left: -12 },
126
+ html: '<div class="nano-token" aria-hidden="true"></div>',
127
+ }),
128
+ );
129
+ } catch {
130
+ /* element not in this diagram */
131
+ }
132
+ }
133
+ tokenOverlaysRef.current = nextOverlays;
134
+ }
135
+
136
+ useEffect(() => {
137
+ applyMarkers();
138
+ // eslint-disable-next-line react-hooks/exhaustive-deps
139
+ }, [activeIds, incidentIds]);
140
+
141
+ return (
142
+ <div
143
+ ref={containerRef}
144
+ className={className}
145
+ style={{ width: "100%", height: "100%" }}
146
+ />
147
+ );
148
+ }
@@ -0,0 +1,18 @@
1
+ // bpmn-js ships no types for the `NavigatedViewer` entry point; declare the
2
+ // minimal surface `BpmnRuntimeView` uses. This ambient declaration is a build-
3
+ // time input only — it is never emitted into `dist/`, so it cannot collide with
4
+ // a consumer's own bpmn-js typings.
5
+ declare module "bpmn-js/lib/NavigatedViewer" {
6
+ export interface ImportResult {
7
+ warnings: unknown[];
8
+ }
9
+ export default class NavigatedViewer {
10
+ constructor(options: { container: HTMLElement });
11
+ importXML(xml: string): Promise<ImportResult>;
12
+ get<T = unknown>(service: string): T;
13
+ destroy(): void;
14
+ }
15
+ }
16
+
17
+ declare module "bpmn-js/dist/assets/diagram-js.css";
18
+ declare module "bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css";