@formicoidea/labre-framework-bpmn 0.32.0 → 0.34.1

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 (49) hide show
  1. package/dist/actions.d.ts +202 -6
  2. package/dist/actions.js +427 -43
  3. package/dist/background.d.ts +2 -0
  4. package/dist/background.js +158 -0
  5. package/dist/commands-manifest.d.ts +18 -0
  6. package/dist/commands-manifest.js +226 -0
  7. package/dist/commands.js +496 -5
  8. package/dist/consts.d.ts +195 -4
  9. package/dist/consts.js +230 -4
  10. package/dist/element-renderer.d.ts +10 -4
  11. package/dist/element-renderer.js +14 -55
  12. package/dist/element-view.d.ts +119 -8
  13. package/dist/element-view.js +274 -30
  14. package/dist/export.d.ts +277 -0
  15. package/dist/export.js +1802 -0
  16. package/dist/facts.d.ts +48 -0
  17. package/dist/facts.js +127 -0
  18. package/dist/import.d.ts +69 -0
  19. package/dist/import.js +1476 -0
  20. package/dist/index.d.ts +12 -0
  21. package/dist/index.js +47 -0
  22. package/dist/interchange.d.ts +109 -0
  23. package/dist/interchange.js +191 -0
  24. package/dist/morph.d.ts +61 -0
  25. package/dist/morph.js +118 -0
  26. package/dist/node/node-renderer.d.ts +0 -9
  27. package/dist/node/node-renderer.js +294 -17
  28. package/dist/pool-hit.d.ts +98 -0
  29. package/dist/pool-hit.js +130 -0
  30. package/dist/presets.d.ts +168 -0
  31. package/dist/presets.js +327 -0
  32. package/dist/profiles.d.ts +2 -0
  33. package/dist/profiles.js +189 -0
  34. package/dist/roles.d.ts +96 -0
  35. package/dist/roles.js +410 -0
  36. package/dist/rules.d.ts +199 -0
  37. package/dist/rules.js +1539 -0
  38. package/dist/templates/index.js +116 -9
  39. package/dist/toolbar/bpmn-senior-button.js +8 -2
  40. package/dist/toolbar/config.d.ts +28 -2
  41. package/dist/toolbar/config.js +93 -4
  42. package/dist/toolbar/icons.d.ts +67 -0
  43. package/dist/toolbar/icons.js +141 -0
  44. package/dist/toolbar/senior-tool.js +1 -0
  45. package/dist/translations.d.ts +3 -1
  46. package/dist/translations.js +38 -3
  47. package/dist/view.d.ts +6 -2
  48. package/dist/view.js +68 -5
  49. package/package.json +6 -2
@@ -0,0 +1,168 @@
1
+ import type { BpmnNodeKind } from '@formicoidea/labre-core/model';
2
+ import { StrokeStyle, TextAlign, TextFitMode, TextVerticalAlign } from '@formicoidea/labre-core/model';
3
+ /**
4
+ * What a BPMN artefact is BORN as — the one description of a node's props, read
5
+ * by every site that creates one.
6
+ *
7
+ * Lifted out of `actions.ts` when the importer arrived, and lifted rather than
8
+ * copied for the reason the copy would have failed: a task read out of a
9
+ * `.bpmn` file and a task drawn from the palette must be the same element in
10
+ * the document, down to the stroke width and the text fit mode. Two builders
11
+ * would agree on the day they were written and drift on the first restyle,
12
+ * and the drift would show up as an imported board that looks subtly unlike a
13
+ * drawn one — the kind of difference nobody reports and everybody notices.
14
+ *
15
+ * Pure data and one pure function: no `BlockStdScope`, no surface, no viewport.
16
+ * The creation gesture supplies the box, the importer supplies the box the file
17
+ * gave it, and neither knows anything the other does not.
18
+ */
19
+ /**
20
+ * The native shape and accent a kind is born with (style C).
21
+ *
22
+ * `glyphBody` is the one non-obvious field: the three data/artifact shapes have
23
+ * a silhouette a native rect cannot make — a folded page, a cylinder, an open
24
+ * bracket — so for those the renderer's glyph paints the BODY as well as the
25
+ * decoration, and the native shape is created unfilled and unstroked. It still
26
+ * earns its keep: it is what carries the inner text, the selection bounds, the
27
+ * resize handles and the connector anchors.
28
+ */
29
+ export interface BpmnNodePreset {
30
+ shapeType: 'ellipse' | 'rect' | 'diamond';
31
+ stroke: string;
32
+ width: number;
33
+ /** Corner radius, `rect` only. Absent means a square corner. */
34
+ radius?: number;
35
+ /** Border style. Absent means a solid line, which is what BPMN mostly draws. */
36
+ strokeStyle?: StrokeStyle;
37
+ /**
38
+ * No fill — an OUTLINE, not a body. It also decides what the artefact does to
39
+ * a click: an unfilled shape is hit near its border and on its label only, so
40
+ * the group never steals a click from the work it encloses.
41
+ */
42
+ hollow?: true;
43
+ /** The glyph draws the body; the native shape paints nothing. */
44
+ glyphBody?: true;
45
+ /**
46
+ * Where the inner text sits. Absent means centred, which is what every
47
+ * artefact whose label names the artefact itself wants. The group is the
48
+ * exception: its label names a region, so it goes in the corner of it.
49
+ */
50
+ textAlign?: TextAlign;
51
+ textVerticalAlign?: TextVerticalAlign;
52
+ }
53
+ /** Per-kind native shape + accent presets (style C). */
54
+ export declare const NODE_PRESETS: Record<BpmnNodeKind, BpmnNodePreset>;
55
+ /** The label typography a foreign box asks for. See {@link bpmnLabelFit}. */
56
+ export interface BpmnLabelFit {
57
+ fontSize: number;
58
+ /** `[vertical, horizontal]`, as `ShapeElementModel.padding` is written. */
59
+ padding: [number, number];
60
+ textFitMode: TextFitMode;
61
+ }
62
+ /**
63
+ * The typography a label needs to sit INSIDE a box this pack did not choose —
64
+ * which in practice means a box an interchange file chose (`import.ts`).
65
+ *
66
+ * ## Why an imported label misbehaves and a drawn one does not
67
+ *
68
+ * Every value the creation builder writes is calibrated against
69
+ * {@link NODE_SIZE}: an 18-unit font and the shape's native 20-unit horizontal
70
+ * inset are comfortable in a 120-unit task, which is what the palette draws.
71
+ * A file draws to its author's scale, and bpmn.io's normative sizes — the ones
72
+ * nearly every `.bpmn` in the wild carries — are a 100×80 task and a 36-unit
73
+ * event. In a 100-unit task the native inset leaves 60 units of line, which is
74
+ * less than the word "Étudier" is wide at 18 units, so the label breaks in the
75
+ * middle of a word; in a 36-unit event it leaves NEGATIVE room, and the name
76
+ * sprawls across the canvas. Both are issue #184, and neither is reachable from
77
+ * the palette.
78
+ *
79
+ * So the three props that decide how a label sits are derived from the box:
80
+ *
81
+ * - **`fontSize`** shrinks with the box, proportionally, and never grows past
82
+ * {@link INNER_FONT_SIZE} — a file that draws BIGGER than this pack gets the
83
+ * pack's own type, not inflated type — with {@link LABEL_MIN_FONT_SIZE} as
84
+ * the floor;
85
+ * - **`padding`** becomes {@link LABEL_INSET_RATIO} of the box, capped at the
86
+ * shape's native inset, so the margin follows the artefact down instead of
87
+ * eating it;
88
+ * - **`textFitMode`** becomes `Contained`, which is the honest statement of
89
+ * what an imported artefact IS: the file fixed the box — re-exporting a
90
+ * different one would rewrite the author's diagram — so the TEXT is what
91
+ * yields. The renderer then shrinks the font further, per frame, whenever a
92
+ * long name still does not fit.
93
+ *
94
+ * Pure arithmetic on two numbers: no measuring, no canvas, no renderer. The
95
+ * fit does not have to be exact, because `Contained` finishes it at paint time
96
+ * with the real font metrics; what this has to do is stop asking for type the
97
+ * box was never going to hold.
98
+ */
99
+ export declare function bpmnLabelFit(kind: BpmnNodeKind, w: number, h: number): BpmnLabelFit;
100
+ /**
101
+ * One BPMN node, as the props `surface.addElement` takes.
102
+ *
103
+ * The single description of what a `bpmnNode` IS in a document: the palette
104
+ * hands it a box centred on the viewport, the importer hands it the box the
105
+ * file's `dc:Bounds` gave, and the element that lands is the same either way.
106
+ *
107
+ * `text` is passed through unchanged, `undefined` included — an artefact whose
108
+ * label the source left empty gets no `text` key at all rather than an empty
109
+ * one, which is what keeps an imported node byte-comparable with a drawn one
110
+ * that was never typed into.
111
+ *
112
+ * `fitLabel` says the box is NOT the pack's own — the caller took it from a
113
+ * file it may not rewrite — so the label is fitted to it
114
+ * ({@link bpmnLabelFit}). It is asked for explicitly rather than inferred from
115
+ * the box's size for two reasons: an importer knows whether it read a
116
+ * `dc:Bounds` and a guess never would, and {@link bpmnMorphProps} calls this
117
+ * builder with a throwaway box whose numbers must not be allowed to mean
118
+ * anything.
119
+ */
120
+ export declare function bpmnNodeProps(kind: BpmnNodeKind, box: {
121
+ xywh: string;
122
+ text?: string;
123
+ fitLabel?: boolean;
124
+ }): Record<string, unknown> & {
125
+ type: string;
126
+ };
127
+ /**
128
+ * What a node's kind is worth to an element that ALREADY EXISTS — the same
129
+ * description as {@link bpmnNodeProps}, minus the three things a morph has no
130
+ * business touching.
131
+ *
132
+ * Derived from the creation builder rather than restated beside it, and that is
133
+ * the whole point of the function.
134
+ *
135
+ * ## Why a `{kind, role}` patch is not enough
136
+ *
137
+ * Because the appearance of a BPMN artefact lives in props the CREATING kind's
138
+ * preset wrote, and nothing else ever rewrites them. One shipped morph pair
139
+ * shows it today: `subProcess` and `callActivity` are the same rounded
140
+ * rectangle and differ only in `strokeWidth` — 2 against 4 — and that thick
141
+ * border IS how a reader tells "this box stands for a process defined
142
+ * elsewhere" from "this box stands for one defined inline". Morph between them
143
+ * with two keys and the call activity arrives wearing the sub-process's thin
144
+ * border, which is a drawing that says the wrong thing.
145
+ *
146
+ * Every other family declared in `./morph.ts` currently shares one preset
147
+ * across its members, so for those the full patch changes nothing — and that is
148
+ * the second reason to write it this way rather than to trim it. A family is
149
+ * DATA (`BPMN_MORPH_FAMILIES`) and grows by declaration, with no code change to
150
+ * prompt anyone to ask whether the presets still agree; deriving the patch from
151
+ * the creation builder means the answer is right in advance. It is also what
152
+ * guarantees that a morphed artefact and one drawn fresh from the palette are
153
+ * the same element — two builders would agree the day they were written and
154
+ * drift on the first restyle, which is the argument this file already makes for
155
+ * having one creation builder at all.
156
+ */
157
+ export declare function bpmnMorphProps(kind: BpmnNodeKind): Record<string, unknown>;
158
+ /**
159
+ * The fields to DELETE from an element after morphing it to `kind` — the keys
160
+ * some other kind writes and this one does not.
161
+ *
162
+ * A patch cannot express absence. `textVerticalAlign` is spread conditionally
163
+ * (see {@link bpmnNodeProps}), so morphing away from the group would leave
164
+ * `Top` sitting in the Y.Map and silently in force over a preset that means
165
+ * "centred". `clearField` removes the key, which is the same call `writeLanes`
166
+ * makes when a pool loses its last lane.
167
+ */
168
+ export declare function bpmnMorphClears(kind: BpmnNodeKind): readonly string[];
@@ -0,0 +1,327 @@
1
+ import { FontFamily, SHAPE_TEXT_PADDING, SHAPE_TEXT_VERTICAL_PADDING, ShapeStyle, StrokeStyle, TextAlign, TextFitMode, TextVerticalAlign, } from '@formicoidea/labre-core/model';
2
+ import { Bound } from '@formicoidea/labre-core/global/gfx';
3
+ import { CALL_ACTIVITY_WIDTH, END_WIDTH, EVENT_END, EVENT_START, GROUP_RADIUS, GROUP_STROKE, INNER_FONT_SIZE, LABEL_INSET_RATIO, LABEL_MIN_FONT_SIZE, NEUTRAL_STROKE, NODE_FILL, NODE_SIZE, NODE_STROKE_WIDTH, START_WIDTH, TASK_RADIUS, } from './consts.js';
4
+ import { BPMN_ROLE_OF_KIND } from './roles.js';
5
+ /** Per-kind native shape + accent presets (style C). */
6
+ export const NODE_PRESETS = {
7
+ // Events: one ellipse, and the ring weight says start or end. The message and
8
+ // timer variants keep their family's ring exactly — a message START is a thin
9
+ // green ring with an envelope in it, and a message END the thick red one.
10
+ startEvent: { shapeType: 'ellipse', stroke: EVENT_START, width: START_WIDTH },
11
+ startEventMessage: {
12
+ shapeType: 'ellipse',
13
+ stroke: EVENT_START,
14
+ width: START_WIDTH,
15
+ },
16
+ startEventTimer: {
17
+ shapeType: 'ellipse',
18
+ stroke: EVENT_START,
19
+ width: START_WIDTH,
20
+ },
21
+ endEvent: { shapeType: 'ellipse', stroke: EVENT_END, width: END_WIDTH },
22
+ endEventMessage: {
23
+ shapeType: 'ellipse',
24
+ stroke: EVENT_END,
25
+ width: END_WIDTH,
26
+ },
27
+ endEventTerminate: {
28
+ shapeType: 'ellipse',
29
+ stroke: EVENT_END,
30
+ width: END_WIDTH,
31
+ },
32
+ // Activities: the same rounded rectangle, and a marker tells them apart —
33
+ // except the call activity, whose thick border IS the distinction (it carries
34
+ // the same `+` as the sub-process).
35
+ task: {
36
+ shapeType: 'rect',
37
+ stroke: NEUTRAL_STROKE,
38
+ width: NODE_STROKE_WIDTH,
39
+ radius: TASK_RADIUS,
40
+ },
41
+ taskUser: {
42
+ shapeType: 'rect',
43
+ stroke: NEUTRAL_STROKE,
44
+ width: NODE_STROKE_WIDTH,
45
+ radius: TASK_RADIUS,
46
+ },
47
+ taskService: {
48
+ shapeType: 'rect',
49
+ stroke: NEUTRAL_STROKE,
50
+ width: NODE_STROKE_WIDTH,
51
+ radius: TASK_RADIUS,
52
+ },
53
+ subProcess: {
54
+ shapeType: 'rect',
55
+ stroke: NEUTRAL_STROKE,
56
+ width: NODE_STROKE_WIDTH,
57
+ radius: TASK_RADIUS,
58
+ },
59
+ callActivity: {
60
+ shapeType: 'rect',
61
+ stroke: NEUTRAL_STROKE,
62
+ width: CALL_ACTIVITY_WIDTH,
63
+ radius: TASK_RADIUS,
64
+ },
65
+ // Gateways: one diamond, one marker each.
66
+ gatewayExclusive: {
67
+ shapeType: 'diamond',
68
+ stroke: NEUTRAL_STROKE,
69
+ width: NODE_STROKE_WIDTH,
70
+ },
71
+ gatewayParallel: {
72
+ shapeType: 'diamond',
73
+ stroke: NEUTRAL_STROKE,
74
+ width: NODE_STROKE_WIDTH,
75
+ },
76
+ // Data and artifacts: body drawn by the glyph (see {@link BpmnNodePreset}).
77
+ dataObject: {
78
+ shapeType: 'rect',
79
+ stroke: NEUTRAL_STROKE,
80
+ width: NODE_STROKE_WIDTH,
81
+ glyphBody: true,
82
+ },
83
+ dataStore: {
84
+ shapeType: 'rect',
85
+ stroke: NEUTRAL_STROKE,
86
+ width: NODE_STROKE_WIDTH,
87
+ glyphBody: true,
88
+ },
89
+ textAnnotation: {
90
+ shapeType: 'rect',
91
+ stroke: NEUTRAL_STROKE,
92
+ width: NODE_STROKE_WIDTH,
93
+ glyphBody: true,
94
+ },
95
+ // The group: a dashed grey outline round part of the picture. Entirely a
96
+ // native shape — no glyph, nothing for the renderer to do — because the
97
+ // notation asks for exactly what `strokeStyle: dash` already draws.
98
+ group: {
99
+ shapeType: 'rect',
100
+ stroke: GROUP_STROKE,
101
+ width: NODE_STROKE_WIDTH,
102
+ radius: GROUP_RADIUS,
103
+ strokeStyle: StrokeStyle.Dash,
104
+ hollow: true,
105
+ textAlign: TextAlign.Left,
106
+ textVerticalAlign: TextVerticalAlign.Top,
107
+ },
108
+ };
109
+ /**
110
+ * `[w, h]` off a serialized box.
111
+ *
112
+ * A `Bound` and not a hand-rolled `split(',')`: the same deserializer the store
113
+ * reads an element's geometry with, so a box this builder measures and a box
114
+ * the canvas draws can never be two different rectangles.
115
+ */
116
+ function boxExtent(xywh) {
117
+ const bound = Bound.deserialize(xywh);
118
+ return [bound.w, bound.h];
119
+ }
120
+ /**
121
+ * How big a box is against the pack's own — and `1`, meaning "the pack's own",
122
+ * for an extent that says nothing.
123
+ *
124
+ * A `dc:Bounds` of zeros, of negatives or of `NaN` (which is what a
125
+ * non-numeric attribute parses to) reaches an importer on its first afternoon
126
+ * in the wild. The answer for a box that says nothing is the DRAWN artefact's
127
+ * type, not a shrunken guess at a size nobody gave — and never `NaN`, which is
128
+ * not a number the store can hold.
129
+ */
130
+ const ratioOf = (extent, standard) => Number.isFinite(extent) && extent > 0 ? extent / standard : 1;
131
+ /** One side's margin: the ratio of the extent, never more than the native inset. */
132
+ const insetOf = (extent, native) => Number.isFinite(extent) && extent > 0
133
+ ? Math.round(Math.min(native, extent * LABEL_INSET_RATIO))
134
+ : native;
135
+ /**
136
+ * The typography a label needs to sit INSIDE a box this pack did not choose —
137
+ * which in practice means a box an interchange file chose (`import.ts`).
138
+ *
139
+ * ## Why an imported label misbehaves and a drawn one does not
140
+ *
141
+ * Every value the creation builder writes is calibrated against
142
+ * {@link NODE_SIZE}: an 18-unit font and the shape's native 20-unit horizontal
143
+ * inset are comfortable in a 120-unit task, which is what the palette draws.
144
+ * A file draws to its author's scale, and bpmn.io's normative sizes — the ones
145
+ * nearly every `.bpmn` in the wild carries — are a 100×80 task and a 36-unit
146
+ * event. In a 100-unit task the native inset leaves 60 units of line, which is
147
+ * less than the word "Étudier" is wide at 18 units, so the label breaks in the
148
+ * middle of a word; in a 36-unit event it leaves NEGATIVE room, and the name
149
+ * sprawls across the canvas. Both are issue #184, and neither is reachable from
150
+ * the palette.
151
+ *
152
+ * So the three props that decide how a label sits are derived from the box:
153
+ *
154
+ * - **`fontSize`** shrinks with the box, proportionally, and never grows past
155
+ * {@link INNER_FONT_SIZE} — a file that draws BIGGER than this pack gets the
156
+ * pack's own type, not inflated type — with {@link LABEL_MIN_FONT_SIZE} as
157
+ * the floor;
158
+ * - **`padding`** becomes {@link LABEL_INSET_RATIO} of the box, capped at the
159
+ * shape's native inset, so the margin follows the artefact down instead of
160
+ * eating it;
161
+ * - **`textFitMode`** becomes `Contained`, which is the honest statement of
162
+ * what an imported artefact IS: the file fixed the box — re-exporting a
163
+ * different one would rewrite the author's diagram — so the TEXT is what
164
+ * yields. The renderer then shrinks the font further, per frame, whenever a
165
+ * long name still does not fit.
166
+ *
167
+ * Pure arithmetic on two numbers: no measuring, no canvas, no renderer. The
168
+ * fit does not have to be exact, because `Contained` finishes it at paint time
169
+ * with the real font metrics; what this has to do is stop asking for type the
170
+ * box was never going to hold.
171
+ */
172
+ export function bpmnLabelFit(kind, w, h) {
173
+ const size = NODE_SIZE[kind];
174
+ const scale = Math.min(1, ratioOf(w, size.w), ratioOf(h, size.h));
175
+ return {
176
+ fontSize: Math.max(LABEL_MIN_FONT_SIZE, Math.round(INNER_FONT_SIZE * scale)),
177
+ padding: [
178
+ insetOf(h, SHAPE_TEXT_VERTICAL_PADDING),
179
+ insetOf(w, SHAPE_TEXT_PADDING),
180
+ ],
181
+ textFitMode: TextFitMode.Contained,
182
+ };
183
+ }
184
+ /**
185
+ * One BPMN node, as the props `surface.addElement` takes.
186
+ *
187
+ * The single description of what a `bpmnNode` IS in a document: the palette
188
+ * hands it a box centred on the viewport, the importer hands it the box the
189
+ * file's `dc:Bounds` gave, and the element that lands is the same either way.
190
+ *
191
+ * `text` is passed through unchanged, `undefined` included — an artefact whose
192
+ * label the source left empty gets no `text` key at all rather than an empty
193
+ * one, which is what keeps an imported node byte-comparable with a drawn one
194
+ * that was never typed into.
195
+ *
196
+ * `fitLabel` says the box is NOT the pack's own — the caller took it from a
197
+ * file it may not rewrite — so the label is fitted to it
198
+ * ({@link bpmnLabelFit}). It is asked for explicitly rather than inferred from
199
+ * the box's size for two reasons: an importer knows whether it read a
200
+ * `dc:Bounds` and a guess never would, and {@link bpmnMorphProps} calls this
201
+ * builder with a throwaway box whose numbers must not be allowed to mean
202
+ * anything.
203
+ */
204
+ export function bpmnNodeProps(kind, box) {
205
+ const preset = NODE_PRESETS[kind];
206
+ // Nothing to fit without a label: an artefact whose name the file left empty
207
+ // stays byte-identical to a drawn one, which is what `presets.ts` is for.
208
+ const fit = box.fitLabel && box.text
209
+ ? bpmnLabelFit(kind, ...boxExtent(box.xywh))
210
+ : undefined;
211
+ return {
212
+ type: 'bpmnNode',
213
+ kind,
214
+ // Semantic identity (B1): posted next to `kind`, which stays untouched and
215
+ // keeps driving the rendering. The role is the authority on what the node
216
+ // MEANS — see the table in `./roles.ts`.
217
+ role: BPMN_ROLE_OF_KIND[kind],
218
+ shapeType: preset.shapeType,
219
+ // A glyph-bodied artefact paints nothing natively: the folded page, the
220
+ // cylinder and the bracket are drawn by the renderer, which reads
221
+ // `fillColor` / `strokeColor` off this same model — so both stay editable
222
+ // from the shape toolbar exactly like every other node's. A `hollow` one
223
+ // paints natively and simply has no body: the group is an outline.
224
+ filled: !preset.glyphBody && !preset.hollow,
225
+ fillColor: NODE_FILL,
226
+ strokeColor: preset.stroke,
227
+ strokeWidth: preset.width,
228
+ strokeStyle: preset.glyphBody
229
+ ? StrokeStyle.None
230
+ : (preset.strokeStyle ?? StrokeStyle.Solid),
231
+ shapeStyle: ShapeStyle.General,
232
+ roughness: 0,
233
+ radius: preset.radius ?? 0,
234
+ text: box.text,
235
+ color: NEUTRAL_STROKE,
236
+ fontFamily: FontFamily.Inter,
237
+ fontSize: fit?.fontSize ?? INNER_FONT_SIZE,
238
+ textAlign: preset.textAlign ?? TextAlign.Center,
239
+ // Spread, never a defaulted key: the model's own default is already
240
+ // `Center`, so writing it here would put a new key in the Y.Map of every
241
+ // artefact that does not ask for one — the same avoidable payload change
242
+ // review caught on `strokeStyle`.
243
+ ...(preset.textVerticalAlign
244
+ ? { textVerticalAlign: preset.textVerticalAlign }
245
+ : {}),
246
+ // BPMN symbols have normative sizes: a long label overflows rather
247
+ // than deforming the node. An imported artefact is the one case where that
248
+ // is not enough — its box is the FILE's and its label is the file's too, so
249
+ // the text is fitted to the box instead of painted past it (#184).
250
+ textFitMode: fit?.textFitMode ?? TextFitMode.Overflow,
251
+ // Spread, never a defaulted key, for the reason `textVerticalAlign` is: a
252
+ // drawn artefact keeps the shape's own inset and puts nothing of its own in
253
+ // the Y.Map.
254
+ ...(fit ? { padding: fit.padding } : {}),
255
+ xywh: box.xywh,
256
+ };
257
+ }
258
+ /**
259
+ * The box {@link bpmnMorphProps} hands {@link bpmnNodeProps} and then throws
260
+ * away. Never written to a document: a morph keeps the geometry the element
261
+ * already has, and this exists only because the one builder takes a box.
262
+ */
263
+ const DISCARDED_BOX = '[0,0,0,0]';
264
+ /** What a morph must never rewrite: identity, geometry, and the user's words. */
265
+ const NOT_A_MORPH = ['type', 'xywh', 'text'];
266
+ /**
267
+ * What a node's kind is worth to an element that ALREADY EXISTS — the same
268
+ * description as {@link bpmnNodeProps}, minus the three things a morph has no
269
+ * business touching.
270
+ *
271
+ * Derived from the creation builder rather than restated beside it, and that is
272
+ * the whole point of the function.
273
+ *
274
+ * ## Why a `{kind, role}` patch is not enough
275
+ *
276
+ * Because the appearance of a BPMN artefact lives in props the CREATING kind's
277
+ * preset wrote, and nothing else ever rewrites them. One shipped morph pair
278
+ * shows it today: `subProcess` and `callActivity` are the same rounded
279
+ * rectangle and differ only in `strokeWidth` — 2 against 4 — and that thick
280
+ * border IS how a reader tells "this box stands for a process defined
281
+ * elsewhere" from "this box stands for one defined inline". Morph between them
282
+ * with two keys and the call activity arrives wearing the sub-process's thin
283
+ * border, which is a drawing that says the wrong thing.
284
+ *
285
+ * Every other family declared in `./morph.ts` currently shares one preset
286
+ * across its members, so for those the full patch changes nothing — and that is
287
+ * the second reason to write it this way rather than to trim it. A family is
288
+ * DATA (`BPMN_MORPH_FAMILIES`) and grows by declaration, with no code change to
289
+ * prompt anyone to ask whether the presets still agree; deriving the patch from
290
+ * the creation builder means the answer is right in advance. It is also what
291
+ * guarantees that a morphed artefact and one drawn fresh from the palette are
292
+ * the same element — two builders would agree the day they were written and
293
+ * drift on the first restyle, which is the argument this file already makes for
294
+ * having one creation builder at all.
295
+ */
296
+ export function bpmnMorphProps(kind) {
297
+ // Widened to the plain record on the way in: `type` is required on what the
298
+ // creation builder returns, and `delete` may only take an optional key.
299
+ const props = {
300
+ ...bpmnNodeProps(kind, { xywh: DISCARDED_BOX }),
301
+ };
302
+ for (const key of NOT_A_MORPH)
303
+ delete props[key];
304
+ return props;
305
+ }
306
+ /**
307
+ * Every key ANY kind's props may carry — the union over the whole pack.
308
+ *
309
+ * Computed rather than listed, so a preset that starts spreading a second
310
+ * conditional key is covered on the day it is added rather than on the day
311
+ * somebody notices.
312
+ */
313
+ const EVERY_MORPH_KEY = new Set(Object.keys(NODE_PRESETS).flatMap(kind => Object.keys(bpmnMorphProps(kind))));
314
+ /**
315
+ * The fields to DELETE from an element after morphing it to `kind` — the keys
316
+ * some other kind writes and this one does not.
317
+ *
318
+ * A patch cannot express absence. `textVerticalAlign` is spread conditionally
319
+ * (see {@link bpmnNodeProps}), so morphing away from the group would leave
320
+ * `Top` sitting in the Y.Map and silently in force over a preset that means
321
+ * "centred". `clearField` removes the key, which is the same call `writeLanes`
322
+ * makes when a pool loses its last lane.
323
+ */
324
+ export function bpmnMorphClears(kind) {
325
+ const present = new Set(Object.keys(bpmnMorphProps(kind)));
326
+ return [...EVERY_MORPH_KEY].filter(key => !present.has(key));
327
+ }
@@ -0,0 +1,2 @@
1
+ import type { ValidationProfile } from '@formicoidea/labre-core/blocks/surface';
2
+ export declare const BPMN_PROFILES: readonly ValidationProfile[];