@formicoidea/labre-framework-bpmn 0.58.0 → 0.60.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.
package/dist/actions.d.ts CHANGED
@@ -19,7 +19,9 @@ export declare function createBpmnPool(std: BlockStdScope): void;
19
19
  /**
20
20
  * Arm the native connector tool, pre-styled for a BPMN sequence flow:
21
21
  * orthogonal, solid, with a filled triangle head. The user then draws from
22
- * one node to another (endpoints attach to centers).
22
+ * one node to another, attaching at the anchor of each outline they pick (a
23
+ * gateway's corners, an activity's side midpoints, an event's cardinal points;
24
+ * `BPMN_CENTRE_ANCHORED`).
23
25
  */
24
26
  export declare function activateBpmnSequenceFlow(std: BlockStdScope): void;
25
27
  /**
@@ -102,12 +104,14 @@ export declare function bpmnPoolsForLaneEdit(std: BlockStdScope): BpmnPoolElemen
102
104
  */
103
105
  export declare function addBpmnLane(std: BlockStdScope): void;
104
106
  /**
105
- * Remove the LAST lane of every selected pool.
107
+ * Remove the FOCUSED lane of every selected pool, or its LAST lane when none is
108
+ * focused.
106
109
  *
107
- * The last one and not the selected one, because a lane is not selectable: it
108
- * is a slice of the pool's plot, painted by the background primitive, with no
109
- * element of its own. Removing from the end is the gesture that undoes the one
110
- * that added it.
110
+ * Focused and not selected, because a lane is not selectable: it is a slice of
111
+ * the pool's plot, painted by the background primitive, with no element of its
112
+ * own. A single click on its title strip, in a selected pool, focuses it
113
+ * ({@link bpmnFocusedLaneIndex}). With nothing focused, removing from the end
114
+ * is the gesture that undoes the one that added it.
111
115
  *
112
116
  * ## Nothing moves
113
117
  *
@@ -221,3 +225,69 @@ export declare function importBpmnSvgFile(std: BlockStdScope): Promise<void>;
221
225
  * same thing that only the bytes can tell apart.
222
226
  */
223
227
  export declare function renameBpmnLane(std: BlockStdScope, model: BpmnPoolElementModel, index: number, name: string): void;
228
+ /**
229
+ * Move the lane at `from` to `to` inside `pool`, carrying what is drawn in it.
230
+ *
231
+ * A lane is a slice of the pool's plot, and what is "in" it is decided by
232
+ * geometry: the centre of an artefact, read by {@link bpmnLaneOf}. Reordering
233
+ * the `lanes` array alone would leave every task where it was on the canvas and
234
+ * silently hand it to whichever lane now covers that spot — the user moves the
235
+ * Sales lane up and finds their sales tasks filed under Ops. So the gesture
236
+ * moves both: the array is reordered, and every unlocked artefact wholly inside
237
+ * the pool is translated by the vertical shift of the lane its centre was in.
238
+ * Connectors attached to them follow through the connector watcher, as on any
239
+ * other move.
240
+ *
241
+ * A GESTURE-time write, not a cascade: it runs once, on the author's client,
242
+ * from a command, and a peer receives the reorder and the moves as ordinary
243
+ * updates. No stored-format change either: the same `lanes` array, the same
244
+ * `xywh` props.
245
+ *
246
+ * One `captureSync`, then one `transact`, so the reorder and every move are ONE
247
+ * undo step. Refused — and nothing written, so no empty undo entry — on a
248
+ * read-only document, a locked pool, an index that names no lane, or a move to
249
+ * where the lane already is. Returns whether it wrote.
250
+ *
251
+ * ponytail: a connector attached to nothing, drawn inside a lane, stays where
252
+ * it is, and a locked artefact is left behind; the upgrade is to translate the
253
+ * free endpoints too, here and nowhere else.
254
+ */
255
+ export declare function moveBpmnLane(std: BlockStdScope, pool: BpmnPoolElementModel, from: number, to: number): boolean;
256
+ /**
257
+ * Something that knows which lane of a pool is FOCUSED — the pool's view
258
+ * (`BpmnPoolView`) in an editor, a plain object in a test.
259
+ *
260
+ * A lane is not an element, so it cannot be selected; the lane commands need
261
+ * another way to know which lane the user means. The focus is set by a single
262
+ * click on a lane's title strip in a selected pool, and it is on-screen state
263
+ * and nothing more: held by the view, never stored, never synced, dropped when
264
+ * the pool is deselected. Another viewer of the same document has their own or
265
+ * none. An id rather than an index, so the focus follows the lane it names
266
+ * through a move.
267
+ */
268
+ export interface BpmnLaneFocusHolder {
269
+ readonly focusedLaneId: string | null;
270
+ }
271
+ /**
272
+ * The index of `pool`'s focused lane in this editor, or `null` when none is
273
+ * focused or the focused one is gone (removed by a peer, undone away).
274
+ *
275
+ * Read through {@link BpmnLaneFocusHolder} rather than the view class, so the
276
+ * commands do not import the view.
277
+ */
278
+ export declare function bpmnFocusedLaneIndex(std: BlockStdScope, pool: BpmnPoolElementModel): number | null;
279
+ /**
280
+ * The selected pool whose focused lane can move one step `by` (`-1` up, `1`
281
+ * down), and the lane's index — `null` when no such pool is selected.
282
+ *
283
+ * The first one that qualifies: a lane is focused by clicking it, on one pool
284
+ * at a time, so a selection holding two pools still names at most one lane.
285
+ */
286
+ export declare function bpmnLaneToMove(std: BlockStdScope, by: -1 | 1): {
287
+ pool: BpmnPoolElementModel;
288
+ index: number;
289
+ } | null;
290
+ /** Move the focused lane of the selected pool one step up. */
291
+ export declare function moveBpmnLaneUp(std: BlockStdScope): void;
292
+ /** Move the focused lane of the selected pool one step down. */
293
+ export declare function moveBpmnLaneDown(std: BlockStdScope): void;
package/dist/actions.js CHANGED
@@ -1,16 +1,18 @@
1
- import { DefaultTool, EdgelessCRUDIdentifier, generateElementId, materializeInterchangeImport, reportInterchangeImport, runInterchangeImportFile, } from '@formicoidea/labre-core/blocks/surface';
1
+ import { backgroundInstanceZones, backgroundPlot, DefaultTool, EdgelessCRUDIdentifier, generateElementId, materializeInterchangeImport, reportInterchangeImport, runInterchangeImportFile, } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { ConnectorTool } from '@formicoidea/labre-core/gfx/connector';
3
3
  import { createGroupCommand } from '@formicoidea/labre-core/gfx/group';
4
- import { BpmnPoolElementModel, ConnectorMode, } from '@formicoidea/labre-core/model';
4
+ import { BpmnPoolElementModel, ConnectorElementModel, ConnectorMode, } from '@formicoidea/labre-core/model';
5
5
  import { NotificationProvider, translateKey, } from '@formicoidea/labre-core/shared/services';
6
6
  import { downloadBlob } from '@formicoidea/labre-core/shared/utils';
7
7
  import { Bound } from '@formicoidea/labre-core/global/gfx';
8
- import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
9
- import { BPMN_EDGE_STYLE, bpmnLabelMode, LANE_NAME_FALLBACK, LANE_NAME_KEY, NODE_LABEL, nodeLabelKey, NODE_SIZE, POOL_NAME_FALLBACK, POOL_NAME_KEY, } from './consts.js';
8
+ import { GfxControllerIdentifier, GfxGroupLikeElementModel, } from '@formicoidea/labre-core/std/gfx';
9
+ import { BPMN_POOL_BACKGROUND } from './background.js';
10
+ import { BPMN_EDGE_STYLE, LANE_NAME_FALLBACK, LANE_NAME_KEY, NODE_LABEL, nodeLabelKey, NODE_SIZE, POOL_NAME_FALLBACK, POOL_NAME_KEY, POOL_REF_HEIGHT, POOL_REF_WIDTH, } from './consts.js';
10
11
  import { BPMN_FORMAT_ID, exportBpmnXmlWithWarnings, } from './export.js';
12
+ import { bpmnLaneOf } from './facts.js';
11
13
  import { BPMN_SVG_IMPORT, BPMN_XML_EXPORT, BPMN_XML_FORMAT, BPMN_XML_IMPORT, bpmnBoardFrom, bpmnSafeFilename, } from './interchange.js';
12
- import { bpmnLabelBoxFor, bpmnLabelProps, bpmnNodeProps, bpmnPoolProps, } from './presets.js';
13
14
  import { BPMN_ROLE } from './roles.js';
15
+ import { bpmnNodeElements, bpmnPoolElement } from './toolbox.js';
14
16
  /**
15
17
  * Standalone creation/activation actions for the BPMN toolbox — lifted out of
16
18
  * `toolbar/bpmn-menu.ts` by PF3 so the menu becomes a pure renderer over the
@@ -52,30 +54,29 @@ export function createBpmnNode(std, kind) {
52
54
  return;
53
55
  const { w, h } = NODE_SIZE[kind];
54
56
  const { centerX: cx, centerY: cy } = gfx.viewport;
55
- // Translated HERE and once: the caption is document content the moment it
56
- // lands, so the host's catalogue is asked at placement and never again
57
- // (`nodeLabelKey`, and #183 on why a French board should not start in
58
- // English).
59
- const seed = NODE_LABEL[kind]
60
- ? translateKey(std, nodeLabelKey(kind), NODE_LABEL[kind])
61
- : undefined;
62
- const external = bpmnLabelMode(kind) === 'external';
63
- // What a node IS lives in one place (`./presets.ts`), because the importer
64
- // creates the same artefacts out of a `.bpmn` file and the two must not
65
- // drift: a task read from a file and a task drawn here are one element type
66
- // in the document, down to the stroke width. The gesture owns the BOX and
67
- // nothing else.
68
- const id = surface.addElement(bpmnNodeProps(kind, {
69
- xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
70
- text: external ? undefined : seed,
71
- }));
72
- if (!external || seed === undefined) {
73
- finish(gfx, id);
74
- return;
57
+ // The elements come from the ONE assembly a host with no editor also calls
58
+ // (`toolbox.ts`, `docs/adr/0045`), on the presets the importer reads too
59
+ // (`presets.ts`): a task drawn here, one read from a `.bpmn` file and one a
60
+ // host places are one element type in the document, down to the stroke
61
+ // width. What only an editor has stays here: the viewport centre, and the
62
+ // caption translated HERE and once (`nodeLabelKey`, and #183 on why a French
63
+ // board should not start in English).
64
+ const elements = bpmnNodeElements(kind, { x: cx - w / 2, y: cy - h / 2 }, { name: translateKey(std, nodeLabelKey(kind), NODE_LABEL[kind]) });
65
+ const minted = new Map();
66
+ for (const { id: local, ...props } of elements) {
67
+ if (props.type === 'group') {
68
+ // Through the native group command and not `addElement`: the group's
69
+ // seed title is numbered against the groups already on the board, and
70
+ // its user layer is its highest member's (ADR 0031) — both the editor's.
71
+ const children = Object.keys(props.children);
72
+ finish(gfx, group(gfx, children.map(child => minted.get(child) ?? child)));
73
+ return;
74
+ }
75
+ minted.set(local, surface.addElement(props));
75
76
  }
76
- const { x, y } = bpmnLabelBoxFor(kind, cx, cy);
77
- const labelId = surface.addElement(bpmnLabelProps(seed, x, y));
78
- finish(gfx, group(gfx, [id, labelId]));
77
+ const id = minted.get(`${kind}:node`);
78
+ if (id)
79
+ finish(gfx, id);
79
80
  }
80
81
  /** Create a pool (background container) centred on the viewport. */
81
82
  export function createBpmnPool(std) {
@@ -83,29 +84,27 @@ export function createBpmnPool(std) {
83
84
  const surface = gfx.surface;
84
85
  if (!surface)
85
86
  return;
86
- const w = 560;
87
- const h = 200;
88
87
  const { centerX: cx, centerY: cy } = gfx.viewport;
89
- // What a pool IS lives in one place (`./presets.ts`), beside what a node is
90
- // and for the same reason: the shipped cards build their participants from it
91
- // too, so a pool laid out by a template and one dropped from the toolbox
92
- // cannot say different things. The gesture owns the BOX and nothing else.
88
+ // What a pool IS lives in one place (`bpmnPoolElement`, `toolbox.ts`, over
89
+ // `bpmnPoolProps`), beside what a node is and for the same reason: the
90
+ // shipped cards and a host build their participants from it too, so a pool
91
+ // laid out by a template, one a host places and one dropped from the toolbox
92
+ // cannot say different things. The gesture owns the centre and nothing else.
93
93
  //
94
94
  // The NAME is the one exception, and it is written HERE rather than left to
95
95
  // the model's default (`BpmnPoolElementModel.name = 'Pool'`, a red-zone
96
96
  // field: a document created before this key existed keeps its literal
97
97
  // 'Pool' verbatim). Resolved at placement and never again — content from
98
98
  // the moment it lands, exactly like a node's caption.
99
- const id = surface.addElement(bpmnPoolProps({
100
- xywh: new Bound(cx - w / 2, cy - h / 2, w, h).serialize(),
101
- name: translateKey(std, POOL_NAME_KEY, POOL_NAME_FALLBACK),
102
- }));
103
- finish(gfx, id);
99
+ const { id: _local, ...props } = bpmnPoolElement({ x: cx - POOL_REF_WIDTH / 2, y: cy - POOL_REF_HEIGHT / 2 }, { name: translateKey(std, POOL_NAME_KEY, POOL_NAME_FALLBACK) });
100
+ finish(gfx, surface.addElement(props));
104
101
  }
105
102
  /**
106
103
  * Arm the native connector tool, pre-styled for a BPMN sequence flow:
107
104
  * orthogonal, solid, with a filled triangle head. The user then draws from
108
- * one node to another (endpoints attach to centers).
105
+ * one node to another, attaching at the anchor of each outline they pick (a
106
+ * gateway's corners, an activity's side midpoints, an event's cardinal points;
107
+ * `BPMN_CENTRE_ANCHORED`).
109
108
  */
110
109
  export function activateBpmnSequenceFlow(std) {
111
110
  gfxOf(std).tool.setTool(ConnectorTool, {
@@ -280,12 +279,14 @@ export function addBpmnLane(std) {
280
279
  }
281
280
  }
282
281
  /**
283
- * Remove the LAST lane of every selected pool.
282
+ * Remove the FOCUSED lane of every selected pool, or its LAST lane when none is
283
+ * focused.
284
284
  *
285
- * The last one and not the selected one, because a lane is not selectable: it
286
- * is a slice of the pool's plot, painted by the background primitive, with no
287
- * element of its own. Removing from the end is the gesture that undoes the one
288
- * that added it.
285
+ * Focused and not selected, because a lane is not selectable: it is a slice of
286
+ * the pool's plot, painted by the background primitive, with no element of its
287
+ * own. A single click on its title strip, in a selected pool, focuses it
288
+ * ({@link bpmnFocusedLaneIndex}). With nothing focused, removing from the end
289
+ * is the gesture that undoes the one that added it.
289
290
  *
290
291
  * ## Nothing moves
291
292
  *
@@ -304,7 +305,13 @@ export function removeBpmnLane(std) {
304
305
  return;
305
306
  std.store.captureSync();
306
307
  for (const model of withLanes) {
307
- writeLanes(std, model, bpmnLanesOf(model).slice(0, -1));
308
+ const lanes = bpmnLanesOf(model);
309
+ // The FOCUSED lane when the user picked one (a click on its title strip),
310
+ // the last one otherwise — which is still the one the previous "Add lane"
311
+ // added.
312
+ const focused = bpmnFocusedLaneIndex(std, model);
313
+ const gone = focused ?? lanes.length - 1;
314
+ writeLanes(std, model, lanes.filter((_, i) => i !== gone));
308
315
  }
309
316
  }
310
317
  /* ── Export (BPMN 2.0 XML) ────────────────────────────────────────────── */
@@ -510,3 +517,126 @@ export function renameBpmnLane(std, model, index, name) {
510
517
  std.store.captureSync();
511
518
  writeLanes(std, model, next);
512
519
  }
520
+ /**
521
+ * Move the lane at `from` to `to` inside `pool`, carrying what is drawn in it.
522
+ *
523
+ * A lane is a slice of the pool's plot, and what is "in" it is decided by
524
+ * geometry: the centre of an artefact, read by {@link bpmnLaneOf}. Reordering
525
+ * the `lanes` array alone would leave every task where it was on the canvas and
526
+ * silently hand it to whichever lane now covers that spot — the user moves the
527
+ * Sales lane up and finds their sales tasks filed under Ops. So the gesture
528
+ * moves both: the array is reordered, and every unlocked artefact wholly inside
529
+ * the pool is translated by the vertical shift of the lane its centre was in.
530
+ * Connectors attached to them follow through the connector watcher, as on any
531
+ * other move.
532
+ *
533
+ * A GESTURE-time write, not a cascade: it runs once, on the author's client,
534
+ * from a command, and a peer receives the reorder and the moves as ordinary
535
+ * updates. No stored-format change either: the same `lanes` array, the same
536
+ * `xywh` props.
537
+ *
538
+ * One `captureSync`, then one `transact`, so the reorder and every move are ONE
539
+ * undo step. Refused — and nothing written, so no empty undo entry — on a
540
+ * read-only document, a locked pool, an index that names no lane, or a move to
541
+ * where the lane already is. Returns whether it wrote.
542
+ *
543
+ * ponytail: a connector attached to nothing, drawn inside a lane, stays where
544
+ * it is, and a locked artefact is left behind; the upgrade is to translate the
545
+ * free endpoints too, here and nowhere else.
546
+ */
547
+ export function moveBpmnLane(std, pool, from, to) {
548
+ if (std.store.readonly || pool.isLocked())
549
+ return false;
550
+ if (from === to)
551
+ return false;
552
+ const lanes = bpmnLanesOf(pool);
553
+ if (!lanes[from] || !lanes[to])
554
+ return false;
555
+ const next = [...lanes];
556
+ const [moved] = next.splice(from, 1);
557
+ next.splice(to, 0, moved);
558
+ // Each band's top, before and after, read off the painted partition: the
559
+ // normalisation the renderer and the audit use, so an artefact moves by
560
+ // exactly the distance its lane moves on the canvas.
561
+ const frame = pool.elementBound;
562
+ const plot = backgroundPlot(BPMN_POOL_BACKGROUND, frame.w, frame.h);
563
+ const topsOf = (rows) => new Map(backgroundInstanceZones(BPMN_POOL_BACKGROUND, { lanes: rows }).map(zone => [zone.id, zone.rect.y * plot.height]));
564
+ const before = topsOf(lanes);
565
+ const after = topsOf(next);
566
+ const prefix = BPMN_POOL_BACKGROUND.instanceZones?.idPrefix ?? 'lane';
567
+ // Membership BEFORE the write: once the array is reordered the bands are
568
+ // where the lanes are going, and a centre would read the wrong lane.
569
+ const moves = [];
570
+ for (const element of gfxOf(std).grid.search(frame, { strict: true })) {
571
+ if (element === pool || element.isLocked())
572
+ continue;
573
+ // A connector follows its endpoints; a group or a mind map derives its
574
+ // bound from its children, which are moved on their own.
575
+ if (element instanceof ConnectorElementModel)
576
+ continue;
577
+ if (element instanceof GfxGroupLikeElementModel)
578
+ continue;
579
+ const bound = Bound.deserialize(element.xywh);
580
+ const lane = bpmnLaneOf(pool, bound);
581
+ if (!lane)
582
+ continue;
583
+ const key = `${prefix}:${lane.id}`;
584
+ const dy = (after.get(key) ?? 0) - (before.get(key) ?? 0);
585
+ if (dy === 0)
586
+ continue;
587
+ moves.push({ id: element.id, xywh: bound.moveDelta(0, dy).serialize() });
588
+ }
589
+ std.store.captureSync();
590
+ std.store.transact(() => {
591
+ writeLanes(std, pool, next);
592
+ const crud = std.get(EdgelessCRUDIdentifier);
593
+ for (const { id, xywh } of moves)
594
+ crud.updateElement(id, { xywh });
595
+ });
596
+ return true;
597
+ }
598
+ /**
599
+ * The index of `pool`'s focused lane in this editor, or `null` when none is
600
+ * focused or the focused one is gone (removed by a peer, undone away).
601
+ *
602
+ * Read through {@link BpmnLaneFocusHolder} rather than the view class, so the
603
+ * commands do not import the view.
604
+ */
605
+ export function bpmnFocusedLaneIndex(std, pool) {
606
+ const holder = gfxOf(std).view?.get(pool.id);
607
+ const id = holder?.focusedLaneId;
608
+ if (!id)
609
+ return null;
610
+ const index = bpmnLanesOf(pool).findIndex(lane => lane.id === id);
611
+ return index < 0 ? null : index;
612
+ }
613
+ /**
614
+ * The selected pool whose focused lane can move one step `by` (`-1` up, `1`
615
+ * down), and the lane's index — `null` when no such pool is selected.
616
+ *
617
+ * The first one that qualifies: a lane is focused by clicking it, on one pool
618
+ * at a time, so a selection holding two pools still names at most one lane.
619
+ */
620
+ export function bpmnLaneToMove(std, by) {
621
+ for (const pool of bpmnPoolsForLaneEdit(std)) {
622
+ const index = bpmnFocusedLaneIndex(std, pool);
623
+ if (index === null)
624
+ continue;
625
+ if (!bpmnLanesOf(pool)[index + by])
626
+ continue;
627
+ return { pool, index };
628
+ }
629
+ return null;
630
+ }
631
+ /** Move the focused lane of the selected pool one step up. */
632
+ export function moveBpmnLaneUp(std) {
633
+ const target = bpmnLaneToMove(std, -1);
634
+ if (target)
635
+ moveBpmnLane(std, target.pool, target.index, target.index - 1);
636
+ }
637
+ /** Move the focused lane of the selected pool one step down. */
638
+ export function moveBpmnLaneDown(std) {
639
+ const target = bpmnLaneToMove(std, 1);
640
+ if (target)
641
+ moveBpmnLane(std, target.pool, target.index, target.index + 1);
642
+ }
@@ -26,7 +26,15 @@ import { BPMN_ROLE } from './roles.js';
26
26
  *
27
27
  * The left margin IS the name band: `margin.left` is both the room the flow
28
28
  * area gives up and the width of the strip the participant name is written in
29
- * (see `BackgroundSideBandDef`).
29
+ * (see `BackgroundSideBandDef`) — its MINIMUM width. The name WRAPS up the band
30
+ * (`fit: 'wrap'`), on words, into as many as three lines side by side, and the
31
+ * band thickens to hold them: one line fits the declared 28 units, every
32
+ * further line adds one line height. The PO preferred this to the ellipsis cut
33
+ * PR #532 shipped (2026-10-27): a band that grows with its name reads as a
34
+ * title, a name cut short reads as a bug. A name past three lines is still cut
35
+ * with "…" on the last one, so a pasted paragraph does not eat the pool. The
36
+ * thickness is derived at paint time (`backgroundBandWidths`) and never
37
+ * stored, and the stored name stays whole.
30
38
  *
31
39
  * ## The lanes
32
40
  *
@@ -43,6 +51,10 @@ import { BPMN_ROLE } from './roles.js';
43
51
  *
44
52
  * Each lane wears its own title band inside the pool's — a narrower strip, no
45
53
  * fill, one divider, the name turned on its side, exactly as BPMN 2.0 draws it.
54
+ * A lane name wraps the same way, up to two lines, and every lane's strip takes
55
+ * the thickness the most demanding name needs (PO, 2026-10-27): strips of
56
+ * different widths would stagger the lanes' leading edges, and lanes of one
57
+ * pool are siblings, so they share one edge.
46
58
  * The strip is CHROME inside the lane and not a smaller lane: a task dropped on
47
59
  * a lane's title band is in that lane, because in BPMN the band belongs to it.
48
60
  */
@@ -99,6 +111,13 @@ export const BPMN_POOL_BACKGROUND = {
99
111
  // pool is the same stroke, so the lanes read as the frame continued.
100
112
  divider: { color: '@frame', width: POOL_FRAME_WIDTH },
101
113
  },
114
+ // Wrapped across the strip, which grows to hold the lines — every lane's
115
+ // strip to the same thickness. Two lines at most: a lane title is a
116
+ // label, and past two a third line would cost the flow area more than
117
+ // the name gains; past that it is cut with "…". The stored name stays
118
+ // whole, and the rename editor shows it in full.
119
+ fit: 'wrap',
120
+ maxLines: 2,
102
121
  style: {
103
122
  size: POOL_LANE_NAME_FONT_SIZE,
104
123
  color: '@name',
@@ -151,6 +170,12 @@ export const BPMN_POOL_BACKGROUND = {
151
170
  },
152
171
  // Read bottom-to-top, as the spec draws a vertical pool name.
153
172
  vertical: true,
173
+ // Wrapped up the band, which grows to hold the lines (three at
174
+ // most, the last cut with "…" if the name is longer still). The
175
+ // anchor stays half the DECLARED width in from the plot edge; the
176
+ // primitive keeps it in the middle of the grown band.
177
+ fit: 'wrap',
178
+ maxLines: 3,
154
179
  },
155
180
  },
156
181
  ],
@@ -199,6 +199,22 @@ export const bpmnCommandsManifest = [
199
199
  scope: 'edgeless',
200
200
  defaultKeys: { mac: [], other: [] },
201
201
  },
202
+ {
203
+ id: 'bpmn.moveLaneUp',
204
+ owner: 'bpmn',
205
+ labelKey: 'com.labre.commands.bpmn.moveLaneUp',
206
+ labelFallback: 'Move lane up',
207
+ scope: 'edgeless',
208
+ defaultKeys: { mac: [], other: [] },
209
+ },
210
+ {
211
+ id: 'bpmn.moveLaneDown',
212
+ owner: 'bpmn',
213
+ labelKey: 'com.labre.commands.bpmn.moveLaneDown',
214
+ labelFallback: 'Move lane down',
215
+ scope: 'edgeless',
216
+ defaultKeys: { mac: [], other: [] },
217
+ },
202
218
  {
203
219
  id: 'bpmn.exportXml',
204
220
  owner: 'bpmn',
package/dist/commands.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { StrokeStyle } from '@formicoidea/labre-core/model';
2
- import { activateBpmnAssociation, activateBpmnMessageFlow, activateBpmnSequenceFlow, addBpmnLane, bpmnLanesOf, bpmnPoolsForLaneEdit, bpmnPoolsSelected, createBpmnNode, createBpmnPool, exportBpmnXmlFile, importBpmnSvgFile, importBpmnXmlFile, removeBpmnLane, } from './actions.js';
2
+ import { activateBpmnAssociation, activateBpmnMessageFlow, activateBpmnSequenceFlow, addBpmnLane, bpmnLanesOf, bpmnPoolsForLaneEdit, bpmnPoolsSelected, createBpmnNode, createBpmnPool, exportBpmnXmlFile, importBpmnSvgFile, importBpmnXmlFile, bpmnLaneToMove, moveBpmnLaneDown, moveBpmnLaneUp, removeBpmnLane, } from './actions.js';
3
3
  import { BPMN_EDGE_STYLE, NODE_FILL, NODE_SIZE, } from './consts.js';
4
4
  import { bpmnNodeProps } from './presets.js';
5
5
  import { BPMN_ROLE, BPMN_ROLE_OF_KIND } from './roles.js';
6
- import { bpmnAssociationIcon, bpmnCallActivityIcon, bpmnDataObjectIcon, bpmnDataStoreIcon, bpmnEndIcon, bpmnEndMessageIcon, bpmnEndTerminateIcon, bpmnExportXmlIcon, bpmnGatewayIcon, bpmnGatewayParallelIcon, bpmnGroupIcon, bpmnImportSvgIcon, bpmnImportXmlIcon, bpmnLaneAddIcon, bpmnLaneRemoveIcon, bpmnMessageIcon, bpmnPoolIcon, bpmnSequenceIcon, bpmnStartIcon, bpmnStartMessageIcon, bpmnStartTimerIcon, bpmnSubProcessIcon, bpmnTaskIcon, bpmnTaskServiceIcon, bpmnTaskUserIcon, bpmnTextAnnotationIcon, bpmnToolbarIcon, } from './toolbar/icons.js';
6
+ import { bpmnAssociationIcon, bpmnCallActivityIcon, bpmnDataObjectIcon, bpmnDataStoreIcon, bpmnEndIcon, bpmnEndMessageIcon, bpmnEndTerminateIcon, bpmnExportXmlIcon, bpmnGatewayIcon, bpmnGatewayParallelIcon, bpmnGroupIcon, bpmnImportSvgIcon, bpmnImportXmlIcon, bpmnLaneAddIcon, bpmnLaneDownIcon, bpmnLaneRemoveIcon, bpmnLaneUpIcon, bpmnMessageIcon, bpmnPoolIcon, bpmnSequenceIcon, bpmnStartIcon, bpmnStartMessageIcon, bpmnStartTimerIcon, bpmnSubProcessIcon, bpmnTaskIcon, bpmnTaskServiceIcon, bpmnTaskUserIcon, bpmnTextAnnotationIcon, bpmnToolbarIcon, } from './toolbar/icons.js';
7
7
  /**
8
8
  * What a spec DOES — its own gesture, or the one its {@link Spec.node} implies.
9
9
  *
@@ -493,7 +493,7 @@ const laneCommands = [
493
493
  labelKey: 'com.labre.commands.bpmn.removeLane',
494
494
  labelFallback: 'Remove lane',
495
495
  descriptionKey: 'com.labre.commands.bpmn.removeLane.description',
496
- descriptionFallback: 'Remove the last lane of the selected pool. Nothing drawn in it moves.',
496
+ descriptionFallback: 'Remove the focused lane of the selected pool, or its last lane. Nothing drawn in it moves.',
497
497
  iconKey: 'bpmn.lane-remove',
498
498
  order: SPECS.length + 1,
499
499
  run: removeBpmnLane,
@@ -501,6 +501,36 @@ const laneCommands = [
501
501
  // …and additionally: a pool with no lane has none to remove.
502
502
  when: std => bpmnPoolsForLaneEdit(std).some(model => bpmnLanesOf(model).length > 0),
503
503
  },
504
+ // The two REORDER commands act on the FOCUSED lane — the one a single click
505
+ // on its title strip picked in the selected pool (`bpmnFocusedLaneIndex`).
506
+ // A lane is not an element and cannot be selected, so without a focus there
507
+ // is no lane to move and the commands are not offered at all.
508
+ {
509
+ ...LANE_COMMON,
510
+ id: 'bpmn.moveLaneUp',
511
+ labelKey: 'com.labre.commands.bpmn.moveLaneUp',
512
+ labelFallback: 'Move lane up',
513
+ descriptionKey: 'com.labre.commands.bpmn.moveLaneUp.description',
514
+ descriptionFallback: 'Move the focused lane above the one before it, with everything drawn in it.',
515
+ iconKey: 'bpmn.lane-up',
516
+ order: SPECS.length + 5,
517
+ run: moveBpmnLaneUp,
518
+ telemetry: { framework: 'bpmn', element: 'pool:lane-move-up' },
519
+ when: std => bpmnLaneToMove(std, -1) !== null,
520
+ },
521
+ {
522
+ ...LANE_COMMON,
523
+ id: 'bpmn.moveLaneDown',
524
+ labelKey: 'com.labre.commands.bpmn.moveLaneDown',
525
+ labelFallback: 'Move lane down',
526
+ descriptionKey: 'com.labre.commands.bpmn.moveLaneDown.description',
527
+ descriptionFallback: 'Move the focused lane below the one after it, with everything drawn in it.',
528
+ iconKey: 'bpmn.lane-down',
529
+ order: SPECS.length + 6,
530
+ run: moveBpmnLaneDown,
531
+ telemetry: { framework: 'bpmn', element: 'pool:lane-move-down' },
532
+ when: std => bpmnLaneToMove(std, 1) !== null,
533
+ },
504
534
  ];
505
535
  /**
506
536
  * The EXPORT — the first BPMN command whose subject is the whole board.
@@ -721,6 +751,8 @@ export const bpmnCommandIcons = {
721
751
  'bpmn.pool': bpmnPoolIcon,
722
752
  'bpmn.lane-add': bpmnLaneAddIcon,
723
753
  'bpmn.lane-remove': bpmnLaneRemoveIcon,
754
+ 'bpmn.lane-up': bpmnLaneUpIcon,
755
+ 'bpmn.lane-down': bpmnLaneDownIcon,
724
756
  'bpmn.export-xml': bpmnExportXmlIcon,
725
757
  'bpmn.import-xml': bpmnImportXmlIcon,
726
758
  'bpmn.import-svg': bpmnImportSvgIcon,
package/dist/consts.d.ts CHANGED
@@ -268,8 +268,8 @@ export declare const POOL_LANE_MIN_HEIGHT = 24;
268
268
  * participant, and a second lane that matched the first one's height would
269
269
  * claim room the process has not asked for.
270
270
  *
271
- * `actions.ts` and the templates still write these two numbers themselves; the
272
- * declaration names them so there is somewhere for them to converge.
271
+ * The toolbox draws its pool at them (`bpmnPoolElement`, which `createBpmnPool`
272
+ * calls); the templates lay their pools out at sizes of their own.
273
273
  */
274
274
  export declare const POOL_REF_WIDTH = 560;
275
275
  export declare const POOL_REF_HEIGHT = 200;
package/dist/consts.js CHANGED
@@ -329,8 +329,8 @@ export const POOL_LANE_MIN_HEIGHT = 24;
329
329
  * participant, and a second lane that matched the first one's height would
330
330
  * claim room the process has not asked for.
331
331
  *
332
- * `actions.ts` and the templates still write these two numbers themselves; the
333
- * declaration names them so there is somewhere for them to converge.
332
+ * The toolbox draws its pool at them (`bpmnPoolElement`, which `createBpmnPool`
333
+ * calls); the templates lay their pools out at sizes of their own.
334
334
  */
335
335
  export const POOL_REF_WIDTH = 560;
336
336
  export const POOL_REF_HEIGHT = 200;
@@ -1,18 +1,26 @@
1
1
  import { type ElementRenderer } from '@formicoidea/labre-core/blocks/surface';
2
2
  import type { BpmnPoolElementModel } from '@formicoidea/labre-core/model';
3
3
  /**
4
- * Canvas renderer for the BPMN pool.
4
+ * Canvas renderer for the BPMN pool: the declared background, then the outline
5
+ * of the FOCUSED lane, if this editor has one (`BpmnLaneFocusHolder`, held by
6
+ * the pool's view and never stored).
5
7
  *
6
- * There is no BPMN drawing code any more: the pool is an INSTANTIATION of the
7
- * framework-background primitive, configured by the `BPMN_POOL_BACKGROUND`
8
- * declaration. What used to be ninety lines of hand-traced rounded rectangle,
9
- * filled band and rotated name is now a declaration any other framework can
10
- * write for itself.
8
+ * The outline is the editor talking about the drawing — "this is the lane the
9
+ * reorder commands will move" — not part of it, so it is the theme's accent
10
+ * (`--affine-primary-color`, resolved through the renderer) rather than a
11
+ * notation colour. It is painted only onto the editor's OWN canvases: the SVG
12
+ * and PNG exports paint through this same renderer into a context of their
13
+ * own, and a file must not carry an on-screen focus. A renderer with no editor
14
+ * behind it (a bare unit test) paints none either, and a host that defines no
15
+ * accent gets none.
16
+ *
17
+ * Painted in the transform the primitive left on the context — element-local
18
+ * units, rotated about the centre like the rest of the pool.
11
19
  *
12
20
  * Exported as a function as well as an extension because the fidelity suite
13
21
  * drives it directly with a canvas stub.
14
22
  */
15
23
  export declare const bpmnPool: ElementRenderer<BpmnPoolElementModel>;
16
- export declare const BpmnPoolRendererExtension: import("@formicoidea/labre-core/_pkgs/store").ExtensionType & {
17
- identifier: import("@formicoidea/labre-core/_pkgs/global/di").ServiceIdentifier<ElementRenderer<BpmnPoolElementModel>>;
24
+ export declare const BpmnPoolRendererExtension: import("@formicoidea/labre-core/_pkgs/store/index.js").ExtensionType & {
25
+ identifier: import("@formicoidea/labre-core/_pkgs/global/di/types.js").ServiceIdentifier<ElementRenderer<BpmnPoolElementModel>>;
18
26
  };