@formicoidea/labre-framework-bpmn 0.32.0 → 0.33.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.
Files changed (47) hide show
  1. package/dist/actions.d.ts +202 -6
  2. package/dist/actions.js +421 -43
  3. package/dist/background.d.ts +2 -0
  4. package/dist/background.js +158 -0
  5. package/dist/commands.js +496 -5
  6. package/dist/consts.d.ts +157 -3
  7. package/dist/consts.js +192 -3
  8. package/dist/element-renderer.d.ts +10 -4
  9. package/dist/element-renderer.js +14 -55
  10. package/dist/element-view.d.ts +100 -8
  11. package/dist/element-view.js +249 -30
  12. package/dist/export.d.ts +277 -0
  13. package/dist/export.js +1802 -0
  14. package/dist/facts.d.ts +48 -0
  15. package/dist/facts.js +127 -0
  16. package/dist/import.d.ts +44 -0
  17. package/dist/import.js +1440 -0
  18. package/dist/index.d.ts +12 -0
  19. package/dist/index.js +44 -0
  20. package/dist/interchange.d.ts +109 -0
  21. package/dist/interchange.js +191 -0
  22. package/dist/morph.d.ts +61 -0
  23. package/dist/morph.js +118 -0
  24. package/dist/node/node-renderer.d.ts +0 -9
  25. package/dist/node/node-renderer.js +294 -17
  26. package/dist/pool-hit.d.ts +98 -0
  27. package/dist/pool-hit.js +130 -0
  28. package/dist/presets.d.ts +114 -0
  29. package/dist/presets.js +232 -0
  30. package/dist/profiles.d.ts +2 -0
  31. package/dist/profiles.js +189 -0
  32. package/dist/roles.d.ts +96 -0
  33. package/dist/roles.js +410 -0
  34. package/dist/rules.d.ts +199 -0
  35. package/dist/rules.js +1539 -0
  36. package/dist/templates/index.js +116 -9
  37. package/dist/toolbar/bpmn-senior-button.js +8 -2
  38. package/dist/toolbar/config.d.ts +27 -2
  39. package/dist/toolbar/config.js +86 -2
  40. package/dist/toolbar/icons.d.ts +67 -0
  41. package/dist/toolbar/icons.js +141 -0
  42. package/dist/toolbar/senior-tool.js +1 -0
  43. package/dist/translations.d.ts +3 -1
  44. package/dist/translations.js +8 -3
  45. package/dist/view.d.ts +6 -2
  46. package/dist/view.js +68 -5
  47. package/package.json +2 -2
@@ -1,33 +1,270 @@
1
1
  import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
2
- import { GfxElementModelView, GfxViewInteractionExtension, } from '@formicoidea/labre-core/std/gfx';
2
+ import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
3
+ import { bpmnLanesOf, renameBpmnLane } from './actions.js';
4
+ import { POOL_LANE_MIN_HEIGHT } from './consts.js';
5
+ import { bpmnLaneBoundaryAt, bpmnPoolBands, bpmnPoolTargetAt, } from './pool-hit.js';
3
6
  /**
4
- * View for a BPMN pool. A double-click edits the participant name in place
5
- * (single field — the whole pool is the hit target). Mirrors the inline label
6
- * editor used by the EDGY / Wardley backgrounds.
7
+ * View for a BPMN pool. Three direct gestures live here:
8
+ *
9
+ * - **dblclick in the pool's own title band** — the left margin strip the
10
+ * participant name is written up — edits that name in place;
11
+ * - **dblclick in a lane's title band** edits THAT lane's name instead. A lane
12
+ * name is not in the declaration's hit-test walk (`backgroundLabelHits`,
13
+ * which Wardley uses): it is a function of the MODEL, not of the declaration,
14
+ * so the box comes from `backgroundInstanceZoneBand` — the very rectangle the
15
+ * renderer paints the name in, rather than a second set of metrics that would
16
+ * one day disagree with it;
17
+ * - **drag on an internal lane boundary** moves the separator, taking from one
18
+ * lane and giving to the other.
19
+ *
20
+ * Both renames are ZONED to their band (PO recette, 2026-08-26). The whole pool
21
+ * used to open the participant editor, which was right while a pool had one
22
+ * name; with a name per lane it would mean a double-click in the middle of the
23
+ * flow area renames the participant — neither of the two things the user could
24
+ * have meant, and the kind of write nobody notices until it is in a
25
+ * deliverable. A double-click on open canvas inside the pool now does nothing,
26
+ * and the `text` cursor over either band is what says where the names are.
27
+ *
28
+ * ## How the separator drag takes the gesture
29
+ *
30
+ * `GfxElementModelView.dispatch` reports a drag as handled whenever a handler
31
+ * is REGISTERED, without consulting what the handler returned, and the default
32
+ * tool stands down on that report. A permanently registered `dragstart` would
33
+ * therefore make a pool undraggable. So the drag handlers are ARMED — attached
34
+ * while the pointer is over a boundary of a selected pool, detached the moment
35
+ * it is not — and the pool moves normally everywhere else.
36
+ *
37
+ * Arming happens on `pointermove` AND on `pointerdown`, and neither is a hover
38
+ * requirement: a touch drag emits its first `pointermove` before the drag
39
+ * threshold is crossed (the move controller listens on the host, the drag
40
+ * controller on the document, so the host listener runs first), which is what
41
+ * makes the grab zone work with no hovering at all. The `ns-resize` cursor is a
42
+ * bonus for whoever has a mouse, never the affordance itself.
43
+ *
44
+ * ## The pool must be selected first
45
+ *
46
+ * Both the cursor and the grab are gated on the pool being selected. On an
47
+ * infinite canvas, dragging over an element means "move it"; silently turning a
48
+ * twelve-unit strip of an UNSELECTED pool into a resize handle would take that
49
+ * away from a user who never said they were working on this pool. One click
50
+ * first, and then the strip is live.
51
+ *
52
+ * ponytail: a ROTATED pool is not accounted for — the pointer is converted to
53
+ * element-local coordinates by subtraction, so every hit box here assumes an
54
+ * upright pool. Same reserve `backgroundAxisFacts` documents, for the same
55
+ * reason: nothing rotates a framework background today. Upgrade: rotate the
56
+ * local point by `-model.rotate` about the element centre, at the one function
57
+ * that has the element in hand (`_localPoint`), not a new declared field.
7
58
  */
8
59
  export class BpmnPoolView extends GfxElementModelView {
9
60
  constructor() {
10
61
  super(...arguments);
11
62
  this._nameEditor = null;
63
+ /** The boundary the pointer is over, and the handlers armed for it. */
64
+ this._armed = null;
65
+ /** Everything a separator drag needs, frozen at `dragstart`. */
66
+ this._drag = null;
12
67
  }
13
68
  static { this.type = 'bpmnPool'; }
14
69
  onCreated() {
15
70
  super.onCreated();
16
71
  this.on('dblclick', e => this._onDblClick(e));
72
+ this.on('pointermove', e => this._updateHover(e));
73
+ this.on('pointerdown', e => this._updateHover(e));
74
+ this.on('pointerleave', () => this._leave());
17
75
  }
18
76
  onDestroyed() {
19
77
  this._closeEditor();
78
+ this._leave();
20
79
  super.onDestroyed();
21
80
  }
81
+ /** Hand the cursor back on the way out; nothing here owns it for long. */
82
+ _leave() {
83
+ this._disarm();
84
+ if (!this._drag)
85
+ this.gfx.cursor$.value = 'default';
86
+ }
87
+ /* ── Lane geometry ─────────────────────────────────────────────────── */
88
+ /** The pointer, in element-local model units. */
89
+ _localPoint(e) {
90
+ const [mx, my] = this.gfx.viewport.toModelCoord(e.x, e.y);
91
+ const [ex, ey] = this.model.deserializedXYWH;
92
+ return [mx - ex, my - ey];
93
+ }
94
+ /**
95
+ * The boxes the gestures below aim at, all of them delegated to `pool-hit.ts`.
96
+ *
97
+ * That module is pure, so every answer here can be asserted without an
98
+ * editor, a viewport or a canvas; and it derives each box from
99
+ * `backgroundInstanceZones` / `backgroundInstanceZoneBand`, the same two
100
+ * functions the renderer paints from and the audit reports from. The view
101
+ * supplies the pointer and the zoom and nothing else.
102
+ */
103
+ _bands() {
104
+ return bpmnPoolBands(this.model);
105
+ }
106
+ _boundaryAt(local) {
107
+ return bpmnLaneBoundaryAt(this.model, local);
108
+ }
109
+ /** Which name this point aims at, if any — lane strip first, see `pool-hit`. */
110
+ _targetAt(local) {
111
+ return bpmnPoolTargetAt(this.model, local, this.gfx.viewport.zoom);
112
+ }
113
+ get _editable() {
114
+ return !this.gfx.std.store.readonly && !this.model.isLocked();
115
+ }
116
+ /* ── Hover: what this point would do ───────────────────────────────── */
117
+ /**
118
+ * One pass over the pointer, deciding both the cursor and whether a
119
+ * separator drag is armed.
120
+ *
121
+ * The order is the order the gestures win in. A separator sits INSIDE a lane
122
+ * title band at every lane boundary, so the two overlap and something has to
123
+ * give: the separator takes it, because it is a twelve-unit strip the user
124
+ * has to aim at deliberately, while the title band is the whole leading edge
125
+ * and has plenty left over. It is also already gated on the pool being
126
+ * selected, so on an unselected pool the band wins uncontested.
127
+ */
128
+ _updateHover(e) {
129
+ const local = this._localPoint(e);
130
+ const selected = this.gfx.selection.selectedIds.includes(this.model.id);
131
+ if (this._editable && selected) {
132
+ const index = this._boundaryAt(local);
133
+ if (index !== null) {
134
+ // Reasserted on every move rather than only on arrival: the cursor is a
135
+ // signal shared with the resize handles and the tools, and whoever set
136
+ // it last wins — so the one that is still true says so again.
137
+ this.gfx.cursor$.value = 'ns-resize';
138
+ if (this._armed?.index !== index) {
139
+ this._disarm();
140
+ this._armed = {
141
+ index,
142
+ disposers: [
143
+ this.on('dragstart', evt => this._onDragStart(evt)),
144
+ this.on('dragmove', evt => this._onDragMove(evt)),
145
+ this.on('dragend', () => this._onDragEnd()),
146
+ ],
147
+ };
148
+ }
149
+ return;
150
+ }
151
+ }
152
+ this._disarm();
153
+ // A title band announces itself, selected or not: finding out that a name
154
+ // can be changed should not cost a click first. Gated on `_editable` all
155
+ // the same — an I-beam over a locked pool would promise an editor that
156
+ // refuses to open, which is the sort of small lie the recette is against.
157
+ const overTitle = this._editable && this._targetAt(local) !== null;
158
+ this.gfx.cursor$.value = overTitle ? 'text' : 'default';
159
+ }
160
+ _disarm() {
161
+ // Never mid-gesture: the pointer leaves the boundary as soon as the drag
162
+ // starts moving, and disarming there would drop the drag on its first step.
163
+ if (this._drag)
164
+ return;
165
+ if (!this._armed)
166
+ return;
167
+ this._armed.disposers.forEach(dispose => dispose());
168
+ this._armed = null;
169
+ }
170
+ _onDragStart(_) {
171
+ const index = this._armed?.index;
172
+ const geometry = this._bands();
173
+ if (index === undefined || !geometry || !this._editable)
174
+ return;
175
+ const lanes = bpmnLanesOf(this.model);
176
+ if (!lanes[index - 1] || !lanes[index])
177
+ return;
178
+ const total = lanes.reduce((sum, lane) => sum + lane.size, 0);
179
+ if (!(total > 0))
180
+ return;
181
+ this._drag = {
182
+ index,
183
+ lanes: lanes.map(lane => ({ ...lane })),
184
+ total,
185
+ plotHeight: geometry.plot.height,
186
+ // The floor is a HEIGHT the user can see, so it is stated in the model
187
+ // units of a pool at its reference height and converted to a weight
188
+ // against this pool's own total — a pool stretched to twice the height
189
+ // keeps the same visible floor, which is the point of weights.
190
+ minWeight: (POOL_LANE_MIN_HEIGHT / geometry.plot.height) * total,
191
+ };
192
+ // Local writes until the release: the intermediate weights repaint the
193
+ // canvas but never reach the document, so the whole drag is ONE undo step.
194
+ this.model.stash('lanes');
195
+ }
196
+ _onDragMove(e) {
197
+ const drag = this._drag;
198
+ if (!drag)
199
+ return;
200
+ // The travel SINCE the drag started, in view pixels (`e.delta` is the step
201
+ // since the last move, which is not the same thing), converted to model
202
+ // units. Always recomputed from the FROZEN pair rather than nudged: a nudge
203
+ // would accumulate the clamp and drift away from the pointer.
204
+ const dy = (e.y - e.start.y) / (this.gfx.viewport.zoom || 1);
205
+ const perUnit = drag.total / drag.plotHeight;
206
+ const above = drag.lanes[drag.index - 1];
207
+ const below = drag.lanes[drag.index];
208
+ const pair = above.size + below.size;
209
+ // The pair's total is invariant: the separator takes from one and gives to
210
+ // the other, so no lane the user is not touching changes size.
211
+ const wanted = above.size + dy * perUnit;
212
+ const floor = Math.min(drag.minWeight, pair / 2);
213
+ const nextAbove = Math.max(floor, Math.min(pair - floor, wanted));
214
+ const next = drag.lanes.map((lane, i) => i === drag.index - 1
215
+ ? { ...lane, size: nextAbove }
216
+ : i === drag.index
217
+ ? { ...lane, size: pair - nextAbove }
218
+ : lane);
219
+ this.model.lanes = next;
220
+ }
221
+ _onDragEnd() {
222
+ const drag = this._drag;
223
+ this._drag = null;
224
+ if (!drag)
225
+ return;
226
+ // Before the commit, not after: `pop` writes straight into the Y.Map, and
227
+ // without a boundary here a separator moved within half a second of the
228
+ // previous edit would be undone together with it.
229
+ this.gfx.std.store.captureSync();
230
+ this.model.pop('lanes');
231
+ this._disarm();
232
+ }
233
+ /* ── In-place naming ───────────────────────────────────────────────── */
234
+ /**
235
+ * A double-click renames whatever TITLE BAND it landed in, and nothing
236
+ * otherwise (PO recette, 2026-08-26).
237
+ *
238
+ * The whole pool used to open the participant editor. That was right while a
239
+ * pool had one name; now that every lane carries one it would mean a
240
+ * double-click in the middle of the flow area renames the participant — not
241
+ * one of the things the user could have meant, and the kind of write nobody
242
+ * notices until it is in a deliverable. A double-click on open canvas inside
243
+ * the pool now does nothing, which is the honest answer.
244
+ */
22
245
  _onDblClick(e) {
23
- if (this.model.isLocked())
246
+ if (!this._editable)
247
+ return;
248
+ const target = this._targetAt(this._localPoint(e));
249
+ if (target === null)
250
+ return;
251
+ if (target.kind === 'lane') {
252
+ const { index } = target;
253
+ const lane = bpmnLanesOf(this.model)[index];
254
+ this._openEditor(e, lane?.name ?? '', value => renameBpmnLane(this.gfx.std, this.model, index, value));
24
255
  return;
25
- this._openEditor(e);
256
+ }
257
+ this._openEditor(e, String(this.model.name ?? ''), value => {
258
+ this.gfx.std.store.captureSync();
259
+ this.gfx.std
260
+ .get(EdgelessCRUDIdentifier)
261
+ .updateElement(this.model.id, { name: value });
262
+ });
26
263
  }
27
- _openEditor(e) {
264
+ _openEditor(e, initial, commit) {
28
265
  this._closeEditor();
29
266
  const input = document.createElement('input');
30
- input.value = String(this.model.name ?? '');
267
+ input.value = initial;
31
268
  Object.assign(input.style, {
32
269
  position: 'fixed',
33
270
  left: `${e.raw.clientX}px`,
@@ -51,28 +288,25 @@ export class BpmnPoolView extends GfxElementModelView {
51
288
  this.gfx.selection.set({ elements: [this.model.id], editing: true });
52
289
  input.focus();
53
290
  input.select();
54
- const commit = () => {
291
+ const onCommit = () => {
55
292
  if (this._nameEditor !== input)
56
293
  return;
57
294
  const value = input.value;
58
295
  this._closeEditor();
59
- this.gfx.std.store.captureSync();
60
- this.gfx.std
61
- .get(EdgelessCRUDIdentifier)
62
- .updateElement(this.model.id, { name: value });
296
+ commit(value);
63
297
  };
64
298
  input.addEventListener('keydown', ev => {
65
299
  ev.stopPropagation();
66
300
  if (ev.key === 'Enter') {
67
301
  ev.preventDefault();
68
- commit();
302
+ onCommit();
69
303
  }
70
304
  else if (ev.key === 'Escape') {
71
305
  ev.preventDefault();
72
306
  this._closeEditor();
73
307
  }
74
308
  });
75
- input.addEventListener('blur', commit);
309
+ input.addEventListener('blur', onCommit);
76
310
  }
77
311
  _closeEditor() {
78
312
  if (!this._nameEditor)
@@ -85,18 +319,3 @@ export class BpmnPoolView extends GfxElementModelView {
85
319
  }
86
320
  }
87
321
  }
88
- /**
89
- * Resize gating: the resize handles are hidden unless `model.resizeEnabled` is
90
- * true (toggled from the toolbar). Moving / selecting stays available.
91
- */
92
- export const BpmnPoolInteraction = GfxViewInteractionExtension(BpmnPoolView.type, {
93
- handleResize({ model }) {
94
- return {
95
- beforeResize({ set }) {
96
- if (!model.resizeEnabled) {
97
- set({ allowedHandlers: [] });
98
- }
99
- },
100
- };
101
- },
102
- });
@@ -0,0 +1,277 @@
1
+ import type { BpmnNodeElementModel, BpmnNodeKind, BpmnPoolElementModel, ConnectorElementModel } from '@formicoidea/labre-core/model';
2
+ /**
3
+ * The board, as a BPMN 2.0 interchange document (clause 15) — semantic model
4
+ * plus BPMN DI, in one `definitions` element.
5
+ *
6
+ * ## Pure by construction
7
+ *
8
+ * Element models in, string out. No `BlockStdScope`, no surface, no DOM, no
9
+ * clock and no randomness — the same discipline `facts.ts` holds itself to, and
10
+ * for the same three reasons: a host can call it, a test can call it with plain
11
+ * stubs, and the same board always serializes to the same bytes. The command
12
+ * that downloads the file is the only thing that knows what a canvas is.
13
+ *
14
+ * ## What it says, and what it refuses to say
15
+ *
16
+ * The export speaks the author's STATEMENTS and nothing else. A connector
17
+ * carrying no BPMN role relates nothing — `docs/adr/0010` is explicit that the
18
+ * role is the statement — so it is not a sequence flow that happens to be
19
+ * untyped, it is not a flow at all, and it is absent. A plain rectangle drawn
20
+ * beside a pool is likewise not an unnamed task. The alternative — guessing —
21
+ * would put words in an architect's mouth in a file they are about to hand to
22
+ * an execution engine.
23
+ *
24
+ * ## Conformance target
25
+ *
26
+ * The **Descriptive** sub-class of BPMN 2.0 (spec Table 2.1), which is exactly
27
+ * the vocabulary the pack draws: the seventeen artefacts map onto the
28
+ * seventeen-odd element names that table lists, and nothing here needs the
29
+ * executable half of the metamodel. Clause 15.1 explicitly licenses a partial
30
+ * model — implementers "disregard missing attributes marked required" — which
31
+ * is what lets a picture drawn for humans round-trip through bpmn.io without
32
+ * inventing an `ioSpecification` nobody asked for.
33
+ */
34
+ /**
35
+ * The four namespaces an interchange file is written in, with the prefixes the
36
+ * spec's own schema uses (`bpmndi`, `di`, `dc`; §12.2.4 and Annex B).
37
+ *
38
+ * Prefixes are arbitrary and URIs are not — bpmn.io writes the same two DD
39
+ * namespaces as `omgdi` / `omgdc` — so the URIs are what is pinned by the tests
40
+ * and the prefixes merely have to be consistent with themselves. The MODEL
41
+ * namespace is given the explicit `bpmn` prefix rather than made the default,
42
+ * because a reader of the file should never have to work out which of two
43
+ * unprefixed vocabularies an element belongs to.
44
+ *
45
+ * The stale `.../BPMNDI/1.0.0` that appears in the spec's own clause 15.3.1
46
+ * example is a documented erratum and is NOT what the normative schema says.
47
+ */
48
+ export declare const BPMN_NS: {
49
+ readonly model: "http://www.omg.org/spec/BPMN/20100524/MODEL";
50
+ readonly bpmndi: "http://www.omg.org/spec/BPMN/20100524/DI";
51
+ readonly di: "http://www.omg.org/spec/DD/20100524/DI";
52
+ readonly dc: "http://www.omg.org/spec/DD/20100524/DC";
53
+ };
54
+ /**
55
+ * The four declarations this library writes, as they appear on `definitions` —
56
+ * the PAIR, prefix and URI together, and the pair is the point.
57
+ *
58
+ * A reader carries a file's namespace declarations because a carried fragment
59
+ * is stored with the prefixes the file spelled it in, and a `camunda:property`
60
+ * or a `bpmn2:boundaryEvent` means nothing under a declaration nobody wrote.
61
+ * What it must NOT carry is a declaration this writer is going to make anyway,
62
+ * or the payload gains four permanent entries, every Labre file reports four
63
+ * things carried, and the "a file we wrote comes back with an empty middle
64
+ * column" property — the no-slow-leak property — stops being true.
65
+ *
66
+ * Keyed by the attribute NAME rather than by the URI, because the prefix is
67
+ * exactly what differs: bpmn.io writes the model namespace as `bpmn2:` and this
68
+ * library writes it as `bpmn:`, and a fragment carrying `bpmn2:` is unreadable
69
+ * unless `xmlns:bpmn2` comes back with it. Same URI, different prefix,
70
+ * different fate — which a set of URIs cannot express.
71
+ */
72
+ export declare const BPMN_OWN_DECLARATIONS: Readonly<Record<string, string>>;
73
+ /**
74
+ * The interchange format's id — the key under which foreign matter from a
75
+ * `.bpmn` rides on an element (`interchange.bpmn`, ADR 0012 D2), and the middle
76
+ * term of both capability ids.
77
+ *
78
+ * Declared here, in the module both directions already depend on, so that the
79
+ * writer, the reader and the registry entry cannot disagree about which key
80
+ * they are talking about.
81
+ */
82
+ export declare const BPMN_FORMAT_ID = "bpmn";
83
+ /**
84
+ * Which slot of `process` an artefact serializes into.
85
+ *
86
+ * Not decoration: the spec's `tProcess` sequence is `laneSet* → flowElement* →
87
+ * artifact*` in that order, and a `textAnnotation` written before a `task` is a
88
+ * document a validating parser rejects. It also decides what a LANE may point
89
+ * at — `flowNodeRef` is an IDREF to a flow NODE, and a data reference is a flow
90
+ * element that is not one, so a data object sitting in a lane is simply not
91
+ * referenced by it.
92
+ */
93
+ type BpmnXmlSlot = 'flowNode' | 'data' | 'artifact';
94
+ export interface BpmnXmlMapping {
95
+ /** The semantic element name, in the MODEL namespace. */
96
+ element: string;
97
+ slot: BpmnXmlSlot;
98
+ /** The child that says what TRIGGERS the event, for the four variants. */
99
+ eventDefinition?: 'messageEventDefinition' | 'timerEventDefinition' | 'terminateEventDefinition';
100
+ }
101
+ /**
102
+ * The whole notation, kind by kind — the table this module is really about.
103
+ *
104
+ * `Record<BpmnNodeKind, …>` and therefore COMPILE-TOTAL: a kind added to the
105
+ * pack without a BPMN element name to serialize it as fails the build here,
106
+ * which is the only place that failure is cheap. A kind that reached a
107
+ * document and had no mapping would be an artefact the author drew, saved, and
108
+ * then silently lost on export.
109
+ *
110
+ * Three of the seventeen do not map one-for-one and the reasons are the spec's:
111
+ *
112
+ * - the four TRIGGERED events are `startEvent` / `endEvent` carrying an event
113
+ * definition child, never elements of their own — "message start event" is a
114
+ * start event with a `messageEventDefinition` in it (§10.4.2);
115
+ * - a data object serializes as `dataObjectReference`, because DI attaches to
116
+ * the REFERENCE and not to the `dataObject` it points at (§10.4.1, and the
117
+ * spec's own rule that "Data Object Reference cannot specify item
118
+ * definitions, and Data Objects cannot specify states"). The `dataObject`
119
+ * itself is emitted alongside it;
120
+ * - a `group` carries no `name` at all. Its visible label is the `value` of the
121
+ * `categoryValue` it points at, which is a ROOT element of the document — the
122
+ * one place in this file where drawing a box round three tasks costs two
123
+ * extra elements somewhere else entirely (§10.4, Table 8.30).
124
+ */
125
+ export declare const BPMN_XML_OF_KIND: Record<BpmnNodeKind, BpmnXmlMapping>;
126
+ /**
127
+ * Character DATA — the three characters that would otherwise start markup.
128
+ *
129
+ * A newline, a tab and a carriage return are left exactly as they are, which is
130
+ * what makes `<bpmn:text>` carry a multi-line annotation faithfully: inside an
131
+ * element, whitespace is content.
132
+ */
133
+ export declare function escapeText(value: string): string;
134
+ /**
135
+ * An attribute VALUE, which needs strictly more than character data does.
136
+ *
137
+ * The quotes are the obvious half. The other half is the one that loses data
138
+ * silently: XML 1.0 §3.3.3 makes every conformant parser replace a literal
139
+ * `#xA`, `#xD` or `#x9` in an attribute value with a SPACE before anyone sees
140
+ * it — attribute-value normalization, and it is not optional. Only a character
141
+ * reference survives it.
142
+ *
143
+ * That matters here because a multi-line label is ordinary on this canvas (it
144
+ * is how a task fits in its box) and `name` is where nearly all of them go:
145
+ * every flow node, the participant, the lane, the flows, and
146
+ * `categoryValue/@value`. Written raw, a two-line task name comes back as one
147
+ * line, with no warning and no way for the author to tell. Written as `&#10;`
148
+ * it comes back as it went in.
149
+ */
150
+ export declare function escapeAttr(value: string): string;
151
+ /**
152
+ * A surface id, as an XML NCName.
153
+ *
154
+ * `id` is `xsd:ID` throughout BPMN, which means NCName and means
155
+ * DOCUMENT-unique — a `BPMNShape` may not carry the id of the `task` it
156
+ * describes. Surface ids are nanoid-shaped: they routinely open on a digit and
157
+ * may carry a `-`, both of which a validating parser refuses on an `xsd:ID`.
158
+ *
159
+ * So: every disallowed character becomes `_`, and an id that does not open on a
160
+ * letter or `_` is prefixed with one. The transformation is lossy on purpose —
161
+ * two distinct surface ids can collapse onto the same NCName — which is what
162
+ * {@link IdMinter} is for.
163
+ */
164
+ export declare function toNcName(raw: string): string;
165
+ /** Whether a string is already an NCName, and can therefore be given back. */
166
+ export declare function isNcName(value: string): boolean;
167
+ /**
168
+ * `.bpmn`'s scope vocabulary — where a carried fragment came off, and therefore
169
+ * where this writer has to put it back (ADR 0012, D2 as amended in #157).
170
+ *
171
+ * Declared HERE, in the module both directions already depend on, for the same
172
+ * reason {@link BPMN_FORMAT_ID} is: the reader files a fragment under a scope
173
+ * and the writer looks it up under one, and a table written twice is a table
174
+ * that drifts. `import.ts` re-exports it.
175
+ *
176
+ * One Labre element stands for several source elements: a pool is a
177
+ * `participant` AND its `process`, plus a `laneSet`, every `lane`, the
178
+ * `BPMNShape` that draws it, and — on the first pool of a document — the
179
+ * `collaboration` and `definitions` themselves. Everything they carry lands in
180
+ * ONE payload, so what came off which is recorded, or two lanes with the same
181
+ * foreign attribute leave one value in a persisted field and a report that says
182
+ * two.
183
+ *
184
+ * A scope is either a source element's **id, verbatim** — every carried flow
185
+ * node, every lane, every carried root element — or one of the `@` keys below,
186
+ * for the parts of the document that have no id worth naming or whose identity
187
+ * is their relation to this element. `@` is not an XML NameStartChar, so no id
188
+ * in a conformant file can ever collide with one.
189
+ *
190
+ * The rule for a fragment is always the same: **the scope is the element it was
191
+ * a child of**. For an attribute it is the element that carried the attribute;
192
+ * for a `di` fragment, what that fragment draws.
193
+ */
194
+ export declare const BPMN_SCOPE: {
195
+ /** The element this payload rides on: the participant, the flow node, the flow. */
196
+ readonly self: "@self";
197
+ /** Its `BPMNShape` or `BPMNEdge`. */
198
+ readonly shape: "@shape";
199
+ /** The `process` behind a participant — the pool's other half. */
200
+ readonly process: "@process";
201
+ /** The pool's `laneSet`. */
202
+ readonly laneSet: "@laneSet";
203
+ /** The `collaboration`, whose residue rides on the first pool (D6). */
204
+ readonly collaboration: "@collaboration";
205
+ /** `definitions` itself: its foreign attributes, its declarations, its roots. */
206
+ readonly definitions: "@definitions";
207
+ };
208
+ /**
209
+ * The board to serialize — everything on the surface, split by what it is.
210
+ *
211
+ * The whole board and not a selection: a BPMN document is a process, and half a
212
+ * process is not a smaller process. The pool whose toolbar launched the export
213
+ * decides the FILENAME and nothing else.
214
+ */
215
+ export interface BpmnExportBoard {
216
+ /** In document order — which is the tie-break `bpmnPoolOf` breaks on. */
217
+ pools: readonly BpmnPoolElementModel[];
218
+ nodes: readonly BpmnNodeElementModel[];
219
+ connectors: readonly ConnectorElementModel[];
220
+ }
221
+ export interface BpmnExportOptions {
222
+ /** Names the `collaboration` / lone `process` and the `BPMNDiagram`. */
223
+ name?: string;
224
+ }
225
+ /**
226
+ * The document, plus what writing it could not say.
227
+ *
228
+ * Three things the board can hold have no honest place in a `.bpmn` file, and
229
+ * until now each of them was documented in a code comment and silent to the
230
+ * person who clicked Export. A warning is one line, in the user's words, and it
231
+ * names the fix rather than the mechanism. Nothing here is an error: the file
232
+ * is valid and the export succeeded — these are the sentences the format
233
+ * refused to carry.
234
+ */
235
+ export interface BpmnExportOutcome {
236
+ text: string;
237
+ /** Empty when the board came out whole, which is the usual case. */
238
+ warnings: string[];
239
+ }
240
+ /**
241
+ * Serialize a board as a BPMN 2.0 XML interchange document.
242
+ *
243
+ * ## The shape of the document, and what decides it
244
+ *
245
+ * One `definitions`, always. Then:
246
+ *
247
+ * - **at least one pool** — a `collaboration` holding one `participant` per
248
+ * pool, one `process` per pool, and the message flows (which are the
249
+ * collaboration's, never a process's). Things drawn OUTSIDE every pool split
250
+ * in two: ARTIFACTS (annotation, group, and the associations that tie them to
251
+ * anything) become children of the collaboration itself, where
252
+ * `tCollaboration` allows them and where bpmn-js draws them; FLOW OBJECTS get
253
+ * ONE extra participant-less process, and only if there are any. See
254
+ * {@link COLLABORATION} for what the live recette found out about the
255
+ * difference, and the note on the orphan process for what it still cannot fix;
256
+ * - **no pool at all** — a single `process` and no collaboration, which is what
257
+ * a process drawn without swimlanes IS. The `BPMNPlane` then points at that
258
+ * process; with a collaboration it must point at the collaboration, or most
259
+ * tools draw the flow and none of the pools (spec §12.3.2).
260
+ *
261
+ * Attribution is {@link bpmnPoolOf} and {@link bpmnLaneOf} — the CENTRE against
262
+ * the pool's PLOT, containment only, no nearest-pool fallback. Deliberately the
263
+ * same arithmetic the audit and the validation rules read, so a task the audit
264
+ * reports in "Back office" is in the `lane` named "Back office" here.
265
+ */
266
+ export declare function exportBpmnXml(board: BpmnExportBoard, options?: BpmnExportOptions): string;
267
+ /**
268
+ * The same serialization, with the loss channel attached — see
269
+ * {@link BpmnExportOutcome}.
270
+ *
271
+ * {@link exportBpmnXml} is the thin wrapper over it, kept because a caller that
272
+ * only wants the bytes should not have to reach past a report to get them, and
273
+ * because #149's forty-six tests and the live integration spec pin that
274
+ * signature. The interchange capability calls THIS one.
275
+ */
276
+ export declare function exportBpmnXmlWithWarnings(board: BpmnExportBoard, options?: BpmnExportOptions): BpmnExportOutcome;
277
+ export {};