@phin-tech/dottie 0.7.0 → 0.8.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/dottie.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! dottie.js 0.7.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
1
+ /*! dottie.js 0.8.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
4
4
  var __publicField = (obj, key, value2) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2);
@@ -8,10 +8,53 @@ var MAX_NAME = 64;
8
8
  function isName(name) {
9
9
  return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
10
10
  }
11
+ var METRIC_NAMES = ["throughput", "load", "delay", "backlog", "depth", "inFlight", "transit", "backup"];
12
+ var OPS = [">=", "<=", "==", "!=", ">", "<"];
13
+ function metricTerm(term) {
14
+ const t = trim(term);
15
+ if (!t.includes("(")) {
16
+ const op2 = t.search(/[>=<!]/);
17
+ if (op2 < 0) return null;
18
+ const name2 = trim(t.slice(0, op2));
19
+ if (!METRIC_NAMES.includes(name2)) return null;
20
+ const m = metricTerm(name2 + "() " + t.slice(op2));
21
+ return m ? { ...m, target: "" } : null;
22
+ }
23
+ const open = t.indexOf("("), close = t.indexOf(")");
24
+ if (open < 0 || close < 0 || open >= close) return null;
25
+ const name = trim(t.slice(0, open)), target = trim(t.slice(open + 1, close));
26
+ let rest = trim(t.slice(close + 1));
27
+ if (!METRIC_NAMES.includes(name) || target.includes("(")) return null;
28
+ const op = OPS.find((o) => rest.startsWith(o));
29
+ if (op === void 0) return null;
30
+ rest = trim(rest.slice(op.length));
31
+ if (!/^-?[0-9]+(\.[0-9]+)?$/.test(rest)) return null;
32
+ return { metric: name, target, op, value: Number(rest) };
33
+ }
34
+ function passes(m, v) {
35
+ switch (m.op) {
36
+ case ">":
37
+ return v > m.value;
38
+ case ">=":
39
+ return v >= m.value;
40
+ case "<":
41
+ return v < m.value;
42
+ case "<=":
43
+ return v <= m.value;
44
+ case "==":
45
+ return v === m.value;
46
+ default:
47
+ return v !== m.value;
48
+ }
49
+ }
50
+ function mentionsMetric(condition) {
51
+ return condition.split("||").some((any) => any.split("&&").some((all) => metricTerm(all) !== null));
52
+ }
11
53
  function conditionNames(condition) {
12
54
  const out = [];
13
55
  for (const any of condition.split("||")) {
14
56
  for (const all of any.split("&&")) {
57
+ if (metricTerm(all) !== null) continue;
15
58
  let term = trim(all);
16
59
  if (term.startsWith("!")) term = trim(term.slice(1));
17
60
  if (!isName(term)) return null;
@@ -20,9 +63,14 @@ function conditionNames(condition) {
20
63
  }
21
64
  return out;
22
65
  }
23
- function holds(condition, state) {
66
+ function holds(condition, state, metric = null) {
24
67
  if (conditionNames(condition) === null) return false;
25
68
  return condition.split("||").some((any) => any.split("&&").every((all) => {
69
+ const m = metricTerm(all);
70
+ if (m !== null) {
71
+ const v = metric ? metric(m) : null;
72
+ return v !== null && passes(m, v);
73
+ }
26
74
  let term = trim(all);
27
75
  let negated = false;
28
76
  if (term.startsWith("!")) {
@@ -44,11 +92,40 @@ function stateHidden(page, choice) {
44
92
  for (const e of page.elements) if (e.when !== null && !holds(e.when, values)) out.add(e.id);
45
93
  return out;
46
94
  }
95
+ function activeOverrides(e, values) {
96
+ if (!values || !e.overrides) return [];
97
+ return e.overrides.filter((o) => !mentionsMetric(o.when) && holds(o.when, values));
98
+ }
99
+ function looksOnly(o) {
100
+ return { ...o, reroute: null, offline: null, cost: null, capacity: null, delay: null };
101
+ }
102
+ function applyOverride(o, e) {
103
+ const out = cloneElement(e);
104
+ if (o.stroke) out.style.stroke = { ...o.stroke };
105
+ if (o.fill) out.style.fill = { ...o.fill };
106
+ if (o.textColor) out.style.textColor = { ...o.textColor };
107
+ if (o.strokeWidth !== null) out.style.strokeWidth = Math.max(0, o.strokeWidth);
108
+ if (o.dash !== null) out.style.dash = o.dash;
109
+ if (o.text !== null) out.text = o.text;
110
+ if (o.cost !== null && out.connector) out.connector.cost = Math.min(Math.max(o.cost, 0), 1e6);
111
+ if (o.capacity !== null) out.capacity = Math.max(o.capacity, 0);
112
+ if (o.delay !== null && out.kind !== "connector") out.delay = Math.max(o.delay, 0);
113
+ if (o.opacity !== null) {
114
+ const v = Math.min(Math.max(o.opacity, 0), 1) * (out.style.opacity ?? 1);
115
+ out.style.opacity = v >= 1 ? null : v;
116
+ }
117
+ return out;
118
+ }
47
119
  function changesState(c) {
48
- return !!c && (Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
120
+ return !!c && (c.reset === true || Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
49
121
  }
50
- function applyClick(c, state) {
51
- const out = { ...state };
122
+ function applyClick(c, state, initial = {}) {
123
+ let out = { ...state };
124
+ if (c.reset === true) {
125
+ out = {};
126
+ for (const k of Object.keys(state)) out[k] = false;
127
+ Object.assign(out, initial);
128
+ }
52
129
  for (const [k, v] of Object.entries(c.set ?? {})) out[k] = v;
53
130
  for (const k of c.toggle ?? []) out[k] = !(out[k] ?? false);
54
131
  return out;
@@ -63,7 +140,9 @@ var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand",
63
140
  var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
64
141
  var KNOWN_FIELDS = {
65
142
  file: ["version", "pages", "images"],
66
- page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables"],
143
+ page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
144
+ flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
145
+ flowOverride: ["when", "volume"],
67
146
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
68
147
  layer: ["id", "name", "visible", "locked"],
69
148
  rgba: ["r", "g", "b", "a"],
@@ -92,6 +171,7 @@ var KNOWN_FIELDS = {
92
171
  "click",
93
172
  "hover",
94
173
  "when",
174
+ "overrides",
95
175
  "symbol",
96
176
  "playback",
97
177
  "offset",
@@ -100,8 +180,13 @@ var KNOWN_FIELDS = {
100
180
  "reveal",
101
181
  "ports",
102
182
  "labelRect",
103
- "extension"
183
+ "extension",
184
+ "capacity",
185
+ "delay",
186
+ "queue",
187
+ "gauge"
104
188
  ],
189
+ gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
105
190
  strokePoint: ["x", "y"],
106
191
  subpath: ["closed", "coords"],
107
192
  inlineImage: ["data", "format"],
@@ -109,7 +194,7 @@ var KNOWN_FIELDS = {
109
194
  extensionRef: ["id", "shape", "version", "params"],
110
195
  normalizedPoint: ["x", "y"],
111
196
  normalizedRect: ["x", "y", "width", "height"],
112
- connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
197
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
113
198
  waypoint: ["x", "y"],
114
199
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
115
200
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -118,8 +203,9 @@ var KNOWN_FIELDS = {
118
203
  repeat: ["start", "end", "count"],
119
204
  cameraKey: ["t", "x", "y", "width", "height", "ease"],
120
205
  marker: ["t", "name", "loop"],
121
- click: ["scene", "url", "set", "toggle"],
122
- hover: ["stroke", "fill", "opacity"]
206
+ click: ["scene", "url", "set", "toggle", "reset"],
207
+ hover: ["stroke", "fill", "opacity"],
208
+ override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
123
209
  };
124
210
  var MAX_DURATION = 3600;
125
211
  var MAX_MARCH = 1e4;
@@ -135,6 +221,8 @@ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
135
221
  var str = (v) => typeof v === "string" ? v : void 0;
136
222
  var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
137
223
  var bool = (v) => typeof v === "boolean" ? v : void 0;
224
+ var NO_ID = "00000000-0000-0000-0000-000000000000";
225
+ var UUID = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;
138
226
  var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
139
227
  function clampTime(t) {
140
228
  if (!Number.isFinite(t)) return t === Infinity ? MAX_DURATION : 0;
@@ -226,7 +314,8 @@ var Reader = class {
226
314
  this.fields(w, "waypoint");
227
315
  return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
228
316
  }) : null,
229
- hops: typeof v.hops === "boolean" ? v.hops : null
317
+ hops: typeof v.hops === "boolean" ? v.hops : null,
318
+ cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
230
319
  };
231
320
  }
232
321
  image(v, table) {
@@ -278,6 +367,7 @@ var Reader = class {
278
367
  click: this.click(v.click),
279
368
  hover: this.hover(v.hover),
280
369
  when: str(v.when) ?? null,
370
+ overrides: this.overrides(v.overrides),
281
371
  symbol: uuid(v.symbol) ?? null,
282
372
  playback: this.playback(v.playback),
283
373
  offset: num(v.offset) ?? null,
@@ -289,7 +379,26 @@ var Reader = class {
289
379
  return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
290
380
  }) : null,
291
381
  labelRect: this.labelRect(v.labelRect),
292
- extension: this.extension(v.extension)
382
+ extension: this.extension(v.extension),
383
+ capacity: num(v.capacity) ?? null,
384
+ delay: num(v.delay) ?? null,
385
+ queue: v.queue === true ? true : null,
386
+ gauge: this.gauge(v.gauge)
387
+ };
388
+ }
389
+ gauge(v) {
390
+ if (!isObj(v)) return null;
391
+ this.fields(v, "gauge");
392
+ const finite = (x) => typeof x === "number" && Number.isFinite(x) ? x : null;
393
+ const style2 = str(v.style);
394
+ return {
395
+ metric: str(v.metric) ?? "",
396
+ of: typeof v.of === "string" && UUID.test(v.of) ? v.of.toUpperCase() : NO_ID,
397
+ max: finite(v.max),
398
+ warn: finite(v.warn),
399
+ alarm: finite(v.alarm),
400
+ style: style2 === "number" || style2 === "bar" || style2 === "dial" ? style2 : null,
401
+ label: str(v.label) ?? null
293
402
  };
294
403
  }
295
404
  labelRect(v) {
@@ -333,7 +442,7 @@ var Reader = class {
333
442
  }
334
443
  const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
335
444
  const toggle = raw ? raw.filter(isName) : null;
336
- return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
445
+ return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null, reset: v.reset === true ? true : null };
337
446
  }
338
447
  playback(v) {
339
448
  const s = str(v);
@@ -369,6 +478,35 @@ var Reader = class {
369
478
  params: strings(v.params)
370
479
  };
371
480
  }
481
+ /** As DottieKit reads them: a reroute whose targets aren't all ids is dropped, as are keys that aren't ids. */
482
+ overrides(v) {
483
+ if (!Array.isArray(v)) return null;
484
+ return v.filter(isObj).map((o) => {
485
+ this.fields(o, "override");
486
+ const dash = str(o.dash);
487
+ let reroute = null;
488
+ if (isObj(o.reroute) && Object.values(o.reroute).every((x) => typeof x === "string" && UUID.test(x))) {
489
+ reroute = {};
490
+ for (const [k, x] of Object.entries(o.reroute)) if (UUID.test(k)) reroute[k.toUpperCase()] = x.toUpperCase();
491
+ if (Object.keys(reroute).length === 0) reroute = null;
492
+ }
493
+ return {
494
+ when: str(o.when) ?? "",
495
+ stroke: rgba(o.stroke) ?? null,
496
+ fill: rgba(o.fill) ?? null,
497
+ textColor: rgba(o.textColor) ?? null,
498
+ opacity: num(o.opacity) ?? null,
499
+ strokeWidth: num(o.strokeWidth) ?? null,
500
+ dash: dash !== void 0 && ["solid", "dashed", "dotted"].includes(dash) ? dash : null,
501
+ text: str(o.text) ?? null,
502
+ reroute,
503
+ offline: o.offline === true ? true : null,
504
+ cost: typeof o.cost === "number" && Number.isFinite(o.cost) ? o.cost : null,
505
+ capacity: typeof o.capacity === "number" && Number.isFinite(o.capacity) ? o.capacity : null,
506
+ delay: typeof o.delay === "number" && Number.isFinite(o.delay) ? o.delay : null
507
+ };
508
+ });
509
+ }
372
510
  hover(v) {
373
511
  if (!isObj(v)) return null;
374
512
  this.fields(v, "hover");
@@ -468,7 +606,33 @@ var Reader = class {
468
606
  theme: str(v.theme) ?? null,
469
607
  timeline: this.timeline(v.timeline),
470
608
  symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
471
- variables: this.variables(v.variables)
609
+ variables: this.variables(v.variables),
610
+ flows: Array.isArray(v.flows) ? v.flows.filter(isObj).map((f) => this.flow(f)) : null
611
+ };
612
+ }
613
+ /** As DottieKit reads one: a missing or bad id, source or destination sends nothing; bad destinations are dropped. */
614
+ flow(f) {
615
+ this.fields(f, "flow");
616
+ const id = (x) => typeof x === "string" && UUID.test(x) ? x.toUpperCase() : NO_ID;
617
+ const finite = (x) => {
618
+ const n = num(x);
619
+ return n !== void 0 && Number.isFinite(n) ? n : null;
620
+ };
621
+ return {
622
+ id: id(f.id),
623
+ from: id(f.from),
624
+ to: Array.isArray(f.to) && f.to.every((x) => typeof x === "string") ? f.to.filter((x) => UUID.test(x)).map((x) => x.toUpperCase()) : [],
625
+ every: finite(f.every),
626
+ speed: finite(f.speed),
627
+ size: finite(f.size),
628
+ color: isObj(f.color) && ["r", "g", "b", "a"].every((k) => typeof f.color[k] === "number") ? rgba(f.color) ?? null : null,
629
+ when: str(f.when) ?? null,
630
+ spread: f.spread === true ? true : null,
631
+ volume: finite(f.volume),
632
+ overrides: Array.isArray(f.overrides) && f.overrides.length > 0 ? f.overrides.filter(isObj).map((o) => {
633
+ this.fields(o, "flowOverride");
634
+ return { when: str(o.when) ?? "", volume: finite(o.volume) };
635
+ }) : null
472
636
  };
473
637
  }
474
638
  };
@@ -530,7 +694,8 @@ function cloneElement(e) {
530
694
  start: { ...e.connector.start },
531
695
  end: { ...e.connector.end },
532
696
  waypoints: e.connector.waypoints?.map((w) => ({ ...w })) ?? null
533
- } : null
697
+ } : null,
698
+ gauge: e.gauge ? { ...e.gauge } : e.gauge
534
699
  };
535
700
  }
536
701
  function orderedElements(page) {
@@ -1281,8 +1446,617 @@ function mulConst(step) {
1281
1446
  return [p[0], p[1]];
1282
1447
  }
1283
1448
 
1449
+ // src/flows.ts
1450
+ var DEFAULT_EVERY = 0.5;
1451
+ var DEFAULT_SPEED = 160;
1452
+ var DEFAULT_SIZE = 12;
1453
+ var DEFAULT_COLOR = { r: 0.95, g: 0.36, b: 0.33, a: 1 };
1454
+ var clamp = (v, fallback, lo, hi) => v === null || !Number.isFinite(v) ? fallback : Math.min(Math.max(v, lo), hi);
1455
+ function packetID(flow, k) {
1456
+ const n = (k + 2 ** 40) % 2 ** 48;
1457
+ return flow.slice(0, 24) + (n < 0 ? n + 2 ** 48 : n).toString(16).toUpperCase().padStart(12, "0");
1458
+ }
1459
+ var routeCost = (cost) => cost === null || !Number.isFinite(cost) ? 1 : Math.min(Math.max(cost, 0), 1e6);
1460
+ function flowTree(from, frame2, offline = /* @__PURE__ */ new Set()) {
1461
+ const shapes2 = new Set(frame2.elements.filter((e) => e.kind !== "connector" && !offline.has(e.id)).map((e) => e.id));
1462
+ if (!shapes2.has(from)) return null;
1463
+ const next = /* @__PURE__ */ new Map();
1464
+ const add2 = (a, edge) => {
1465
+ const list = next.get(a);
1466
+ if (list) list.push(edge);
1467
+ else next.set(a, [edge]);
1468
+ };
1469
+ for (const e of frame2.elements) {
1470
+ const c = e.connector;
1471
+ const a = c?.start.elementID ?? null, b = c?.end.elementID ?? null;
1472
+ if (!c || offline.has(e.id) || a === null || b === null || a === b || !shapes2.has(a) || !shapes2.has(b)) continue;
1473
+ const cost = routeCost(c.cost);
1474
+ add2(a, { to: b, via: e.id, back: false, cost });
1475
+ if (c.startHead !== "none") add2(b, { to: a, via: e.id, back: true, cost });
1476
+ }
1477
+ const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
1478
+ let counter = 1;
1479
+ const tree = new FlowTree(from);
1480
+ const open = [from];
1481
+ while (open.length > 0) {
1482
+ let best = 0;
1483
+ for (let i = 1; i < open.length; i++) {
1484
+ const da = dist.get(open[i]), db = dist.get(open[best]);
1485
+ if (da < db || da === db && order.get(open[i]) < order.get(open[best])) best = i;
1486
+ }
1487
+ const n = open.splice(best, 1)[0];
1488
+ tree.settled.add(n);
1489
+ for (const edge of next.get(n) ?? []) {
1490
+ if (tree.settled.has(edge.to)) continue;
1491
+ const d = dist.get(n) + edge.cost;
1492
+ const old = dist.get(edge.to);
1493
+ if (old !== void 0 && old <= d) continue;
1494
+ if (old === void 0) open.push(edge.to);
1495
+ dist.set(edge.to, d);
1496
+ order.set(edge.to, counter++);
1497
+ tree.came.set(edge.to, { from: n, via: edge.via, back: edge.back });
1498
+ }
1499
+ }
1500
+ return tree;
1501
+ }
1502
+ var FlowTree = class {
1503
+ constructor(from) {
1504
+ __publicField(this, "settled", /* @__PURE__ */ new Set());
1505
+ __publicField(this, "came", /* @__PURE__ */ new Map());
1506
+ __publicField(this, "from");
1507
+ this.from = from;
1508
+ }
1509
+ /** The first of `to` that can be reached; with `spread`, when the first can't be, every other one that can. */
1510
+ goals(to, spread) {
1511
+ const reachable = to.filter((x) => x !== this.from && this.settled.has(x));
1512
+ if (reachable.length === 0) return [];
1513
+ return spread && reachable[0] !== to[0] ? reachable : [reachable[0]];
1514
+ }
1515
+ /** The connectors to `goal`, as [connector, backwards] hops. */
1516
+ hops(goal) {
1517
+ const hops = [];
1518
+ for (let at = goal, step = this.came.get(at); step; at = step.from, step = this.came.get(at)) hops.push([step.via, step.back]);
1519
+ return hops.reverse();
1520
+ }
1521
+ };
1522
+
1523
+ // src/measure.ts
1524
+ var emptyMetrics = () => ({ elements: /* @__PURE__ */ new Map(), flows: /* @__PURE__ */ new Map(), carry: {} });
1525
+ function metricValue(m, metric, id) {
1526
+ const f = m.flows.get(id);
1527
+ if (f) {
1528
+ switch (metric) {
1529
+ case "throughput":
1530
+ return f.placed;
1531
+ case "backlog":
1532
+ return f.backlog;
1533
+ case "backup":
1534
+ return f.backup;
1535
+ case "transit":
1536
+ return f.transit;
1537
+ default:
1538
+ return null;
1539
+ }
1540
+ }
1541
+ const e = m.elements.get(id);
1542
+ if (!e) return null;
1543
+ switch (metric) {
1544
+ case "throughput":
1545
+ return e.throughput;
1546
+ case "load":
1547
+ return e.load;
1548
+ case "delay":
1549
+ return e.delay;
1550
+ case "backlog":
1551
+ return e.backlog;
1552
+ case "inFlight":
1553
+ return e.inFlight;
1554
+ case "depth":
1555
+ return e.depth;
1556
+ default:
1557
+ return null;
1558
+ }
1559
+ }
1560
+ var TINY = 1e-9;
1561
+ var amount = (f) => clamp(f.volume, 1, 0, 1e12);
1562
+ function amountIn(f, values) {
1563
+ if (values === null || !f.overrides) return amount(f);
1564
+ let v = null;
1565
+ for (const o of f.overrides) if (o.volume !== null && !mentionsMetric(o.when) && holds(o.when, values)) v = o.volume;
1566
+ return v === null ? amount(f) : Math.min(Math.max(Number.isFinite(v) ? v : 1, 0), 1e12);
1567
+ }
1568
+ function delayOf(e) {
1569
+ const d = e.delay ?? null;
1570
+ if (e.kind === "connector" || d === null || !Number.isFinite(d)) return 0;
1571
+ return Math.min(Math.max(d, 0), 3600);
1572
+ }
1573
+ var queueKey = (shape, flow) => shape + "|" + flow;
1574
+ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set(), sinceChange = null, carried = {}) {
1575
+ const since = Math.max(0, Math.min(sinceChange ?? t, 1e6));
1576
+ const byID = /* @__PURE__ */ new Map();
1577
+ for (const e of frame2.elements) if (!byID.has(e.id)) byID.set(e.id, e);
1578
+ const metrics2 = emptyMetrics();
1579
+ for (const e of frame2.elements) {
1580
+ if (e.kind === "connector" && !e.connector) continue;
1581
+ metrics2.elements.set(e.id, { throughput: 0, load: null, delay: e.kind !== "connector" ? delayOf(e) : 0, backlog: 0, inFlight: 0, depth: 0 });
1582
+ }
1583
+ const list = flows ?? [];
1584
+ const room = /* @__PURE__ */ new Map();
1585
+ for (const e of frame2.elements) {
1586
+ const c = e.capacity ?? null;
1587
+ if (c !== null && Number.isFinite(c)) room.set(e.id, Math.max(c, 0));
1588
+ }
1589
+ const full = () => new Set([...room].filter(([, v]) => v <= TINY).map(([id]) => id));
1590
+ const route2 = (tree, goal) => {
1591
+ const hops = [], shapes2 = [];
1592
+ let at = goal;
1593
+ for (let step = tree.came.get(at); step; step = tree.came.get(at)) {
1594
+ hops.push([step.via, step.back]);
1595
+ shapes2.push(at);
1596
+ at = step.from;
1597
+ }
1598
+ shapes2.push(at);
1599
+ return { hops: hops.reverse(), shapes: shapes2.reverse() };
1600
+ };
1601
+ const way = (hops, shapes2) => {
1602
+ const points = [], inside = [], stretches = [];
1603
+ let length = 0;
1604
+ hops.forEach(([id, back], k) => {
1605
+ const c = byID.get(id);
1606
+ if (!c) return;
1607
+ const line2 = back ? [...tracing(c, frame2).points].reverse() : tracing(c, frame2).points;
1608
+ if (line2.length === 0) return;
1609
+ const last = points[points.length - 1];
1610
+ if (last) {
1611
+ const gap = Math.hypot(line2[0].x - last.x, line2[0].y - last.y);
1612
+ const shape = byID.get(shapes2[k]);
1613
+ inside.push([length, length + gap, shape ? delayOf(shape) : 0]);
1614
+ length += gap;
1615
+ }
1616
+ const start = length;
1617
+ for (let q = 1; q < line2.length; q++) length += Math.hypot(line2[q].x - line2[q - 1].x, line2[q].y - line2[q - 1].y);
1618
+ stretches.push([id, start, length]);
1619
+ points.push(...line2);
1620
+ });
1621
+ return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
1622
+ };
1623
+ const along = (w, elapsedAt, speed) => {
1624
+ let elapsed = elapsedAt, d = 0;
1625
+ for (const [a, b, delay] of w.inside) {
1626
+ const reach = (a - d) / speed;
1627
+ if (elapsed <= reach) break;
1628
+ elapsed -= reach;
1629
+ d = a;
1630
+ const stay = (b - a) / speed + delay;
1631
+ if (elapsed < stay) return null;
1632
+ elapsed -= stay;
1633
+ d = b;
1634
+ }
1635
+ d += elapsed * speed;
1636
+ return d >= 0 && d <= w.length ? d : null;
1637
+ };
1638
+ const union2 = (a, b) => /* @__PURE__ */ new Set([...a, ...b]);
1639
+ const bump = (id, f) => {
1640
+ const m = metrics2.elements.get(id);
1641
+ if (m) f(m);
1642
+ };
1643
+ const leaving = /* @__PURE__ */ new Map(), turn = [];
1644
+ for (const flow of list) {
1645
+ const n = leaving.get(flow.from) ?? 0;
1646
+ turn.push(n);
1647
+ leaving.set(flow.from, n + 1);
1648
+ }
1649
+ const biggest = list.length > 0 ? Math.max(...list.map(amount)) : 1;
1650
+ const packets = [];
1651
+ const waitingBy = /* @__PURE__ */ new Map();
1652
+ const everyOf = (flow) => Math.min(Math.max(clamp(flow.every, DEFAULT_EVERY, 0.05, 60) * biggest / Math.max(amountIn(flow, values), TINY), 0.05), 60);
1653
+ for (const [i, flow] of list.entries()) {
1654
+ if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
1655
+ const source = byID.get(flow.from);
1656
+ const base2 = source ? flowTree(flow.from, frame2, offline) : null;
1657
+ if (!source || !base2) continue;
1658
+ const volume = amountIn(flow, values);
1659
+ const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
1660
+ const placements = [];
1661
+ const waiting = [];
1662
+ const goals = base2.goals(flow.to, flow.spread === true);
1663
+ const shares = goals.length > 1 ? goals.map((g) => [[g], volume / goals.length]) : [[flow.to, volume]];
1664
+ for (const [candidates, share] of shares) {
1665
+ if (!(share > TINY)) continue;
1666
+ let left = share;
1667
+ for (let n = 0; n < 32 && left > TINY; n++) {
1668
+ const tree = flowTree(flow.from, frame2, union2(offline, full()));
1669
+ const goal2 = tree?.goals(candidates, false)[0];
1670
+ if (!tree || goal2 === void 0) break;
1671
+ const r = route2(tree, goal2);
1672
+ const ids = [...r.shapes, ...r.hops.map(([id]) => id)];
1673
+ const rooms = ids.filter((id) => room.has(id)).map((id) => room.get(id));
1674
+ const fits = Math.min(left, rooms.length > 0 ? Math.min(...rooms) : Infinity);
1675
+ if (!(fits > TINY)) break;
1676
+ for (const id of ids) if (room.has(id)) room.set(id, room.get(id) - fits);
1677
+ placements.push({ hops: r.hops, shapes: r.shapes, goal: goal2, amount: fits });
1678
+ left -= fits;
1679
+ }
1680
+ if (!(left > TINY)) continue;
1681
+ let at = flow.from;
1682
+ const goal = base2.goals(candidates, false)[0];
1683
+ if (goal !== void 0) {
1684
+ const r = route2(base2, goal), done = full();
1685
+ const sequence = [[r.shapes[0], r.shapes[0]]];
1686
+ r.hops.forEach(([id], k) => {
1687
+ sequence.push([id, r.shapes[k]], [r.shapes[k + 1], r.shapes[k]]);
1688
+ });
1689
+ const first = sequence.find(([id]) => done.has(id));
1690
+ if (first) {
1691
+ at = first[1];
1692
+ const upTo = r.shapes.indexOf(at);
1693
+ for (let q = upTo; q >= 0; q--) {
1694
+ const s = byID.get(r.shapes[q]);
1695
+ if (s && s.queue === true && s.kind !== "connector") {
1696
+ at = r.shapes[q];
1697
+ break;
1698
+ }
1699
+ }
1700
+ }
1701
+ const n = r.shapes.indexOf(at);
1702
+ if (at !== flow.from && n >= 0) {
1703
+ const shapes2 = r.shapes.slice(0, n + 1), hops = r.hops.slice(0, n);
1704
+ for (const id of [...shapes2, ...hops.map(([h]) => h)]) if (room.has(id)) room.set(id, Math.max(0, room.get(id) - left));
1705
+ placements.push({ hops, shapes: shapes2, goal: at, amount: left, waits: true });
1706
+ }
1707
+ }
1708
+ const existing = waiting.find(([s]) => s === at);
1709
+ if (existing) existing[1] += left;
1710
+ else waiting.push([at, left]);
1711
+ }
1712
+ waitingBy.set(flow.id, waiting);
1713
+ for (const p of placements) {
1714
+ for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
1715
+ m.throughput += p.amount;
1716
+ });
1717
+ if (!p.waits) fm.placed += p.amount;
1718
+ }
1719
+ for (const [at, a] of waiting) {
1720
+ bump(at, (m) => {
1721
+ m.backlog += a;
1722
+ });
1723
+ fm.backlog += a;
1724
+ }
1725
+ const reachedFirst = placements.filter((p) => !p.waits && p.goal === flow.to[0]).reduce((s, p) => s + p.amount, 0);
1726
+ fm.backup = volume > TINY ? Math.max(0, (volume - reachedFirst) / volume) : 0;
1727
+ const every = everyOf(flow);
1728
+ const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
1729
+ const phase = turn[i] / leaving.get(flow.from) * every;
1730
+ const color2 = flow.color ?? DEFAULT_COLOR;
1731
+ const cumulative = [];
1732
+ let sum = 0;
1733
+ for (const p of placements) {
1734
+ sum += p.amount / Math.max(volume, TINY);
1735
+ cumulative.push(sum);
1736
+ }
1737
+ if (cumulative.length > 0 && Math.abs(cumulative[cumulative.length - 1] - 1) < 1e-9) cumulative[cumulative.length - 1] = 1;
1738
+ let transitSum = 0;
1739
+ placements.forEach((p, r) => {
1740
+ const w = way(p.hops, p.shapes);
1741
+ if (!w) return;
1742
+ const travel = w.length / speed + w.inside.reduce((s, x) => s + x[2], 0);
1743
+ if (!p.waits) transitSum += travel * p.amount;
1744
+ const lo = Math.ceil((t - phase - travel) / every);
1745
+ const hi = Math.floor((t - phase) / every);
1746
+ const below = r === 0 ? 0 : cumulative[r - 1], upTo = cumulative[r];
1747
+ const dealt = (c, k) => Math.floor((k + 1) * c) - Math.floor(k * c);
1748
+ for (let k = Math.max(lo, hi - 3999); k <= hi; k++) {
1749
+ if (dealt(upTo, k) - dealt(below, k) !== 1) continue;
1750
+ const d = along(w, t - k * every - phase, speed);
1751
+ if (d === null) continue;
1752
+ const s = w.stretches.find(([, a, b]) => d >= a && d <= b);
1753
+ if (s) bump(s[0], (m) => {
1754
+ m.inFlight += 1;
1755
+ });
1756
+ const pt2 = w.line.point(d / w.length);
1757
+ packets.push(packet(packetID(flow.id, k), pt2.x - size / 2, pt2.y - size / 2, size, color2, source.layerID));
1758
+ }
1759
+ });
1760
+ fm.transit = fm.placed > TINY ? transitSum / fm.placed : null;
1761
+ metrics2.flows.set(flow.id, fm);
1762
+ }
1763
+ const piles = /* @__PURE__ */ new Map();
1764
+ for (const flow of list) {
1765
+ const waiting = waitingBy.get(flow.id) ?? [];
1766
+ const shapes2 = waiting.map(([s]) => s);
1767
+ for (const e of frame2.elements) {
1768
+ const c = carried[queueKey(e.id, flow.id)];
1769
+ if (c !== void 0 && c > TINY && !shapes2.includes(e.id)) shapes2.push(e.id);
1770
+ }
1771
+ if (shapes2.length === 0) continue;
1772
+ const volume = amountIn(flow, values);
1773
+ const every = everyOf(flow);
1774
+ const unit = every * Math.max(volume, TINY);
1775
+ const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
1776
+ const color2 = flow.color ?? DEFAULT_COLOR;
1777
+ shapes2.forEach((at, j) => {
1778
+ const shape = byID.get(at);
1779
+ if (!shape) return;
1780
+ const layer = byID.get(flow.from)?.layerID ?? shape.layerID;
1781
+ const backlog = waiting.find(([s]) => s === at)?.[1] ?? 0;
1782
+ const before = Math.max(0, carried[queueKey(at, flow.id)] ?? 0);
1783
+ let depth = before + backlog * since;
1784
+ if (backlog <= TINY && before > TINY) {
1785
+ const trial = new Map(room);
1786
+ const outlets = [];
1787
+ let total = 0;
1788
+ for (let n2 = 0; n2 < 32; n2++) {
1789
+ const blocked = new Set([...trial].filter(([, v]) => v <= TINY).map(([id]) => id));
1790
+ const tree = flowTree(at, frame2, union2(offline, blocked));
1791
+ const goal = tree?.goals(flow.to, false)[0];
1792
+ if (!tree || goal === void 0) break;
1793
+ const r = route2(tree, goal);
1794
+ const ids = [...r.shapes.slice(1), ...r.hops.map(([id]) => id)];
1795
+ const rooms = ids.filter((id) => trial.has(id)).map((id) => trial.get(id));
1796
+ const spare = rooms.length > 0 ? Math.min(...rooms) : Infinity;
1797
+ const rate = Number.isFinite(spare) ? spare : Math.max(before / 0.5 - total, TINY * 2);
1798
+ if (!(rate > TINY)) break;
1799
+ for (const id of ids) if (trial.has(id)) trial.set(id, trial.get(id) - rate);
1800
+ outlets.push({ hops: r.hops, shapes: r.shapes, rate });
1801
+ total += rate;
1802
+ if (!Number.isFinite(spare)) break;
1803
+ }
1804
+ if (total > TINY) {
1805
+ depth = Math.max(0, before - total * since);
1806
+ if (depth > TINY) {
1807
+ for (const [id, v] of trial) room.set(id, v);
1808
+ for (const o of outlets) for (const id of [...o.shapes, ...o.hops.map(([h]) => h)]) bump(id, (m) => {
1809
+ m.throughput += o.rate;
1810
+ });
1811
+ }
1812
+ const lasts = before / total;
1813
+ outlets.forEach((o, i) => {
1814
+ const w = way(o.hops, o.shapes);
1815
+ if (!w) return;
1816
+ const spacing = Math.max(unit / o.rate, 0.05);
1817
+ for (let k = 0; k * spacing < lasts && k * spacing <= since && k < 2048; k++) {
1818
+ const d = along(w, since - k * spacing, speed);
1819
+ if (d === null) continue;
1820
+ const pt2 = w.line.point(d / w.length);
1821
+ packets.push(packet(packetID(flow.id, 2 ** 39 + 2 ** 20 + j * 2 ** 14 + i * 2 ** 11 + k), pt2.x - size / 2, pt2.y - size / 2, size, color2, layer));
1822
+ }
1823
+ });
1824
+ }
1825
+ }
1826
+ if (!(depth > TINY || backlog > TINY)) return;
1827
+ bump(at, (m) => {
1828
+ m.depth += depth;
1829
+ });
1830
+ if (depth > TINY) metrics2.carry[queueKey(at, flow.id)] = depth;
1831
+ const n = Math.min(Math.max(Math.ceil(depth / unit - 1e-9), 1), 30);
1832
+ const rows = Math.trunc((n + 9) / 10);
1833
+ const base2 = piles.get(at) ?? 0;
1834
+ piles.set(at, base2 + rows);
1835
+ for (let q = 0; q < n; q++) {
1836
+ const cx = shape.x + 2 + size / 2 + q % 10 * (size + 3);
1837
+ const cy = shape.y - 4 - size / 2 - (base2 + Math.trunc(q / 10)) * (size + 3);
1838
+ packets.push(packet(packetID(flow.id, 2 ** 39 + j * 64 + q), cx - size / 2, cy - size / 2, size, color2, layer));
1839
+ }
1840
+ });
1841
+ }
1842
+ for (const e of frame2.elements) {
1843
+ const c = e.capacity ?? null;
1844
+ const m = metrics2.elements.get(e.id);
1845
+ if (c === null || !Number.isFinite(c) || !m) continue;
1846
+ m.load = c > 0 ? m.throughput / c : 1;
1847
+ }
1848
+ return { packets, metrics: metrics2 };
1849
+ }
1850
+ var BLANK = {
1851
+ seed: 1,
1852
+ text: null,
1853
+ connector: null,
1854
+ groupID: null,
1855
+ points: null,
1856
+ path: null,
1857
+ subpaths: null,
1858
+ evenOdd: null,
1859
+ rotation: null,
1860
+ link: null,
1861
+ url: null,
1862
+ anchor: null,
1863
+ click: null,
1864
+ hover: null,
1865
+ when: null,
1866
+ overrides: null,
1867
+ symbol: null,
1868
+ playback: null,
1869
+ offset: null,
1870
+ args: null,
1871
+ image: null,
1872
+ reveal: null
1873
+ };
1874
+ function style(stroke, fill, strokeWidth, fillStyle = "solid", opacity2 = null) {
1875
+ return {
1876
+ stroke: { ...stroke },
1877
+ fill: { ...fill },
1878
+ fillStyle,
1879
+ strokeWidth,
1880
+ dash: "solid",
1881
+ roughness: 0,
1882
+ bowing: 1,
1883
+ fontSize: 18,
1884
+ font: null,
1885
+ gridSize: null,
1886
+ opacity: opacity2,
1887
+ dashOffset: null,
1888
+ textColor: null
1889
+ };
1890
+ }
1891
+ function packet(id, x, y, size, color2, layerID) {
1892
+ return { ...BLANK, id, kind: "ellipse", layerID, x, y, width: size, height: size, style: style(color2, color2, 1.5) };
1893
+ }
1894
+ function metricLookup(m, frame2) {
1895
+ const anchors = /* @__PURE__ */ new Map();
1896
+ for (const e of frame2.elements) if (e.anchor !== null && !anchors.has(e.anchor)) anchors.set(e.anchor, e.id);
1897
+ const UUID2 = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;
1898
+ return (term, own) => {
1899
+ const id = term.target === "" ? own : anchors.get(term.target) ?? (UUID2.test(term.target) ? term.target.toUpperCase() : null);
1900
+ return id === null ? null : metricValue(m, term.metric, id);
1901
+ };
1902
+ }
1903
+ function finishing(elements, metrics2, values, frame2, flows, since = Infinity, carried = {}) {
1904
+ const lookup = metricLookup(metrics2, frame2);
1905
+ const out = [], readings = {};
1906
+ for (let e of elements) {
1907
+ if (values !== null && e.overrides) {
1908
+ for (const o of e.overrides) {
1909
+ const own = e.id;
1910
+ if (mentionsMetric(o.when) && holds(o.when, values, (m) => lookup(m, own))) e = applyOverride(looksOnly(o), e);
1911
+ }
1912
+ }
1913
+ if (!e.gauge || values === null) {
1914
+ out.push(e);
1915
+ continue;
1916
+ }
1917
+ const g = e.gauge;
1918
+ let shown = metricValue(metrics2, g.metric, g.of);
1919
+ const then = carried[gaugeKey(e.id)];
1920
+ if (shown !== null && then !== void 0 && g.metric !== "depth" && g.metric !== "inFlight" && since < GAUGE_EASE) {
1921
+ const f = Math.max(0, since / GAUGE_EASE), smooth = f * f * (3 - 2 * f);
1922
+ shown = then + (shown - then) * smooth;
1923
+ }
1924
+ if (shown !== null) readings[gaugeKey(e.id)] = shown;
1925
+ out.push(...gauge(e, g, shown, frame2, flows));
1926
+ }
1927
+ return { elements: out, readings };
1928
+ }
1929
+ var gaugeKey = (id) => "gauge|" + id;
1930
+ var GAUGE_EASE = 1;
1931
+ var hex = (h) => {
1932
+ const v = parseInt(h.slice(1), 16);
1933
+ return { r: (v >> 16 & 255) / 255, g: (v >> 8 & 255) / 255, b: (v & 255) / 255, a: 1 };
1934
+ };
1935
+ var GAUGE_GREEN = hex("#16a34a");
1936
+ var GAUGE_AMBER = hex("#d97706");
1937
+ var GAUGE_RED = hex("#dc2626");
1938
+ var GAUGE_BLUE = hex("#2f6fdf");
1939
+ var GAUGE_TRACK = hex("#e5e7eb");
1940
+ var GAUGE_GRAY = hex("#9ca3af");
1941
+ function formatted(metric, v) {
1942
+ if (v === null || !Number.isFinite(v)) return "\u2013";
1943
+ switch (metric) {
1944
+ case "load":
1945
+ case "backup":
1946
+ return formatNumber(v * 100, 0) + "%";
1947
+ case "transit":
1948
+ case "delay":
1949
+ return formatNumber(v, Math.abs(v) >= 10 ? 1 : 2) + "s";
1950
+ default:
1951
+ return formatNumber(v, Math.abs(v) >= 100 ? 0 : Math.abs(v) >= 10 ? 1 : 2);
1952
+ }
1953
+ }
1954
+ function formatNumber(v, places) {
1955
+ const scale = places === 0 ? 1 : places === 1 ? 10 : 100;
1956
+ const n = Math.floor(Math.abs(v) * scale + 0.5);
1957
+ if (!(n < 1e15)) return v < 0 ? "-\u221E" : "\u221E";
1958
+ const whole = Math.trunc(n / scale), part = n - whole * scale;
1959
+ let s = String(whole);
1960
+ if (places > 0 && part > 0) {
1961
+ let digits = String(part);
1962
+ while (digits.length < places) digits = "0" + digits;
1963
+ while (digits.endsWith("0")) digits = digits.slice(0, -1);
1964
+ s += "." + digits;
1965
+ }
1966
+ return (v < 0 && n > 0 ? "-" : "") + s;
1967
+ }
1968
+ function gauge(e, g, value2, frame2, flows) {
1969
+ const target = frame2.elements.find((x) => x.id === g.of);
1970
+ const full = g.max ?? (g.metric === "load" ? 1 : target?.capacity ?? value2 ?? 1);
1971
+ const fraction = value2 === null ? 0 : full > 0 ? Math.min(Math.max(value2 / full, 0), 1) : 0;
1972
+ const warn = g.warn ?? (g.metric === "load" ? 0.7 : null), alarm = g.alarm ?? (g.metric === "load" ? 0.9 : null);
1973
+ let color2 = warn === null && alarm === null ? GAUGE_BLUE : GAUGE_GREEN;
1974
+ if (value2 !== null && warn !== null && value2 >= warn) color2 = GAUGE_AMBER;
1975
+ if (value2 !== null && alarm !== null && value2 >= alarm) color2 = GAUGE_RED;
1976
+ if (value2 === null) color2 = GAUGE_GRAY;
1977
+ let name = g.label;
1978
+ if (name === null) {
1979
+ const what = target ? target.text !== null ? target.text.split("\n")[0] : target.anchor : null;
1980
+ name = `${g.metric} ${what ?? (flows.some((f) => f.id === g.of) ? "flow" : "?")}`;
1981
+ }
1982
+ const kind = g.style ?? "number";
1983
+ const w = Math.max(e.width, 1), h = Math.max(e.height, 1);
1984
+ const line2 = (a, b) => [
1985
+ a.x / w,
1986
+ a.y / h,
1987
+ (a.x + (b.x - a.x) / 3) / w,
1988
+ (a.y + (b.y - a.y) / 3) / h,
1989
+ (a.x + 2 * (b.x - a.x) / 3) / w,
1990
+ (a.y + 2 * (b.y - a.y) / 3) / h,
1991
+ b.x / w,
1992
+ b.y / h
1993
+ ];
1994
+ const box = (x, y, bw, bh) => {
1995
+ const p = [{ x, y }, { x: x + bw, y }, { x: x + bw, y: y + bh }, { x, y: y + bh }];
1996
+ let coords = line2(p[0], p[1]);
1997
+ for (let k = 1; k < 4; k++) coords = coords.concat(line2(p[k], p[(k + 1) % 4]).slice(2));
1998
+ return { closed: true, coords };
1999
+ };
2000
+ const arc = (c, radius, a0, a1) => {
2001
+ const pieces2 = Math.max(1, Math.ceil(Math.abs(a1 - a0) / (Math.PI / 2) - 1e-9));
2002
+ const step = (a1 - a0) / pieces2, kappa = 4 / 3 * Math.tan(step / 4);
2003
+ const at = (a) => ({ x: c.x + radius * Math.cos(a), y: c.y + radius * Math.sin(a) });
2004
+ const s = at(a0);
2005
+ const coords = [s.x / w, s.y / h];
2006
+ for (let k = 0; k < pieces2; k++) {
2007
+ const a = a0 + k * step, b = a + step;
2008
+ const p0 = at(a), p3 = at(b);
2009
+ const c1 = { x: p0.x - kappa * radius * Math.sin(a), y: p0.y + kappa * radius * Math.cos(a) };
2010
+ const c2 = { x: p3.x + kappa * radius * Math.sin(b), y: p3.y - kappa * radius * Math.cos(b) };
2011
+ coords.push(c1.x / w, c1.y / h, c2.x / w, c2.y / h, p3.x / w, p3.y / h);
2012
+ }
2013
+ return { closed: false, coords };
2014
+ };
2015
+ const part = (k, subpaths, stroke, fill, width) => ({
2016
+ ...BLANK,
2017
+ id: packetID(e.id, -(2 ** 38) - k),
2018
+ kind: "path",
2019
+ layerID: e.layerID,
2020
+ x: e.x,
2021
+ y: e.y,
2022
+ width: e.width,
2023
+ height: e.height,
2024
+ style: style(stroke, fill ?? { r: 0, g: 0, b: 0, a: 0 }, width, "solid", e.style.opacity),
2025
+ subpaths
2026
+ });
2027
+ const back = {
2028
+ ...e,
2029
+ gauge: null,
2030
+ kind: "path",
2031
+ subpaths: [box(0, 0, w, h)],
2032
+ text: name + "\n" + formatted(g.metric, value2),
2033
+ style: { ...e.style }
2034
+ };
2035
+ const parts = [];
2036
+ if (kind === "number") {
2037
+ back.labelRect = { x: 0, y: 0, width: 1, height: 1 };
2038
+ back.style.textColor = { ...color2 };
2039
+ } else if (kind === "bar") {
2040
+ back.labelRect = { x: 0, y: 0, width: 1, height: 0.66 };
2041
+ const inset2 = Math.min(10, w * 0.08), barH = Math.min(12, h * 0.18), top = h * 0.72;
2042
+ const trackW = w - 2 * inset2;
2043
+ parts.push(part(1, [box(inset2, top, trackW, barH)], GAUGE_TRACK, GAUGE_TRACK, 0.5));
2044
+ if (fraction > 0) parts.push(part(2, [box(inset2, top, trackW * fraction, barH)], color2, color2, 0.5));
2045
+ } else {
2046
+ back.labelRect = { x: 0, y: 0.54, width: 1, height: 0.46 };
2047
+ const radius = Math.min(w * 0.38, h * 0.4), center2 = { x: w / 2, y: h * 0.5 };
2048
+ const thick = Math.max(2, radius * 0.16);
2049
+ parts.push(part(1, [arc(center2, radius, Math.PI, 2 * Math.PI)], GAUGE_TRACK, null, thick));
2050
+ if (fraction > 0) parts.push(part(2, [arc(center2, radius, Math.PI, Math.PI + Math.PI * fraction)], color2, null, thick));
2051
+ const a = Math.PI + Math.PI * fraction;
2052
+ const tip = { x: center2.x + radius * 0.85 * Math.cos(a), y: center2.y + radius * 0.85 * Math.sin(a) };
2053
+ parts.push(part(3, [{ closed: false, coords: line2(center2, tip) }], { r: 0.2, g: 0.2, b: 0.2, a: 1 }, null, 2));
2054
+ }
2055
+ return [back, ...parts];
2056
+ }
2057
+
1284
2058
  // src/rough.ts
1285
- var offset = (rng, amount, roughness) => rng.signed() * amount * roughness;
2059
+ var offset = (rng, amount2, roughness) => rng.signed() * amount2 * roughness;
1286
2060
  function line(a, b, rng, roughness, bowing, preserveEnds, into) {
1287
2061
  const length = Math.hypot(b.x - a.x, b.y - a.y);
1288
2062
  if (!(length > 0.5)) return;
@@ -2138,16 +2912,39 @@ function trackMap(tl) {
2138
2912
  for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
2139
2913
  return m;
2140
2914
  }
2141
- function resolved(page, t, state = "initial") {
2142
- return expandingSymbols(posed(page, t, state), clampTime(t));
2915
+ function resolved(page, t, state = "initial", since = null, carried = {}) {
2916
+ return expandingSymbols(posed(page, t, state, since, carried), clampTime(t));
2143
2917
  }
2144
2918
  var EMPTY_TIMELINE = { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
2145
- function posed(page, t, state = "initial") {
2919
+ function posed(page, t, state = "initial", since = null, carried = {}) {
2920
+ return posedWithMetrics(page, t, state, since, carried).frame;
2921
+ }
2922
+ function posedWithMetrics(page, t, state = "initial", since = null, carried = {}) {
2146
2923
  const off = stateHidden(page, state);
2147
- const timeline = page.timeline ?? (off.size === 0 ? null : EMPTY_TIMELINE);
2148
- if (!timeline) return page;
2924
+ const values = stateValues(page, state);
2925
+ const active = /* @__PURE__ */ new Map();
2926
+ for (const e of page.elements) {
2927
+ const o = activeOverrides(e, values);
2928
+ if (o.length > 0) active.set(e.id, o);
2929
+ }
2930
+ const flowing = (page.flows ?? []).length > 0;
2931
+ const measuring = flowing || page.elements.some((e) => e.gauge || (e.overrides ?? []).some((o) => mentionsMetric(o.when)));
2932
+ const timeline = page.timeline ?? (off.size === 0 && active.size === 0 && !measuring ? null : EMPTY_TIMELINE);
2933
+ if (!timeline) return { frame: page, metrics: emptyMetrics() };
2149
2934
  t = clampTime(t);
2150
2935
  const tracks = trackMap(timeline);
2936
+ for (const [id, overrides] of active) {
2937
+ const track = tracks.get(id);
2938
+ if (!track?.follow || !overrides.some((o) => o.reroute)) continue;
2939
+ tracks.set(id, {
2940
+ ...track,
2941
+ follow: track.follow.map((f) => {
2942
+ let path = f.path;
2943
+ for (const o of overrides) path = o.reroute?.[path] ?? path;
2944
+ return { ...f, path };
2945
+ })
2946
+ });
2947
+ }
2151
2948
  const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
2152
2949
  const hidden = /* @__PURE__ */ new Set();
2153
2950
  for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
@@ -2167,6 +2964,14 @@ function posed(page, t, state = "initial") {
2167
2964
  let elements = page.elements.map((e) => {
2168
2965
  const track = tracks.get(e.id);
2169
2966
  let out = track ? applyTrack(track, e, t) : e;
2967
+ for (const o of active.get(e.id) ?? []) out = applyOverride(o, out);
2968
+ const list = active.get(e.id);
2969
+ if (list) {
2970
+ const caps = list.map((o) => o.capacity).filter((c) => c !== null && Number.isFinite(c));
2971
+ const delays = list.map((o) => o.delay).filter((d) => d !== null && Number.isFinite(d));
2972
+ if (caps.length > 0) out.capacity = Math.max(Math.min(...caps), 0);
2973
+ if (delays.length > 0 && out.kind !== "connector") out.delay = Math.max(Math.max(...delays), 0);
2974
+ }
2170
2975
  if (reseed !== null && reseed > 0 && boils(e)) {
2171
2976
  if (out === e) out = cloneElement(e);
2172
2977
  out.seed = boiled(e.seed, reseed);
@@ -2200,7 +3005,18 @@ function posed(page, t, state = "initial") {
2200
3005
  pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
2201
3006
  return pinned;
2202
3007
  });
2203
- return { ...page, elements: elements.filter((e) => !gone.has(e.id)), timeline: null };
3008
+ elements = elements.filter((e) => !gone.has(e.id));
3009
+ let metrics2 = emptyMetrics();
3010
+ if (measuring) {
3011
+ const offline = new Set([...active].filter(([, list]) => list.some((o) => o.offline === true)).map(([id]) => id));
3012
+ const frame2 = { ...page, elements, timeline: null };
3013
+ const out = flowFrame(page.flows, frame2, t, values, offline, since, carried);
3014
+ metrics2 = out.metrics;
3015
+ const finished = finishing(elements, out.metrics, values, frame2, page.flows ?? [], Math.max(0, Math.min(since ?? t, 1e6)), carried);
3016
+ Object.assign(metrics2.carry, finished.readings);
3017
+ elements = [...finished.elements, ...out.packets];
3018
+ }
3019
+ return { frame: { ...page, elements, timeline: null, flows: measuring ? null : page.flows }, metrics: metrics2 };
2204
3020
  }
2205
3021
  function posterTime(page) {
2206
3022
  return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
@@ -2315,7 +3131,7 @@ function filled(s, args) {
2315
3131
  }) };
2316
3132
  }
2317
3133
  function symbolPage(s) {
2318
- return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null };
3134
+ return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null, flows: null };
2319
3135
  }
2320
3136
  function hasInstances(page) {
2321
3137
  return page.elements.some((e) => e.kind === "symbol");
@@ -2422,8 +3238,8 @@ function mix2(x) {
2422
3238
  return z ^ z >> 31n;
2423
3239
  }
2424
3240
  function halves(id) {
2425
- const hex = id.replace(/-/g, "").padStart(32, "0").slice(-32);
2426
- return [BigInt(`0x${hex.slice(0, 16)}`), BigInt(`0x${hex.slice(16)}`)];
3241
+ const hex2 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
3242
+ return [BigInt(`0x${hex2.slice(0, 16)}`), BigInt(`0x${hex2.slice(16)}`)];
2427
3243
  }
2428
3244
  function derivedID(instance, inner) {
2429
3245
  const [a, b] = halves(instance), [c, d] = halves(inner);
@@ -2444,7 +3260,7 @@ function clickTargets(page, file) {
2444
3260
  let pageOf = null;
2445
3261
  for (const e of page.elements) {
2446
3262
  const c = e.click;
2447
- const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {} } : null;
3263
+ const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {}, ...c.reset ? { reset: true } : {} } : null;
2448
3264
  if (c?.scene) {
2449
3265
  const i = sceneNamed(c.scene, markers);
2450
3266
  if (i >= 0) {
@@ -2477,14 +3293,14 @@ function clickTargets(page, file) {
2477
3293
  function hoveredElement(e) {
2478
3294
  const h = e.hover;
2479
3295
  if (!h) return e;
2480
- const style = { ...e.style };
2481
- if (h.stroke) style.stroke = h.stroke;
2482
- if (h.fill) style.fill = h.fill;
3296
+ const style2 = { ...e.style };
3297
+ if (h.stroke) style2.stroke = h.stroke;
3298
+ if (h.fill) style2.fill = h.fill;
2483
3299
  if (h.opacity !== null && Number.isFinite(h.opacity)) {
2484
3300
  const o = Math.min(Math.max(h.opacity, 0), 1);
2485
- style.opacity = o >= 1 ? null : o;
3301
+ style2.opacity = o >= 1 ? null : o;
2486
3302
  }
2487
- return { ...e, style };
3303
+ return { ...e, style: style2 };
2488
3304
  }
2489
3305
  function hovered(page, ids) {
2490
3306
  return { ...page, elements: page.elements.map((e) => ids.has(e.id) ? hoveredElement(e) : e) };
@@ -2688,14 +3504,14 @@ function parseList(lines, start) {
2688
3504
  }
2689
3505
  return { block: { kind: "list", ordered, items }, next: i };
2690
3506
  }
2691
- function inline(src, style = {}) {
3507
+ function inline(src, style2 = {}) {
2692
3508
  const out = [];
2693
3509
  const push = (text, s) => {
2694
3510
  if (text) out.push({ ...s, text });
2695
3511
  };
2696
3512
  let i = 0, buf = "";
2697
3513
  const emit = () => {
2698
- push(buf, style);
3514
+ push(buf, style2);
2699
3515
  buf = "";
2700
3516
  };
2701
3517
  while (i < src.length) {
@@ -2723,43 +3539,43 @@ function inline(src, style = {}) {
2723
3539
  emit();
2724
3540
  let code = m[2].replace(/\n/g, " ");
2725
3541
  if (/^ .* $/.test(code)) code = code.slice(1, -1);
2726
- push(code, { ...style, mono: true });
3542
+ push(code, { ...style2, mono: true });
2727
3543
  i += m[0].length;
2728
3544
  continue;
2729
3545
  }
2730
3546
  if (m = /^!\[([^\]]*)\]\([^)]*\)/.exec(rest)) {
2731
3547
  emit();
2732
- out.push(...inline(m[1], style));
3548
+ out.push(...inline(m[1], style2));
2733
3549
  i += m[0].length;
2734
3550
  continue;
2735
3551
  }
2736
3552
  if (m = /^\[([^\]]+)\]\([^)]*\)/.exec(rest)) {
2737
3553
  emit();
2738
- out.push(...inline(m[1], { ...style, link: true }));
3554
+ out.push(...inline(m[1], { ...style2, link: true }));
2739
3555
  i += m[0].length;
2740
3556
  continue;
2741
3557
  }
2742
3558
  if (m = /^<((?:https?|ftp|mailto):[^>\s]+)>/.exec(rest)) {
2743
3559
  emit();
2744
- push(m[1], { ...style, link: true });
3560
+ push(m[1], { ...style2, link: true });
2745
3561
  i += m[0].length;
2746
3562
  continue;
2747
3563
  }
2748
3564
  if (m = /^~~(?=\S)([\s\S]*?\S)~~/.exec(rest)) {
2749
3565
  emit();
2750
- out.push(...inline(m[1], { ...style, strike: true }));
3566
+ out.push(...inline(m[1], { ...style2, strike: true }));
2751
3567
  i += m[0].length;
2752
3568
  continue;
2753
3569
  }
2754
3570
  if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
2755
3571
  emit();
2756
- out.push(...inline(m[2], { ...style, bold: true }));
3572
+ out.push(...inline(m[2], { ...style2, bold: true }));
2757
3573
  i += m[0].length;
2758
3574
  continue;
2759
3575
  }
2760
3576
  if ((m = /^(\*|_)(?=\S)([\s\S]*?\S)\1(?!\1)/.exec(rest)) && (m[1] === "*" || !/\w/.test(src[i - 1] ?? ""))) {
2761
3577
  emit();
2762
- out.push(...inline(m[2], { ...style, italic: true }));
3578
+ out.push(...inline(m[2], { ...style2, italic: true }));
2763
3579
  i += m[0].length;
2764
3580
  continue;
2765
3581
  }
@@ -3376,6 +4192,12 @@ var Playback = class {
3376
4192
  __publicField(this, "sceneLoops");
3377
4193
  /** The page's variables now: their starting values until clicks change them. */
3378
4194
  __publicField(this, "state");
4195
+ /** When the variables last changed (performance.now() milliseconds): backlog piles up from then (ADR 0003). */
4196
+ __publicField(this, "stateSince", typeof performance === "undefined" ? 0 : performance.now());
4197
+ /** A still (the `at` attribute): backlog is drawn for the page time, not the wall clock. */
4198
+ __publicField(this, "still", false);
4199
+ /** What each queue held when the variables last changed, which it drains from (FrameMetrics.carry). */
4200
+ __publicField(this, "carried", {});
3379
4201
  __publicField(this, "background");
3380
4202
  __publicField(this, "ink");
3381
4203
  /** What clicking each clickable element does, by element id. */
@@ -3447,6 +4269,10 @@ var Playback = class {
3447
4269
  this.svg.style.maxWidth = `${Math.ceil(this.plan.size.width)}px`;
3448
4270
  into.replaceChildren(this.svg);
3449
4271
  }
4272
+ /** Seconds since the variables last changed, or null for a still. */
4273
+ sinceChange(now = typeof performance === "undefined" ? 0 : performance.now()) {
4274
+ return this.still || typeof performance === "undefined" ? null : (now - this.stateSince) / 1e3;
4275
+ }
3450
4276
  get frameCount() {
3451
4277
  return this.plan.times.length;
3452
4278
  }
@@ -3487,7 +4313,7 @@ var Playback = class {
3487
4313
  const times = /* @__PURE__ */ new Map();
3488
4314
  for (const [id, start] of this.replayStarts) times.set(id, Math.min((now - start) / 1e3, this.replays.get(id) ?? 0));
3489
4315
  const tc = clampTime(t);
3490
- const pose = posed(this.page, tc, this.state);
4316
+ const pose = posed(this.page, tc, this.state, this.sinceChange(now), this.carried);
3491
4317
  const frame2 = expandingSymbols(pose, tc, true, times);
3492
4318
  const { parts } = svgParts(frame2, this.background, r);
3493
4319
  const owned = (m, parts2) => {
@@ -3744,6 +4570,7 @@ var DottiePlayer = class extends ElementBase {
3744
4570
  const file = this.file;
3745
4571
  const page = file.pages[pageIndex(file, this.getAttribute("page"))];
3746
4572
  const p = new Playback(page, this.stage, { clean: this.hasAttribute("clean"), file });
4573
+ p.still = this.hasAttribute("at");
3747
4574
  this.playback = p;
3748
4575
  this.style.setProperty("--dottie-page", color(p.background));
3749
4576
  this.style.setProperty("--dottie-ink", color(p.ink));
@@ -3837,7 +4664,13 @@ var DottiePlayer = class extends ElementBase {
3837
4664
  this.scheduleReplay();
3838
4665
  }
3839
4666
  if (!click) return;
3840
- if (click.set || click.toggle) this.setState(applyClick({ scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null }, p.state));
4667
+ if (click.set || click.toggle || click.reset) {
4668
+ this.setState(applyClick(
4669
+ { scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null, reset: click.reset ?? null },
4670
+ p.state,
4671
+ p.page.variables ?? {}
4672
+ ));
4673
+ }
3841
4674
  if ("state" in click) return;
3842
4675
  if ("url" in click) {
3843
4676
  if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
@@ -3863,7 +4696,10 @@ var DottiePlayer = class extends ElementBase {
3863
4696
  if (!p) return;
3864
4697
  const next = { ...p.state, ...values };
3865
4698
  if (Object.keys(next).every((k) => next[k] === p.state[k])) return;
4699
+ const t = clampTime(p.last.t);
4700
+ p.carried = posedWithMetrics(p.page, t, p.state, p.sinceChange(), p.carried).metrics.carry;
3866
4701
  p.state = next;
4702
+ p.stateSince = performance.now();
3867
4703
  p.redraw();
3868
4704
  this.dispatchEvent(new CustomEvent("state", { detail: { variables: { ...next } } }));
3869
4705
  }
@@ -4061,7 +4897,7 @@ function render(source, options = {}) {
4061
4897
  function isFile(v) {
4062
4898
  return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
4063
4899
  }
4064
- var VERSION = "0.7.0";
4900
+ var VERSION = "0.8.0";
4065
4901
  export {
4066
4902
  BOIL_DRAWINGS,
4067
4903
  BUNDLED_FONTS,