@uipath/maestro-builder-sdk 6.66.4 → 6.66.5

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/merge.d.ts CHANGED
@@ -18,7 +18,8 @@
18
18
  * what a faithful round-trip of the original produces. When a node in
19
19
  * `edited` is byte-equal to its `baseline` counterpart, the user did NOT
20
20
  * touch it, so the merge keeps the ORIGINAL node verbatim (its real type,
21
- * inputs, layout — even for a placeholder). Only nodes the user actually
21
+ * inputs, layout — even for a placeholder; its position moves only to make
22
+ * room for an added node, see node-placement.ts). Only nodes the user actually
22
23
  * changed (or added) are taken from `edited`, and a changed node takes only
23
24
  * the fields that differ from the baseline (see withEdits).
24
25
  * - Without a `baseline`, the merge is two-way: it takes `edited`'s graph and
@@ -27,7 +28,8 @@
27
28
  *
28
29
  * Layout, `metadata`, `solutionId`, `projectId`, `runtime` and `layout` are
29
30
  * preserved from the original, apart from the `layout.nodes` entries of
30
- * removed nodes (dropped) and added nodes (node-placement.ts); `subflows` and
31
+ * removed nodes (dropped) and added nodes, and the positions and container
32
+ * sizes placement moves to make room for them (node-placement.ts); `subflows` and
31
33
  * edges (with their ids) come from `edited`; root `variables` merge as a
32
34
  * delta per entry, like nodes (merge-variables.ts); `definitions` and
33
35
  * `bindings` are unioned
package/dist/merge.js CHANGED
@@ -18,7 +18,8 @@
18
18
  * what a faithful round-trip of the original produces. When a node in
19
19
  * `edited` is byte-equal to its `baseline` counterpart, the user did NOT
20
20
  * touch it, so the merge keeps the ORIGINAL node verbatim (its real type,
21
- * inputs, layout — even for a placeholder). Only nodes the user actually
21
+ * inputs, layout — even for a placeholder; its position moves only to make
22
+ * room for an added node, see node-placement.ts). Only nodes the user actually
22
23
  * changed (or added) are taken from `edited`, and a changed node takes only
23
24
  * the fields that differ from the baseline (see withEdits).
24
25
  * - Without a `baseline`, the merge is two-way: it takes `edited`'s graph and
@@ -27,7 +28,8 @@
27
28
  *
28
29
  * Layout, `metadata`, `solutionId`, `projectId`, `runtime` and `layout` are
29
30
  * preserved from the original, apart from the `layout.nodes` entries of
30
- * removed nodes (dropped) and added nodes (node-placement.ts); `subflows` and
31
+ * removed nodes (dropped) and added nodes, and the positions and container
32
+ * sizes placement moves to make room for them (node-placement.ts); `subflows` and
31
33
  * edges (with their ids) come from `edited`; root `variables` merge as a
32
34
  * delta per entry, like nodes (merge-variables.ts); `definitions` and
33
35
  * `bindings` are unioned
@@ -432,8 +434,9 @@ function mergeFlowFindings(original, edited, options) {
432
434
  // ── Placement: a node the edit ADDED has no original position. Unless the
433
435
  // author set one, it has compile's placeholder, an emission-index slot
434
436
  // that can land on an original node. Put it next to its predecessor,
435
- // collision-free, and store every added node's position where the
436
- // original stores positions — see node-placement.ts. Root scope only: a
437
+ // moving the nodes downstream of it and growing its containers to make
438
+ // room, and store every added node's position where the original stores
439
+ // positions — see node-placement.ts. Root scope only: a
437
440
  // subflow is taken from `edited` whole, layout included. ──
438
441
  placeAddedNodes(finalNodes, result.edges, positionStoreOf(original) && layoutNodesOf(result), (id) => !origNodes.has(id));
439
442
  // ── Definitions: the (type, version) pairs used by EVERY node in the file —
@@ -458,7 +461,7 @@ function mergeFlowFindings(original, edited, options) {
458
461
  // metadata / solutionId / projectId / layout / runtime were carried by the
459
462
  // initial clone of `original` and are intentionally left untouched, apart
460
463
  // from the `layout.nodes` entries of removed nodes (dropped above) and added
461
- // nodes (placement).
464
+ // nodes, and the positions and container sizes placement moves to make room.
462
465
  //
463
466
  // The declared file-format `version` follows the same delta rule the nodes
464
467
  // do. The original's wins by default, because a compile of an edited source
@@ -6,15 +6,34 @@
6
6
  * `x = 256 + 288 × index, y = 144` (`+160` inside a container). Merge replaces
7
7
  * that placeholder on each node the edit added:
8
8
  *
9
- * - next to a positioned predecessor in the same container: one slot right;
9
+ * - right of its rightmost positioned predecessor in the same container, past
10
+ * its width, on that predecessor's row; when the node leads into another
11
+ * lane (its first successor's row does not meet the predecessor's) and that
12
+ * successor is no join another lane already feeds, on that lane's row; when
13
+ * its direct successor is tucked under the predecessor (a context index
14
+ * under its agent), in that successor's spot, pushing it a slot;
10
15
  * - else, when the incoming edge comes from the node's own container (the first
11
16
  * node of a loop body): at the body origin, inside the container's padding;
12
- * - else, one slot left of a positioned successor in the same container;
13
- * - then the nearest free spot around that candidate.
17
+ * - else, one slot left of a positioned successor in the same container, or in
18
+ * its place when that is taken.
19
+ *
20
+ * A new branch whose spot holds another new branch out of the same
21
+ * predecessor takes the next row down instead of pushing it. A sticky note
22
+ * moves with the node it covers most (by id on a tie); one over no node stays.
23
+ *
24
+ * Added nodes are placed in waves by id, each node once every predecessor it
25
+ * has in its container is placed, so the result does not depend on the order
26
+ * of the node or edge arrays, and a join lands right of all its branches.
27
+ *
28
+ * When the spot is taken, or a successor would sit left of the new node, the
29
+ * nodes downstream of the new one, the ones it lands on, and whatever they
30
+ * would land on move right to make room; when one of them has to stay, the
31
+ * new node takes the nearest free spot instead. Each container that gained or
32
+ * moved a child then grows to fit it, and pushes what is past its old edges.
14
33
  *
15
34
  * Example: the edit inserts `auditLog` between `extractInvoice (448,144)` and
16
- * `routeInvoice (640,144)`. The candidate `(640,144)` is taken, so `auditLog`
17
- * goes to the first free spot, `(640,264)`.
35
+ * `routeInvoice (640,144)`. `auditLog` takes `(640,144)`; `routeInvoice` and
36
+ * `end` move 192 right.
18
37
  *
19
38
  * A child's position is relative to its container, as on the canvas.
20
39
  *
@@ -66,21 +85,32 @@ interface PlacedNode {
66
85
  [k: string]: unknown;
67
86
  }
68
87
  /**
69
- * Give each node the edit added a position next to its predecessor, in place.
88
+ * Give each node the edit added a position next to its predecessor, in place,
89
+ * making room for it.
70
90
  *
71
91
  * A node is placed when it is not in the original and its position is a
72
- * compile placeholder (an author `.layout()` position is kept). Its candidate,
73
- * in order: one slot right of the first predecessor in the same container that
74
- * already has a position (an original node, or an added node placed before it);
75
- * the body origin, when an edge enters it from its own container; one slot left
76
- * of a positioned successor in the same container. With none, the node keeps
77
- * its placeholder, and placed nodes avoid it.
92
+ * compile placeholder; the module header lists where it goes. When the spot is
93
+ * taken, or one of its first positioned successors would sit left of its
94
+ * right edge, the nodes past the insertion point that it lands on, and every
95
+ * node downstream of the new one, move right by the room it needs; so does
96
+ * whatever a moved node would then land on, and its downstream. Downstream
97
+ * reaches through added nodes not placed yet. When that would move a node that
98
+ * has to stay (an author position, an added node that can never be anchored),
99
+ * nothing moves and the node takes the nearest free spot. Then each container
100
+ * that gained or moved a child grows as far as those children need, keeping
101
+ * its padding, and pushes the siblings past its old right or bottom edge the
102
+ * same way, up to the root, keeping at least the row gap to its new edge.
103
+ *
104
+ * An author `.layout()` position is kept when a node is placed, but a growing
105
+ * container moves one in its way or downstream of it past its old edge (it
106
+ * was chosen against the old size), so growth never covers a node.
78
107
  *
79
108
  * Where the result is written follows the file: when the original stores
80
109
  * positions in `layout.nodes` (every canvas-saved file does), every added
81
110
  * node's `node.ui`, placed or not, sticky notes included, replaces its
82
111
  * `layout.nodes` entry, so the node has one position; otherwise
83
- * `node.ui.position` is updated.
112
+ * `node.ui.position` is updated. A moved or grown node keeps each field
113
+ * where it already is.
84
114
  *
85
115
  * @param nodes - The merged root nodes (mutated).
86
116
  * @param edges - The merged root edges.
@@ -6,15 +6,34 @@
6
6
  * `x = 256 + 288 × index, y = 144` (`+160` inside a container). Merge replaces
7
7
  * that placeholder on each node the edit added:
8
8
  *
9
- * - next to a positioned predecessor in the same container: one slot right;
9
+ * - right of its rightmost positioned predecessor in the same container, past
10
+ * its width, on that predecessor's row; when the node leads into another
11
+ * lane (its first successor's row does not meet the predecessor's) and that
12
+ * successor is no join another lane already feeds, on that lane's row; when
13
+ * its direct successor is tucked under the predecessor (a context index
14
+ * under its agent), in that successor's spot, pushing it a slot;
10
15
  * - else, when the incoming edge comes from the node's own container (the first
11
16
  * node of a loop body): at the body origin, inside the container's padding;
12
- * - else, one slot left of a positioned successor in the same container;
13
- * - then the nearest free spot around that candidate.
17
+ * - else, one slot left of a positioned successor in the same container, or in
18
+ * its place when that is taken.
19
+ *
20
+ * A new branch whose spot holds another new branch out of the same
21
+ * predecessor takes the next row down instead of pushing it. A sticky note
22
+ * moves with the node it covers most (by id on a tie); one over no node stays.
23
+ *
24
+ * Added nodes are placed in waves by id, each node once every predecessor it
25
+ * has in its container is placed, so the result does not depend on the order
26
+ * of the node or edge arrays, and a join lands right of all its branches.
27
+ *
28
+ * When the spot is taken, or a successor would sit left of the new node, the
29
+ * nodes downstream of the new one, the ones it lands on, and whatever they
30
+ * would land on move right to make room; when one of them has to stay, the
31
+ * new node takes the nearest free spot instead. Each container that gained or
32
+ * moved a child then grows to fit it, and pushes what is past its old edges.
14
33
  *
15
34
  * Example: the edit inserts `auditLog` between `extractInvoice (448,144)` and
16
- * `routeInvoice (640,144)`. The candidate `(640,144)` is taken, so `auditLog`
17
- * goes to the first free spot, `(640,264)`.
35
+ * `routeInvoice (640,144)`. `auditLog` takes `(640,144)`; `routeInvoice` and
36
+ * `end` move 192 right.
18
37
  *
19
38
  * A child's position is relative to its container, as on the canvas.
20
39
  *
@@ -37,6 +56,10 @@ const PLACEHOLDER = { x0: 256, dx: 288, y0: 144, nestedDy: 160 };
37
56
  const DEFAULT_SIZE = 96;
38
57
  /** One slot to the right of a node: its width plus an equal gap, the spacing of a canvas row. */
39
58
  const SLOT_DX = 192;
59
+ /** The space between two nodes in a canvas row. */
60
+ const GAP = SLOT_DX - DEFAULT_SIZE;
61
+ /** The box a container with no size gets (flow-core's container default). */
62
+ const CONTAINER_SIZE = { width: 560, height: 320 };
40
63
  /** flow-core `findFreePosition` defaults. */
41
64
  const PADDING = 24;
42
65
  const STEP = 40;
@@ -69,6 +92,8 @@ export function isPlaceholderPosition(p) {
69
92
  }
70
93
  const overlaps = (a, b) => !(a.x + a.width + PADDING <= b.x || b.x + b.width + PADDING <= a.x
71
94
  || a.y + a.height + PADDING <= b.y || b.y + b.height + PADDING <= a.y);
95
+ /** Whether two boxes share a row: their vertical extents meet, padding included. */
96
+ const rowsMeet = (a, b) => a.y < b.y + b.height + PADDING && b.y < a.y + a.height + PADDING;
72
97
  /** flow-core `findFreePosition`: the candidate, else the first free spot on rings around it. */
73
98
  function findFreePosition(candidate, size, others) {
74
99
  const free = (p) => !others.some((o) => overlaps({ ...p, ...size }, o));
@@ -98,22 +123,41 @@ function moveAddedUi(nodes, layoutNodes, isAdded) {
98
123
  delete n.ui;
99
124
  }
100
125
  }
126
+ /** What a push does when a moved node would land on one it may not move. */
127
+ var Blocked;
128
+ (function (Blocked) {
129
+ /** The push is called off: nothing moves. */
130
+ Blocked[Blocked["Refuse"] = 0] = "Refuse";
131
+ /** That node moves too. */
132
+ Blocked[Blocked["Push"] = 1] = "Push";
133
+ })(Blocked || (Blocked = {}));
101
134
  /**
102
- * Give each node the edit added a position next to its predecessor, in place.
135
+ * Give each node the edit added a position next to its predecessor, in place,
136
+ * making room for it.
103
137
  *
104
138
  * A node is placed when it is not in the original and its position is a
105
- * compile placeholder (an author `.layout()` position is kept). Its candidate,
106
- * in order: one slot right of the first predecessor in the same container that
107
- * already has a position (an original node, or an added node placed before it);
108
- * the body origin, when an edge enters it from its own container; one slot left
109
- * of a positioned successor in the same container. With none, the node keeps
110
- * its placeholder, and placed nodes avoid it.
139
+ * compile placeholder; the module header lists where it goes. When the spot is
140
+ * taken, or one of its first positioned successors would sit left of its
141
+ * right edge, the nodes past the insertion point that it lands on, and every
142
+ * node downstream of the new one, move right by the room it needs; so does
143
+ * whatever a moved node would then land on, and its downstream. Downstream
144
+ * reaches through added nodes not placed yet. When that would move a node that
145
+ * has to stay (an author position, an added node that can never be anchored),
146
+ * nothing moves and the node takes the nearest free spot. Then each container
147
+ * that gained or moved a child grows as far as those children need, keeping
148
+ * its padding, and pushes the siblings past its old right or bottom edge the
149
+ * same way, up to the root, keeping at least the row gap to its new edge.
150
+ *
151
+ * An author `.layout()` position is kept when a node is placed, but a growing
152
+ * container moves one in its way or downstream of it past its old edge (it
153
+ * was chosen against the old size), so growth never covers a node.
111
154
  *
112
155
  * Where the result is written follows the file: when the original stores
113
156
  * positions in `layout.nodes` (every canvas-saved file does), every added
114
157
  * node's `node.ui`, placed or not, sticky notes included, replaces its
115
158
  * `layout.nodes` entry, so the node has one position; otherwise
116
- * `node.ui.position` is updated.
159
+ * `node.ui.position` is updated. A moved or grown node keeps each field
160
+ * where it already is.
117
161
  *
118
162
  * @param nodes - The merged root nodes (mutated).
119
163
  * @param edges - The merged root edges.
@@ -123,56 +167,441 @@ function moveAddedUi(nodes, layoutNodes, isAdded) {
123
167
  export function placeAddedNodes(nodes, edges, layoutNodes, isAdded) {
124
168
  if (layoutNodes)
125
169
  moveAddedUi(nodes, layoutNodes, isAdded);
170
+ const s = sceneOf(nodes, edges, layoutNodes, isAdded);
171
+ const before = new Map([...s.containers].map((id) => [id, bodyExtent(s, id, (o) => !isAdded(o.id))]));
172
+ placeInWaves(s);
173
+ fitContainers(s, before);
174
+ }
175
+ function sceneOf(nodes, edges, layoutNodes, isAdded) {
126
176
  const byId = new Map(nodes.map((n) => [n.id, n]));
127
- const uiOf = (n) => {
128
- const entry = layoutNodes?.[n.id];
177
+ const outOf = new Map();
178
+ const inOf = new Map();
179
+ for (const e of edges) {
180
+ if (!outOf.has(e.sourceNodeId))
181
+ outOf.set(e.sourceNodeId, []);
182
+ if (!inOf.has(e.targetNodeId))
183
+ inOf.set(e.targetNodeId, []);
184
+ outOf.get(e.sourceNodeId).push(e.targetNodeId);
185
+ inOf.get(e.targetNodeId).push(e.sourceNodeId);
186
+ }
187
+ const s = {
188
+ nodes,
189
+ byId,
190
+ layoutNodes,
191
+ isAdded,
192
+ containers: new Set(nodes.flatMap((n) => (n.parentId !== undefined && byId.has(n.parentId) ? [n.parentId] : []))),
193
+ outOf,
194
+ inOf,
195
+ pending: new Set(),
196
+ placed: new Set(),
197
+ anchored: new Set(),
198
+ toFit: new Set(),
199
+ notesOf: new Map(),
200
+ };
201
+ for (const n of nodes) {
202
+ if (isAdded(n.id) && n.type !== STICKY_NOTE_TYPE && isPlaceholderPosition(uiOf(s, n)?.position))
203
+ s.pending.add(n.id);
204
+ }
205
+ // A pending node no edge path links to a positioned one in its container
206
+ // (or to the container's entry) is never placed: it keeps its placeholder,
207
+ // and nothing may land on it.
208
+ const reach = [...nodes.filter((n) => isPositioned(s, n.id)).map((n) => n.id),
209
+ ...[...s.pending].filter((id) => enteredFromContainer(s, id))];
210
+ for (const id of reach)
211
+ s.anchored.add(id);
212
+ for (let i = 0; i < reach.length; i++) {
213
+ const scope = byId.get(reach[i])?.parentId;
214
+ for (const t of [...(outOf.get(reach[i]) ?? []), ...(inOf.get(reach[i]) ?? [])]) {
215
+ if (s.anchored.has(t) || !s.pending.has(t) || byId.get(t)?.parentId !== scope)
216
+ continue;
217
+ s.anchored.add(t);
218
+ reach.push(t);
219
+ }
220
+ }
221
+ assignNotes(s);
222
+ return s;
223
+ }
224
+ /**
225
+ * Give each sticky note to the positioned node in its container it covers
226
+ * most, by id on a tie, so the note moves with it; a note over no node stays.
227
+ */
228
+ function assignNotes(s) {
229
+ const area = (a, b) => Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
230
+ for (const note of s.nodes) {
231
+ if (note.type !== STICKY_NOTE_TYPE || !hasPosition(s, note))
232
+ continue;
233
+ const r = rectOf(s, note);
234
+ const [owner] = positionedIn(s, note.parentId)
235
+ .map((o) => ({ id: o.id, covered: area(r, rectOf(s, o)) }))
236
+ .filter(({ covered }) => covered > 0)
237
+ .sort((a, b) => b.covered - a.covered || a.id.localeCompare(b.id));
238
+ if (!owner)
239
+ continue;
240
+ if (!s.notesOf.has(owner.id))
241
+ s.notesOf.set(owner.id, []);
242
+ s.notesOf.get(owner.id).push(note.id);
243
+ }
244
+ }
245
+ // ── Reading and writing positions ──
246
+ function uiOf(s, n) {
247
+ const entry = s.layoutNodes?.[n.id];
248
+ const own = n.ui;
249
+ return entry ? { ...own, ...entry } : own;
250
+ }
251
+ function sizeOf(s, n) {
252
+ const fallback = s.containers.has(n.id) ? CONTAINER_SIZE : { width: DEFAULT_SIZE, height: DEFAULT_SIZE };
253
+ return { width: uiOf(s, n)?.size?.width ?? fallback.width, height: uiOf(s, n)?.size?.height ?? fallback.height };
254
+ }
255
+ const rectOf = (s, n) => ({ ...uiOf(s, n).position, ...sizeOf(s, n) });
256
+ /** Write each field where it already lives; a new one goes where the file stores positions. */
257
+ function write(s, n, patch) {
258
+ for (const [key, value] of Object.entries(patch)) {
129
259
  const own = n.ui;
130
- return entry ? { ...own, ...entry } : own;
260
+ const entry = s.layoutNodes?.[n.id];
261
+ if (s.layoutNodes && (entry?.[key] !== undefined || own?.[key] === undefined))
262
+ s.layoutNodes[n.id] = { ...entry, [key]: value };
263
+ else
264
+ n.ui = { ...own, [key]: value };
265
+ }
266
+ }
267
+ /** Move `ids`, and the sticky notes they carry, by `by`. */
268
+ function shift(s, ids, by) {
269
+ for (const id of [...ids, ...[...ids].flatMap((i) => s.notesOf.get(i) ?? [])]) {
270
+ const o = s.byId.get(id);
271
+ const p = uiOf(s, o).position;
272
+ write(s, o, { position: { x: p.x + by.x, y: p.y + by.y } });
273
+ }
274
+ }
275
+ // ── What may move ──
276
+ const hasPosition = (s, o) => uiOf(s, o)?.position !== undefined;
277
+ function isPositioned(s, id) {
278
+ const n = s.byId.get(id);
279
+ return n !== undefined && !s.pending.has(id) && hasPosition(s, n);
280
+ }
281
+ /** Positioned, and not an author `.layout()` position: those stay where the author put them. */
282
+ const isMovable = (s, n) => isPositioned(s, n.id) && (!s.isAdded(n.id) || s.placed.has(n.id));
283
+ /** Whether an edge enters `id` from its own container, which is in the scope: the first node of a body. */
284
+ function enteredFromContainer(s, id) {
285
+ const parent = s.byId.get(id)?.parentId;
286
+ return parent !== undefined && s.byId.has(parent) && (s.inOf.get(id) ?? []).includes(parent);
287
+ }
288
+ /** A pending node that will never be placed: it keeps its placeholder. */
289
+ const isFloating = (s, o) => s.pending.has(o.id) && !s.anchored.has(o.id);
290
+ /** The nodes in `scope` a node may not overlap: positioned ones and floating placeholders. Pending ones are placed later, around them. */
291
+ const obstaclesIn = (s, scope, except) => s.nodes.filter((o) => o.id !== except && o.parentId === scope && o.type !== STICKY_NOTE_TYPE && hasPosition(s, o)
292
+ && (!s.pending.has(o.id) || isFloating(s, o)));
293
+ const positionedIn = (s, scope) => obstaclesIn(s, scope, '').filter((o) => isPositioned(s, o.id));
294
+ /** Every node an edge path from `from` reaches inside its container, through pending ones. */
295
+ function downstreamOf(s, from) {
296
+ const scope = s.byId.get(from)?.parentId;
297
+ const seen = new Set();
298
+ const queue = [from];
299
+ for (let i = 0; i < queue.length; i++) {
300
+ for (const t of s.outOf.get(queue[i]) ?? []) {
301
+ if (t === from || seen.has(t) || !s.byId.has(t) || s.byId.get(t).parentId !== scope)
302
+ continue;
303
+ seen.add(t);
304
+ queue.push(t);
305
+ }
306
+ }
307
+ return seen;
308
+ }
309
+ /**
310
+ * The first positioned nodes `from` reaches in its container, through pending
311
+ * ones, each with how many pending ones lie on the way.
312
+ */
313
+ function frontierOf(s, from) {
314
+ const scope = s.byId.get(from)?.parentId;
315
+ const found = new Map();
316
+ const hops = new Map([[from, 0]]);
317
+ const queue = [from];
318
+ for (let i = 0; i < queue.length; i++) {
319
+ for (const t of s.outOf.get(queue[i]) ?? []) {
320
+ if (t === from || hops.has(t) || !s.byId.has(t) || s.byId.get(t).parentId !== scope)
321
+ continue;
322
+ const h = hops.get(queue[i]) + (queue[i] === from ? 0 : 1);
323
+ hops.set(t, h);
324
+ if (s.pending.has(t))
325
+ queue.push(t);
326
+ else if (isPositioned(s, t))
327
+ found.set(t, h);
328
+ }
329
+ }
330
+ return found;
331
+ }
332
+ // ── Pushing ──
333
+ /** The cell span of the x-grid that indexes boxes for collision checks. */
334
+ const GRID = 1024;
335
+ /** A box spanning more cells than this is checked against every moved box instead of indexed. */
336
+ const MAX_CELLS = 64;
337
+ /** Boxes indexed by the x-grid cells they span, so a lookup only checks boxes near it. */
338
+ function gridOf(s, boxes) {
339
+ const cells = new Map();
340
+ const wide = [];
341
+ const cell = (x) => Math.floor(x / GRID);
342
+ for (const o of boxes) {
343
+ const r = rectOf(s, o);
344
+ const first = cell(r.x - PADDING);
345
+ const last = cell(r.x + r.width + PADDING);
346
+ if (last - first >= MAX_CELLS) {
347
+ wide.push(o);
348
+ continue;
349
+ }
350
+ for (let c = first; c <= last; c++) {
351
+ if (!cells.has(c))
352
+ cells.set(c, []);
353
+ cells.get(c).push(o);
354
+ }
355
+ }
356
+ return (r) => {
357
+ const near = new Set(wide);
358
+ for (let c = cell(r.x - PADDING); c <= cell(r.x + r.width + PADDING); c++)
359
+ for (const o of cells.get(c) ?? [])
360
+ near.add(o);
361
+ return [...near];
131
362
  };
132
- const sizeOf = (n) => ({
133
- width: uiOf(n)?.size?.width ?? DEFAULT_SIZE,
134
- height: uiOf(n)?.size?.height ?? DEFAULT_SIZE,
135
- });
136
- const pending = new Set(nodes.filter((n) => isAdded(n.id) && n.type !== STICKY_NOTE_TYPE && isPlaceholderPosition(uiOf(n)?.position)).map((n) => n.id));
137
- const positioned = (id) => {
138
- const n = byId.get(id);
139
- return n !== undefined && !pending.has(id) && uiOf(n)?.position !== undefined;
363
+ }
364
+ /**
365
+ * What `opts.seed` carries along: the seed, the nodes downstream of a moved
366
+ * one that `opts.mayMove` allows (through pending ones, which do not move),
367
+ * and whatever a moved node would land on, with its downstream. Undefined when
368
+ * a moved node lands on one it may not move and `opts.blocked` refuses.
369
+ */
370
+ function spread(s, opts) {
371
+ if (opts.seed.length === 0)
372
+ return new Set();
373
+ const scope = opts.seed[0].parentId;
374
+ const nearby = gridOf(s, obstaclesIn(s, scope, opts.origin));
375
+ const moved = new Set();
376
+ const seen = new Set([opts.origin]);
377
+ const queue = [];
378
+ const visit = (id, moves) => {
379
+ if (seen.has(id))
380
+ return;
381
+ seen.add(id);
382
+ if (moves)
383
+ moved.add(id);
384
+ queue.push(id);
140
385
  };
141
- // Place in waves, so a run of added nodes chains off the first one placed.
142
- for (let progress = true; progress && pending.size > 0;) {
143
- progress = false;
144
- for (const id of [...pending]) {
145
- const n = byId.get(id);
146
- const sameScope = (other) => byId.get(other)?.parentId === n.parentId;
147
- const pred = edges.find((e) => e.targetNodeId === id && sameScope(e.sourceNodeId) && positioned(e.sourceNodeId));
148
- const entersBody = !pred && n.parentId !== undefined && byId.has(n.parentId)
149
- && edges.some((e) => e.targetNodeId === id && e.sourceNodeId === n.parentId);
150
- const succ = pred || entersBody ? undefined
151
- : edges.find((e) => e.sourceNodeId === id && sameScope(e.targetNodeId) && positioned(e.targetNodeId));
152
- let candidate;
153
- if (pred || succ) {
154
- const anchor = uiOf(byId.get(pred?.sourceNodeId ?? succ.targetNodeId)).position;
155
- candidate = { x: anchor.x + (pred ? SLOT_DX : -SLOT_DX), y: anchor.y };
156
- }
157
- else if (entersBody) {
158
- candidate = { ...BODY_ORIGIN };
159
- }
160
- else {
386
+ for (const o of opts.seed)
387
+ visit(o.id, true);
388
+ for (let i = 0; i < queue.length; i++) {
389
+ const id = queue[i];
390
+ for (const t of s.outOf.get(id) ?? []) {
391
+ const d = s.byId.get(t);
392
+ if (!d || d.parentId !== scope || seen.has(t))
393
+ continue;
394
+ if (s.pending.has(t))
395
+ visit(t, false);
396
+ else if (isPositioned(s, t) && opts.mayMove(d))
397
+ visit(t, true);
398
+ }
399
+ if (!moved.has(id))
400
+ continue;
401
+ const r = rectOf(s, s.byId.get(id));
402
+ const box = { ...r, x: r.x + opts.by.x, y: r.y + opts.by.y };
403
+ if (opts.blocked === Blocked.Refuse && opts.keepClear && overlaps(box, opts.keepClear))
404
+ return undefined;
405
+ for (const other of nearby(box)) {
406
+ if (moved.has(other.id) || !overlaps(box, rectOf(s, other)))
161
407
  continue;
162
- }
163
- // Still-pending nodes count at their placeholder: one that never finds an
164
- // anchor stays there, and nothing may land on it.
165
- const others = nodes
166
- .filter((o) => o.id !== id && o.parentId === n.parentId && o.type !== STICKY_NOTE_TYPE && uiOf(o)?.position !== undefined)
167
- .map((o) => ({ ...uiOf(o).position, ...sizeOf(o) }));
168
- const position = findFreePosition(candidate, sizeOf(n), others);
169
- const ui = { ...uiOf(n), position };
170
- if (layoutNodes)
171
- layoutNodes[id] = ui;
172
- else
173
- n.ui = ui;
174
- pending.delete(id);
175
- progress = true;
408
+ if (opts.blocked === Blocked.Refuse && !opts.mayMove(other))
409
+ return undefined;
410
+ visit(other.id, true);
411
+ }
412
+ }
413
+ return moved;
414
+ }
415
+ // ── Placing ──
416
+ /** Place in waves, by id, until no pending node has an anchor left. */
417
+ function placeInWaves(s) {
418
+ for (let wave = nextWave(s); wave.length > 0; wave = nextWave(s)) {
419
+ for (const id of wave)
420
+ placeNode(s, id);
421
+ }
422
+ }
423
+ /**
424
+ * The pending nodes to place next: those with a positioned predecessor (or
425
+ * their container's entry) and no predecessor in their container still to be
426
+ * placed; when the rest are in a cycle, those with a positioned predecessor;
427
+ * else those with a positioned successor.
428
+ */
429
+ function nextWave(s) {
430
+ const pending = [...s.pending].sort();
431
+ const scopeOfId = (id) => s.byId.get(id).parentId;
432
+ const positionedNear = (id, other) => s.byId.get(other)?.parentId === scopeOfId(id) && isPositioned(s, other);
433
+ const fromBehind = (id) => enteredFromContainer(s, id) || (s.inOf.get(id) ?? []).some((p) => positionedNear(id, p));
434
+ const predsPlaced = (id) => (s.inOf.get(id) ?? []).every((p) => p === id || !s.anchored.has(p) || !s.pending.has(p) || s.byId.get(p)?.parentId !== scopeOfId(id));
435
+ const behind = pending.filter(fromBehind);
436
+ const settled = behind.filter(predsPlaced);
437
+ if (settled.length > 0)
438
+ return settled;
439
+ if (behind.length > 0)
440
+ return behind;
441
+ return pending.filter((id) => (s.outOf.get(id) ?? []).some((t) => positionedNear(id, t)));
442
+ }
443
+ /** Where a node goes before making room, and which nodes count as past it. */
444
+ function candidateOf(s, n) {
445
+ const inScope = (other) => s.byId.get(other)?.parentId === n.parentId && isPositioned(s, other);
446
+ const rightEdge = (id) => rectOf(s, s.byId.get(id)).x + rectOf(s, s.byId.get(id)).width;
447
+ const [pred] = (s.inOf.get(n.id) ?? []).filter(inScope).sort((a, b) => rightEdge(b) - rightEdge(a) || a.localeCompare(b));
448
+ if (pred !== undefined) {
449
+ const anchor = rectOf(s, s.byId.get(pred));
450
+ const [lead] = [...frontierOf(s, n.id)].filter(([t]) => t !== pred)
451
+ .map(([t, hops]) => ({ t, hops, r: rectOf(s, s.byId.get(t)) }))
452
+ .sort((a, b) => a.hops - b.hops || a.r.x - b.r.x || a.t.localeCompare(b.t));
453
+ // A successor tucked under its predecessor (a context index under its agent): take its spot and push it a slot.
454
+ if (lead && lead.hops === 0 && lead.r.x >= anchor.x && lead.r.x < anchor.x + anchor.width) {
455
+ return { at: { x: lead.r.x, y: lead.r.y }, past: (o) => o.id !== pred && rectOf(s, o).x >= lead.r.x, pred };
456
+ }
457
+ // A join another lane already feeds keeps the node in its predecessor's lane.
458
+ const joins = lead !== undefined && (s.inOf.get(lead.t) ?? [])
459
+ .some((p) => p !== pred && s.byId.get(p)?.parentId === n.parentId && isPositioned(s, p));
460
+ const y = lead && !joins && !rowsMeet(anchor, lead.r) ? lead.r.y : anchor.y;
461
+ // At the predecessor's x counts as past (a context index under its agent); the predecessor does not.
462
+ return { at: { x: anchor.x + anchor.width + GAP, y }, past: (o) => o.id !== pred && rectOf(s, o).x >= anchor.x, pred };
463
+ }
464
+ if (enteredFromContainer(s, n.id))
465
+ return { at: { ...BODY_ORIGIN }, past: () => true };
466
+ const [succ] = (s.outOf.get(n.id) ?? []).filter(inScope)
467
+ .sort((a, b) => rectOf(s, s.byId.get(a)).x - rectOf(s, s.byId.get(b)).x || a.localeCompare(b));
468
+ const at = rectOf(s, s.byId.get(succ));
469
+ const left = { x: at.x - SLOT_DX, y: at.y };
470
+ if (!obstaclesIn(s, n.parentId, n.id).some((o) => overlaps({ ...left, ...sizeOf(s, n) }, rectOf(s, o))))
471
+ return { at: left };
472
+ return { at: { x: at.x, y: at.y }, past: (o) => rectOf(s, o).x >= at.x };
473
+ }
474
+ /** The new branches already placed out of `pred`, other than the node's own downstream. */
475
+ function siblingsOf(s, pred, downstream) {
476
+ if (pred === undefined)
477
+ return new Set();
478
+ return new Set((s.outOf.get(pred) ?? []).filter((t) => s.placed.has(t) && !downstream.has(t)));
479
+ }
480
+ /**
481
+ * Move `at` down, row by row, while everything a box of `size` there would
482
+ * overlap is a sibling, in one sweep: only obstacles in its column can be
483
+ * hit, and the rows only go down.
484
+ */
485
+ function stackBelowSiblings(s, at, size, obstacles, siblings) {
486
+ if (siblings.size === 0)
487
+ return;
488
+ const column = obstacles.map((o) => ({ id: o.id, r: rectOf(s, o) }))
489
+ .filter(({ r }) => r.x < at.x + size.width + PADDING && at.x < r.x + r.width + PADDING)
490
+ .sort((a, b) => a.r.y - b.r.y);
491
+ let row = [];
492
+ for (let next = 0;;) {
493
+ while (next < column.length && column[next].r.y < at.y + size.height + PADDING)
494
+ row.push(column[next++]);
495
+ row = row.filter(({ r }) => r.y + r.height + PADDING > at.y);
496
+ if (row.length === 0 || !row.every(({ id }) => siblings.has(id)))
497
+ return;
498
+ at.y = Math.max(...row.map(({ r }) => r.y + r.height)) + GAP;
499
+ }
500
+ }
501
+ function placeNode(s, id) {
502
+ const n = s.byId.get(id);
503
+ const { at, past, pred } = candidateOf(s, n);
504
+ const obstacles = obstaclesIn(s, n.parentId, id);
505
+ // An added sibling (another new branch out of the same predecessor) on the spot: take the next row down instead of pushing it.
506
+ const downstream = downstreamOf(s, id);
507
+ stackBelowSiblings(s, at, sizeOf(s, n), obstacles, siblingsOf(s, pred, downstream));
508
+ const box = { ...at, ...sizeOf(s, n) };
509
+ const hits = obstacles.filter((o) => overlaps(box, rectOf(s, o)));
510
+ const freeSpot = () => findFreePosition(at, sizeOf(s, n), obstacles.map((o) => rectOf(s, o)));
511
+ let position = at;
512
+ if (past) {
513
+ const pushable = (o) => isMovable(s, o) && past(o);
514
+ const roomFor = (o) => box.x + box.width + GAP - rectOf(s, o).x;
515
+ const after = [...downstream].map((d) => s.byId.get(d)).filter((o) => isPositioned(s, o.id) && pushable(o));
516
+ const rowNeed = [...hits, ...after].filter((o) => pushable(o) && rowsMeet(box, rectOf(s, o))).map(roomFor);
517
+ const successorNeed = [...frontierOf(s, id).keys()].map((t) => s.byId.get(t)).filter(pushable).map(roomFor);
518
+ const by = { x: Math.max(0, ...rowNeed, ...successorNeed), y: 0 };
519
+ const moved = hits.every(pushable)
520
+ ? spread(s, { origin: id, seed: [...hits, ...after], by, mayMove: pushable, blocked: Blocked.Refuse, keepClear: box })
521
+ : undefined;
522
+ if (moved)
523
+ shift(s, moved, by);
524
+ else if (hits.length > 0)
525
+ position = freeSpot();
526
+ }
527
+ else if (hits.length > 0) {
528
+ position = freeSpot();
529
+ }
530
+ write(s, n, { position });
531
+ s.pending.delete(id);
532
+ s.placed.add(id);
533
+ if (n.parentId !== undefined && s.byId.has(n.parentId))
534
+ s.toFit.add(n.parentId);
535
+ }
536
+ // ── Growing containers ──
537
+ /** The right and bottom edge of a container's body, over the children `which` keeps. */
538
+ function bodyExtent(s, id, which) {
539
+ const body = positionedIn(s, id).filter(which).map((o) => rectOf(s, o));
540
+ if (body.length === 0)
541
+ return undefined;
542
+ return { right: Math.max(...body.map((r) => r.x + r.width)), bottom: Math.max(...body.map((r) => r.y + r.height)) };
543
+ }
544
+ /**
545
+ * Grow each container that gained or moved a child, innermost first (by id on
546
+ * a tie), by as much as its body grew past the padding it had (the body
547
+ * origin's, for a body that was empty). A grown container pushes every
548
+ * sibling in its way or downstream of it past its old edges, an author
549
+ * position or placeholder included (it was chosen against the old size), and
550
+ * keeps at least the row gap to its new right edge. Its own container is
551
+ * fitted next.
552
+ */
553
+ function fitContainers(s, before) {
554
+ const fitted = new Set();
555
+ for (let id = deepest(s.toFit, s.byId); id !== undefined; id = deepest(s.toFit, s.byId)) {
556
+ s.toFit.delete(id);
557
+ fitted.add(id);
558
+ const c = s.byId.get(id);
559
+ const now = bodyExtent(s, id, () => true);
560
+ if (!now)
561
+ continue;
562
+ const old = sizeOf(s, c);
563
+ const was = before.get(id);
564
+ const padRight = was ? Math.min(BODY_ORIGIN.x, old.width - was.right) : BODY_ORIGIN.x;
565
+ const padBottom = was ? Math.min(BODY_ORIGIN.y, old.height - was.bottom) : BODY_ORIGIN.y;
566
+ const width = Math.max(old.width, now.right + padRight);
567
+ const height = Math.max(old.height, now.bottom + padBottom);
568
+ if (width === old.width && height === old.height)
569
+ continue;
570
+ write(s, c, { size: { ...uiOf(s, c)?.size, width, height } });
571
+ if (c.parentId !== undefined && s.byId.has(c.parentId) && !fitted.has(c.parentId))
572
+ s.toFit.add(c.parentId);
573
+ if (!hasPosition(s, c))
574
+ continue;
575
+ const at = rectOf(s, c);
576
+ const grown = { ...at, width, height };
577
+ const siblings = obstaclesIn(s, c.parentId, id);
578
+ const downstream = downstreamOf(s, id);
579
+ if (width > old.width) {
580
+ const right = siblings.filter((o) => rectOf(s, o).x >= at.x + old.width && (downstream.has(o.id) || overlaps(grown, rectOf(s, o))));
581
+ const gapNeed = right.filter((o) => rowsMeet(grown, rectOf(s, o))).map((o) => at.x + width + GAP - rectOf(s, o).x);
582
+ const by = { x: Math.max(width - old.width, ...gapNeed), y: 0 };
583
+ const pastRight = (o) => hasPosition(s, o) && rectOf(s, o).x >= at.x + old.width;
584
+ const moved = spread(s, { origin: id, seed: right, by, mayMove: pastRight, blocked: Blocked.Push });
585
+ if (moved)
586
+ shift(s, moved, by);
587
+ }
588
+ if (height > old.height) {
589
+ const under = siblings.filter((o) => rectOf(s, o).y >= at.y + old.height && overlaps(grown, rectOf(s, o)));
590
+ const by = { x: 0, y: height - old.height };
591
+ const pastBottom = (o) => hasPosition(s, o) && rectOf(s, o).y >= at.y + old.height;
592
+ const moved = spread(s, { origin: id, seed: under, by, mayMove: pastBottom, blocked: Blocked.Push });
593
+ if (moved)
594
+ shift(s, moved, by);
176
595
  }
177
596
  }
178
597
  }
598
+ /** The id in `ids` nested in the most containers, by id on a tie; a parent cycle stops counting at the node count. */
599
+ function deepest(ids, byId) {
600
+ const depthOf = (id) => {
601
+ let depth = 0;
602
+ for (let p = byId.get(id)?.parentId; p !== undefined && byId.has(p) && depth < byId.size; p = byId.get(p)?.parentId)
603
+ depth += 1;
604
+ return depth;
605
+ };
606
+ return [...ids].sort((a, b) => depthOf(b) - depthOf(a) || a.localeCompare(b))[0];
607
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.66.4",
3
+ "version": "6.66.5",
4
4
  "description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.uipath.com/maestro",
@@ -100,5 +100,5 @@
100
100
  "@types/node": "^22.7.0",
101
101
  "esbuild": "^0.28.1"
102
102
  },
103
- "gitref": "af11f232febf9ed683dec9f44b4ae7fe3c2426fe"
103
+ "gitref": "453ee3d98495e2def39548eab76cb0aedf237337"
104
104
  }