@formicoidea/labre-framework-bpmn 0.33.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.
package/dist/actions.js CHANGED
@@ -5,7 +5,7 @@ import { NotificationProvider, translateKey, } from '@formicoidea/labre-core/sha
5
5
  import { downloadBlob } from '@formicoidea/labre-core/shared/utils';
6
6
  import { Bound } from '@formicoidea/labre-core/global/gfx';
7
7
  import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
8
- import { ASSOCIATION_STROKE, ASSOCIATION_WIDTH, MESSAGE_STROKE, MESSAGE_WIDTH, NODE_LABEL, NODE_SIZE, SEQUENCE_STROKE, SEQUENCE_WIDTH, } from './consts.js';
8
+ import { ASSOCIATION_STROKE, ASSOCIATION_WIDTH, MESSAGE_STROKE, MESSAGE_WIDTH, NODE_LABEL, nodeLabelKey, NODE_SIZE, SEQUENCE_STROKE, SEQUENCE_WIDTH, } from './consts.js';
9
9
  import { BPMN_FORMAT_ID } from './export.js';
10
10
  import { BPMN_SVG_IMPORT, BPMN_XML_EXPORT, BPMN_XML_FORMAT, BPMN_XML_IMPORT, bpmnBoardFrom, bpmnSafeFilename, } from './interchange.js';
11
11
  import { bpmnNodeProps } from './presets.js';
@@ -37,7 +37,13 @@ export function createBpmnNode(std, kind) {
37
37
  // nothing else.
38
38
  const id = surface.addElement(bpmnNodeProps(kind, {
39
39
  xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
40
- text: NODE_LABEL[kind] || undefined,
40
+ // Translated HERE and once: the caption is document content the moment
41
+ // it lands, so the host's catalogue is asked at placement and never
42
+ // again (`nodeLabelKey`, and #183 on why a French board should not
43
+ // start in English).
44
+ text: NODE_LABEL[kind]
45
+ ? translateKey(std, nodeLabelKey(kind), NODE_LABEL[kind])
46
+ : undefined,
41
47
  }));
42
48
  finish(gfx, id);
43
49
  }
@@ -0,0 +1,18 @@
1
+ import type { ShortcutManifestEntry } from '@formicoidea/labre-core/std';
2
+ /**
3
+ * The BPMN commands as SHORTCUT-MANIFEST rows — id, label, chord, scope,
4
+ * owner — and nothing else.
5
+ *
6
+ * DATA ONLY, and that is the whole point (`docs/adr/0008` § Packaging). A
7
+ * `CommandDescriptor` carries its `run`, so a host settings pane that imports
8
+ * the package entry to list names and chords drags the entire action graph —
9
+ * the import/export machinery, the surface and gfx deep paths — into its
10
+ * chunk. This module has type-only imports, so the published bundle exposes it
11
+ * as `./commands-manifest`: a few hundred bytes that reference nothing.
12
+ *
13
+ * GENERATED-SHAPED, hand-committed: `commands-manifest.unit.spec.ts` asserts
14
+ * row-for-row equality with `toShortcutManifestEntry` over {@link bpmnCommands},
15
+ * so this file cannot drift from the commands it projects. Add a command and
16
+ * the test says exactly what to add here.
17
+ */
18
+ export declare const bpmnCommandsManifest: ShortcutManifestEntry[];
@@ -0,0 +1,226 @@
1
+ /**
2
+ * The BPMN commands as SHORTCUT-MANIFEST rows — id, label, chord, scope,
3
+ * owner — and nothing else.
4
+ *
5
+ * DATA ONLY, and that is the whole point (`docs/adr/0008` § Packaging). A
6
+ * `CommandDescriptor` carries its `run`, so a host settings pane that imports
7
+ * the package entry to list names and chords drags the entire action graph —
8
+ * the import/export machinery, the surface and gfx deep paths — into its
9
+ * chunk. This module has type-only imports, so the published bundle exposes it
10
+ * as `./commands-manifest`: a few hundred bytes that reference nothing.
11
+ *
12
+ * GENERATED-SHAPED, hand-committed: `commands-manifest.unit.spec.ts` asserts
13
+ * row-for-row equality with `toShortcutManifestEntry` over {@link bpmnCommands},
14
+ * so this file cannot drift from the commands it projects. Add a command and
15
+ * the test says exactly what to add here.
16
+ */
17
+ export const bpmnCommandsManifest = [
18
+ {
19
+ id: 'bpmn.addStartEvent',
20
+ owner: 'bpmn',
21
+ labelKey: 'com.labre.commands.bpmn.addStartEvent',
22
+ labelFallback: 'Start event',
23
+ scope: 'edgeless',
24
+ defaultKeys: { mac: [], other: [] },
25
+ },
26
+ {
27
+ id: 'bpmn.addEndEvent',
28
+ owner: 'bpmn',
29
+ labelKey: 'com.labre.commands.bpmn.addEndEvent',
30
+ labelFallback: 'End event',
31
+ scope: 'edgeless',
32
+ defaultKeys: { mac: [], other: [] },
33
+ },
34
+ {
35
+ id: 'bpmn.addTask',
36
+ owner: 'bpmn',
37
+ labelKey: 'com.labre.commands.bpmn.addTask',
38
+ labelFallback: 'Task',
39
+ scope: 'edgeless',
40
+ defaultKeys: { mac: [], other: [] },
41
+ },
42
+ {
43
+ id: 'bpmn.addExclusiveGateway',
44
+ owner: 'bpmn',
45
+ labelKey: 'com.labre.commands.bpmn.addExclusiveGateway',
46
+ labelFallback: 'Exclusive gateway',
47
+ scope: 'edgeless',
48
+ defaultKeys: { mac: [], other: [] },
49
+ },
50
+ {
51
+ id: 'bpmn.sequenceFlowTool',
52
+ owner: 'bpmn',
53
+ labelKey: 'com.labre.commands.bpmn.sequenceFlowTool',
54
+ labelFallback: 'Sequence flow',
55
+ scope: 'edgeless',
56
+ defaultKeys: { mac: [], other: [] },
57
+ },
58
+ {
59
+ id: 'bpmn.addPool',
60
+ owner: 'bpmn',
61
+ labelKey: 'com.labre.commands.bpmn.addPool',
62
+ labelFallback: 'Pool',
63
+ scope: 'edgeless',
64
+ defaultKeys: { mac: [], other: [] },
65
+ },
66
+ {
67
+ id: 'bpmn.messageFlowTool',
68
+ owner: 'bpmn',
69
+ labelKey: 'com.labre.commands.bpmn.messageFlowTool',
70
+ labelFallback: 'Message flow',
71
+ scope: 'edgeless',
72
+ defaultKeys: { mac: [], other: [] },
73
+ },
74
+ {
75
+ id: 'bpmn.addUserTask',
76
+ owner: 'bpmn',
77
+ labelKey: 'com.labre.commands.bpmn.addUserTask',
78
+ labelFallback: 'User task',
79
+ scope: 'edgeless',
80
+ defaultKeys: { mac: [], other: [] },
81
+ },
82
+ {
83
+ id: 'bpmn.addServiceTask',
84
+ owner: 'bpmn',
85
+ labelKey: 'com.labre.commands.bpmn.addServiceTask',
86
+ labelFallback: 'Service task',
87
+ scope: 'edgeless',
88
+ defaultKeys: { mac: [], other: [] },
89
+ },
90
+ {
91
+ id: 'bpmn.addSubProcess',
92
+ owner: 'bpmn',
93
+ labelKey: 'com.labre.commands.bpmn.addSubProcess',
94
+ labelFallback: 'Sub-process',
95
+ scope: 'edgeless',
96
+ defaultKeys: { mac: [], other: [] },
97
+ },
98
+ {
99
+ id: 'bpmn.addCallActivity',
100
+ owner: 'bpmn',
101
+ labelKey: 'com.labre.commands.bpmn.addCallActivity',
102
+ labelFallback: 'Call activity',
103
+ scope: 'edgeless',
104
+ defaultKeys: { mac: [], other: [] },
105
+ },
106
+ {
107
+ id: 'bpmn.addParallelGateway',
108
+ owner: 'bpmn',
109
+ labelKey: 'com.labre.commands.bpmn.addParallelGateway',
110
+ labelFallback: 'Parallel gateway',
111
+ scope: 'edgeless',
112
+ defaultKeys: { mac: [], other: [] },
113
+ },
114
+ {
115
+ id: 'bpmn.addMessageStartEvent',
116
+ owner: 'bpmn',
117
+ labelKey: 'com.labre.commands.bpmn.addMessageStartEvent',
118
+ labelFallback: 'Message start event',
119
+ scope: 'edgeless',
120
+ defaultKeys: { mac: [], other: [] },
121
+ },
122
+ {
123
+ id: 'bpmn.addTimerStartEvent',
124
+ owner: 'bpmn',
125
+ labelKey: 'com.labre.commands.bpmn.addTimerStartEvent',
126
+ labelFallback: 'Timer start event',
127
+ scope: 'edgeless',
128
+ defaultKeys: { mac: [], other: [] },
129
+ },
130
+ {
131
+ id: 'bpmn.addMessageEndEvent',
132
+ owner: 'bpmn',
133
+ labelKey: 'com.labre.commands.bpmn.addMessageEndEvent',
134
+ labelFallback: 'Message end event',
135
+ scope: 'edgeless',
136
+ defaultKeys: { mac: [], other: [] },
137
+ },
138
+ {
139
+ id: 'bpmn.addTerminateEndEvent',
140
+ owner: 'bpmn',
141
+ labelKey: 'com.labre.commands.bpmn.addTerminateEndEvent',
142
+ labelFallback: 'Terminate end event',
143
+ scope: 'edgeless',
144
+ defaultKeys: { mac: [], other: [] },
145
+ },
146
+ {
147
+ id: 'bpmn.associationTool',
148
+ owner: 'bpmn',
149
+ labelKey: 'com.labre.commands.bpmn.associationTool',
150
+ labelFallback: 'Association',
151
+ scope: 'edgeless',
152
+ defaultKeys: { mac: [], other: [] },
153
+ },
154
+ {
155
+ id: 'bpmn.addDataObject',
156
+ owner: 'bpmn',
157
+ labelKey: 'com.labre.commands.bpmn.addDataObject',
158
+ labelFallback: 'Data object',
159
+ scope: 'edgeless',
160
+ defaultKeys: { mac: [], other: [] },
161
+ },
162
+ {
163
+ id: 'bpmn.addDataStore',
164
+ owner: 'bpmn',
165
+ labelKey: 'com.labre.commands.bpmn.addDataStore',
166
+ labelFallback: 'Data store',
167
+ scope: 'edgeless',
168
+ defaultKeys: { mac: [], other: [] },
169
+ },
170
+ {
171
+ id: 'bpmn.addTextAnnotation',
172
+ owner: 'bpmn',
173
+ labelKey: 'com.labre.commands.bpmn.addTextAnnotation',
174
+ labelFallback: 'Text annotation',
175
+ scope: 'edgeless',
176
+ defaultKeys: { mac: [], other: [] },
177
+ },
178
+ {
179
+ id: 'bpmn.addGroup',
180
+ owner: 'bpmn',
181
+ labelKey: 'com.labre.commands.bpmn.addGroup',
182
+ labelFallback: 'Group',
183
+ scope: 'edgeless',
184
+ defaultKeys: { mac: [], other: [] },
185
+ },
186
+ {
187
+ id: 'bpmn.addLane',
188
+ owner: 'bpmn',
189
+ labelKey: 'com.labre.commands.bpmn.addLane',
190
+ labelFallback: 'Add lane',
191
+ scope: 'edgeless',
192
+ defaultKeys: { mac: [], other: [] },
193
+ },
194
+ {
195
+ id: 'bpmn.removeLane',
196
+ owner: 'bpmn',
197
+ labelKey: 'com.labre.commands.bpmn.removeLane',
198
+ labelFallback: 'Remove lane',
199
+ scope: 'edgeless',
200
+ defaultKeys: { mac: [], other: [] },
201
+ },
202
+ {
203
+ id: 'bpmn.exportXml',
204
+ owner: 'bpmn',
205
+ labelKey: 'com.labre.commands.bpmn.exportXml',
206
+ labelFallback: 'Export BPMN XML',
207
+ scope: 'edgeless',
208
+ defaultKeys: { mac: [], other: [] },
209
+ },
210
+ {
211
+ id: 'bpmn.importXml',
212
+ owner: 'bpmn',
213
+ labelKey: 'com.labre.commands.bpmn.importXml',
214
+ labelFallback: 'Import BPMN XML',
215
+ scope: 'edgeless',
216
+ defaultKeys: { mac: [], other: [] },
217
+ },
218
+ {
219
+ id: 'bpmn.importSvg',
220
+ owner: 'bpmn',
221
+ labelKey: 'com.labre.commands.bpmn.importSvg',
222
+ labelFallback: 'Import SVG sketch',
223
+ scope: 'edgeless',
224
+ defaultKeys: { mac: [], other: [] },
225
+ },
226
+ ];
package/dist/consts.d.ts CHANGED
@@ -45,6 +45,38 @@ export declare const GROUP_RADIUS = 20;
45
45
  export declare const GROUP_STROKE = "#8e8d91";
46
46
  /** Inner-text font for the task label. */
47
47
  export declare const INNER_FONT_SIZE = 18;
48
+ /**
49
+ * The smallest font a FITTED label is asked for — the importer's floor, and
50
+ * only its (`bpmnLabelFit` in `presets.ts`).
51
+ *
52
+ * A file draws its artefacts at whatever scale its author's tool uses, and
53
+ * bpmn.io's normative sizes are about six tenths of this pack's: a 36-unit
54
+ * event against a 56-unit one, a 100×72 task against a 120×72. The label of an
55
+ * imported artefact is therefore asked for at a size proportional to the box
56
+ * the FILE gave, and 10 is where that shrinking stops — under it a label is no
57
+ * longer read, it is guessed at.
58
+ *
59
+ * It is a floor on what is ASKED for, not on what is painted: the shape
60
+ * renderer's own `TextFitMode.Contained` pass shrinks further, to its own floor
61
+ * of 8 (`MIN_CONTAINED_FONT_SIZE`), when even this does not fit. Kept here as a
62
+ * number of our own rather than imported from `@labre/affine-gfx-shape`, so the
63
+ * reader stays a pure function of a string with no renderer behind it (ADR
64
+ * 0012, P3).
65
+ */
66
+ export declare const LABEL_MIN_FONT_SIZE = 10;
67
+ /**
68
+ * How much of a box a fitted label gives up to its margin, per side.
69
+ *
70
+ * A shape's native inset is a FIXED 20 units horizontally
71
+ * (`SHAPE_TEXT_PADDING`), which is a sixth of this pack's 120-unit task and
72
+ * therefore invisible on a drawn board — and more than the whole width of a
73
+ * 36-unit event, which leaves NEGATIVE room for the text and is why an imported
74
+ * label breaks in the middle of a word. Expressed as a ratio, the margin
75
+ * follows the artefact down: it is exactly the native inset at the pack's own
76
+ * task width and it never grows past it, so a drawn node is untouched and a
77
+ * small one keeps a usable line.
78
+ */
79
+ export declare const LABEL_INSET_RATIO: number;
48
80
  /**
49
81
  * Default node sizes (model units) per kind.
50
82
  *
@@ -83,13 +115,25 @@ export declare const NODE_SIZE: Record<BpmnNodeKind, {
83
115
  * go inside it, and inside is where the folded page and the cylinder already
84
116
  * are. The user can still type — the text simply overflows, which is the
85
117
  * honest failure rather than a label painted over the glyph.
118
+ *
119
+ * The caption is asked of the host's catalogue under {@link nodeLabelKey} when
120
+ * the artefact is placed, with the entry below as the English default.
86
121
  */
87
122
  export declare const NODE_LABEL: Record<BpmnNodeKind, string>;
123
+ /**
124
+ * The i18n key {@link NODE_LABEL} is the English default of.
125
+ *
126
+ * Resolved AT PLACEMENT (`createBpmnNode`) and never afterwards: what a gesture
127
+ * writes into the document is content the author owns from that moment on, and
128
+ * a renderer that re-translated it on every paint would silently overwrite a
129
+ * name somebody typed. The kinds with an empty label ask for nothing — there is
130
+ * no word to translate, and BPMN puts an event's name outside its glyph anyway.
131
+ */
132
+ export declare const nodeLabelKey: (kind: BpmnNodeKind) => string;
88
133
  /**
89
134
  * Pool (background container) defaults — read by the `BPMN_POOL_BACKGROUND`
90
135
  * declaration (`background.ts`), which is the only thing that draws a pool.
91
136
  */
92
- export declare const POOL_BAND_WIDTH = 28;
93
137
  export declare const POOL_FRAME_COLOR = "#262626";
94
138
  /**
95
139
  * The card. The same white every framework background paints — it is what
@@ -113,20 +157,13 @@ export declare const POOL_FONT_FAMILY = "Inter, sans-serif";
113
157
  */
114
158
  export declare const POOL_LANE_NAME_FONT_SIZE = 13;
115
159
  /**
116
- * Width of a lane's own title band, in model units the strip immediately
117
- * inside the participant band, with the lane name turned on its side.
118
- *
119
- * Four units narrower than {@link POOL_BAND_WIDTH}, and for the same reason the
120
- * font is two points smaller: the two strips sit side by side, so the
121
- * subordinate one has to say so. Identical widths read as a single 56-unit
122
- * gutter rather than as a participant containing lanes, which is exactly the
123
- * relationship the picture has to carry.
124
- *
125
- * NO fill, divider only — what bpmn.io, Camunda and Visio all draw. A second
126
- * grey strip beside the pool's own would double the furniture and leave the
127
- * flow area looking inset by two margins.
160
+ * The two band widths live in `@labre/affine-model`, beside the pool model that
161
+ * hit-tests against them (a pool is clickable by its title bands as well as by
162
+ * its border — the bpmn.io convention, issue #194). They are re-exported here
163
+ * so this file stays the one place a reader looks for a pool's metrics, and so
164
+ * the declaration keeps reading them from where it always did.
128
165
  */
129
- export declare const POOL_LANE_BAND_WIDTH = 24;
166
+ export { POOL_BAND_WIDTH, POOL_LANE_BAND_WIDTH } from '@formicoidea/labre-core/model';
130
167
  /**
131
168
  * How close to an internal lane boundary a pointer has to be, in MODEL units,
132
169
  * for the gesture to be a separator drag rather than a click on the pool.
package/dist/consts.js CHANGED
@@ -44,6 +44,38 @@ export const GROUP_RADIUS = 20;
44
44
  export const GROUP_STROKE = '#8e8d91';
45
45
  /** Inner-text font for the task label. */
46
46
  export const INNER_FONT_SIZE = 18;
47
+ /**
48
+ * The smallest font a FITTED label is asked for — the importer's floor, and
49
+ * only its (`bpmnLabelFit` in `presets.ts`).
50
+ *
51
+ * A file draws its artefacts at whatever scale its author's tool uses, and
52
+ * bpmn.io's normative sizes are about six tenths of this pack's: a 36-unit
53
+ * event against a 56-unit one, a 100×72 task against a 120×72. The label of an
54
+ * imported artefact is therefore asked for at a size proportional to the box
55
+ * the FILE gave, and 10 is where that shrinking stops — under it a label is no
56
+ * longer read, it is guessed at.
57
+ *
58
+ * It is a floor on what is ASKED for, not on what is painted: the shape
59
+ * renderer's own `TextFitMode.Contained` pass shrinks further, to its own floor
60
+ * of 8 (`MIN_CONTAINED_FONT_SIZE`), when even this does not fit. Kept here as a
61
+ * number of our own rather than imported from `@labre/affine-gfx-shape`, so the
62
+ * reader stays a pure function of a string with no renderer behind it (ADR
63
+ * 0012, P3).
64
+ */
65
+ export const LABEL_MIN_FONT_SIZE = 10;
66
+ /**
67
+ * How much of a box a fitted label gives up to its margin, per side.
68
+ *
69
+ * A shape's native inset is a FIXED 20 units horizontally
70
+ * (`SHAPE_TEXT_PADDING`), which is a sixth of this pack's 120-unit task and
71
+ * therefore invisible on a drawn board — and more than the whole width of a
72
+ * 36-unit event, which leaves NEGATIVE room for the text and is why an imported
73
+ * label breaks in the middle of a word. Expressed as a ratio, the margin
74
+ * follows the artefact down: it is exactly the native inset at the pack's own
75
+ * task width and it never grows past it, so a drawn node is untouched and a
76
+ * small one keeps a usable line.
77
+ */
78
+ export const LABEL_INSET_RATIO = 1 / 6;
47
79
  /**
48
80
  * Default node sizes (model units) per kind.
49
81
  *
@@ -97,6 +129,9 @@ export const NODE_SIZE = {
97
129
  * go inside it, and inside is where the folded page and the cylinder already
98
130
  * are. The user can still type — the text simply overflows, which is the
99
131
  * honest failure rather than a label painted over the glyph.
132
+ *
133
+ * The caption is asked of the host's catalogue under {@link nodeLabelKey} when
134
+ * the artefact is placed, with the entry below as the English default.
100
135
  */
101
136
  export const NODE_LABEL = {
102
137
  startEvent: '',
@@ -121,11 +156,20 @@ export const NODE_LABEL = {
121
156
  // centred so it does not float over whatever the group encloses.
122
157
  group: 'Group',
123
158
  };
159
+ /**
160
+ * The i18n key {@link NODE_LABEL} is the English default of.
161
+ *
162
+ * Resolved AT PLACEMENT (`createBpmnNode`) and never afterwards: what a gesture
163
+ * writes into the document is content the author owns from that moment on, and
164
+ * a renderer that re-translated it on every paint would silently overwrite a
165
+ * name somebody typed. The kinds with an empty label ask for nothing — there is
166
+ * no word to translate, and BPMN puts an event's name outside its glyph anyway.
167
+ */
168
+ export const nodeLabelKey = (kind) => `com.labre.bpmn.seed.${kind}`;
124
169
  /**
125
170
  * Pool (background container) defaults — read by the `BPMN_POOL_BACKGROUND`
126
171
  * declaration (`background.ts`), which is the only thing that draws a pool.
127
172
  */
128
- export const POOL_BAND_WIDTH = 28;
129
173
  export const POOL_FRAME_COLOR = '#262626';
130
174
  /**
131
175
  * The card. The same white every framework background paints — it is what
@@ -149,20 +193,13 @@ export const POOL_FONT_FAMILY = 'Inter, sans-serif';
149
193
  */
150
194
  export const POOL_LANE_NAME_FONT_SIZE = 13;
151
195
  /**
152
- * Width of a lane's own title band, in model units the strip immediately
153
- * inside the participant band, with the lane name turned on its side.
154
- *
155
- * Four units narrower than {@link POOL_BAND_WIDTH}, and for the same reason the
156
- * font is two points smaller: the two strips sit side by side, so the
157
- * subordinate one has to say so. Identical widths read as a single 56-unit
158
- * gutter rather than as a participant containing lanes, which is exactly the
159
- * relationship the picture has to carry.
160
- *
161
- * NO fill, divider only — what bpmn.io, Camunda and Visio all draw. A second
162
- * grey strip beside the pool's own would double the furniture and leave the
163
- * flow area looking inset by two margins.
196
+ * The two band widths live in `@labre/affine-model`, beside the pool model that
197
+ * hit-tests against them (a pool is clickable by its title bands as well as by
198
+ * its border — the bpmn.io convention, issue #194). They are re-exported here
199
+ * so this file stays the one place a reader looks for a pool's metrics, and so
200
+ * the declaration keeps reading them from where it always did.
164
201
  */
165
- export const POOL_LANE_BAND_WIDTH = 24;
202
+ export { POOL_BAND_WIDTH, POOL_LANE_BAND_WIDTH } from '@formicoidea/labre-core/model';
166
203
  /**
167
204
  * How close to an internal lane boundary a pointer has to be, in MODEL units,
168
205
  * for the gesture to be a separator drag rather than a click on the pool.
@@ -1,4 +1,6 @@
1
1
  import type { BpmnPoolElementModel } from '@formicoidea/labre-core/model';
2
+ import type { EditorHost } from '@formicoidea/labre-core/std';
3
+ import type { PointTestOptions } from '@formicoidea/labre-core/std/gfx';
2
4
  import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
3
5
  /**
4
6
  * View for a BPMN pool. Three direct gestures live here:
@@ -80,6 +82,23 @@ export declare class BpmnPoolView extends GfxElementModelView<BpmnPoolElementMod
80
82
  /** Which name this point aims at, if any — lane strip first, see `pool-hit`. */
81
83
  private _targetAt;
82
84
  private get _editable();
85
+ /**
86
+ * A pool is SELECTED by its border and its painted title bands
87
+ * (`BpmnPoolElementModel.includesPoint`) — but two of this view's gestures
88
+ * live elsewhere on it and must still receive their pointer events:
89
+ *
90
+ * - the internal LANE SEPARATORS, twelve-unit strips in the middle of the
91
+ * flow area, which arm the drag that moves them;
92
+ * - the zoom-GROWN rename targets, which reach past the painted bands when
93
+ * the board is small on screen so a fingertip can still land on one.
94
+ *
95
+ * Neither is a selection zone — a click in the flow area still goes to
96
+ * whatever the user dropped there, because picking asks the MODEL. This
97
+ * override only decides where the pool's own gestures are heard, which is why
98
+ * it is declared here, beside the code that draws and handles them, rather
99
+ * than in `affine-model` where none of this geometry is reachable.
100
+ */
101
+ includesPoint(x: number, y: number, options: PointTestOptions, host: EditorHost): boolean;
83
102
  /**
84
103
  * One pass over the pointer, deciding both the cursor and whether a
85
104
  * separator drag is armed.
@@ -113,6 +113,31 @@ export class BpmnPoolView extends GfxElementModelView {
113
113
  get _editable() {
114
114
  return !this.gfx.std.store.readonly && !this.model.isLocked();
115
115
  }
116
+ /**
117
+ * A pool is SELECTED by its border and its painted title bands
118
+ * (`BpmnPoolElementModel.includesPoint`) — but two of this view's gestures
119
+ * live elsewhere on it and must still receive their pointer events:
120
+ *
121
+ * - the internal LANE SEPARATORS, twelve-unit strips in the middle of the
122
+ * flow area, which arm the drag that moves them;
123
+ * - the zoom-GROWN rename targets, which reach past the painted bands when
124
+ * the board is small on screen so a fingertip can still land on one.
125
+ *
126
+ * Neither is a selection zone — a click in the flow area still goes to
127
+ * whatever the user dropped there, because picking asks the MODEL. This
128
+ * override only decides where the pool's own gestures are heard, which is why
129
+ * it is declared here, beside the code that draws and handles them, rather
130
+ * than in `affine-model` where none of this geometry is reachable.
131
+ */
132
+ includesPoint(x, y, options, host) {
133
+ if (super.includesPoint(x, y, options, host))
134
+ return true;
135
+ // Element-local by subtraction, like every other box in this view — see the
136
+ // rotation reserve in the class comment.
137
+ const [ex, ey] = this.model.deserializedXYWH;
138
+ const local = [x - ex, y - ey];
139
+ return this._boundaryAt(local) !== null || this._targetAt(local) !== null;
140
+ }
116
141
  /* ── Hover: what this point would do ───────────────────────────────── */
117
142
  /**
118
143
  * One pass over the pointer, deciding both the cursor and whether a
package/dist/import.d.ts CHANGED
@@ -1,6 +1,31 @@
1
1
  import type { InterchangeImportContext, InterchangeImportResult } from '@formicoidea/labre-core/blocks/surface';
2
2
  import type { BpmnNodeKind } from '@formicoidea/labre-core/model';
3
3
  import { BPMN_SCOPE } from './export.js';
4
+ /**
5
+ * The remarks whose wording is FIXED, as `[key, English]` pairs.
6
+ *
7
+ * A reader is a pure function of text (`docs/adr/0012`, P3) and has no `std`,
8
+ * so it cannot ask the host's catalogue for anything: it declares the key on
9
+ * the note and `reportInterchangeImport` resolves it when it draws the report
10
+ * (`InterchangeNote.messageKey`). The English string stays here and stays the
11
+ * fallback, so the console table and a playground with no catalogue read
12
+ * exactly what they read before.
13
+ *
14
+ * These three and no others, and the line is not arbitrary: every remaining
15
+ * remark NAMES something out of the file — `<boundaryEvent>`, an id, a count of
16
+ * lanes — and the seam has neither interpolation nor pluralisation. A key for
17
+ * one of those would be a sentence with holes in it that a translator cannot
18
+ * see the shape of, which is the same refusal the interchange count labels
19
+ * already make (`interchange-import.ts`).
20
+ *
21
+ * They are contributed to the manifest by `./translations.ts`, with the
22
+ * framework, because they ship in the BPMN bundle rather than in core.
23
+ */
24
+ export declare const BPMN_IMPORT_REMARKS: {
25
+ readonly inventedPool: readonly ["com.labre.bpmn.import.remark.invented-pool", "This file names no participant, so its process was drawn in a pool of Labre's own. The pool is not the file's: exporting writes the process back without one."];
26
+ readonly laneGap: readonly ["com.labre.bpmn.import.remark.lane-gap", "The lanes of this pool are drawn with a gap or an overlap between them. Labre lays its bands end to end, so their heights were kept in proportion and the space between them was closed."];
27
+ readonly mustUnderstand: readonly ["com.labre.bpmn.import.remark.must-understand", "The file declares an extension that it says MUST be understood to read the model correctly. Labre does not understand it: the import went ahead, and this reading of the process may be wrong."];
28
+ };
4
29
  /**
5
30
  * `startEvent` + `messageEventDefinition` → `startEventMessage`, and the
6
31
  * sixteen other answers.
package/dist/import.js CHANGED
@@ -4,6 +4,40 @@ import { ASSOCIATION_STROKE, ASSOCIATION_WIDTH, MESSAGE_STROKE, MESSAGE_WIDTH, N
4
4
  import { BPMN_FORMAT_ID, BPMN_NS, BPMN_OWN_DECLARATIONS, BPMN_SCOPE, BPMN_XML_OF_KIND, escapeAttr, escapeText, } from './export.js';
5
5
  import { bpmnNodeProps } from './presets.js';
6
6
  import { BPMN_ROLE } from './roles.js';
7
+ /**
8
+ * The remarks whose wording is FIXED, as `[key, English]` pairs.
9
+ *
10
+ * A reader is a pure function of text (`docs/adr/0012`, P3) and has no `std`,
11
+ * so it cannot ask the host's catalogue for anything: it declares the key on
12
+ * the note and `reportInterchangeImport` resolves it when it draws the report
13
+ * (`InterchangeNote.messageKey`). The English string stays here and stays the
14
+ * fallback, so the console table and a playground with no catalogue read
15
+ * exactly what they read before.
16
+ *
17
+ * These three and no others, and the line is not arbitrary: every remaining
18
+ * remark NAMES something out of the file — `<boundaryEvent>`, an id, a count of
19
+ * lanes — and the seam has neither interpolation nor pluralisation. A key for
20
+ * one of those would be a sentence with holes in it that a translator cannot
21
+ * see the shape of, which is the same refusal the interchange count labels
22
+ * already make (`interchange-import.ts`).
23
+ *
24
+ * They are contributed to the manifest by `./translations.ts`, with the
25
+ * framework, because they ship in the BPMN bundle rather than in core.
26
+ */
27
+ export const BPMN_IMPORT_REMARKS = {
28
+ inventedPool: [
29
+ 'com.labre.bpmn.import.remark.invented-pool',
30
+ "This file names no participant, so its process was drawn in a pool of Labre's own. The pool is not the file's: exporting writes the process back without one.",
31
+ ],
32
+ laneGap: [
33
+ 'com.labre.bpmn.import.remark.lane-gap',
34
+ 'The lanes of this pool are drawn with a gap or an overlap between them. Labre lays its bands end to end, so their heights were kept in proportion and the space between them was closed.',
35
+ ],
36
+ mustUnderstand: [
37
+ 'com.labre.bpmn.import.remark.must-understand',
38
+ 'The file declares an extension that it says MUST be understood to read the model correctly. Labre does not understand it: the import went ahead, and this reading of the process may be wrong.',
39
+ ],
40
+ };
7
41
  /**
8
42
  * A BPMN 2.0 interchange document, read as a board — the inverse of `export.ts`
9
43
  * on the vocabulary Labre draws, and an honest accounting of everything else
@@ -814,9 +848,8 @@ export function importBpmnXml(source, context = {}) {
814
848
  kind: 'invented-layout',
815
849
  sourceId,
816
850
  element: 'process',
817
- message: `This file names no participant, so its process was drawn in a pool of ` +
818
- `Labre's own. The pool is not the file's: exporting writes the process ` +
819
- `back without one.`,
851
+ messageKey: BPMN_IMPORT_REMARKS.inventedPool[0],
852
+ message: BPMN_IMPORT_REMARKS.inventedPool[1],
820
853
  });
821
854
  }
822
855
  /**
@@ -945,9 +978,8 @@ export function importBpmnXml(source, context = {}) {
945
978
  kind: 'invented-layout',
946
979
  sourceId: attrOf(laneSet, 'id'),
947
980
  element: 'laneSet',
948
- message: `The lanes of this pool are drawn with a gap or an overlap between ` +
949
- `them. Labre lays its bands end to end, so their heights were kept ` +
950
- `in proportion and the space between them was closed.`,
981
+ messageKey: BPMN_IMPORT_REMARKS.laneGap[0],
982
+ message: BPMN_IMPORT_REMARKS.laneGap[1],
951
983
  });
952
984
  }
953
985
  }
@@ -1102,6 +1134,11 @@ export function importBpmnXml(source, context = {}) {
1102
1134
  ? new Bound(bounds.x, bounds.y, bounds.w, bounds.h)
1103
1135
  : new Bound(0, 0, size.w, size.h)).serialize(),
1104
1136
  text: text || undefined,
1137
+ // The file's own box, which this reader may not rewrite — so the label
1138
+ // is fitted to it rather than painted past it (#184). A shape the file
1139
+ // did not draw is laid out at the pack's own size below, and wants the
1140
+ // pack's own type: no fit, and an element identical to a drawn one.
1141
+ ...(bounds ? { fitLabel: true } : {}),
1105
1142
  }),
1106
1143
  payload,
1107
1144
  order: shape?.index ?? Number.POSITIVE_INFINITY,
@@ -1314,9 +1351,8 @@ export function importBpmnXml(source, context = {}) {
1314
1351
  note({
1315
1352
  kind: 'warning',
1316
1353
  element: 'extension',
1317
- message: `The file declares an extension that it says MUST be understood to ` +
1318
- `read the model correctly. Labre does not understand it: the import ` +
1319
- `went ahead, and this reading of the process may be wrong.`,
1354
+ messageKey: BPMN_IMPORT_REMARKS.mustUnderstand[0],
1355
+ message: BPMN_IMPORT_REMARKS.mustUnderstand[1],
1320
1356
  });
1321
1357
  }
1322
1358
  carryChild(host.payload, BPMN_SCOPE.definitions, root, attrOf(definitions, 'id'));
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export { materializeBpmnImport, reportBpmnImport } from './actions.js';
4
4
  export { bpmnCommandIcons, bpmnCommands } from './commands.js';
5
5
  export { BPMN_FORMAT_ID, BPMN_NS, BPMN_XML_OF_KIND, type BpmnExportBoard, type BpmnExportOptions, type BpmnExportOutcome, type BpmnXmlMapping, exportBpmnXml, exportBpmnXmlWithWarnings, isNcName, toNcName, } from './export.js';
6
6
  export { BPMN_KIND_OF_XML, BPMN_QUARANTINE_REASON, importBpmnXml, } from './import.js';
7
- export { bpmnMorphClears, bpmnMorphProps, bpmnNodeProps, type BpmnNodePreset, NODE_PRESETS, } from './presets.js';
7
+ export { bpmnLabelFit, type BpmnLabelFit, bpmnMorphClears, bpmnMorphProps, bpmnNodeProps, type BpmnNodePreset, NODE_PRESETS, } from './presets.js';
8
8
  export { BPMN_MORPH_FAMILIES, BPMN_MORPH_SPEC } from './morph.js';
9
9
  export { BPMN_INTERCHANGE, BPMN_SVG_FORMAT, BPMN_SVG_IMPORT, BPMN_XML_EXPORT, BPMN_XML_EXTENSION, BPMN_XML_IMPORT, BPMN_XML_FORMAT, BPMN_XML_MIME, bpmnBoardFrom, bpmnSafeFilename, } from './interchange.js';
10
10
  export { bpmnLaneOf, bpmnPoolOf } from './facts.js';
package/dist/index.js CHANGED
@@ -26,7 +26,10 @@ export { BPMN_KIND_OF_XML, BPMN_QUARANTINE_REASON, importBpmnXml, } from './impo
26
26
  // What an artefact is BORN as, shared by the palette and the importer so a task
27
27
  // read out of a file and a task drawn by hand are one element in the document —
28
28
  // and, since the morph, by the toolbar that says one artefact more precisely.
29
- export { bpmnMorphClears, bpmnMorphProps, bpmnNodeProps, NODE_PRESETS, } from './presets.js';
29
+ // `bpmnLabelFit` rides along with it: a host that materializes artefacts from a
30
+ // geometry of its own — a file, a generator, another editor's board — fits their
31
+ // labels the way the importer does, rather than reinventing the arithmetic.
32
+ export { bpmnLabelFit, bpmnMorphClears, bpmnMorphProps, bpmnNodeProps, NODE_PRESETS, } from './presets.js';
30
33
  // What an artefact may BECOME: the declared families, and the spec the generic
31
34
  // morph module is registered with. Data a host can read without an editor.
32
35
  export { BPMN_MORPH_FAMILIES, BPMN_MORPH_SPEC } from './morph.js';
package/dist/presets.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { BpmnNodeKind } from '@formicoidea/labre-core/model';
2
- import { StrokeStyle, TextAlign, TextVerticalAlign } from '@formicoidea/labre-core/model';
2
+ import { StrokeStyle, TextAlign, TextFitMode, TextVerticalAlign } from '@formicoidea/labre-core/model';
3
3
  /**
4
4
  * What a BPMN artefact is BORN as — the one description of a node's props, read
5
5
  * by every site that creates one.
@@ -52,6 +52,51 @@ export interface BpmnNodePreset {
52
52
  }
53
53
  /** Per-kind native shape + accent presets (style C). */
54
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;
55
100
  /**
56
101
  * One BPMN node, as the props `surface.addElement` takes.
57
102
  *
@@ -63,10 +108,19 @@ export declare const NODE_PRESETS: Record<BpmnNodeKind, BpmnNodePreset>;
63
108
  * label the source left empty gets no `text` key at all rather than an empty
64
109
  * one, which is what keeps an imported node byte-comparable with a drawn one
65
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.
66
119
  */
67
120
  export declare function bpmnNodeProps(kind: BpmnNodeKind, box: {
68
121
  xywh: string;
69
122
  text?: string;
123
+ fitLabel?: boolean;
70
124
  }): Record<string, unknown> & {
71
125
  type: string;
72
126
  };
package/dist/presets.js CHANGED
@@ -1,5 +1,6 @@
1
- import { FontFamily, ShapeStyle, StrokeStyle, TextAlign, TextFitMode, TextVerticalAlign, } from '@formicoidea/labre-core/model';
2
- import { CALL_ACTIVITY_WIDTH, END_WIDTH, EVENT_END, EVENT_START, GROUP_RADIUS, GROUP_STROKE, INNER_FONT_SIZE, NEUTRAL_STROKE, NODE_FILL, NODE_STROKE_WIDTH, START_WIDTH, TASK_RADIUS, } from './consts.js';
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';
3
4
  import { BPMN_ROLE_OF_KIND } from './roles.js';
4
5
  /** Per-kind native shape + accent presets (style C). */
5
6
  export const NODE_PRESETS = {
@@ -105,6 +106,81 @@ export const NODE_PRESETS = {
105
106
  textVerticalAlign: TextVerticalAlign.Top,
106
107
  },
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
+ }
108
184
  /**
109
185
  * One BPMN node, as the props `surface.addElement` takes.
110
186
  *
@@ -116,9 +192,22 @@ export const NODE_PRESETS = {
116
192
  * label the source left empty gets no `text` key at all rather than an empty
117
193
  * one, which is what keeps an imported node byte-comparable with a drawn one
118
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.
119
203
  */
120
204
  export function bpmnNodeProps(kind, box) {
121
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;
122
211
  return {
123
212
  type: 'bpmnNode',
124
213
  kind,
@@ -145,7 +234,7 @@ export function bpmnNodeProps(kind, box) {
145
234
  text: box.text,
146
235
  color: NEUTRAL_STROKE,
147
236
  fontFamily: FontFamily.Inter,
148
- fontSize: INNER_FONT_SIZE,
237
+ fontSize: fit?.fontSize ?? INNER_FONT_SIZE,
149
238
  textAlign: preset.textAlign ?? TextAlign.Center,
150
239
  // Spread, never a defaulted key: the model's own default is already
151
240
  // `Center`, so writing it here would put a new key in the Y.Map of every
@@ -155,8 +244,14 @@ export function bpmnNodeProps(kind, box) {
155
244
  ? { textVerticalAlign: preset.textVerticalAlign }
156
245
  : {}),
157
246
  // BPMN symbols have normative sizes: a long label overflows rather
158
- // than deforming the node
159
- textFitMode: TextFitMode.Overflow,
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 } : {}),
160
255
  xywh: box.xywh,
161
256
  };
162
257
  }
@@ -1,9 +1,10 @@
1
- import { ActionPlacement, type ToolbarContext } from '@formicoidea/labre-core/shared/services';
1
+ import { ActionPlacement, type ChromeWording, type ToolbarContext } from '@formicoidea/labre-core/shared/services';
2
2
  import { type TemplateResult } from 'lit';
3
3
  export declare const bpmnPoolToolbarConfig: {
4
4
  readonly actions: [{
5
5
  id: string;
6
6
  tooltip: string;
7
+ tooltipWording: ChromeWording | undefined;
7
8
  icon: TemplateResult;
8
9
  active(ctx: ToolbarContext): boolean;
9
10
  run(ctx: ToolbarContext): void;
@@ -1,6 +1,6 @@
1
1
  import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { BpmnPoolElementModel } from '@formicoidea/labre-core/model';
3
- import { ActionPlacement, ToolbarModuleExtension, translateKey, } from '@formicoidea/labre-core/shared/services';
3
+ import { ActionPlacement, BOARD_RESIZE_TOGGLE, ToolbarModuleExtension, translateKey, } from '@formicoidea/labre-core/shared/services';
4
4
  import { BlockFlavourIdentifier, getRegisteredCommands, runCommand, } from '@formicoidea/labre-core/std';
5
5
  import { html } from 'lit';
6
6
  import { bpmnExportXmlIcon, bpmnLaneAddIcon, bpmnLaneRemoveIcon, } from './icons.js';
@@ -24,7 +24,12 @@ const ResizeIcon = html `<svg
24
24
  function booleanToggle(id, tooltip, icon, prop) {
25
25
  return {
26
26
  id,
27
- tooltip,
27
+ tooltip: typeof tooltip === 'string' ? tooltip : tooltip[1],
28
+ // The declared wording, when the caller gave one instead of a literal:
29
+ // `combine` resolves it against the host's catalogue and writes it over
30
+ // the English default above, so a playground with no catalogue reads
31
+ // exactly what it read before (#183).
32
+ tooltipWording: typeof tooltip === 'string' ? undefined : tooltip,
28
33
  icon,
29
34
  active(ctx) {
30
35
  const models = ctx.getSurfaceModelsByType(BpmnPoolElementModel);
@@ -119,7 +124,7 @@ function commandMoreAction(id, commandId, labelKey, labelFallback, icon) {
119
124
  const findCommand = (ctx, id) => getRegisteredCommands(ctx.std).find(candidate => candidate.id === id);
120
125
  export const bpmnPoolToolbarConfig = {
121
126
  actions: [
122
- booleanToggle('a.toggle-resize', 'Enable / lock resizing', ResizeIcon, 'resizeEnabled'),
127
+ booleanToggle('a.toggle-resize', BOARD_RESIZE_TOGGLE, ResizeIcon, 'resizeEnabled'),
123
128
  // The two lane gestures, next to the resize toggle: everything on this row
124
129
  // is about the SHAPE of the pool rather than about what is drawn in it.
125
130
  // Add before remove, because that is the order they can be used in — a pool
@@ -1,8 +1,32 @@
1
- import { collectTranslationKeys, commandTranslationEntries, mergeTranslationEntries, } from '@formicoidea/labre-core/std';
1
+ import { collectTranslationKeys, commandCategoryTranslationEntries, commandTranslationEntries, mergeTranslationEntries, } from '@formicoidea/labre-core/std';
2
2
  import { bpmnCommands } from './commands.js';
3
+ import { NODE_LABEL, nodeLabelKey } from './consts.js';
4
+ import { BPMN_IMPORT_REMARKS } from './import.js';
3
5
  import { BPMN_PROFILES } from './profiles.js';
4
6
  import { BPMN_ROLES } from './roles.js';
5
7
  import { BPMN_RULES } from './rules.js';
8
+ /**
9
+ * The captions a placed artefact is seeded with, and the fixed-wording remarks
10
+ * an import can produce — the two families of BPMN prose that live in tables
11
+ * rather than in a declaration the walker can reach.
12
+ *
13
+ * Derived from those tables, never restated: the fallback IS `NODE_LABEL[kind]`
14
+ * and `BPMN_IMPORT_REMARKS[…][1]`, so the wording a host is offered and the
15
+ * wording the library writes cannot drift. The kinds with no caption
16
+ * contribute no key — there is nothing to translate.
17
+ */
18
+ const seedEntries = () => Object.entries(NODE_LABEL)
19
+ .filter(([, label]) => label !== '')
20
+ .map(([kind, label]) => ({
21
+ key: nodeLabelKey(kind),
22
+ fallback: label,
23
+ source: 'seed',
24
+ }));
25
+ const importRemarkEntries = () => Object.values(BPMN_IMPORT_REMARKS).map(([key, english]) => ({
26
+ key,
27
+ fallback: english,
28
+ source: 'chrome',
29
+ }));
6
30
  /**
7
31
  * THIS framework's contribution to the translation-key manifest — every
8
32
  * `com.labre.*` key BPMN can hand to `TranslationProvider.t`, derived from the
@@ -17,4 +41,10 @@ import { BPMN_RULES } from './rules.js';
17
41
  * `bpmnCommands` into the command registry. See
18
42
  * `packages/affine/all/src/translations.ts`.
19
43
  */
20
- export const bpmnTranslationEntries = mergeTranslationEntries(commandTranslationEntries(bpmnCommands), collectTranslationKeys('role', BPMN_ROLES), collectTranslationKeys('rule', BPMN_RULES), collectTranslationKeys('profile', BPMN_PROFILES));
44
+ export const bpmnTranslationEntries = mergeTranslationEntries(commandTranslationEntries(bpmnCommands),
45
+ // The catalogue's own group headers, derived from the very categories
46
+ // these commands declare. They ship WITH the framework because core's
47
+ // registry names no framework category in the bundled distribution, so a
48
+ // host that composed core's manifest alone drew translated entries under
49
+ // English headers (#183).
50
+ commandCategoryTranslationEntries(bpmnCommands), collectTranslationKeys('role', BPMN_ROLES), collectTranslationKeys('rule', BPMN_RULES), collectTranslationKeys('profile', BPMN_PROFILES), seedEntries(), importRemarkEntries());
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formicoidea/labre-framework-bpmn",
3
3
  "description": "Labre bpmn framework for @formicoidea/labre-core.",
4
- "version": "0.33.0",
4
+ "version": "0.34.1",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "author": "lajola",
@@ -21,13 +21,17 @@
21
21
  "./descriptor": {
22
22
  "types": "./dist/descriptor.d.ts",
23
23
  "import": "./dist/descriptor.js"
24
+ },
25
+ "./commands-manifest": {
26
+ "types": "./dist/commands-manifest.d.ts",
27
+ "import": "./dist/commands-manifest.js"
24
28
  }
25
29
  },
26
30
  "files": [
27
31
  "dist"
28
32
  ],
29
33
  "dependencies": {
30
- "@formicoidea/labre-core": "0.33.0",
34
+ "@formicoidea/labre-core": "0.34.1",
31
35
  "lit": "^3.2.0"
32
36
  }
33
37
  }