@formicoidea/labre-framework-bpmn 0.31.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 (52) hide show
  1. package/dist/actions.d.ts +213 -0
  2. package/dist/actions.js +467 -0
  3. package/dist/background.d.ts +2 -0
  4. package/dist/background.js +158 -0
  5. package/dist/commands.d.ts +4 -0
  6. package/dist/commands.js +567 -0
  7. package/dist/consts.d.ts +157 -3
  8. package/dist/consts.js +192 -3
  9. package/dist/descriptor.d.ts +8 -3
  10. package/dist/descriptor.js +6 -3
  11. package/dist/element-renderer.d.ts +10 -4
  12. package/dist/element-renderer.js +14 -55
  13. package/dist/element-view.d.ts +100 -8
  14. package/dist/element-view.js +249 -30
  15. package/dist/export.d.ts +277 -0
  16. package/dist/export.js +1802 -0
  17. package/dist/facts.d.ts +48 -0
  18. package/dist/facts.js +127 -0
  19. package/dist/import.d.ts +44 -0
  20. package/dist/import.js +1440 -0
  21. package/dist/index.d.ts +14 -1
  22. package/dist/index.js +46 -1
  23. package/dist/interchange.d.ts +109 -0
  24. package/dist/interchange.js +191 -0
  25. package/dist/morph.d.ts +61 -0
  26. package/dist/morph.js +118 -0
  27. package/dist/node/node-renderer.d.ts +0 -9
  28. package/dist/node/node-renderer.js +294 -17
  29. package/dist/pool-hit.d.ts +98 -0
  30. package/dist/pool-hit.js +130 -0
  31. package/dist/presets.d.ts +114 -0
  32. package/dist/presets.js +232 -0
  33. package/dist/profiles.d.ts +2 -0
  34. package/dist/profiles.js +189 -0
  35. package/dist/roles.d.ts +96 -0
  36. package/dist/roles.js +410 -0
  37. package/dist/rules.d.ts +199 -0
  38. package/dist/rules.js +1539 -0
  39. package/dist/templates/index.js +116 -9
  40. package/dist/toolbar/bpmn-menu.d.ts +6 -21
  41. package/dist/toolbar/bpmn-menu.js +6 -173
  42. package/dist/toolbar/bpmn-senior-button.js +8 -2
  43. package/dist/toolbar/config.d.ts +27 -2
  44. package/dist/toolbar/config.js +86 -2
  45. package/dist/toolbar/icons.d.ts +67 -0
  46. package/dist/toolbar/icons.js +141 -0
  47. package/dist/toolbar/senior-tool.js +1 -0
  48. package/dist/translations.d.ts +16 -0
  49. package/dist/translations.js +20 -0
  50. package/dist/view.d.ts +18 -0
  51. package/dist/view.js +95 -7
  52. package/package.json +2 -2
@@ -1,12 +1,27 @@
1
1
  import { makeTemplateSnapshot, surfaceText, } from '@formicoidea/labre-core/gfx/template';
2
2
  import { ConnectorMode, FontFamily, PointStyle, ShapeStyle, StrokeStyle, } from '@formicoidea/labre-core/model';
3
- import { END_WIDTH, EVENT_END, EVENT_START, INNER_FONT_SIZE, NEUTRAL_STROKE, NODE_FILL, NODE_SIZE, NODE_STROKE_WIDTH, SEQUENCE_STROKE, SEQUENCE_WIDTH, START_WIDTH, TASK_RADIUS, } from '../consts.js';
4
- /** One BPMN flow-object node, as a surface-element JSON entry. */
3
+ import { END_WIDTH, EVENT_END, EVENT_START, INNER_FONT_SIZE, MESSAGE_STROKE, MESSAGE_WIDTH, NEUTRAL_STROKE, NODE_FILL, NODE_SIZE, NODE_STROKE_WIDTH, SEQUENCE_STROKE, SEQUENCE_WIDTH, START_WIDTH, TASK_RADIUS, } from '../consts.js';
4
+ import { BPMN_ROLE, BPMN_ROLE_OF_KIND } from '../roles.js';
5
+ /**
6
+ * One BPMN flow-object node, as a surface-element JSON entry.
7
+ *
8
+ * Every kind that {@link NodeKind} admits paints its own body natively, as an
9
+ * ellipse, a rounded rect or a diamond. The GLYPH-BODIED kinds (`dataObject`,
10
+ * `dataStore`, `textAnnotation`) do not: they are created unfilled and
11
+ * unstroked and the renderer draws their silhouette, so a card that spelled one
12
+ * out here would insert an invisible rectangle. `group` is out for a neighbouring
13
+ * reason — it is `hollow`, and the branches below all fill. When any of them
14
+ * earns a card, this helper reads `NODE_PRESETS` instead of branching.
15
+ */
5
16
  function node(kind, x, y, text) {
6
17
  const { w, h } = NODE_SIZE[kind];
7
18
  const base = {
8
19
  type: 'bpmnNode',
9
20
  kind,
21
+ // A template must produce the same typed artefacts as the toolbox, or a
22
+ // process started from a preset would read differently from a hand-drawn
23
+ // one.
24
+ role: BPMN_ROLE_OF_KIND[kind],
10
25
  filled: true,
11
26
  fillColor: NODE_FILL,
12
27
  shapeStyle: ShapeStyle.General,
@@ -14,11 +29,26 @@ function node(kind, x, y, text) {
14
29
  xywh: `[${x},${y},${w},${h}]`,
15
30
  };
16
31
  if (kind === 'startEvent')
17
- return { ...base, shapeType: 'ellipse', strokeColor: EVENT_START, strokeWidth: START_WIDTH };
32
+ return {
33
+ ...base,
34
+ shapeType: 'ellipse',
35
+ strokeColor: EVENT_START,
36
+ strokeWidth: START_WIDTH,
37
+ };
18
38
  if (kind === 'endEvent')
19
- return { ...base, shapeType: 'ellipse', strokeColor: EVENT_END, strokeWidth: END_WIDTH };
39
+ return {
40
+ ...base,
41
+ shapeType: 'ellipse',
42
+ strokeColor: EVENT_END,
43
+ strokeWidth: END_WIDTH,
44
+ };
20
45
  if (kind === 'gatewayExclusive')
21
- return { ...base, shapeType: 'diamond', strokeColor: NEUTRAL_STROKE, strokeWidth: NODE_STROKE_WIDTH };
46
+ return {
47
+ ...base,
48
+ shapeType: 'diamond',
49
+ strokeColor: NEUTRAL_STROKE,
50
+ strokeWidth: NODE_STROKE_WIDTH,
51
+ };
22
52
  return {
23
53
  ...base,
24
54
  shapeType: 'rect',
@@ -33,12 +63,21 @@ function node(kind, x, y, text) {
33
63
  };
34
64
  }
35
65
  function pool(x, y, w, h, name = 'Pool') {
36
- return { type: 'bpmnPool', name, xywh: `[${x},${y},${w},${h}]` };
66
+ return {
67
+ type: 'bpmnPool',
68
+ role: BPMN_ROLE.pool,
69
+ name,
70
+ xywh: `[${x},${y},${w},${h}]`,
71
+ };
37
72
  }
38
73
  /** A sequence-flow connector; ids are remapped on insert. */
39
74
  function seq(source, target) {
40
75
  return {
41
76
  type: 'connector',
77
+ // Both ends are BOUND, so this arrow relates two named things and its
78
+ // direction is a claim the template makes: source first, target next
79
+ // (`docs/adr/0010`).
80
+ role: BPMN_ROLE.sequenceFlow,
42
81
  mode: ConnectorMode.Orthogonal,
43
82
  stroke: SEQUENCE_STROKE,
44
83
  strokeWidth: SEQUENCE_WIDTH,
@@ -49,7 +88,40 @@ function seq(source, target) {
49
88
  target: { id: target, position: [0.5, 0.5] },
50
89
  };
51
90
  }
52
- /** A standalone (free) sequence-flow arrow for the prefab card. */
91
+ /**
92
+ * A message-flow connector; ids are remapped on insert.
93
+ *
94
+ * The same shape as {@link seq} and the same claim — both ends are BOUND, so
95
+ * the arrow relates two named things — but a different sentence: "sends a
96
+ * message to", from the participant that sends to the one that receives
97
+ * (`docs/adr/0010`). Its style is the one `activateBpmnMessageFlow` arms the
98
+ * connector tool with, so a message flow dropped from this card and one drawn
99
+ * by hand are indistinguishable in the document.
100
+ */
101
+ function msg(source, target) {
102
+ return {
103
+ type: 'connector',
104
+ role: BPMN_ROLE.messageFlow,
105
+ mode: ConnectorMode.Orthogonal,
106
+ stroke: MESSAGE_STROKE,
107
+ strokeWidth: MESSAGE_WIDTH,
108
+ strokeStyle: StrokeStyle.Dash,
109
+ frontEndpointStyle: PointStyle.Circle,
110
+ rearEndpointStyle: PointStyle.Arrow,
111
+ source: { id: source, position: [0.5, 0.5] },
112
+ target: { id: target, position: [0.5, 0.5] },
113
+ };
114
+ }
115
+ /**
116
+ * A standalone (free) sequence-flow arrow for the prefab card.
117
+ *
118
+ * NEUTRAL on purpose (`docs/adr/0010` § Compatibility, and the same call the
119
+ * Wardley "Link" swatch makes): this is a horizontal stroke bound to nothing —
120
+ * a sample of a STYLE, in a palette. A typed edge claims "this is followed by
121
+ * that", and a stroke attached to neither end has no this and no that to say it
122
+ * about. Drawing it with the sequence-flow tool, or dropping this one and then
123
+ * attaching both of its ends, is what makes it a statement.
124
+ */
53
125
  function freeSeq() {
54
126
  return {
55
127
  type: 'connector',
@@ -63,7 +135,9 @@ function freeSeq() {
63
135
  target: { position: [140, 0] },
64
136
  };
65
137
  }
66
- const single = (el) => ({ a: el });
138
+ const single = (el) => ({
139
+ a: el,
140
+ });
67
141
  const PREVIEW_ATTRS = 'width="100%" height="100%" viewBox="0 0 135 80" xmlns="http://www.w3.org/2000/svg"';
68
142
  const previews = {
69
143
  process: `<svg ${PREVIEW_ATTRS} fill="none"><circle cx="16" cy="40" r="8" stroke="#43a06b" stroke-width="2"/><rect x="34" y="31" width="26" height="18" rx="3" stroke="#262626" stroke-width="1.6"/><path d="M78 31 L88 40 L78 49 L68 40 Z" stroke="#262626" stroke-width="1.4"/><path d="M73 37 L83 43 M83 37 L73 43" stroke="#262626" stroke-width="1.2"/><circle cx="118" cy="40" r="8" stroke="#cf5648" stroke-width="3"/><path d="M24 40 H34 M60 40 H68 M88 40 H110" stroke="#262626" stroke-width="1.2"/></svg>`,
@@ -73,8 +147,17 @@ const previews = {
73
147
  gateway: `<svg ${PREVIEW_ATTRS} fill="none"><path d="M67 16 L92 40 L67 64 L42 40 Z" stroke="#262626" stroke-width="2.4" stroke-linejoin="round"/><path d="M58 31 L76 49 M76 31 L58 49" stroke="#262626" stroke-width="2.2" stroke-linecap="round"/></svg>`,
74
148
  sequence: `<svg ${PREVIEW_ATTRS} fill="none"><path d="M24 40 H96" stroke="#262626" stroke-width="2.4" stroke-linecap="round"/><path d="M94 33 L108 40 L94 47 Z" fill="#262626"/></svg>`,
75
149
  pool: `<svg ${PREVIEW_ATTRS} fill="none"><rect x="14" y="20" width="107" height="40" rx="3" stroke="#262626" stroke-width="2"/><path d="M30 20 V60" stroke="#262626" stroke-width="1.8"/><rect x="14" y="20" width="16" height="40" fill="#f4f4f5"/><path d="M30 20 V60" stroke="#262626" stroke-width="1.8"/></svg>`,
150
+ // Two participants stacked, and the dashed line between them is the whole
151
+ // point of the card: a message flow is the one arrow that crosses a pool.
152
+ // Its source terminator is a FILLED disc, matching what `renderCircle`
153
+ // actually paints rather than the hollow ring the norm asks for — a preview
154
+ // has one job, which is to look like what lands on the board.
155
+ messageExchange: `<svg ${PREVIEW_ATTRS} fill="none"><rect x="14" y="8" width="107" height="27" rx="3" stroke="#262626" stroke-width="1.6"/><rect x="14" y="8" width="11" height="27" fill="#f4f4f5"/><path d="M25 8 V35" stroke="#262626" stroke-width="1.4"/><rect x="14" y="45" width="107" height="27" rx="3" stroke="#262626" stroke-width="1.6"/><rect x="14" y="45" width="11" height="27" fill="#f4f4f5"/><path d="M25 45 V72" stroke="#262626" stroke-width="1.4"/><circle cx="36" cy="21.5" r="4.5" stroke="#43a06b" stroke-width="1.6"/><rect x="52" y="14" width="30" height="15" rx="3" stroke="#262626" stroke-width="1.6"/><rect x="52" y="51" width="30" height="15" rx="3" stroke="#262626" stroke-width="1.6"/><path d="M45 21.5 H52" stroke="#262626" stroke-width="1.4"/><circle cx="67" cy="31.5" r="2.2" fill="#262626"/><path d="M67 34 V48" stroke="#262626" stroke-width="1.4" stroke-dasharray="3 2.4"/><path d="M64 46 L67 50 L70 46" stroke="#262626" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
76
156
  };
77
- /** The lean BPMN basics: a simple worked process + every prefab the menu makes. */
157
+ /**
158
+ * The BPMN basics: two worked scenes — a process end to end and a two-party
159
+ * message exchange — plus a prefab for each of the lean artefacts.
160
+ */
78
161
  function bpmnTemplates() {
79
162
  const process = {
80
163
  pool: pool(0, 0, 640, 200, 'Process'),
@@ -91,6 +174,29 @@ function bpmnTemplates() {
91
174
  c5: seq('task2', 'end'),
92
175
  c6: seq('task3', 'end'),
93
176
  };
177
+ /**
178
+ * Two participants, and the one arrow that is allowed to cross between them.
179
+ *
180
+ * The card exists because the message flow is the piece of BPMN people get
181
+ * wrong first: a sequence flow may never leave its pool, and the thing that
182
+ * does leave is a different statement with a different line. Laying the two
183
+ * pools out one above the other and drawing both flows once is the shortest
184
+ * way to show that — the solid arrow stays home, the dashed one crosses.
185
+ *
186
+ * Stacked vertically with a 40-unit gutter, so the message flow is a straight
187
+ * orthogonal drop between two tasks that already line up.
188
+ */
189
+ const messageExchange = {
190
+ customer: pool(0, 0, 640, 200, 'Customer'),
191
+ supplier: pool(0, 240, 640, 200, 'Supplier'),
192
+ start: node('startEvent', 40, 72),
193
+ ask: node('taskUser', 140, 64, 'Place order'),
194
+ answer: node('taskService', 140, 304, 'Confirm order'),
195
+ // Inside the first participant: what happens, and in what order.
196
+ inside: seq('start', 'ask'),
197
+ // Across the two: who told whom.
198
+ across: msg('ask', 'answer'),
199
+ };
94
200
  const t = (name, preview, elements) => ({
95
201
  name,
96
202
  type: 'template',
@@ -99,6 +205,7 @@ function bpmnTemplates() {
99
205
  });
100
206
  return [
101
207
  t('Simple process', previews.process, process),
208
+ t('Message exchange', previews.messageExchange, messageExchange),
102
209
  t('Start event', previews.startEvent, single(node('startEvent', 0, 0))),
103
210
  t('End event', previews.endEvent, single(node('endEvent', 0, 0))),
104
211
  t('Task', previews.task, single(node('task', 0, 0))),
@@ -1,26 +1,11 @@
1
1
  import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
2
- import { LitElement } from 'lit';
3
- declare const EdgelessBpmnMenu_base: typeof LitElement & import("@formicoidea/labre-core/_pkgs/global/utils").Constructor<import("@formicoidea/labre-core/_pkgs/affine-widget-edgeless-toolbar").EdgelessToolbarToolClass>;
2
+ import { EdgelessCommandMenu } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
4
3
  /**
5
- * The popover above the toolbar for the BPMN toolbox. Items create the flow
6
- * objects (native shapes, so they stay editable), the pool background, and arm
7
- * the native connector tool for sequence flows. Mirrors the EDGY menu.
4
+ * The popover above the toolbar for the BPMN toolbox. Since PF3 it declares
5
+ * nothing: {@link EdgelessCommandMenu} renders `bpmnCommands` for the
6
+ * `senior-menu` surface (`docs/adr/0008`).
8
7
  */
9
- export declare class EdgelessBpmnMenu extends EdgelessBpmnMenu_base {
10
- static styles: import("lit").CSSResult;
8
+ export declare class EdgelessBpmnMenu extends EdgelessCommandMenu {
9
+ protected owner: "bpmn";
11
10
  type: typeof EmptyTool;
12
- /** Create a flow-object node (native shape) centred on the viewport. */
13
- private _createNode;
14
- /** Create a pool (background container) centred on the viewport. */
15
- private _createPool;
16
- /**
17
- * Arm the native connector tool, pre-styled for a BPMN sequence flow:
18
- * orthogonal, solid, with a filled triangle head. The user then draws from
19
- * one node to another (endpoints attach to centers).
20
- */
21
- private _activateSequenceFlow;
22
- private _finish;
23
- private _track;
24
- render(): import("lit-html").TemplateResult<1>;
25
11
  }
26
- export {};
@@ -1,181 +1,14 @@
1
- import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
- import { ConnectorTool } from '@formicoidea/labre-core/gfx/connector';
3
1
  import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
4
- import { ConnectorMode, FontFamily, PointStyle, ShapeStyle, StrokeStyle, TextFitMode, } from '@formicoidea/labre-core/model';
5
- import { EditPropsStore, TelemetryProvider, } from '@formicoidea/labre-core/shared/services';
6
- import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
7
- import { Bound } from '@formicoidea/labre-core/global/gfx';
8
- import { css, html, LitElement } from 'lit';
9
- import { EVENT_END, EVENT_START, END_WIDTH, INNER_FONT_SIZE, NEUTRAL_STROKE, NODE_FILL, NODE_LABEL, NODE_SIZE, NODE_STROKE_WIDTH, SEQUENCE_STROKE, SEQUENCE_WIDTH, START_WIDTH, TASK_RADIUS, } from '../consts.js';
10
- import { bpmnEndIcon, bpmnGatewayIcon, bpmnPoolIcon, bpmnSequenceIcon, bpmnStartIcon, bpmnTaskIcon, } from './icons.js';
11
- /** Per-kind native shape + accent presets (style C). */
12
- const NODE_PRESETS = {
13
- startEvent: { shapeType: 'ellipse', stroke: EVENT_START, width: START_WIDTH },
14
- endEvent: { shapeType: 'ellipse', stroke: EVENT_END, width: END_WIDTH },
15
- task: { shapeType: 'rect', stroke: NEUTRAL_STROKE, width: NODE_STROKE_WIDTH },
16
- gatewayExclusive: {
17
- shapeType: 'diamond',
18
- stroke: NEUTRAL_STROKE,
19
- width: NODE_STROKE_WIDTH,
20
- },
21
- };
2
+ import { EdgelessCommandMenu } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
22
3
  /**
23
- * The popover above the toolbar for the BPMN toolbox. Items create the flow
24
- * objects (native shapes, so they stay editable), the pool background, and arm
25
- * the native connector tool for sequence flows. Mirrors the EDGY menu.
4
+ * The popover above the toolbar for the BPMN toolbox. Since PF3 it declares
5
+ * nothing: {@link EdgelessCommandMenu} renders `bpmnCommands` for the
6
+ * `senior-menu` surface (`docs/adr/0008`).
26
7
  */
27
- export class EdgelessBpmnMenu extends EdgelessToolbarToolMixin(LitElement) {
8
+ export class EdgelessBpmnMenu extends EdgelessCommandMenu {
28
9
  constructor() {
29
10
  super(...arguments);
11
+ this.owner = 'bpmn';
30
12
  this.type = EmptyTool;
31
13
  }
32
- static { this.styles = css `
33
- :host {
34
- display: flex;
35
- z-index: -1;
36
- justify-content: center;
37
- }
38
- .menu-content {
39
- display: flex;
40
- align-items: center;
41
- justify-content: center;
42
- }
43
- .button-group-container {
44
- display: flex;
45
- align-items: center;
46
- gap: 14px;
47
- fill: var(--affine-icon-color);
48
- }
49
- .button-group-container svg {
50
- width: 24px;
51
- height: 24px;
52
- }
53
- `; }
54
- /** Create a flow-object node (native shape) centred on the viewport. */
55
- _createNode(kind) {
56
- const surface = this.gfx.surface;
57
- if (!surface)
58
- return;
59
- const { w, h } = NODE_SIZE[kind];
60
- const { centerX: cx, centerY: cy } = this.gfx.viewport;
61
- const preset = NODE_PRESETS[kind];
62
- const id = surface.addElement({
63
- type: 'bpmnNode',
64
- kind,
65
- shapeType: preset.shapeType,
66
- filled: true,
67
- fillColor: NODE_FILL,
68
- strokeColor: preset.stroke,
69
- strokeWidth: preset.width,
70
- shapeStyle: ShapeStyle.General,
71
- roughness: 0,
72
- radius: kind === 'task' ? TASK_RADIUS : 0,
73
- text: NODE_LABEL[kind] || undefined,
74
- color: NEUTRAL_STROKE,
75
- fontFamily: FontFamily.Inter,
76
- fontSize: INNER_FONT_SIZE,
77
- textAlign: 'center',
78
- // BPMN symbols have normative sizes: a long label overflows rather
79
- // than deforming the node
80
- textFitMode: TextFitMode.Overflow,
81
- xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
82
- });
83
- this._track('FrameworkElementAdded', `node:${kind}`);
84
- this._finish(id);
85
- }
86
- /** Create a pool (background container) centred on the viewport. */
87
- _createPool() {
88
- const surface = this.gfx.surface;
89
- if (!surface)
90
- return;
91
- const w = 560;
92
- const h = 200;
93
- const { centerX: cx, centerY: cy } = this.gfx.viewport;
94
- const id = surface.addElement({
95
- type: 'bpmnPool',
96
- xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
97
- });
98
- this._track('FrameworkElementAdded', 'pool');
99
- this._finish(id);
100
- }
101
- /**
102
- * Arm the native connector tool, pre-styled for a BPMN sequence flow:
103
- * orthogonal, solid, with a filled triangle head. The user then draws from
104
- * one node to another (endpoints attach to centers).
105
- */
106
- _activateSequenceFlow() {
107
- this.edgeless.std.get(EditPropsStore).recordLastProps('connector', {
108
- mode: ConnectorMode.Orthogonal,
109
- stroke: SEQUENCE_STROKE,
110
- strokeStyle: StrokeStyle.Solid,
111
- strokeWidth: SEQUENCE_WIDTH,
112
- frontEndpointStyle: PointStyle.None,
113
- rearEndpointStyle: PointStyle.Triangle,
114
- });
115
- this._track('FrameworkToolPicked', 'connector:sequence');
116
- this.gfx.tool.setTool(ConnectorTool, { mode: ConnectorMode.Orthogonal });
117
- // Keep the palette open (native sub-menu behaviour).
118
- }
119
- _finish(id) {
120
- const { gfx } = this;
121
- gfx.doc.captureSync();
122
- gfx.tool.setTool(DefaultTool);
123
- gfx.selection.set({ elements: [id], editing: false });
124
- // Keep the palette open (native sub-menu behaviour).
125
- }
126
- _track(event, element) {
127
- this.edgeless.std.getOptional(TelemetryProvider)?.track(event, {
128
- framework: 'bpmn',
129
- element,
130
- page: 'whiteboard editor',
131
- segment: 'bpmn toolbox',
132
- module: 'bpmn menu',
133
- });
134
- }
135
- render() {
136
- return html `
137
- <edgeless-slide-menu>
138
- <div class="menu-content">
139
- <div class="button-group-container">
140
- <edgeless-tool-icon-button
141
- .tooltip=${'Start event'}
142
- @click=${() => this._createNode('startEvent')}
143
- >
144
- ${bpmnStartIcon}
145
- </edgeless-tool-icon-button>
146
- <edgeless-tool-icon-button
147
- .tooltip=${'End event'}
148
- @click=${() => this._createNode('endEvent')}
149
- >
150
- ${bpmnEndIcon}
151
- </edgeless-tool-icon-button>
152
- <edgeless-tool-icon-button
153
- .tooltip=${'Task'}
154
- @click=${() => this._createNode('task')}
155
- >
156
- ${bpmnTaskIcon}
157
- </edgeless-tool-icon-button>
158
- <edgeless-tool-icon-button
159
- .tooltip=${'Exclusive gateway'}
160
- @click=${() => this._createNode('gatewayExclusive')}
161
- >
162
- ${bpmnGatewayIcon}
163
- </edgeless-tool-icon-button>
164
- <edgeless-tool-icon-button
165
- .tooltip=${'Sequence flow'}
166
- @click=${this._activateSequenceFlow}
167
- >
168
- ${bpmnSequenceIcon}
169
- </edgeless-tool-icon-button>
170
- <edgeless-tool-icon-button
171
- .tooltip=${'Pool'}
172
- @click=${this._createPool}
173
- >
174
- ${bpmnPoolIcon}
175
- </edgeless-tool-icon-button>
176
- </div>
177
- </div>
178
- </edgeless-slide-menu>
179
- `;
180
- }
181
14
  }
@@ -1,5 +1,6 @@
1
1
  import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
3
+ import { translateKey } from '@formicoidea/labre-core/shared/services';
3
4
  import { EdgelessToolbarToolMixin } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
4
5
  import { SignalWatcher } from '@formicoidea/labre-core/global/lit';
5
6
  import { css, html, LitElement } from 'lit';
@@ -44,7 +45,10 @@ export class EdgelessBpmnSeniorButton extends EdgelessToolbarToolMixin(SignalWat
44
45
  translate: var(--active-x, 0) var(--active-y, 0);
45
46
  rotate: var(--active-r, -2deg);
46
47
  scale: var(--active-s, 1);
47
- transition: transform 0.3s ease, translate 0.3s ease, rotate 0.3s ease,
48
+ transition:
49
+ transform 0.3s ease,
50
+ translate 0.3s ease,
51
+ rotate 0.3s ease,
48
52
  scale 0.3s ease;
49
53
  }
50
54
  .bpmn-card svg {
@@ -71,7 +75,9 @@ export class EdgelessBpmnSeniorButton extends EdgelessToolbarToolMixin(SignalWat
71
75
  render() {
72
76
  return html `<edgeless-toolbar-button
73
77
  class="bpmn-button"
74
- .tooltip=${this.popper ? '' : 'BPMN'}
78
+ .tooltip=${this.popper
79
+ ? ''
80
+ : translateKey(this.edgeless.std, 'com.labre.framework.bpmn', 'BPMN')}
75
81
  .tooltipOffset=${4}
76
82
  .active=${!!this.popper}
77
83
  @click=${this._toggleMenu}
@@ -1,4 +1,4 @@
1
- import { type ToolbarContext } from '@formicoidea/labre-core/shared/services';
1
+ import { ActionPlacement, 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: [{
@@ -7,7 +7,32 @@ export declare const bpmnPoolToolbarConfig: {
7
7
  icon: TemplateResult;
8
8
  active(ctx: ToolbarContext): boolean;
9
9
  run(ctx: ToolbarContext): void;
10
+ }, {
11
+ id: string;
12
+ when: (ctx: ToolbarContext) => boolean;
13
+ generate: (ctx: ToolbarContext) => {
14
+ icon: TemplateResult;
15
+ tooltip: string;
16
+ run: (runCtx: ToolbarContext) => void;
17
+ };
18
+ }, {
19
+ id: string;
20
+ when: (ctx: ToolbarContext) => boolean;
21
+ generate: (ctx: ToolbarContext) => {
22
+ icon: TemplateResult;
23
+ tooltip: string;
24
+ run: (runCtx: ToolbarContext) => void;
25
+ };
26
+ }, {
27
+ id: string;
28
+ placement: ActionPlacement;
29
+ when: (ctx: ToolbarContext) => boolean;
30
+ generate: (ctx: ToolbarContext) => {
31
+ icon: TemplateResult;
32
+ label: string;
33
+ run: (runCtx: ToolbarContext) => void;
34
+ };
10
35
  }];
11
36
  readonly when: (ctx: ToolbarContext) => boolean;
12
37
  };
13
- export declare const bpmnPoolToolbarExtension: import("@formicoidea/labre-core/store").ExtensionType;
38
+ export declare const bpmnPoolToolbarExtension: import("@formicoidea/labre-core/store/index.js").ExtensionType;
@@ -1,8 +1,9 @@
1
1
  import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { BpmnPoolElementModel } from '@formicoidea/labre-core/model';
3
- import { ToolbarModuleExtension, } from '@formicoidea/labre-core/shared/services';
4
- import { BlockFlavourIdentifier } from '@formicoidea/labre-core/std';
3
+ import { ActionPlacement, ToolbarModuleExtension, translateKey, } from '@formicoidea/labre-core/shared/services';
4
+ import { BlockFlavourIdentifier, getRegisteredCommands, runCommand, } from '@formicoidea/labre-core/std';
5
5
  import { html } from 'lit';
6
+ import { bpmnExportXmlIcon, bpmnLaneAddIcon, bpmnLaneRemoveIcon, } from './icons.js';
6
7
  const ResizeIcon = html `<svg
7
8
  width="24"
8
9
  height="24"
@@ -42,9 +43,92 @@ function booleanToggle(id, tooltip, icon, prop) {
42
43
  },
43
44
  };
44
45
  }
46
+ /**
47
+ * A toolbar entry that INVOKES a registered command instead of restating what
48
+ * it does — the shape `docs/adr/0010` M3 introduced for the typed-edge
49
+ * inversion, and the reason a lane gesture has one behaviour, one availability
50
+ * rule and one telemetry emission whether it is reached from here, from the
51
+ * palette, from Settings › Shortcuts or from the agent.
52
+ *
53
+ * `generate` rather than a static entry because the i18n seam needs `std`:
54
+ * `translateKey` is what reaches the host's catalogue, and a hard-coded English
55
+ * tooltip would be the one wording a host could not override.
56
+ */
57
+ function commandAction(id, commandId, labelKey, labelFallback, icon) {
58
+ return {
59
+ id,
60
+ when: (ctx) => {
61
+ const command = findCommand(ctx, commandId);
62
+ return command !== undefined && (command.when?.(ctx.std) ?? true);
63
+ },
64
+ generate: (ctx) => {
65
+ const label = translateKey(ctx.std, labelKey, labelFallback);
66
+ return {
67
+ icon,
68
+ tooltip: label,
69
+ run: (runCtx) => {
70
+ const command = findCommand(runCtx, commandId);
71
+ if (!command)
72
+ return;
73
+ runCommand(runCtx.std, command, {
74
+ surface: 'contextual-toolbar',
75
+ source: 'toolbar:general',
76
+ });
77
+ },
78
+ };
79
+ },
80
+ };
81
+ }
82
+ /**
83
+ * The same thing, for the "⋮" menu instead of the row.
84
+ *
85
+ * Two differences, and the widget imposes both: a menu line is drawn from
86
+ * `label` (a tooltip on a line that is already words would be a second copy of
87
+ * them), and `placement: ActionPlacement.More` is what partitions it out of the
88
+ * row in the first place — `renderToolbar` splits on exactly that flag, and the
89
+ * entries it collects come from every module contributing to the element,
90
+ * so a framework can add to the "⋮" without owning it.
91
+ */
92
+ function commandMoreAction(id, commandId, labelKey, labelFallback, icon) {
93
+ return {
94
+ id,
95
+ placement: ActionPlacement.More,
96
+ when: (ctx) => {
97
+ const command = findCommand(ctx, commandId);
98
+ return command !== undefined && (command.when?.(ctx.std) ?? true);
99
+ },
100
+ generate: (ctx) => ({
101
+ icon,
102
+ label: translateKey(ctx.std, labelKey, labelFallback),
103
+ run: (runCtx) => {
104
+ const command = findCommand(runCtx, commandId);
105
+ if (!command)
106
+ return;
107
+ // The same `source` the row's own entries report: the "⋮" is a
108
+ // degradation of the row, not a surface of its own, and the
109
+ // `ElementCreationSource` union deliberately names places rather than
110
+ // widths (`docs/adr/0008`).
111
+ runCommand(runCtx.std, command, {
112
+ surface: 'contextual-toolbar',
113
+ source: 'toolbar:general',
114
+ });
115
+ },
116
+ }),
117
+ };
118
+ }
119
+ const findCommand = (ctx, id) => getRegisteredCommands(ctx.std).find(candidate => candidate.id === id);
45
120
  export const bpmnPoolToolbarConfig = {
46
121
  actions: [
47
122
  booleanToggle('a.toggle-resize', 'Enable / lock resizing', ResizeIcon, 'resizeEnabled'),
123
+ // The two lane gestures, next to the resize toggle: everything on this row
124
+ // is about the SHAPE of the pool rather than about what is drawn in it.
125
+ // Add before remove, because that is the order they can be used in — a pool
126
+ // has no lane to remove until one has been added.
127
+ commandAction('b.add-lane', 'bpmn.addLane', 'com.labre.commands.bpmn.addLane', 'Add lane', bpmnLaneAddIcon),
128
+ commandAction('b.remove-lane', 'bpmn.removeLane', 'com.labre.commands.bpmn.removeLane', 'Remove lane', bpmnLaneRemoveIcon),
129
+ // …and, in the "⋮", the one thing you do to a finished process rather than
130
+ // to the shape of a pool: take it away as a file.
131
+ commandMoreAction('z.export-xml', 'bpmn.exportXml', 'com.labre.commands.bpmn.exportXml', 'Export BPMN XML', bpmnExportXmlIcon),
48
132
  ],
49
133
  when: ctx => ctx.getSurfaceModelsByType(BpmnPoolElementModel).length > 0,
50
134
  };