@weasel-js/diagram 1.13.0 → 1.14.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 (35) hide show
  1. package/README.md +122 -17
  2. package/dist/{chunk-Y72FK3Y7.js → chunk-5VNPLOIX.js} +94 -6
  3. package/dist/chunk-5VNPLOIX.js.map +1 -0
  4. package/dist/{chunk-ESFIS7BB.js → chunk-62SMY45E.js} +918 -574
  5. package/dist/chunk-62SMY45E.js.map +1 -0
  6. package/dist/{chunk-FAQXYGMX.js → chunk-TBG6DFB2.js} +3 -3
  7. package/dist/{chunk-FAQXYGMX.js.map → chunk-TBG6DFB2.js.map} +1 -1
  8. package/dist/{chunk-L4H444QI.js → chunk-VB6HUKNG.js} +659 -185
  9. package/dist/chunk-VB6HUKNG.js.map +1 -0
  10. package/dist/{chunk-QU4QZUC6.js → chunk-ZWXM5F4I.js} +141 -39
  11. package/dist/chunk-ZWXM5F4I.js.map +1 -0
  12. package/dist/{dataTypes-DW-MljuN.d.ts → dataTypes-BIkhAOc4.d.ts} +13 -2
  13. package/dist/effect-audio.d.ts +2 -1
  14. package/dist/effect-audio.js +6 -5
  15. package/dist/effect-audio.js.map +1 -1
  16. package/dist/effect.d.ts +46 -16
  17. package/dist/effect.js +257 -102
  18. package/dist/effect.js.map +1 -1
  19. package/dist/icons.table.js +262 -0
  20. package/dist/icons.table.js.map +1 -1
  21. package/dist/icons.table.json +262 -0
  22. package/dist/index.d.ts +491 -167
  23. package/dist/index.js +586 -36
  24. package/dist/index.js.map +1 -1
  25. package/dist/labelTrait-BBjRgxbz.d.ts +44 -0
  26. package/dist/layout.d.ts +4 -3
  27. package/dist/{layout.entry-CQoPNfzA.d.ts → layout.entry-Ca6pVvqo.d.ts} +23 -2
  28. package/dist/layout.js +2 -2
  29. package/dist/{stageTypes-DvnR8cKQ.d.ts → stageTypes-UiO25hcn.d.ts} +7 -0
  30. package/dist/{types-vbW21fHm.d.ts → types-B0n2CNTW.d.ts} +108 -81
  31. package/package.json +11 -9
  32. package/dist/chunk-ESFIS7BB.js.map +0 -1
  33. package/dist/chunk-L4H444QI.js.map +0 -1
  34. package/dist/chunk-QU4QZUC6.js.map +0 -1
  35. package/dist/chunk-Y72FK3Y7.js.map +0 -1
package/dist/effect.js CHANGED
@@ -1,12 +1,14 @@
1
- import { DiagramView, reorderContribution, ANYWHERE, isBodyEnd, sequenceSlots } from './chunk-ESFIS7BB.js';
2
- import './chunk-Y72FK3Y7.js';
3
- import { stageFromType, LOOK_THEME, drawableWires, uniqueStages, shownPorts, valueRowId, NO_VALUE, previewRowId, formatParam, stageStyle, PROBLEM_COLOR, typeColors, kindOf, boxNode, portOn, wireStyle, isControl, wireEdge, sceneStyles, uniqueGroups, wireEdges, exitLabel, splitProblem, checkPipeline, PREVIEW_DENSITY, runOrder, PREVIEW_SIZE, closeBitmaps, previewsOf, paramValues } from './chunk-FAQXYGMX.js';
4
- export { LOOK_THEME, NO_VALUE, PREVIEW_SIZE, PROBLEM_COLOR, checkPipeline, drawableWires, exitLabel, formatParam, isControl, kindOf, portOn, previewRowId, runOrder, shownPorts, stageFromType, stageRows, stageStyle, typeColors, uniqueGroups, uniqueStages, valueRowId, wireEdges, wireStyle } from './chunk-FAQXYGMX.js';
5
- import { diagramScene, diagramPages, edgeIdOf, titleIdOf, groupLabelIdOf, nestGroups, groupMinWidth, LABEL_PLACEMENT, EDGE_DERIVE_PATH } from './chunk-QU4QZUC6.js';
6
- import { portOf, insetOf, diagramNodeOf, DEFAULT_GROUP_INSET, SIDE_TRACK_GAP, widenForMinWidth, rankGaps, portSitesOf, labelCenter } from './chunk-L4H444QI.js';
1
+ import { DiagramView, reorderContribution, ANYWHERE, isBodyEnd, sequenceSlots } from './chunk-62SMY45E.js';
2
+ import './chunk-5VNPLOIX.js';
3
+ import { stageFromType, LOOK_THEME, drawableWires, uniqueStages, shownPorts, valueRowId, NO_VALUE, previewRowId, formatParam, stageStyle, PROBLEM_COLOR, typeColors, kindOf, boxNode, portOn, wireStyle, isControl, wireEdge, sceneStyles, uniqueGroups, wireEdges, exitLabel, splitProblem, checkPipeline, PREVIEW_DENSITY, runOrder, PREVIEW_SIZE, closeBitmaps, previewsOf, paramValues } from './chunk-TBG6DFB2.js';
4
+ export { LOOK_THEME, NO_VALUE, PREVIEW_SIZE, PROBLEM_COLOR, checkPipeline, drawableWires, exitLabel, formatParam, isControl, kindOf, portOn, previewRowId, runOrder, shownPorts, stageFromType, stageRows, stageStyle, typeColors, uniqueGroups, uniqueStages, valueRowId, wireEdges, wireStyle } from './chunk-TBG6DFB2.js';
5
+ import { diagramScene, diagramPages, edgeIdOf, titleIdOf, groupLabelIdOf, nestGroups, groupMinWidth, LABEL_PLACEMENT, EDGE_DERIVE_PATH } from './chunk-ZWXM5F4I.js';
6
+ import { portOf, insetOf, diagramNodeOf, DEFAULT_GROUP_INSET, SIDE_TRACK_GAP, widenForMinWidth, rankGaps, portSitesOf, labelCenter, resolveOutline } from './chunk-VB6HUKNG.js';
7
+ import { moveItems, imeKey, isNoopDrop } from '@weasel-js/headless';
8
+ import { useLatest, bitmapSrc, ActionDisabledReason, worldPosesOf, RIGID_POSE_COMPOSITION, IDENTITY_POSE_COMPOSITION, pointAlongPath } from '@weasel-js/core';
7
9
  import { polygonFromPoints } from '@weasel-js/core/math';
10
+ import { pathToD, transformPath, pathFromD, worldCoords } from '@weasel-js/geom';
8
11
  import { forwardRef, useMemo, useRef, useState, useImperativeHandle, useEffect, useCallback, useSyncExternalStore } from 'react';
9
- import { useLatest, bitmapSrc, ActionDisabledReason, pointAlongPath } from '@weasel-js/core';
10
12
  import { releaseImageSource } from '@weasel-js/core/renderer';
11
13
  import { jsx } from 'react/jsx-runtime';
12
14
  import { useLatest as useLatest$1, adoptCssOnce, useVisibleRaf } from '@weasel-js/react';
@@ -113,8 +115,6 @@ function moveGroup(doc, id, offset) {
113
115
  ...boxes !== void 0 ? { groupBoxes: boxes } : {}
114
116
  };
115
117
  }
116
-
117
- // src/effect/lookRoutes.ts
118
118
  var EPS = 1e-3;
119
119
  var near = (a, b) => Math.abs(a.x - b.x) <= EPS && Math.abs(a.y - b.y) <= EPS;
120
120
  var copy = (p) => ({ x: p.x, y: p.y });
@@ -139,9 +139,10 @@ function wireEnds(specs, edge) {
139
139
  const spec = specs.find((s) => s.id === edge);
140
140
  const trait = spec?.data?.diagram;
141
141
  const [from, to] = spec?.dependsOn ?? [];
142
- const point = (node, port) => {
143
- const n = node === void 0 || port === void 0 ? void 0 : specs.find((s) => s.id === node);
144
- return n === void 0 ? void 0 : portOf({ id: node, kind: n.kind ?? "mark", data: n.data }, n.pose, port)?.point;
142
+ const world = worldPosesOf(specs, RIGID_POSE_COMPOSITION);
143
+ const point = (node, port2) => {
144
+ const n = node === void 0 || port2 === void 0 ? void 0 : specs.find((s) => s.id === node);
145
+ return n === void 0 ? void 0 : portOf({ id: node, kind: n.kind ?? "mark", data: n.data }, world.get(node), port2)?.point;
145
146
  };
146
147
  const a = point(from, trait?.from?.port);
147
148
  const b = point(to, trait?.to?.port);
@@ -206,11 +207,24 @@ function removeStages(doc, ids) {
206
207
  });
207
208
  }
208
209
  function pruneRoutes(doc, rewired = []) {
209
- const pruned = pruneLookRoutes(doc, rewired);
210
+ const pruned = pruneLabels(pruneLookRoutes(doc, rewired));
210
211
  if (pruned.routes === void 0) return pruned;
211
212
  const ids = new Set(doc.pipeline.wires.flatMap((w) => w.id === void 0 || rewired.includes(w.id) ? [] : [w.id]));
212
213
  return { ...pruned, routes: Object.fromEntries(Object.entries(pruned.routes).filter(([id]) => ids.has(id))) };
213
214
  }
215
+ function pruneLabels(doc) {
216
+ if (doc.labels === void 0) return doc;
217
+ const ids = new Set(doc.pipeline.wires.flatMap((w) => w.id === void 0 ? [] : [w.id]));
218
+ const kept = Object.entries(doc.labels).filter(([id]) => ids.has(id));
219
+ const { labels: _was, ...rest } = doc;
220
+ return kept.length > 0 ? { ...rest, labels: Object.fromEntries(kept) } : rest;
221
+ }
222
+ function setWireLabel(doc, wire, placement) {
223
+ const { [wire]: _was, ...others } = doc.labels ?? {};
224
+ const { labels: _all, ...rest } = doc;
225
+ const labels = placement === null ? others : { ...others, [wire]: placement };
226
+ return Object.keys(labels).length > 0 ? { ...rest, labels } : rest;
227
+ }
214
228
  function setWireRoute(doc, wire, points) {
215
229
  const { [wire]: _was, ...rest } = doc.routes ?? {};
216
230
  return { ...doc, routes: points.length > 0 ? { ...rest, [wire]: points.map((p) => ({ x: p.x, y: p.y })) } : rest };
@@ -332,12 +346,6 @@ function chainLinks(pipeline, chain) {
332
346
  return { mainIn, mainOut, between: between2 };
333
347
  }
334
348
  var portsOf = (p, stage, side) => p.stages.find((s) => s.id === stage)?.[side] ?? [];
335
- function movedChain(chain, i, j, to) {
336
- const run = chain.slice(i, j + 1);
337
- const out = [...chain.slice(0, i), ...chain.slice(j + 1)];
338
- out.splice(to > j ? to - run.length : to, 0, ...run);
339
- return out;
340
- }
341
349
  function moveInChain(doc, stage, to) {
342
350
  return moveRunInChain(doc, stage, stage, to);
343
351
  }
@@ -346,7 +354,8 @@ function moveRunInChain(doc, first, last, to) {
346
354
  const chain = mainChain(p);
347
355
  const i = chain.indexOf(first);
348
356
  const j = chain.indexOf(last);
349
- if (i < 0 || j < i || !Number.isInteger(to) || to < 0 || to > chain.length || to >= i && to <= j + 1) return null;
357
+ const run = Array.from({ length: j - i + 1 }, (_, k) => i + k);
358
+ if (i < 0 || j < i || !Number.isInteger(to) || to < 0 || to > chain.length || isNoopDrop(run, to)) return null;
350
359
  const { mainIn, mainOut, between: between2 } = chainLinks(p, chain);
351
360
  const wIn = i > 0 ? between2[i - 1] : void 0;
352
361
  const wOut = j < chain.length - 1 ? between2[j] : void 0;
@@ -354,8 +363,8 @@ function moveRunInChain(doc, first, last, to) {
354
363
  const gone = new Set([wIn, wOut, wAB].filter((k) => k !== void 0));
355
364
  const kept = p.wires.filter((_, k) => !gone.has(k));
356
365
  const free = (ref) => !kept.some((w) => w.to.stage === ref.stage && w.to.port === ref.port);
357
- const pick = (id, side, type) => portsOf(p, id, side).find((port) => (type === void 0 || port.type === type) && (side === "outputs" || free({ stage: id, port: port.id })))?.id;
358
- const typeOf = (id, port) => [...portsOf(p, id, "inputs"), ...portsOf(p, id, "outputs")].find((x) => x.id === port)?.type;
366
+ const pick = (id, side, type) => portsOf(p, id, side).find((port2) => (type === void 0 || port2.type === type) && (side === "outputs" || free({ stage: id, port: port2.id })))?.id;
367
+ const typeOf = (id, port2) => [...portsOf(p, id, "inputs"), ...portsOf(p, id, "outputs")].find((x) => x.id === port2)?.type;
359
368
  const made = [];
360
369
  const prev = chain[i - 1];
361
370
  const next = chain[j + 1];
@@ -386,7 +395,7 @@ function moveRunInChain(doc, first, last, to) {
386
395
  wires.push({ id: nextWireId({ ...p, wires }), from: m.from, to: m.to });
387
396
  }
388
397
  const pipeline = { ...p, wires };
389
- const want = movedChain(chain, i, j, to);
398
+ const want = moveItems(chain, run, to);
390
399
  const got = mainChain(pipeline);
391
400
  if (got.length !== want.length || got.some((id, k) => id !== want[k])) return null;
392
401
  return pruneRoutes({ ...doc, pipeline }, [...gone].flatMap((k) => p.wires[k].id ?? []));
@@ -394,6 +403,49 @@ function moveRunInChain(doc, first, last, to) {
394
403
  function canMoveInChain(doc, stage, to) {
395
404
  return moveInChain(doc, stage, to) !== null;
396
405
  }
406
+ var DIAGONAL = [0, 1, 1, 0, 0, 0];
407
+ var SYMMETRIC = /* @__PURE__ */ new Set(["rect", "diamond", "ellipse"]);
408
+ var anchor = (at) => ({ u: at.v, v: at.u });
409
+ var vec = (p) => ({ x: p.y, y: p.x });
410
+ var transposeD = (d) => pathToD(transformPath(pathFromD(d), DIAGONAL));
411
+ function outline(o) {
412
+ if (typeof o === "string" && SYMMETRIC.has(o)) return o;
413
+ const path = typeof o === "string" ? resolveOutline(o).path({ x: 0, y: 0, width: 1, height: 1 }) : o;
414
+ return transformPath(path, DIAGONAL);
415
+ }
416
+ function port(p) {
417
+ const out = { ...p, at: anchor(p.at) };
418
+ if (p.sites !== void 0) {
419
+ out.sites = p.sites.map((s) => ({ ...s, ...anchor(s), ...s.normal ? { normal: vec(s.normal) } : {} }));
420
+ }
421
+ if (p.normal) out.normal = vec(p.normal);
422
+ return out;
423
+ }
424
+ function mark(m) {
425
+ if ("d" in m) return { ...m, d: transposeD(m.d) };
426
+ return {
427
+ ...m,
428
+ at: anchor(m.at),
429
+ ...m.self !== void 0 ? { self: anchor(m.self) } : {},
430
+ ...m.offset !== void 0 ? { offset: vec(m.offset) } : {}
431
+ };
432
+ }
433
+ function transpose(node) {
434
+ const { width, height, ...rest } = node;
435
+ return {
436
+ ...rest,
437
+ ...node.outline !== void 0 ? { outline: outline(node.outline) } : {},
438
+ ...node.ports !== void 0 ? { ports: node.ports.map(port) } : {},
439
+ ...node.marks !== void 0 ? { marks: node.marks.map(mark) } : {},
440
+ ...height !== void 0 ? { width: height } : {},
441
+ ...width !== void 0 ? { height: width } : {}
442
+ };
443
+ }
444
+ var flowing = (symbol) => (stage, pipeline, options) => {
445
+ if (options?.direction !== "down") return symbol(stage, pipeline, options);
446
+ const row = { ...options, direction: "right", controlSide: options.controlSide === "right" ? "bottom" : "top" };
447
+ return transpose(symbol(stage, pipeline, row));
448
+ };
397
449
 
398
450
  // src/effect/symbolParts.ts
399
451
  function along(side, i, n) {
@@ -468,7 +520,10 @@ var hexagon = (stage, pipeline, options) => ({
468
520
  lines: [stage.title],
469
521
  ports: [...inputs(stage, pipeline, options, void 0, onHexagon), ...onHexagon(stage.outputs ?? [], "right")]
470
522
  });
471
- var step = (stage, pipeline, options) => {
523
+ var CUT_SIZE = 10;
524
+ var CUT_GAP = 3;
525
+ var CUT_CHAR = 0.6 * CUT_SIZE;
526
+ var stepAlong = (titleGap) => (stage, pipeline, options) => {
472
527
  if (!canBand(stage)) return diamond(stage, pipeline, options);
473
528
  const exits = stage.outputs ?? [];
474
529
  const n = exits.length;
@@ -483,10 +538,10 @@ var step = (stage, pipeline, options) => {
483
538
  text: String(cut),
484
539
  at: { u: riser(k), v: 1 },
485
540
  self: { u: 0.5, v: 0 },
486
- offset: { x: 0, y: 3 },
487
- style: { fontSize: 10 }
541
+ offset: { x: 0, y: CUT_GAP },
542
+ style: { fontSize: CUT_SIZE }
488
543
  }));
489
- marks.push(title(stage, options, 18));
544
+ marks.push(title(stage, options, titleGap));
490
545
  return {
491
546
  outline: "rect",
492
547
  lines: [],
@@ -497,6 +552,12 @@ var step = (stage, pipeline, options) => {
497
552
  marks
498
553
  };
499
554
  };
555
+ var step = (stage, pipeline, options) => {
556
+ if (options?.direction !== "down") return stepAlong(18)(stage, pipeline, options);
557
+ if (options.controlSide === "right") return flowing(stepAlong(18))(stage, pipeline, options);
558
+ const widest = Math.max(0, ...(stage.split?.at ?? []).map((cut) => String(cut).length * CUT_CHAR));
559
+ return flowing(stepAlong(Math.max(18, CUT_GAP + widest + 4)))(stage, pipeline, options);
560
+ };
500
561
  var TRIANGLE = [[0, 0], [1, 0.5], [0, 1]];
501
562
  var COMPARATOR = outlineOf(TRIANGLE);
502
563
  var EXIT_AT = 0.7;
@@ -615,10 +676,14 @@ var demux = (stage, pipeline, options) => {
615
676
  var LEVER_FIRST = "M0.2 0.5 L0.78 0.27";
616
677
  var CONTACT_FIRST = dot(0.8, 0.25, 0.07);
617
678
  var CONTACT_SECOND = dot(0.8, 0.75, 0.07);
618
- var THROWN_SECOND = /* @__PURE__ */ new Map([
679
+ var THROWN_ALONG = [
619
680
  [LEVER_FIRST, "M0.2 0.5 L0.78 0.73"],
620
681
  [CONTACT_FIRST, CONTACT_SECOND],
621
682
  [CONTACT_SECOND, CONTACT_FIRST]
683
+ ];
684
+ var THROWN_SECOND = new Map([
685
+ ...THROWN_ALONG,
686
+ ...THROWN_ALONG.map(([from, to]) => [transposeD(from), transposeD(to)])
622
687
  ]);
623
688
  var thrownToSecond = (d) => THROWN_SECOND.get(d);
624
689
  var switchSymbol = (stage, pipeline, options) => {
@@ -646,15 +711,15 @@ var switchSymbol = (stage, pipeline, options) => {
646
711
  };
647
712
  };
648
713
  var BRANCH_SYMBOLS = Object.freeze({
649
- diamond,
650
- bar,
651
- demux,
652
- switch: switchSymbol,
653
- hexagon,
714
+ diamond: flowing(diamond),
715
+ bar: flowing(bar),
716
+ demux: flowing(demux),
717
+ switch: flowing(switchSymbol),
718
+ hexagon: flowing(hexagon),
654
719
  step,
655
- comparator,
656
- crossover,
657
- fork
720
+ comparator: flowing(comparator),
721
+ crossover: flowing(crossover),
722
+ fork: flowing(fork)
658
723
  });
659
724
  var NEEDS = {
660
725
  bar: canBand,
@@ -679,7 +744,7 @@ function sidePorts(stage) {
679
744
  ...(stage.outputs ?? []).map((p) => ({ id: p.id, at: { u: 1, v: 0.5 } }))
680
745
  ];
681
746
  }
682
- var untyped = (ports) => (ports ?? []).map(({ type: _type, ...port }) => port);
747
+ var untyped = (ports) => (ports ?? []).map(({ type: _type, ...port2 }) => port2);
683
748
  function nodeOf(stage, pipeline, branch, theme) {
684
749
  if (kindOf(stage) !== "decision") {
685
750
  return { ...boxNode(stage, { showParams: false, rowPorts: false, ports: sidePorts(stage) }), padding: COMPACT_PADDING };
@@ -823,8 +888,8 @@ function ownerOf(stages) {
823
888
  const parent = spec.parent;
824
889
  if (parent !== void 0 && stages.has(parent)) return parent;
825
890
  const deps = spec.dependsOn;
826
- const anchor = Array.isArray(deps) && deps.length === 1 ? deps[0] : void 0;
827
- return anchor !== void 0 && stages.has(anchor) ? anchor : void 0;
891
+ const anchor2 = Array.isArray(deps) && deps.length === 1 ? deps[0] : void 0;
892
+ return anchor2 !== void 0 && stages.has(anchor2) ? anchor2 : void 0;
828
893
  };
829
894
  }
830
895
  function wireEnds2(spec) {
@@ -859,9 +924,10 @@ function costSpec(stage, own, of, text, problem) {
859
924
  const fill = problem ? { paint: { color: PROBLEM_COLOR } } : titleData.fill ?? { paint: { color: stageStyle(of).text } };
860
925
  const width = text.length * fontSize * 0.6;
861
926
  const height = Math.ceil(fontSize * 1.2);
862
- const box = stage.pose;
927
+ const world = worldPosesOf([stage, ...own], RIGID_POSE_COMPOSITION);
928
+ const box = world.get(stage.id);
863
929
  const top = Math.min(box.y, ...own.flatMap((s) => {
864
- const p = s.pose;
930
+ const p = world.get(s.id);
865
931
  return s !== stage && p !== void 0 && p.y + p.height <= box.y ? [p.y] : [];
866
932
  }));
867
933
  return {
@@ -885,8 +951,8 @@ function useRunPortOptions(color, pipeline, run) {
885
951
  shape: "circle",
886
952
  sizePx: 8,
887
953
  // Every port a look declares has a value type; the compass defaults have none.
888
- showsPort: (port) => port.type !== void 0,
889
- fill: (port) => ({ paint: { color: color(port.type), ...faded.has(port.nodeId) ? { opacity: DIM } : {} } })
954
+ showsPort: (port2) => port2.type !== void 0,
955
+ fill: (port2) => ({ paint: { color: color(port2.type), ...faded.has(port2.nodeId) ? { opacity: DIM } : {} } })
890
956
  };
891
957
  }, [color, missed]);
892
958
  }
@@ -1010,10 +1076,15 @@ function orient(p, a, b) {
1010
1076
  if (canWire(p, b, a)) return { from: b, to: a };
1011
1077
  return null;
1012
1078
  }
1013
- var fed = (p, to, except) => p.wires.some((w) => w.to.stage === to.stage && w.to.port === to.port && (except === void 0 || w.id !== except));
1079
+ var same = (a, b) => a.stage === b.stage && a.port === b.port;
1080
+ var takesMany = (p, to) => p.stages.find((s) => s.id === to.stage)?.inputs?.find((i) => i.id === to.port)?.many === true;
1081
+ var fed = (p, from, to, except) => {
1082
+ const many = takesMany(p, to);
1083
+ return p.wires.some((w) => same(w.to, to) && (!many || same(w.from, from)) && (except === void 0 || w.id !== except));
1084
+ };
1014
1085
  function wireFor(p, a, b, except) {
1015
1086
  const wire = orient(p, a, b);
1016
- return wire !== null && !fed(p, wire.to, except) ? wire : null;
1087
+ return wire !== null && !fed(p, wire.from, wire.to, except) ? wire : null;
1017
1088
  }
1018
1089
  function pinShown(doc, specs) {
1019
1090
  const shown = Object.fromEntries([...stageBoxes(doc, specs)].map(([id, b]) => [id, { x: b.x, y: b.y }]));
@@ -1021,7 +1092,8 @@ function pinShown(doc, specs) {
1021
1092
  }
1022
1093
  function stageBoxes(doc, specs) {
1023
1094
  const ids = new Set(doc.pipeline.stages.map((s) => s.id));
1024
- return new Map(specs.filter((s) => ids.has(s.id)).map((s) => [s.id, s.pose]));
1095
+ const world = worldPosesOf(specs, RIGID_POSE_COMPOSITION);
1096
+ return new Map(specs.filter((s) => ids.has(s.id)).map((s) => [s.id, world.get(s.id)]));
1025
1097
  }
1026
1098
  function editHandlers(now, unpick, rename = () => {
1027
1099
  }) {
@@ -1040,7 +1112,7 @@ function editHandlers(now, unpick, rename = () => {
1040
1112
  held = false;
1041
1113
  const reports = { moves: drag.moves.splice(0), boxes: drag.boxes.splice(0) };
1042
1114
  const { doc, built, specs, onChange } = now();
1043
- const boxes = new Map(specs.map((s) => [s.id, s.pose]));
1115
+ const boxes = worldPosesOf(specs, RIGID_POSE_COMPOSITION);
1044
1116
  const out = dragEdit(pinShown(ensureWireIds(doc), specs), built, reports, (id) => boxes.get(id));
1045
1117
  if (out !== null) onChange(out.doc, out.label);
1046
1118
  });
@@ -1064,7 +1136,7 @@ function editHandlers(now, unpick, rename = () => {
1064
1136
  if (id === void 0 || wire === void 0 || c.port === void 0) return null;
1065
1137
  const moved = { stage: c.node, port: c.port };
1066
1138
  const [from, to] = c.end === "from" ? [moved, wire.to] : [wire.from, moved];
1067
- return canWire(d.pipeline, from, to) && !fed(d.pipeline, to, id) ? reconnectWire(d, id, c.end, moved) : null;
1139
+ return canWire(d.pipeline, from, to) && !fed(d.pipeline, from, to, id) ? reconnectWire(d, id, c.end, moved) : null;
1068
1140
  }),
1069
1141
  onEditRoute: (c) => commit("Edit route", (d, b) => {
1070
1142
  const id = b.wireOf(c.edge);
@@ -1078,6 +1150,11 @@ function editHandlers(now, unpick, rename = () => {
1078
1150
  const ends = wireEnds(specs, c.edge);
1079
1151
  return ends === void 0 ? null : setLookRoute(d, look, id, c.waypoints, ends);
1080
1152
  }),
1153
+ onMoveLabel: (m) => commit("Move label", (d, b) => {
1154
+ const id = b.wireOf(m.edge);
1155
+ if (id === void 0 || m.placement === null && d.labels?.[id] === void 0) return null;
1156
+ return setWireLabel(d, id, m.placement);
1157
+ }),
1081
1158
  onDelete: (r) => commit("Delete", (d, b) => {
1082
1159
  unpick();
1083
1160
  const stages = r.nodes.flatMap((n) => b.stageOf(n) ?? []);
@@ -1105,14 +1182,14 @@ function useEditorPicks(built, given) {
1105
1182
  const [group, setGroup] = useState(null);
1106
1183
  const [edge, setEdge] = useState(null);
1107
1184
  const latest = useLatest$1({ built, given });
1108
- const pick = useCallback((id) => {
1185
+ const pick = useCallback((id, ids = id === null ? [] : [id]) => {
1109
1186
  const { built: b, given: g } = latest.current;
1110
1187
  const s = id === null ? void 0 : b.stageOf(id);
1111
1188
  const k = id === null ? void 0 : b.groupOf(id);
1112
1189
  setStage(s ?? null);
1113
1190
  setGroup(k ?? null);
1114
1191
  setEdge(id !== null && s === void 0 && k === void 0 && b.wireOf(id) !== void 0 ? id : null);
1115
- g.onSelect?.(s ?? null);
1192
+ g.onSelect?.(s ?? null, ids.filter((i) => isPart(b, i)));
1116
1193
  g.onSelectGroup?.(k ?? null);
1117
1194
  }, [latest]);
1118
1195
  const unpick = useCallback(() => setEdge(null), []);
@@ -1166,6 +1243,7 @@ function GroupTitleField({ initial, onDone }) {
1166
1243
  onChange: (e) => setText(e.target.value),
1167
1244
  onKeyDown: (e) => {
1168
1245
  e.stopPropagation();
1246
+ if (imeKey(e.nativeEvent)) return;
1169
1247
  if (e.key === "Enter") finish(text.trim());
1170
1248
  else if (e.key === "Escape") finish(null);
1171
1249
  },
@@ -1390,6 +1468,7 @@ function clearLabel(path, size, offset, keepOut) {
1390
1468
  }
1391
1469
  return void 0;
1392
1470
  }
1471
+ var worldSpecs = (data, options) => diagramScene(data, { ...options, composition: IDENTITY_POSE_COMPOSITION });
1393
1472
  var inkOf = (id, specs) => specs.filter((s) => s.id === id || s.parent === id || String(s.id).startsWith(`${id}/mark:`)).map((s) => s.pose);
1394
1473
  function walledIn(grid, walls) {
1395
1474
  if (walls.length === 0) return grid;
@@ -1404,7 +1483,7 @@ function walledIn(grid, walls) {
1404
1483
  };
1405
1484
  }
1406
1485
  function routeWires(wires, nodes, laid) {
1407
- const specs = diagramScene({ nodes, edges: wires.map((w) => w.edge), ...laid.groups !== void 0 ? { groups: laid.groups } : {} }, {
1486
+ const specs = worldSpecs({ nodes, edges: wires.map((w) => w.edge), ...laid.groups !== void 0 ? { groups: laid.groups } : {} }, {
1408
1487
  layout: "none",
1409
1488
  router: laid.router,
1410
1489
  ...laid.measure !== void 0 ? { measure: laid.measure } : {}
@@ -1412,9 +1491,9 @@ function routeWires(wires, nodes, laid) {
1412
1491
  const spec = new Map(specs.map((s) => [s.id, s]));
1413
1492
  const ink = new Map(nodes.map((n) => [n.id, inkOf(n.id, specs)]));
1414
1493
  const grid = { ...laid.grid, ink: laid.grid.columns.map((_, c) => [...ink].flatMap(([id, b]) => laid.column.get(id) === c ? b : [])) };
1415
- const end = (stage, port) => {
1494
+ const end = (stage, port2) => {
1416
1495
  const s = spec.get(stage);
1417
- const [site, ...sites] = portSitesOf({ id: stage, data: s.data }, s.pose, port);
1496
+ const [site, ...sites] = portSitesOf({ id: stage, data: s.data }, s.pose, port2);
1418
1497
  return { column: laid.column.get(stage), point: site?.point ?? { x: 0, y: 0 }, normal: site?.normal ?? null, sites };
1419
1498
  };
1420
1499
  const walls = nestGroups(laid.groups ?? [], (id) => ink.has(id)).flatMap((g) => {
@@ -1438,7 +1517,7 @@ function routeWires(wires, nodes, laid) {
1438
1517
  return { waypoints, path: routeOf(spec.get(edge.from), edge.fromPort, spec.get(edge.to), edge.toPort, laid.router, waypoints) };
1439
1518
  });
1440
1519
  const segments = routes.map(({ path }) => {
1441
- const c = path?.kind === "polygon" ? path.coords : [];
1520
+ const c = path?.kind === "polygon" ? worldCoords(path) : [];
1442
1521
  return Array.from({ length: Math.max(0, c.length / 2 - 1) }, (_, i) => {
1443
1522
  const [x0, y0, x1, y1] = [c[2 * i], c[2 * i + 1], c[2 * i + 2], c[2 * i + 3]];
1444
1523
  return { x: Math.min(x0, x1) - 1, y: Math.min(y0, y1) - 1, width: Math.abs(x1 - x0) + 2, height: Math.abs(y1 - y0) + 2 };
@@ -1482,11 +1561,11 @@ function laneHeights(count, least, needs, insets) {
1482
1561
  }
1483
1562
  function lanePorts(stage, box, lane, band) {
1484
1563
  const side = (ports, u) => ports.map((p) => {
1485
- const same = ports.filter((q) => q.type === p.type);
1564
+ const same2 = ports.filter((q) => q.type === p.type);
1486
1565
  const [top, bottom] = band(lane.get(p.type));
1487
1566
  const a = Math.max(top, box.y);
1488
1567
  const b = Math.min(bottom, box.y + box.height);
1489
- const y = a + (b - a) * (same.indexOf(p) + 1) / (same.length + 1);
1568
+ const y = a + (b - a) * (same2.indexOf(p) + 1) / (same2.length + 1);
1490
1569
  return { id: p.id, type: p.type, at: { u, v: (y - box.y) / box.height }, normal: { x: u === 0 ? -1 : 1, y: 0 } };
1491
1570
  });
1492
1571
  return [...side(stage.inputs ?? [], 0), ...side(stage.outputs ?? [], 1)];
@@ -1499,7 +1578,7 @@ function controlSideOf(stage, pipeline, lane) {
1499
1578
  return control.length > 0 && signal.length > 0 && mean(control) > mean(signal) ? "bottom" : "top";
1500
1579
  }
1501
1580
  function sizesOf(nodes, groups, measure) {
1502
- const specs = diagramScene({ nodes, edges: [], groups }, { layout: "none", ...measure !== void 0 ? { measure } : {} });
1581
+ const specs = worldSpecs({ nodes, edges: [], groups }, { layout: "none", ...measure !== void 0 ? { measure } : {} });
1503
1582
  const pose = new Map(specs.map((s) => [s.id, s.pose]));
1504
1583
  const spec = new Map(specs.map((s) => [s.id, s]));
1505
1584
  const nested = nestGroups(groups, (id) => nodes.some((n) => n.id === id)).map((g) => ({ members: g.members, inset: insetOf(diagramNodeOf({ id: g.id, kind: "mark", data: spec.get(g.id)?.data })?.group?.inset) }));
@@ -1645,10 +1724,27 @@ var lanes = (pipeline, options = {}) => {
1645
1724
  // src/effect/rack.ts
1646
1725
  var SKIP_OUT = "~skip-out";
1647
1726
  var SKIP_IN = "~skip-in";
1727
+ var TAP = "~tap:";
1728
+ var untapped = (port2) => port2.startsWith(TAP) ? port2.slice(TAP.length) : port2;
1648
1729
  var SIDE_PORT_GAP = 24;
1649
- var spreadAt = (ports, v) => ports.map((p, i) => ({ id: p.id, type: p.type, at: { u: (i + 1) / (ports.length + 1), v }, normal: { x: 0, y: v * 2 - 1 } }));
1650
- function roomFor(ports) {
1651
- const count = (v) => ports.filter((p) => p.at.v === v && p.at.u > 0 && p.at.u < 1).length;
1730
+ var ROW = {
1731
+ anchor: (along2, across) => ({ u: along2, v: across }),
1732
+ along: (at) => at.u,
1733
+ across: (at) => at.v,
1734
+ normal: (across) => ({ x: 0, y: across * 2 - 1 })
1735
+ };
1736
+ var COLUMN = {
1737
+ anchor: (along2, across) => ({ u: across, v: along2 }),
1738
+ along: (at) => at.v,
1739
+ across: (at) => at.u,
1740
+ normal: (across) => ({ x: across * 2 - 1, y: 0 })
1741
+ };
1742
+ var spreadAt = (frame, ports, across) => ports.map((p, i) => ({ id: p.id, type: p.type, at: frame.anchor((i + 1) / (ports.length + 1), across), normal: frame.normal(across) }));
1743
+ function roomFor(frame, ports) {
1744
+ const count = (across) => ports.filter((p) => {
1745
+ const along2 = frame.along(p.at);
1746
+ return frame.across(p.at) === across && along2 > 0 && along2 < 1;
1747
+ }).length;
1652
1748
  return (Math.max(count(0), count(1)) + 1) * SIDE_PORT_GAP;
1653
1749
  }
1654
1750
  function rackWires(pipeline, skips) {
@@ -1684,26 +1780,37 @@ function rackWires(pipeline, skips) {
1684
1780
  }
1685
1781
  return wires;
1686
1782
  }
1783
+ function tapped(wires, line, boxed) {
1784
+ return wires.map((w) => {
1785
+ const from = line.at.get(w.from.stage);
1786
+ const to = line.at.get(w.to.stage);
1787
+ const across = from !== void 0 && to !== void 0 && to - from > 1 && !w.arc && boxed(w.from.stage) && w.from.port === line.mainOut.get(w.from.stage) && w.to.port !== line.mainIn.get(w.to.stage);
1788
+ return across ? { ...w, from: { stage: w.from.stage, port: TAP + w.from.port }, exit: w.exit ?? w.from } : w;
1789
+ });
1790
+ }
1687
1791
  function lineOf(pipeline, ordered, chain) {
1688
1792
  const { mainIn, mainOut } = chainLinks(pipeline, ordered);
1689
1793
  return { at: new Map(chain.map((id, i) => [id, i])), mainIn, mainOut };
1690
1794
  }
1691
- function portsOf2(stage, line, wires, near2, far) {
1692
- if (!line.at.has(stage.id)) return [...spreadAt(stage.outputs ?? [], far), ...spreadAt(stage.inputs ?? [], near2)];
1693
- const typed = (p, anchor) => ({ id: p.id, type: p.type, at: anchor });
1795
+ function portsOf2(stage, line, wires, frame, near2, far) {
1796
+ if (!line.at.has(stage.id)) return [...spreadAt(frame, stage.outputs ?? [], far), ...spreadAt(frame, stage.inputs ?? [], near2)];
1797
+ const typed = (p, anchor2) => ({ id: p.id, type: p.type, at: anchor2 });
1694
1798
  const out = [];
1695
1799
  const side = [];
1696
1800
  for (const p of stage.inputs ?? []) {
1697
- if (p.id === line.mainIn.get(stage.id)) out.push(typed(p, { u: 0, v: 0.5 }));
1801
+ if (p.id === line.mainIn.get(stage.id)) out.push(typed(p, frame.anchor(0, 0.5)));
1698
1802
  else side.push(p);
1699
1803
  }
1700
1804
  for (const p of stage.outputs ?? []) {
1701
- if (p.id === line.mainOut.get(stage.id)) out.push(typed(p, { u: 1, v: 0.5 }));
1805
+ if (p.id === line.mainOut.get(stage.id)) out.push(typed(p, frame.anchor(1, 0.5)));
1702
1806
  else side.push(p);
1703
1807
  }
1704
- out.push(...spreadAt(side, near2));
1808
+ const main = portOn(stage, line.mainOut.get(stage.id) ?? "");
1809
+ const taps = new Set(wires.flatMap((w) => w.from.stage === stage.id && w.from.port.startsWith(TAP) ? [w.from.port] : []));
1810
+ if (main !== void 0) for (const id of taps) side.push({ ...main, id });
1811
+ out.push(...spreadAt(frame, side, near2));
1705
1812
  const type = portOn(stage, line.mainOut.get(stage.id) ?? line.mainIn.get(stage.id) ?? "")?.type;
1706
- const arc = (id, u) => ({ id, at: { u, v: far }, ...type !== void 0 ? { type } : {} });
1813
+ const arc = (id, along2) => ({ id, at: frame.anchor(along2, far), ...type !== void 0 ? { type } : {} });
1707
1814
  if (wires.some((w) => w.arc && w.from.stage === stage.id)) out.push(arc(SKIP_OUT, 0.75));
1708
1815
  if (wires.some((w) => w.arc && w.to.stage === stage.id)) out.push(arc(SKIP_IN, 0.25));
1709
1816
  return out;
@@ -1711,6 +1818,8 @@ function portsOf2(stage, line, wires, near2, far) {
1711
1818
  var rack = (pipeline, options = {}) => {
1712
1819
  const theme = options.theme ?? LOOK_THEME;
1713
1820
  const color = typeColors(pipeline, options.colors, theme.series);
1821
+ const down = options.direction === "down";
1822
+ const frame = down ? COLUMN : ROW;
1714
1823
  const below = options.side !== "above";
1715
1824
  const near2 = below ? 1 : 0;
1716
1825
  const far = below ? 0 : 1;
@@ -1720,16 +1829,18 @@ var rack = (pipeline, options = {}) => {
1720
1829
  const skips = options.collapseSkips === false ? [] : skipDecisions(pipeline, ordered);
1721
1830
  const gone = new Set(skips.map((s) => s.decision.id));
1722
1831
  const chain = ordered.filter((id) => !gone.has(id));
1723
- const wires = rackWires(pipeline, skips);
1724
1832
  const line = lineOf(pipeline, ordered, chain);
1833
+ const decisions = new Set(stages.filter((st) => kindOf(st) === "decision").map((st) => st.id));
1834
+ const wires = tapped(rackWires(pipeline, skips), line, (id) => !decisions.has(id));
1725
1835
  const kept = stages.filter((s) => !gone.has(s.id));
1726
- const controlSide = below ? "bottom" : "top";
1836
+ const flow = { direction: down ? "down" : "right", controlSide: down ? below ? "right" : "left" : below ? "bottom" : "top" };
1727
1837
  const nodes = kept.map((stage) => {
1728
- if (kindOf(stage) === "decision") return { id: stage.id, ...branchNode(stage, pipeline, options.branch, { controlSide, style: stageStyle(stage, theme) }) };
1729
- const ports = portsOf2(stage, line, wires, near2, far);
1838
+ if (kindOf(stage) === "decision") return { id: stage.id, ...branchNode(stage, pipeline, options.branch, { ...flow, style: stageStyle(stage, theme) }) };
1839
+ const ports = portsOf2(stage, line, wires, frame, near2, far);
1730
1840
  const node = boxNode(stage, { showParams: options.showParams !== false, rowPorts: false, ports, run: options });
1731
- const room = roomFor(ports);
1732
- return room > 2 * SIDE_PORT_GAP ? { ...node, width: room } : node;
1841
+ const room = roomFor(frame, ports);
1842
+ if (room <= 2 * SIDE_PORT_GAP) return node;
1843
+ return down ? { ...node, height: Math.max(node.height ?? 0, room) } : { ...node, width: room };
1733
1844
  });
1734
1845
  const byId = new Map(kept.map((s) => [s.id, s]));
1735
1846
  const edges = wires.map((wire) => {
@@ -1750,7 +1861,7 @@ var rack = (pipeline, options = {}) => {
1750
1861
  scene: {
1751
1862
  layout: "chain",
1752
1863
  layoutOptions: {
1753
- direction: "right",
1864
+ direction: down ? "down" : "right",
1754
1865
  chain,
1755
1866
  chainSide: below ? "after" : "before",
1756
1867
  ...options.nodeGap !== void 0 ? { nodeGap: options.nodeGap } : {},
@@ -1760,14 +1871,14 @@ var rack = (pipeline, options = {}) => {
1760
1871
  ...sceneStyles(kept, edges, theme)
1761
1872
  },
1762
1873
  color,
1763
- order: { axis: "x", items: chain.map((id) => ({ id })) },
1874
+ order: { axis: down ? "y" : "x", items: chain.map((id) => ({ id })) },
1764
1875
  exits: new Map(wires.flatMap((wire, i) => wire.exit === void 0 ? [] : [[edgeIdOf(edges[i].edge), wire.exit]]))
1765
1876
  };
1766
1877
  };
1767
1878
 
1768
1879
  // src/effect/editorMapping.ts
1769
1880
  var endsKey = (from, to) => JSON.stringify([from.stage, from.port, to.stage, to.port]);
1770
- var edgeEnds = (e) => endsKey({ stage: e.from, port: e.fromPort ?? "" }, { stage: e.to, port: e.toPort ?? "" });
1881
+ var edgeEnds = (e) => endsKey({ stage: e.from, port: untapped(e.fromPort ?? "") }, { stage: e.to, port: e.toPort ?? "" });
1771
1882
  function editorScene(doc, look = "nodeGraph", options) {
1772
1883
  const drawn = drawLook(doc, look, options);
1773
1884
  const stages = new Set(doc.pipeline.stages.map((s) => s.id));
@@ -1783,10 +1894,16 @@ function editorScene(doc, look = "nodeGraph", options) {
1783
1894
  return id === void 0 ? [] : [[edgeIdOf(e), id]];
1784
1895
  }));
1785
1896
  const routes = look === "nodeGraph" ? doc.routes : void 0;
1786
- const edges = routes === void 0 ? drawn.data.edges : drawn.data.edges.map((e) => {
1897
+ const { labels } = doc;
1898
+ const edges = routes === void 0 && labels === void 0 ? drawn.data.edges : drawn.data.edges.map((e) => {
1787
1899
  const id = wires.get(edgeIdOf(e));
1788
- const route = id === void 0 ? void 0 : routes[id];
1789
- return route === void 0 ? e : { ...e, waypoints: route };
1900
+ const route = id === void 0 ? void 0 : routes?.[id];
1901
+ const label = id === void 0 ? void 0 : labels?.[id];
1902
+ return {
1903
+ ...e,
1904
+ ...route !== void 0 ? { waypoints: route } : {},
1905
+ ...label !== void 0 ? { labelPlacement: label } : {}
1906
+ };
1790
1907
  });
1791
1908
  return {
1792
1909
  ...drawn,
@@ -1948,9 +2065,7 @@ function groupMove(doc, order, units, move) {
1948
2065
  }
1949
2066
  function laneMove(doc, order, move) {
1950
2067
  const types = order.items.flatMap((i) => laneTypeOf(i.id) ?? []);
1951
- const moved = types.filter((_, k) => k !== move.from);
1952
- moved.splice(move.to > move.from ? move.to - 1 : move.to, 0, types[move.from]);
1953
- return setLaneOrder(doc, moved);
2068
+ return setLaneOrder(doc, moveItems(types, [move.from], move.to));
1954
2069
  }
1955
2070
  function unitsBefore(move) {
1956
2071
  const units = move.order.filter((id) => id !== move.id);
@@ -2017,7 +2132,8 @@ function renameGroupContribution(now, rename) {
2017
2132
  invoker: {
2018
2133
  timing: "immediate",
2019
2134
  run(_, params) {
2020
- const group = under({ x: Number(params?.worldX), y: Number(params?.worldY) });
2135
+ const world = params?.world;
2136
+ const group = world ? under(world) : void 0;
2021
2137
  if (group !== void 0) rename(group);
2022
2138
  }
2023
2139
  }
@@ -2049,7 +2165,7 @@ function freeSpot(boxes, size, view) {
2049
2165
  const last = boxes.reduce((a, b) => b.x + b.width > a.x + a.width ? b : a);
2050
2166
  return { x: last.x + last.width + PLACE_GAP, y: last.y };
2051
2167
  }
2052
- var EffectEditor = forwardRef(function EffectEditor2({ doc, onChange, run, selected, onSelect, selectedGroup, onSelectGroup, look = "nodeGraph", options, width, height, background, className }, ref) {
2168
+ var EffectEditor = forwardRef(function EffectEditor2({ doc, onChange, run, selected, onSelect, selectedGroup, onSelectGroup, look = "nodeGraph", options, width, height, maxScale, bounded, refit, background, className }, ref) {
2053
2169
  const built = useMemo(() => editorScene(doc, look, options), [doc, look, options]);
2054
2170
  const specs = useMemo(() => applyLookRoutes(diagramScene(built.data, built.scene), doc, look, built).map((s) => s.pickable === false || isPart(built, s.id) ? s : { ...s, pickable: false }), [built, doc, look]);
2055
2171
  const painted = useMemo(() => paintRun(doc.pipeline, specs, run, built.exits), [doc.pipeline, specs, run, built]);
@@ -2113,6 +2229,9 @@ var EffectEditor = forwardRef(function EffectEditor2({ doc, onChange, run, selec
2113
2229
  width,
2114
2230
  height,
2115
2231
  minScale: EFFECT_MIN_SCALE,
2232
+ ...maxScale !== void 0 ? { maxScale } : {},
2233
+ ...bounded !== void 0 ? { bounded } : {},
2234
+ ...refit !== void 0 ? { refit } : {},
2116
2235
  anchor: "start",
2117
2236
  ports: "connect",
2118
2237
  portOptions,
@@ -2120,9 +2239,11 @@ var EffectEditor = forwardRef(function EffectEditor2({ doc, onChange, run, selec
2120
2239
  onConnect: handlers.onConnect,
2121
2240
  onReconnect: handlers.onReconnect,
2122
2241
  onEditRoute: handlers.onEditRoute,
2242
+ onMoveLabel: handlers.onMoveLabel,
2123
2243
  onDelete: handlers.onDelete,
2124
2244
  ...look === "nodeGraph" ? { onMove: handlers.onMove } : { ambient },
2125
2245
  ...look === "nodeGraph" ? { onEditContainer: handlers.onEditContainer } : {},
2246
+ selectionMode: "multiple",
2126
2247
  onSelect: picks.pick,
2127
2248
  ...picks.shown !== void 0 ? { selected: picks.shown } : {},
2128
2249
  ...pages !== void 0 ? { pages } : {},
@@ -2145,9 +2266,9 @@ function draw(width, height, paint) {
2145
2266
  paint(canvas.getContext("2d"));
2146
2267
  return canvas.transferToImageBitmap();
2147
2268
  }
2148
- function need(ctx, port) {
2149
- const value = ctx.inputs[port];
2150
- if (value === void 0) throw new Error(`${port} is not connected`);
2269
+ function need(ctx, port2) {
2270
+ const value = ctx.inputs[port2];
2271
+ if (value === void 0) throw new Error(`${port2} is not connected`);
2151
2272
  return value;
2152
2273
  }
2153
2274
  function decide(ctx, value) {
@@ -2324,14 +2445,14 @@ function createPass(pipeline, opts) {
2324
2445
  const values = {};
2325
2446
  const previews = {};
2326
2447
  const seen = { ...inputs2, ...outputs };
2327
- for (const port of shownPorts(stage)) {
2328
- const value = seen[port.id];
2448
+ for (const port2 of shownPorts(stage)) {
2449
+ const value = seen[port2.id];
2329
2450
  if (value === void 0) continue;
2330
- const kind = opts.kinds?.[port.type];
2451
+ const kind = opts.kinds?.[port2.type];
2331
2452
  const text = kind?.show !== void 0 ? kind.show(value) : plainText(value);
2332
- if (text !== void 0) values[port.id] = text;
2453
+ if (text !== void 0) values[port2.id] = text;
2333
2454
  const preview = opts.previews === false ? void 0 : await kind?.preview?.(value, size);
2334
- if (preview !== void 0) previews[port.id] = preview;
2455
+ if (preview !== void 0) previews[port2.id] = preview;
2335
2456
  }
2336
2457
  const full = {
2337
2458
  ...entry,
@@ -2376,11 +2497,11 @@ function createPass(pipeline, opts) {
2376
2497
  const given = new Set(Object.values(sources));
2377
2498
  const done = /* @__PURE__ */ new Set();
2378
2499
  for (const [id, outputs] of made) {
2379
- for (const port of stages.get(id)?.outputs ?? []) {
2380
- const value = outputs[port.id];
2500
+ for (const port2 of stages.get(id)?.outputs ?? []) {
2501
+ const value = outputs[port2.id];
2381
2502
  if (value === void 0 || given.has(value) || done.has(value)) continue;
2382
2503
  done.add(value);
2383
- opts.kinds?.[port.type]?.dispose?.(value);
2504
+ opts.kinds?.[port2.type]?.dispose?.(value);
2384
2505
  }
2385
2506
  }
2386
2507
  made.clear();
@@ -2389,9 +2510,9 @@ function createPass(pipeline, opts) {
2389
2510
  }
2390
2511
  function passThrough(stage, inputs2) {
2391
2512
  const out = {};
2392
- for (const port of stage.outputs ?? []) {
2393
- const from = (stage.inputs ?? []).find((i) => i.type === port.type && inputs2[i.id] !== void 0);
2394
- if (from !== void 0) out[port.id] = inputs2[from.id];
2513
+ for (const port2 of stage.outputs ?? []) {
2514
+ const from = (stage.inputs ?? []).find((i) => i.type === port2.type && inputs2[i.id] !== void 0);
2515
+ if (from !== void 0) out[port2.id] = inputs2[from.id];
2395
2516
  }
2396
2517
  return out;
2397
2518
  }
@@ -2418,6 +2539,7 @@ function createEffectRunner(opts) {
2418
2539
  let controller = new AbortController();
2419
2540
  let tail = Promise.resolve();
2420
2541
  let pending = 0;
2542
+ let behind = false;
2421
2543
  let published = [];
2422
2544
  const listeners = /* @__PURE__ */ new Set();
2423
2545
  const set = (next) => {
@@ -2479,6 +2601,7 @@ function createEffectRunner(opts) {
2479
2601
  controller.abort();
2480
2602
  controller = new AbortController();
2481
2603
  dropStepping();
2604
+ behind = false;
2482
2605
  set({ run: void 0, playing: false, time: 0 });
2483
2606
  },
2484
2607
  advance(seconds) {
@@ -2486,6 +2609,17 @@ function createEffectRunner(opts) {
2486
2609
  state = { ...state, time: state.time + seconds };
2487
2610
  if (pending === 0) void enqueue(wholeRun(state.time));
2488
2611
  },
2612
+ seek(seconds) {
2613
+ state = { ...state, time: seconds };
2614
+ if (pending === 0) void enqueue(wholeRun(seconds));
2615
+ else if (!behind) {
2616
+ behind = true;
2617
+ void enqueue((signal) => {
2618
+ behind = false;
2619
+ return wholeRun(state.time)(signal);
2620
+ });
2621
+ }
2622
+ },
2489
2623
  setPipeline(next) {
2490
2624
  if (next === pipeline) return;
2491
2625
  pipeline = next;
@@ -2520,7 +2654,7 @@ function createEffectRunner(opts) {
2520
2654
  };
2521
2655
  }
2522
2656
  function useEffectRunner(opts) {
2523
- const { pipeline, sources, evaluators, kinds, previews, previewSize, target, requestFrame, cancelFrame } = opts;
2657
+ const { pipeline, sources, evaluators, kinds, previews, previewSize, target, requestFrame, cancelFrame, clock } = opts;
2524
2658
  const [runner] = useState(() => createEffectRunner(opts));
2525
2659
  useEffect(() => {
2526
2660
  runner.setPipeline(pipeline);
@@ -2553,9 +2687,30 @@ function useEffectRunner(opts) {
2553
2687
  });
2554
2688
  useEffect(() => {
2555
2689
  last.current = null;
2556
- if (state.playing) loop.request();
2690
+ if (state.playing && clock === void 0) loop.request();
2557
2691
  else loop.cancel();
2558
- }, [state.playing, loop]);
2692
+ }, [state.playing, loop, clock]);
2693
+ useEffect(() => {
2694
+ if (clock === void 0) return;
2695
+ const c = clock.clock;
2696
+ const follow = () => {
2697
+ if (c.rate !== 0) runner.play();
2698
+ else runner.pause();
2699
+ };
2700
+ follow();
2701
+ if (runner.state.time !== c.elapsed / 1e3) runner.seek(c.elapsed / 1e3);
2702
+ const offClock = c.subscribe(follow);
2703
+ const offFrame = clock.onFrame((elapsed) => runner.seek(elapsed / 1e3));
2704
+ const offRunner = runner.subscribe(() => {
2705
+ const { playing } = runner.state;
2706
+ if (playing !== (c.rate !== 0)) c.rate = playing ? c.speed : 0;
2707
+ });
2708
+ return () => {
2709
+ offClock();
2710
+ offFrame();
2711
+ offRunner();
2712
+ };
2713
+ }, [runner, clock]);
2559
2714
  useEffect(() => {
2560
2715
  runner.drawn(state.run);
2561
2716
  }, [runner, state.run]);
@@ -2563,6 +2718,6 @@ function useEffectRunner(opts) {
2563
2718
  return { ...state, runner };
2564
2719
  }
2565
2720
 
2566
- export { AT_COLOR, BRANCH_SYMBOLS, COMPACT_PADDING, EFFECT_LOOKS, EFFECT_MIN_SCALE, EffectChart, EffectEditor, IMAGE_STAGE_TYPES, LANE_INSET, REORDER_LANES_ACTION_ID, addStage, addWire, applyLookRoutes, branchNode, canMoveInChain, canWire, chainLinks, compact, costText, createEffectRunner, createPass, editorScene, ensureWireIds, groupContents, groupStages, imageEvaluators, imageValueKinds, laneId, laneTypeOf, lanes, mainChain, moveGroup, moveInChain, moveStages, moveToGroup, nextGroupId, nextStageId, nextWireId, nodeGraph, paintRun, pruneLookRoutes, rack, reconnectWire, removeStages, removeWires, renameGroup, runPipeline, setGroupBox, setLaneOrder, setLookRoute, setParam, setWireRoute, symbolNameFor, testCard, ungroup, useEffectRunner, wireEnds };
2721
+ export { AT_COLOR, BRANCH_SYMBOLS, COMPACT_PADDING, EFFECT_LOOKS, EFFECT_MIN_SCALE, EffectChart, EffectEditor, IMAGE_STAGE_TYPES, LANE_INSET, REORDER_LANES_ACTION_ID, addStage, addWire, applyLookRoutes, branchNode, canMoveInChain, canWire, chainLinks, compact, costText, createEffectRunner, createPass, editorScene, ensureWireIds, groupContents, groupStages, imageEvaluators, imageValueKinds, laneId, laneTypeOf, lanes, mainChain, moveGroup, moveInChain, moveStages, moveToGroup, nextGroupId, nextStageId, nextWireId, nodeGraph, paintRun, pruneLookRoutes, rack, reconnectWire, removeStages, removeWires, renameGroup, runPipeline, setGroupBox, setLaneOrder, setLookRoute, setParam, setWireLabel, setWireRoute, symbolNameFor, testCard, ungroup, useEffectRunner, wireEnds };
2567
2722
  //# sourceMappingURL=effect.js.map
2568
2723
  //# sourceMappingURL=effect.js.map