@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.
@@ -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 __export = (target, all) => {
@@ -71,10 +71,53 @@ var MAX_NAME = 64;
71
71
  function isName(name) {
72
72
  return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
73
73
  }
74
+ var METRIC_NAMES = ["throughput", "load", "delay", "backlog", "depth", "inFlight", "transit", "backup"];
75
+ var OPS = [">=", "<=", "==", "!=", ">", "<"];
76
+ function metricTerm(term) {
77
+ const t = trim(term);
78
+ if (!t.includes("(")) {
79
+ const op2 = t.search(/[>=<!]/);
80
+ if (op2 < 0) return null;
81
+ const name2 = trim(t.slice(0, op2));
82
+ if (!METRIC_NAMES.includes(name2)) return null;
83
+ const m = metricTerm(name2 + "() " + t.slice(op2));
84
+ return m ? { ...m, target: "" } : null;
85
+ }
86
+ const open = t.indexOf("("), close = t.indexOf(")");
87
+ if (open < 0 || close < 0 || open >= close) return null;
88
+ const name = trim(t.slice(0, open)), target = trim(t.slice(open + 1, close));
89
+ let rest = trim(t.slice(close + 1));
90
+ if (!METRIC_NAMES.includes(name) || target.includes("(")) return null;
91
+ const op = OPS.find((o) => rest.startsWith(o));
92
+ if (op === void 0) return null;
93
+ rest = trim(rest.slice(op.length));
94
+ if (!/^-?[0-9]+(\.[0-9]+)?$/.test(rest)) return null;
95
+ return { metric: name, target, op, value: Number(rest) };
96
+ }
97
+ function passes(m, v) {
98
+ switch (m.op) {
99
+ case ">":
100
+ return v > m.value;
101
+ case ">=":
102
+ return v >= m.value;
103
+ case "<":
104
+ return v < m.value;
105
+ case "<=":
106
+ return v <= m.value;
107
+ case "==":
108
+ return v === m.value;
109
+ default:
110
+ return v !== m.value;
111
+ }
112
+ }
113
+ function mentionsMetric(condition) {
114
+ return condition.split("||").some((any) => any.split("&&").some((all) => metricTerm(all) !== null));
115
+ }
74
116
  function conditionNames(condition) {
75
117
  const out = [];
76
118
  for (const any of condition.split("||")) {
77
119
  for (const all of any.split("&&")) {
120
+ if (metricTerm(all) !== null) continue;
78
121
  let term = trim(all);
79
122
  if (term.startsWith("!")) term = trim(term.slice(1));
80
123
  if (!isName(term)) return null;
@@ -83,9 +126,14 @@ function conditionNames(condition) {
83
126
  }
84
127
  return out;
85
128
  }
86
- function holds(condition, state) {
129
+ function holds(condition, state, metric = null) {
87
130
  if (conditionNames(condition) === null) return false;
88
131
  return condition.split("||").some((any) => any.split("&&").every((all) => {
132
+ const m = metricTerm(all);
133
+ if (m !== null) {
134
+ const v = metric ? metric(m) : null;
135
+ return v !== null && passes(m, v);
136
+ }
89
137
  let term = trim(all);
90
138
  let negated = false;
91
139
  if (term.startsWith("!")) {
@@ -107,11 +155,40 @@ function stateHidden(page, choice) {
107
155
  for (const e of page.elements) if (e.when !== null && !holds(e.when, values)) out.add(e.id);
108
156
  return out;
109
157
  }
158
+ function activeOverrides(e, values) {
159
+ if (!values || !e.overrides) return [];
160
+ return e.overrides.filter((o) => !mentionsMetric(o.when) && holds(o.when, values));
161
+ }
162
+ function looksOnly(o) {
163
+ return { ...o, reroute: null, offline: null, cost: null, capacity: null, delay: null };
164
+ }
165
+ function applyOverride(o, e) {
166
+ const out = cloneElement(e);
167
+ if (o.stroke) out.style.stroke = { ...o.stroke };
168
+ if (o.fill) out.style.fill = { ...o.fill };
169
+ if (o.textColor) out.style.textColor = { ...o.textColor };
170
+ if (o.strokeWidth !== null) out.style.strokeWidth = Math.max(0, o.strokeWidth);
171
+ if (o.dash !== null) out.style.dash = o.dash;
172
+ if (o.text !== null) out.text = o.text;
173
+ if (o.cost !== null && out.connector) out.connector.cost = Math.min(Math.max(o.cost, 0), 1e6);
174
+ if (o.capacity !== null) out.capacity = Math.max(o.capacity, 0);
175
+ if (o.delay !== null && out.kind !== "connector") out.delay = Math.max(o.delay, 0);
176
+ if (o.opacity !== null) {
177
+ const v = Math.min(Math.max(o.opacity, 0), 1) * (out.style.opacity ?? 1);
178
+ out.style.opacity = v >= 1 ? null : v;
179
+ }
180
+ return out;
181
+ }
110
182
  function changesState(c) {
111
- return !!c && (Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
183
+ return !!c && (c.reset === true || Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
112
184
  }
113
- function applyClick(c, state) {
114
- const out = { ...state };
185
+ function applyClick(c, state, initial = {}) {
186
+ let out = { ...state };
187
+ if (c.reset === true) {
188
+ out = {};
189
+ for (const k of Object.keys(state)) out[k] = false;
190
+ Object.assign(out, initial);
191
+ }
115
192
  for (const [k, v] of Object.entries(c.set ?? {})) out[k] = v;
116
193
  for (const k of c.toggle ?? []) out[k] = !(out[k] ?? false);
117
194
  return out;
@@ -126,7 +203,9 @@ var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand",
126
203
  var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
127
204
  var KNOWN_FIELDS = {
128
205
  file: ["version", "pages", "images"],
129
- page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables"],
206
+ page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
207
+ flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
208
+ flowOverride: ["when", "volume"],
130
209
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
131
210
  layer: ["id", "name", "visible", "locked"],
132
211
  rgba: ["r", "g", "b", "a"],
@@ -155,6 +234,7 @@ var KNOWN_FIELDS = {
155
234
  "click",
156
235
  "hover",
157
236
  "when",
237
+ "overrides",
158
238
  "symbol",
159
239
  "playback",
160
240
  "offset",
@@ -163,8 +243,13 @@ var KNOWN_FIELDS = {
163
243
  "reveal",
164
244
  "ports",
165
245
  "labelRect",
166
- "extension"
246
+ "extension",
247
+ "capacity",
248
+ "delay",
249
+ "queue",
250
+ "gauge"
167
251
  ],
252
+ gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
168
253
  strokePoint: ["x", "y"],
169
254
  subpath: ["closed", "coords"],
170
255
  inlineImage: ["data", "format"],
@@ -172,7 +257,7 @@ var KNOWN_FIELDS = {
172
257
  extensionRef: ["id", "shape", "version", "params"],
173
258
  normalizedPoint: ["x", "y"],
174
259
  normalizedRect: ["x", "y", "width", "height"],
175
- connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
260
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
176
261
  waypoint: ["x", "y"],
177
262
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
178
263
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -181,8 +266,9 @@ var KNOWN_FIELDS = {
181
266
  repeat: ["start", "end", "count"],
182
267
  cameraKey: ["t", "x", "y", "width", "height", "ease"],
183
268
  marker: ["t", "name", "loop"],
184
- click: ["scene", "url", "set", "toggle"],
185
- hover: ["stroke", "fill", "opacity"]
269
+ click: ["scene", "url", "set", "toggle", "reset"],
270
+ hover: ["stroke", "fill", "opacity"],
271
+ override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
186
272
  };
187
273
  var MAX_DURATION = 3600;
188
274
  var MAX_MARCH = 1e4;
@@ -198,6 +284,8 @@ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
198
284
  var str = (v) => typeof v === "string" ? v : void 0;
199
285
  var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
200
286
  var bool = (v) => typeof v === "boolean" ? v : void 0;
287
+ var NO_ID = "00000000-0000-0000-0000-000000000000";
288
+ 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}$/;
201
289
  var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
202
290
  function clampTime(t) {
203
291
  if (!Number.isFinite(t)) return t === Infinity ? MAX_DURATION : 0;
@@ -289,7 +377,8 @@ var Reader = class {
289
377
  this.fields(w, "waypoint");
290
378
  return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
291
379
  }) : null,
292
- hops: typeof v.hops === "boolean" ? v.hops : null
380
+ hops: typeof v.hops === "boolean" ? v.hops : null,
381
+ cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
293
382
  };
294
383
  }
295
384
  image(v, table) {
@@ -341,6 +430,7 @@ var Reader = class {
341
430
  click: this.click(v.click),
342
431
  hover: this.hover(v.hover),
343
432
  when: str(v.when) ?? null,
433
+ overrides: this.overrides(v.overrides),
344
434
  symbol: uuid(v.symbol) ?? null,
345
435
  playback: this.playback(v.playback),
346
436
  offset: num(v.offset) ?? null,
@@ -352,7 +442,26 @@ var Reader = class {
352
442
  return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
353
443
  }) : null,
354
444
  labelRect: this.labelRect(v.labelRect),
355
- extension: this.extension(v.extension)
445
+ extension: this.extension(v.extension),
446
+ capacity: num(v.capacity) ?? null,
447
+ delay: num(v.delay) ?? null,
448
+ queue: v.queue === true ? true : null,
449
+ gauge: this.gauge(v.gauge)
450
+ };
451
+ }
452
+ gauge(v) {
453
+ if (!isObj(v)) return null;
454
+ this.fields(v, "gauge");
455
+ const finite = (x) => typeof x === "number" && Number.isFinite(x) ? x : null;
456
+ const style2 = str(v.style);
457
+ return {
458
+ metric: str(v.metric) ?? "",
459
+ of: typeof v.of === "string" && UUID.test(v.of) ? v.of.toUpperCase() : NO_ID,
460
+ max: finite(v.max),
461
+ warn: finite(v.warn),
462
+ alarm: finite(v.alarm),
463
+ style: style2 === "number" || style2 === "bar" || style2 === "dial" ? style2 : null,
464
+ label: str(v.label) ?? null
356
465
  };
357
466
  }
358
467
  labelRect(v) {
@@ -396,7 +505,7 @@ var Reader = class {
396
505
  }
397
506
  const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
398
507
  const toggle = raw ? raw.filter(isName) : null;
399
- return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
508
+ return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null, reset: v.reset === true ? true : null };
400
509
  }
401
510
  playback(v) {
402
511
  const s = str(v);
@@ -432,6 +541,35 @@ var Reader = class {
432
541
  params: strings(v.params)
433
542
  };
434
543
  }
544
+ /** As DottieKit reads them: a reroute whose targets aren't all ids is dropped, as are keys that aren't ids. */
545
+ overrides(v) {
546
+ if (!Array.isArray(v)) return null;
547
+ return v.filter(isObj).map((o) => {
548
+ this.fields(o, "override");
549
+ const dash = str(o.dash);
550
+ let reroute = null;
551
+ if (isObj(o.reroute) && Object.values(o.reroute).every((x) => typeof x === "string" && UUID.test(x))) {
552
+ reroute = {};
553
+ for (const [k, x] of Object.entries(o.reroute)) if (UUID.test(k)) reroute[k.toUpperCase()] = x.toUpperCase();
554
+ if (Object.keys(reroute).length === 0) reroute = null;
555
+ }
556
+ return {
557
+ when: str(o.when) ?? "",
558
+ stroke: rgba(o.stroke) ?? null,
559
+ fill: rgba(o.fill) ?? null,
560
+ textColor: rgba(o.textColor) ?? null,
561
+ opacity: num(o.opacity) ?? null,
562
+ strokeWidth: num(o.strokeWidth) ?? null,
563
+ dash: dash !== void 0 && ["solid", "dashed", "dotted"].includes(dash) ? dash : null,
564
+ text: str(o.text) ?? null,
565
+ reroute,
566
+ offline: o.offline === true ? true : null,
567
+ cost: typeof o.cost === "number" && Number.isFinite(o.cost) ? o.cost : null,
568
+ capacity: typeof o.capacity === "number" && Number.isFinite(o.capacity) ? o.capacity : null,
569
+ delay: typeof o.delay === "number" && Number.isFinite(o.delay) ? o.delay : null
570
+ };
571
+ });
572
+ }
435
573
  hover(v) {
436
574
  if (!isObj(v)) return null;
437
575
  this.fields(v, "hover");
@@ -531,7 +669,33 @@ var Reader = class {
531
669
  theme: str(v.theme) ?? null,
532
670
  timeline: this.timeline(v.timeline),
533
671
  symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
534
- variables: this.variables(v.variables)
672
+ variables: this.variables(v.variables),
673
+ flows: Array.isArray(v.flows) ? v.flows.filter(isObj).map((f) => this.flow(f)) : null
674
+ };
675
+ }
676
+ /** As DottieKit reads one: a missing or bad id, source or destination sends nothing; bad destinations are dropped. */
677
+ flow(f) {
678
+ this.fields(f, "flow");
679
+ const id = (x) => typeof x === "string" && UUID.test(x) ? x.toUpperCase() : NO_ID;
680
+ const finite = (x) => {
681
+ const n = num(x);
682
+ return n !== void 0 && Number.isFinite(n) ? n : null;
683
+ };
684
+ return {
685
+ id: id(f.id),
686
+ from: id(f.from),
687
+ to: Array.isArray(f.to) && f.to.every((x) => typeof x === "string") ? f.to.filter((x) => UUID.test(x)).map((x) => x.toUpperCase()) : [],
688
+ every: finite(f.every),
689
+ speed: finite(f.speed),
690
+ size: finite(f.size),
691
+ color: isObj(f.color) && ["r", "g", "b", "a"].every((k) => typeof f.color[k] === "number") ? rgba(f.color) ?? null : null,
692
+ when: str(f.when) ?? null,
693
+ spread: f.spread === true ? true : null,
694
+ volume: finite(f.volume),
695
+ overrides: Array.isArray(f.overrides) && f.overrides.length > 0 ? f.overrides.filter(isObj).map((o) => {
696
+ this.fields(o, "flowOverride");
697
+ return { when: str(o.when) ?? "", volume: finite(o.volume) };
698
+ }) : null
535
699
  };
536
700
  }
537
701
  };
@@ -593,7 +757,8 @@ function cloneElement(e) {
593
757
  start: { ...e.connector.start },
594
758
  end: { ...e.connector.end },
595
759
  waypoints: e.connector.waypoints?.map((w) => ({ ...w })) ?? null
596
- } : null
760
+ } : null,
761
+ gauge: e.gauge ? { ...e.gauge } : e.gauge
597
762
  };
598
763
  }
599
764
  function orderedElements(page) {
@@ -1344,8 +1509,617 @@ function mulConst(step) {
1344
1509
  return [p[0], p[1]];
1345
1510
  }
1346
1511
 
1512
+ // src/flows.ts
1513
+ var DEFAULT_EVERY = 0.5;
1514
+ var DEFAULT_SPEED = 160;
1515
+ var DEFAULT_SIZE = 12;
1516
+ var DEFAULT_COLOR = { r: 0.95, g: 0.36, b: 0.33, a: 1 };
1517
+ var clamp = (v, fallback, lo, hi) => v === null || !Number.isFinite(v) ? fallback : Math.min(Math.max(v, lo), hi);
1518
+ function packetID(flow, k) {
1519
+ const n = (k + 2 ** 40) % 2 ** 48;
1520
+ return flow.slice(0, 24) + (n < 0 ? n + 2 ** 48 : n).toString(16).toUpperCase().padStart(12, "0");
1521
+ }
1522
+ var routeCost = (cost) => cost === null || !Number.isFinite(cost) ? 1 : Math.min(Math.max(cost, 0), 1e6);
1523
+ function flowTree(from, frame2, offline = /* @__PURE__ */ new Set()) {
1524
+ const shapes2 = new Set(frame2.elements.filter((e) => e.kind !== "connector" && !offline.has(e.id)).map((e) => e.id));
1525
+ if (!shapes2.has(from)) return null;
1526
+ const next = /* @__PURE__ */ new Map();
1527
+ const add2 = (a, edge) => {
1528
+ const list = next.get(a);
1529
+ if (list) list.push(edge);
1530
+ else next.set(a, [edge]);
1531
+ };
1532
+ for (const e of frame2.elements) {
1533
+ const c = e.connector;
1534
+ const a = c?.start.elementID ?? null, b = c?.end.elementID ?? null;
1535
+ if (!c || offline.has(e.id) || a === null || b === null || a === b || !shapes2.has(a) || !shapes2.has(b)) continue;
1536
+ const cost = routeCost(c.cost);
1537
+ add2(a, { to: b, via: e.id, back: false, cost });
1538
+ if (c.startHead !== "none") add2(b, { to: a, via: e.id, back: true, cost });
1539
+ }
1540
+ const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
1541
+ let counter = 1;
1542
+ const tree = new FlowTree(from);
1543
+ const open = [from];
1544
+ while (open.length > 0) {
1545
+ let best = 0;
1546
+ for (let i = 1; i < open.length; i++) {
1547
+ const da = dist.get(open[i]), db = dist.get(open[best]);
1548
+ if (da < db || da === db && order.get(open[i]) < order.get(open[best])) best = i;
1549
+ }
1550
+ const n = open.splice(best, 1)[0];
1551
+ tree.settled.add(n);
1552
+ for (const edge of next.get(n) ?? []) {
1553
+ if (tree.settled.has(edge.to)) continue;
1554
+ const d = dist.get(n) + edge.cost;
1555
+ const old = dist.get(edge.to);
1556
+ if (old !== void 0 && old <= d) continue;
1557
+ if (old === void 0) open.push(edge.to);
1558
+ dist.set(edge.to, d);
1559
+ order.set(edge.to, counter++);
1560
+ tree.came.set(edge.to, { from: n, via: edge.via, back: edge.back });
1561
+ }
1562
+ }
1563
+ return tree;
1564
+ }
1565
+ var FlowTree = class {
1566
+ constructor(from) {
1567
+ __publicField(this, "settled", /* @__PURE__ */ new Set());
1568
+ __publicField(this, "came", /* @__PURE__ */ new Map());
1569
+ __publicField(this, "from");
1570
+ this.from = from;
1571
+ }
1572
+ /** The first of `to` that can be reached; with `spread`, when the first can't be, every other one that can. */
1573
+ goals(to, spread) {
1574
+ const reachable = to.filter((x) => x !== this.from && this.settled.has(x));
1575
+ if (reachable.length === 0) return [];
1576
+ return spread && reachable[0] !== to[0] ? reachable : [reachable[0]];
1577
+ }
1578
+ /** The connectors to `goal`, as [connector, backwards] hops. */
1579
+ hops(goal) {
1580
+ const hops = [];
1581
+ for (let at = goal, step = this.came.get(at); step; at = step.from, step = this.came.get(at)) hops.push([step.via, step.back]);
1582
+ return hops.reverse();
1583
+ }
1584
+ };
1585
+
1586
+ // src/measure.ts
1587
+ var emptyMetrics = () => ({ elements: /* @__PURE__ */ new Map(), flows: /* @__PURE__ */ new Map(), carry: {} });
1588
+ function metricValue(m, metric, id) {
1589
+ const f = m.flows.get(id);
1590
+ if (f) {
1591
+ switch (metric) {
1592
+ case "throughput":
1593
+ return f.placed;
1594
+ case "backlog":
1595
+ return f.backlog;
1596
+ case "backup":
1597
+ return f.backup;
1598
+ case "transit":
1599
+ return f.transit;
1600
+ default:
1601
+ return null;
1602
+ }
1603
+ }
1604
+ const e = m.elements.get(id);
1605
+ if (!e) return null;
1606
+ switch (metric) {
1607
+ case "throughput":
1608
+ return e.throughput;
1609
+ case "load":
1610
+ return e.load;
1611
+ case "delay":
1612
+ return e.delay;
1613
+ case "backlog":
1614
+ return e.backlog;
1615
+ case "inFlight":
1616
+ return e.inFlight;
1617
+ case "depth":
1618
+ return e.depth;
1619
+ default:
1620
+ return null;
1621
+ }
1622
+ }
1623
+ var TINY = 1e-9;
1624
+ var amount = (f) => clamp(f.volume, 1, 0, 1e12);
1625
+ function amountIn(f, values) {
1626
+ if (values === null || !f.overrides) return amount(f);
1627
+ let v = null;
1628
+ for (const o of f.overrides) if (o.volume !== null && !mentionsMetric(o.when) && holds(o.when, values)) v = o.volume;
1629
+ return v === null ? amount(f) : Math.min(Math.max(Number.isFinite(v) ? v : 1, 0), 1e12);
1630
+ }
1631
+ function delayOf(e) {
1632
+ const d = e.delay ?? null;
1633
+ if (e.kind === "connector" || d === null || !Number.isFinite(d)) return 0;
1634
+ return Math.min(Math.max(d, 0), 3600);
1635
+ }
1636
+ var queueKey = (shape, flow) => shape + "|" + flow;
1637
+ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set(), sinceChange = null, carried = {}) {
1638
+ const since = Math.max(0, Math.min(sinceChange ?? t, 1e6));
1639
+ const byID = /* @__PURE__ */ new Map();
1640
+ for (const e of frame2.elements) if (!byID.has(e.id)) byID.set(e.id, e);
1641
+ const metrics2 = emptyMetrics();
1642
+ for (const e of frame2.elements) {
1643
+ if (e.kind === "connector" && !e.connector) continue;
1644
+ metrics2.elements.set(e.id, { throughput: 0, load: null, delay: e.kind !== "connector" ? delayOf(e) : 0, backlog: 0, inFlight: 0, depth: 0 });
1645
+ }
1646
+ const list = flows ?? [];
1647
+ const room = /* @__PURE__ */ new Map();
1648
+ for (const e of frame2.elements) {
1649
+ const c = e.capacity ?? null;
1650
+ if (c !== null && Number.isFinite(c)) room.set(e.id, Math.max(c, 0));
1651
+ }
1652
+ const full = () => new Set([...room].filter(([, v]) => v <= TINY).map(([id]) => id));
1653
+ const route2 = (tree, goal) => {
1654
+ const hops = [], shapes2 = [];
1655
+ let at = goal;
1656
+ for (let step = tree.came.get(at); step; step = tree.came.get(at)) {
1657
+ hops.push([step.via, step.back]);
1658
+ shapes2.push(at);
1659
+ at = step.from;
1660
+ }
1661
+ shapes2.push(at);
1662
+ return { hops: hops.reverse(), shapes: shapes2.reverse() };
1663
+ };
1664
+ const way = (hops, shapes2) => {
1665
+ const points = [], inside = [], stretches = [];
1666
+ let length = 0;
1667
+ hops.forEach(([id, back], k) => {
1668
+ const c = byID.get(id);
1669
+ if (!c) return;
1670
+ const line2 = back ? [...tracing(c, frame2).points].reverse() : tracing(c, frame2).points;
1671
+ if (line2.length === 0) return;
1672
+ const last = points[points.length - 1];
1673
+ if (last) {
1674
+ const gap = Math.hypot(line2[0].x - last.x, line2[0].y - last.y);
1675
+ const shape = byID.get(shapes2[k]);
1676
+ inside.push([length, length + gap, shape ? delayOf(shape) : 0]);
1677
+ length += gap;
1678
+ }
1679
+ const start = length;
1680
+ 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);
1681
+ stretches.push([id, start, length]);
1682
+ points.push(...line2);
1683
+ });
1684
+ return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
1685
+ };
1686
+ const along = (w, elapsedAt, speed) => {
1687
+ let elapsed = elapsedAt, d = 0;
1688
+ for (const [a, b, delay] of w.inside) {
1689
+ const reach = (a - d) / speed;
1690
+ if (elapsed <= reach) break;
1691
+ elapsed -= reach;
1692
+ d = a;
1693
+ const stay = (b - a) / speed + delay;
1694
+ if (elapsed < stay) return null;
1695
+ elapsed -= stay;
1696
+ d = b;
1697
+ }
1698
+ d += elapsed * speed;
1699
+ return d >= 0 && d <= w.length ? d : null;
1700
+ };
1701
+ const union2 = (a, b) => /* @__PURE__ */ new Set([...a, ...b]);
1702
+ const bump = (id, f) => {
1703
+ const m = metrics2.elements.get(id);
1704
+ if (m) f(m);
1705
+ };
1706
+ const leaving = /* @__PURE__ */ new Map(), turn = [];
1707
+ for (const flow of list) {
1708
+ const n = leaving.get(flow.from) ?? 0;
1709
+ turn.push(n);
1710
+ leaving.set(flow.from, n + 1);
1711
+ }
1712
+ const biggest = list.length > 0 ? Math.max(...list.map(amount)) : 1;
1713
+ const packets = [];
1714
+ const waitingBy = /* @__PURE__ */ new Map();
1715
+ 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);
1716
+ for (const [i, flow] of list.entries()) {
1717
+ if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
1718
+ const source = byID.get(flow.from);
1719
+ const base2 = source ? flowTree(flow.from, frame2, offline) : null;
1720
+ if (!source || !base2) continue;
1721
+ const volume = amountIn(flow, values);
1722
+ const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
1723
+ const placements = [];
1724
+ const waiting = [];
1725
+ const goals = base2.goals(flow.to, flow.spread === true);
1726
+ const shares = goals.length > 1 ? goals.map((g) => [[g], volume / goals.length]) : [[flow.to, volume]];
1727
+ for (const [candidates, share] of shares) {
1728
+ if (!(share > TINY)) continue;
1729
+ let left = share;
1730
+ for (let n = 0; n < 32 && left > TINY; n++) {
1731
+ const tree = flowTree(flow.from, frame2, union2(offline, full()));
1732
+ const goal2 = tree?.goals(candidates, false)[0];
1733
+ if (!tree || goal2 === void 0) break;
1734
+ const r = route2(tree, goal2);
1735
+ const ids = [...r.shapes, ...r.hops.map(([id]) => id)];
1736
+ const rooms = ids.filter((id) => room.has(id)).map((id) => room.get(id));
1737
+ const fits = Math.min(left, rooms.length > 0 ? Math.min(...rooms) : Infinity);
1738
+ if (!(fits > TINY)) break;
1739
+ for (const id of ids) if (room.has(id)) room.set(id, room.get(id) - fits);
1740
+ placements.push({ hops: r.hops, shapes: r.shapes, goal: goal2, amount: fits });
1741
+ left -= fits;
1742
+ }
1743
+ if (!(left > TINY)) continue;
1744
+ let at = flow.from;
1745
+ const goal = base2.goals(candidates, false)[0];
1746
+ if (goal !== void 0) {
1747
+ const r = route2(base2, goal), done = full();
1748
+ const sequence = [[r.shapes[0], r.shapes[0]]];
1749
+ r.hops.forEach(([id], k) => {
1750
+ sequence.push([id, r.shapes[k]], [r.shapes[k + 1], r.shapes[k]]);
1751
+ });
1752
+ const first = sequence.find(([id]) => done.has(id));
1753
+ if (first) {
1754
+ at = first[1];
1755
+ const upTo = r.shapes.indexOf(at);
1756
+ for (let q = upTo; q >= 0; q--) {
1757
+ const s = byID.get(r.shapes[q]);
1758
+ if (s && s.queue === true && s.kind !== "connector") {
1759
+ at = r.shapes[q];
1760
+ break;
1761
+ }
1762
+ }
1763
+ }
1764
+ const n = r.shapes.indexOf(at);
1765
+ if (at !== flow.from && n >= 0) {
1766
+ const shapes2 = r.shapes.slice(0, n + 1), hops = r.hops.slice(0, n);
1767
+ for (const id of [...shapes2, ...hops.map(([h]) => h)]) if (room.has(id)) room.set(id, Math.max(0, room.get(id) - left));
1768
+ placements.push({ hops, shapes: shapes2, goal: at, amount: left, waits: true });
1769
+ }
1770
+ }
1771
+ const existing = waiting.find(([s]) => s === at);
1772
+ if (existing) existing[1] += left;
1773
+ else waiting.push([at, left]);
1774
+ }
1775
+ waitingBy.set(flow.id, waiting);
1776
+ for (const p of placements) {
1777
+ for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
1778
+ m.throughput += p.amount;
1779
+ });
1780
+ if (!p.waits) fm.placed += p.amount;
1781
+ }
1782
+ for (const [at, a] of waiting) {
1783
+ bump(at, (m) => {
1784
+ m.backlog += a;
1785
+ });
1786
+ fm.backlog += a;
1787
+ }
1788
+ const reachedFirst = placements.filter((p) => !p.waits && p.goal === flow.to[0]).reduce((s, p) => s + p.amount, 0);
1789
+ fm.backup = volume > TINY ? Math.max(0, (volume - reachedFirst) / volume) : 0;
1790
+ const every = everyOf(flow);
1791
+ const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
1792
+ const phase = turn[i] / leaving.get(flow.from) * every;
1793
+ const color2 = flow.color ?? DEFAULT_COLOR;
1794
+ const cumulative = [];
1795
+ let sum = 0;
1796
+ for (const p of placements) {
1797
+ sum += p.amount / Math.max(volume, TINY);
1798
+ cumulative.push(sum);
1799
+ }
1800
+ if (cumulative.length > 0 && Math.abs(cumulative[cumulative.length - 1] - 1) < 1e-9) cumulative[cumulative.length - 1] = 1;
1801
+ let transitSum = 0;
1802
+ placements.forEach((p, r) => {
1803
+ const w = way(p.hops, p.shapes);
1804
+ if (!w) return;
1805
+ const travel = w.length / speed + w.inside.reduce((s, x) => s + x[2], 0);
1806
+ if (!p.waits) transitSum += travel * p.amount;
1807
+ const lo = Math.ceil((t - phase - travel) / every);
1808
+ const hi = Math.floor((t - phase) / every);
1809
+ const below = r === 0 ? 0 : cumulative[r - 1], upTo = cumulative[r];
1810
+ const dealt = (c, k) => Math.floor((k + 1) * c) - Math.floor(k * c);
1811
+ for (let k = Math.max(lo, hi - 3999); k <= hi; k++) {
1812
+ if (dealt(upTo, k) - dealt(below, k) !== 1) continue;
1813
+ const d = along(w, t - k * every - phase, speed);
1814
+ if (d === null) continue;
1815
+ const s = w.stretches.find(([, a, b]) => d >= a && d <= b);
1816
+ if (s) bump(s[0], (m) => {
1817
+ m.inFlight += 1;
1818
+ });
1819
+ const pt2 = w.line.point(d / w.length);
1820
+ packets.push(packet(packetID(flow.id, k), pt2.x - size / 2, pt2.y - size / 2, size, color2, source.layerID));
1821
+ }
1822
+ });
1823
+ fm.transit = fm.placed > TINY ? transitSum / fm.placed : null;
1824
+ metrics2.flows.set(flow.id, fm);
1825
+ }
1826
+ const piles = /* @__PURE__ */ new Map();
1827
+ for (const flow of list) {
1828
+ const waiting = waitingBy.get(flow.id) ?? [];
1829
+ const shapes2 = waiting.map(([s]) => s);
1830
+ for (const e of frame2.elements) {
1831
+ const c = carried[queueKey(e.id, flow.id)];
1832
+ if (c !== void 0 && c > TINY && !shapes2.includes(e.id)) shapes2.push(e.id);
1833
+ }
1834
+ if (shapes2.length === 0) continue;
1835
+ const volume = amountIn(flow, values);
1836
+ const every = everyOf(flow);
1837
+ const unit = every * Math.max(volume, TINY);
1838
+ const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
1839
+ const color2 = flow.color ?? DEFAULT_COLOR;
1840
+ shapes2.forEach((at, j) => {
1841
+ const shape = byID.get(at);
1842
+ if (!shape) return;
1843
+ const layer = byID.get(flow.from)?.layerID ?? shape.layerID;
1844
+ const backlog = waiting.find(([s]) => s === at)?.[1] ?? 0;
1845
+ const before = Math.max(0, carried[queueKey(at, flow.id)] ?? 0);
1846
+ let depth = before + backlog * since;
1847
+ if (backlog <= TINY && before > TINY) {
1848
+ const trial = new Map(room);
1849
+ const outlets = [];
1850
+ let total = 0;
1851
+ for (let n2 = 0; n2 < 32; n2++) {
1852
+ const blocked = new Set([...trial].filter(([, v]) => v <= TINY).map(([id]) => id));
1853
+ const tree = flowTree(at, frame2, union2(offline, blocked));
1854
+ const goal = tree?.goals(flow.to, false)[0];
1855
+ if (!tree || goal === void 0) break;
1856
+ const r = route2(tree, goal);
1857
+ const ids = [...r.shapes.slice(1), ...r.hops.map(([id]) => id)];
1858
+ const rooms = ids.filter((id) => trial.has(id)).map((id) => trial.get(id));
1859
+ const spare = rooms.length > 0 ? Math.min(...rooms) : Infinity;
1860
+ const rate = Number.isFinite(spare) ? spare : Math.max(before / 0.5 - total, TINY * 2);
1861
+ if (!(rate > TINY)) break;
1862
+ for (const id of ids) if (trial.has(id)) trial.set(id, trial.get(id) - rate);
1863
+ outlets.push({ hops: r.hops, shapes: r.shapes, rate });
1864
+ total += rate;
1865
+ if (!Number.isFinite(spare)) break;
1866
+ }
1867
+ if (total > TINY) {
1868
+ depth = Math.max(0, before - total * since);
1869
+ if (depth > TINY) {
1870
+ for (const [id, v] of trial) room.set(id, v);
1871
+ for (const o of outlets) for (const id of [...o.shapes, ...o.hops.map(([h]) => h)]) bump(id, (m) => {
1872
+ m.throughput += o.rate;
1873
+ });
1874
+ }
1875
+ const lasts = before / total;
1876
+ outlets.forEach((o, i) => {
1877
+ const w = way(o.hops, o.shapes);
1878
+ if (!w) return;
1879
+ const spacing = Math.max(unit / o.rate, 0.05);
1880
+ for (let k = 0; k * spacing < lasts && k * spacing <= since && k < 2048; k++) {
1881
+ const d = along(w, since - k * spacing, speed);
1882
+ if (d === null) continue;
1883
+ const pt2 = w.line.point(d / w.length);
1884
+ 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));
1885
+ }
1886
+ });
1887
+ }
1888
+ }
1889
+ if (!(depth > TINY || backlog > TINY)) return;
1890
+ bump(at, (m) => {
1891
+ m.depth += depth;
1892
+ });
1893
+ if (depth > TINY) metrics2.carry[queueKey(at, flow.id)] = depth;
1894
+ const n = Math.min(Math.max(Math.ceil(depth / unit - 1e-9), 1), 30);
1895
+ const rows = Math.trunc((n + 9) / 10);
1896
+ const base2 = piles.get(at) ?? 0;
1897
+ piles.set(at, base2 + rows);
1898
+ for (let q = 0; q < n; q++) {
1899
+ const cx = shape.x + 2 + size / 2 + q % 10 * (size + 3);
1900
+ const cy = shape.y - 4 - size / 2 - (base2 + Math.trunc(q / 10)) * (size + 3);
1901
+ packets.push(packet(packetID(flow.id, 2 ** 39 + j * 64 + q), cx - size / 2, cy - size / 2, size, color2, layer));
1902
+ }
1903
+ });
1904
+ }
1905
+ for (const e of frame2.elements) {
1906
+ const c = e.capacity ?? null;
1907
+ const m = metrics2.elements.get(e.id);
1908
+ if (c === null || !Number.isFinite(c) || !m) continue;
1909
+ m.load = c > 0 ? m.throughput / c : 1;
1910
+ }
1911
+ return { packets, metrics: metrics2 };
1912
+ }
1913
+ var BLANK = {
1914
+ seed: 1,
1915
+ text: null,
1916
+ connector: null,
1917
+ groupID: null,
1918
+ points: null,
1919
+ path: null,
1920
+ subpaths: null,
1921
+ evenOdd: null,
1922
+ rotation: null,
1923
+ link: null,
1924
+ url: null,
1925
+ anchor: null,
1926
+ click: null,
1927
+ hover: null,
1928
+ when: null,
1929
+ overrides: null,
1930
+ symbol: null,
1931
+ playback: null,
1932
+ offset: null,
1933
+ args: null,
1934
+ image: null,
1935
+ reveal: null
1936
+ };
1937
+ function style(stroke, fill, strokeWidth, fillStyle = "solid", opacity2 = null) {
1938
+ return {
1939
+ stroke: { ...stroke },
1940
+ fill: { ...fill },
1941
+ fillStyle,
1942
+ strokeWidth,
1943
+ dash: "solid",
1944
+ roughness: 0,
1945
+ bowing: 1,
1946
+ fontSize: 18,
1947
+ font: null,
1948
+ gridSize: null,
1949
+ opacity: opacity2,
1950
+ dashOffset: null,
1951
+ textColor: null
1952
+ };
1953
+ }
1954
+ function packet(id, x, y, size, color2, layerID) {
1955
+ return { ...BLANK, id, kind: "ellipse", layerID, x, y, width: size, height: size, style: style(color2, color2, 1.5) };
1956
+ }
1957
+ function metricLookup(m, frame2) {
1958
+ const anchors = /* @__PURE__ */ new Map();
1959
+ for (const e of frame2.elements) if (e.anchor !== null && !anchors.has(e.anchor)) anchors.set(e.anchor, e.id);
1960
+ 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}$/;
1961
+ return (term, own) => {
1962
+ const id = term.target === "" ? own : anchors.get(term.target) ?? (UUID2.test(term.target) ? term.target.toUpperCase() : null);
1963
+ return id === null ? null : metricValue(m, term.metric, id);
1964
+ };
1965
+ }
1966
+ function finishing(elements, metrics2, values, frame2, flows, since = Infinity, carried = {}) {
1967
+ const lookup = metricLookup(metrics2, frame2);
1968
+ const out = [], readings = {};
1969
+ for (let e of elements) {
1970
+ if (values !== null && e.overrides) {
1971
+ for (const o of e.overrides) {
1972
+ const own = e.id;
1973
+ if (mentionsMetric(o.when) && holds(o.when, values, (m) => lookup(m, own))) e = applyOverride(looksOnly(o), e);
1974
+ }
1975
+ }
1976
+ if (!e.gauge || values === null) {
1977
+ out.push(e);
1978
+ continue;
1979
+ }
1980
+ const g = e.gauge;
1981
+ let shown = metricValue(metrics2, g.metric, g.of);
1982
+ const then = carried[gaugeKey(e.id)];
1983
+ if (shown !== null && then !== void 0 && g.metric !== "depth" && g.metric !== "inFlight" && since < GAUGE_EASE) {
1984
+ const f = Math.max(0, since / GAUGE_EASE), smooth = f * f * (3 - 2 * f);
1985
+ shown = then + (shown - then) * smooth;
1986
+ }
1987
+ if (shown !== null) readings[gaugeKey(e.id)] = shown;
1988
+ out.push(...gauge(e, g, shown, frame2, flows));
1989
+ }
1990
+ return { elements: out, readings };
1991
+ }
1992
+ var gaugeKey = (id) => "gauge|" + id;
1993
+ var GAUGE_EASE = 1;
1994
+ var hex = (h) => {
1995
+ const v = parseInt(h.slice(1), 16);
1996
+ return { r: (v >> 16 & 255) / 255, g: (v >> 8 & 255) / 255, b: (v & 255) / 255, a: 1 };
1997
+ };
1998
+ var GAUGE_GREEN = hex("#16a34a");
1999
+ var GAUGE_AMBER = hex("#d97706");
2000
+ var GAUGE_RED = hex("#dc2626");
2001
+ var GAUGE_BLUE = hex("#2f6fdf");
2002
+ var GAUGE_TRACK = hex("#e5e7eb");
2003
+ var GAUGE_GRAY = hex("#9ca3af");
2004
+ function formatted(metric, v) {
2005
+ if (v === null || !Number.isFinite(v)) return "\u2013";
2006
+ switch (metric) {
2007
+ case "load":
2008
+ case "backup":
2009
+ return formatNumber(v * 100, 0) + "%";
2010
+ case "transit":
2011
+ case "delay":
2012
+ return formatNumber(v, Math.abs(v) >= 10 ? 1 : 2) + "s";
2013
+ default:
2014
+ return formatNumber(v, Math.abs(v) >= 100 ? 0 : Math.abs(v) >= 10 ? 1 : 2);
2015
+ }
2016
+ }
2017
+ function formatNumber(v, places) {
2018
+ const scale = places === 0 ? 1 : places === 1 ? 10 : 100;
2019
+ const n = Math.floor(Math.abs(v) * scale + 0.5);
2020
+ if (!(n < 1e15)) return v < 0 ? "-\u221E" : "\u221E";
2021
+ const whole = Math.trunc(n / scale), part = n - whole * scale;
2022
+ let s = String(whole);
2023
+ if (places > 0 && part > 0) {
2024
+ let digits = String(part);
2025
+ while (digits.length < places) digits = "0" + digits;
2026
+ while (digits.endsWith("0")) digits = digits.slice(0, -1);
2027
+ s += "." + digits;
2028
+ }
2029
+ return (v < 0 && n > 0 ? "-" : "") + s;
2030
+ }
2031
+ function gauge(e, g, value2, frame2, flows) {
2032
+ const target = frame2.elements.find((x) => x.id === g.of);
2033
+ const full = g.max ?? (g.metric === "load" ? 1 : target?.capacity ?? value2 ?? 1);
2034
+ const fraction = value2 === null ? 0 : full > 0 ? Math.min(Math.max(value2 / full, 0), 1) : 0;
2035
+ const warn = g.warn ?? (g.metric === "load" ? 0.7 : null), alarm = g.alarm ?? (g.metric === "load" ? 0.9 : null);
2036
+ let color2 = warn === null && alarm === null ? GAUGE_BLUE : GAUGE_GREEN;
2037
+ if (value2 !== null && warn !== null && value2 >= warn) color2 = GAUGE_AMBER;
2038
+ if (value2 !== null && alarm !== null && value2 >= alarm) color2 = GAUGE_RED;
2039
+ if (value2 === null) color2 = GAUGE_GRAY;
2040
+ let name = g.label;
2041
+ if (name === null) {
2042
+ const what = target ? target.text !== null ? target.text.split("\n")[0] : target.anchor : null;
2043
+ name = `${g.metric} ${what ?? (flows.some((f) => f.id === g.of) ? "flow" : "?")}`;
2044
+ }
2045
+ const kind = g.style ?? "number";
2046
+ const w = Math.max(e.width, 1), h = Math.max(e.height, 1);
2047
+ const line2 = (a, b) => [
2048
+ a.x / w,
2049
+ a.y / h,
2050
+ (a.x + (b.x - a.x) / 3) / w,
2051
+ (a.y + (b.y - a.y) / 3) / h,
2052
+ (a.x + 2 * (b.x - a.x) / 3) / w,
2053
+ (a.y + 2 * (b.y - a.y) / 3) / h,
2054
+ b.x / w,
2055
+ b.y / h
2056
+ ];
2057
+ const box = (x, y, bw, bh) => {
2058
+ const p = [{ x, y }, { x: x + bw, y }, { x: x + bw, y: y + bh }, { x, y: y + bh }];
2059
+ let coords = line2(p[0], p[1]);
2060
+ for (let k = 1; k < 4; k++) coords = coords.concat(line2(p[k], p[(k + 1) % 4]).slice(2));
2061
+ return { closed: true, coords };
2062
+ };
2063
+ const arc = (c, radius, a0, a1) => {
2064
+ const pieces2 = Math.max(1, Math.ceil(Math.abs(a1 - a0) / (Math.PI / 2) - 1e-9));
2065
+ const step = (a1 - a0) / pieces2, kappa = 4 / 3 * Math.tan(step / 4);
2066
+ const at = (a) => ({ x: c.x + radius * Math.cos(a), y: c.y + radius * Math.sin(a) });
2067
+ const s = at(a0);
2068
+ const coords = [s.x / w, s.y / h];
2069
+ for (let k = 0; k < pieces2; k++) {
2070
+ const a = a0 + k * step, b = a + step;
2071
+ const p0 = at(a), p3 = at(b);
2072
+ const c1 = { x: p0.x - kappa * radius * Math.sin(a), y: p0.y + kappa * radius * Math.cos(a) };
2073
+ const c2 = { x: p3.x + kappa * radius * Math.sin(b), y: p3.y - kappa * radius * Math.cos(b) };
2074
+ coords.push(c1.x / w, c1.y / h, c2.x / w, c2.y / h, p3.x / w, p3.y / h);
2075
+ }
2076
+ return { closed: false, coords };
2077
+ };
2078
+ const part = (k, subpaths, stroke, fill, width) => ({
2079
+ ...BLANK,
2080
+ id: packetID(e.id, -(2 ** 38) - k),
2081
+ kind: "path",
2082
+ layerID: e.layerID,
2083
+ x: e.x,
2084
+ y: e.y,
2085
+ width: e.width,
2086
+ height: e.height,
2087
+ style: style(stroke, fill ?? { r: 0, g: 0, b: 0, a: 0 }, width, "solid", e.style.opacity),
2088
+ subpaths
2089
+ });
2090
+ const back = {
2091
+ ...e,
2092
+ gauge: null,
2093
+ kind: "path",
2094
+ subpaths: [box(0, 0, w, h)],
2095
+ text: name + "\n" + formatted(g.metric, value2),
2096
+ style: { ...e.style }
2097
+ };
2098
+ const parts = [];
2099
+ if (kind === "number") {
2100
+ back.labelRect = { x: 0, y: 0, width: 1, height: 1 };
2101
+ back.style.textColor = { ...color2 };
2102
+ } else if (kind === "bar") {
2103
+ back.labelRect = { x: 0, y: 0, width: 1, height: 0.66 };
2104
+ const inset2 = Math.min(10, w * 0.08), barH = Math.min(12, h * 0.18), top = h * 0.72;
2105
+ const trackW = w - 2 * inset2;
2106
+ parts.push(part(1, [box(inset2, top, trackW, barH)], GAUGE_TRACK, GAUGE_TRACK, 0.5));
2107
+ if (fraction > 0) parts.push(part(2, [box(inset2, top, trackW * fraction, barH)], color2, color2, 0.5));
2108
+ } else {
2109
+ back.labelRect = { x: 0, y: 0.54, width: 1, height: 0.46 };
2110
+ const radius = Math.min(w * 0.38, h * 0.4), center2 = { x: w / 2, y: h * 0.5 };
2111
+ const thick = Math.max(2, radius * 0.16);
2112
+ parts.push(part(1, [arc(center2, radius, Math.PI, 2 * Math.PI)], GAUGE_TRACK, null, thick));
2113
+ if (fraction > 0) parts.push(part(2, [arc(center2, radius, Math.PI, Math.PI + Math.PI * fraction)], color2, null, thick));
2114
+ const a = Math.PI + Math.PI * fraction;
2115
+ const tip = { x: center2.x + radius * 0.85 * Math.cos(a), y: center2.y + radius * 0.85 * Math.sin(a) };
2116
+ parts.push(part(3, [{ closed: false, coords: line2(center2, tip) }], { r: 0.2, g: 0.2, b: 0.2, a: 1 }, null, 2));
2117
+ }
2118
+ return [back, ...parts];
2119
+ }
2120
+
1347
2121
  // src/rough.ts
1348
- var offset = (rng, amount, roughness) => rng.signed() * amount * roughness;
2122
+ var offset = (rng, amount2, roughness) => rng.signed() * amount2 * roughness;
1349
2123
  function line(a, b, rng, roughness, bowing, preserveEnds, into) {
1350
2124
  const length = Math.hypot(b.x - a.x, b.y - a.y);
1351
2125
  if (!(length > 0.5)) return;
@@ -2201,16 +2975,39 @@ function trackMap(tl) {
2201
2975
  for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
2202
2976
  return m;
2203
2977
  }
2204
- function resolved(page, t, state = "initial") {
2205
- return expandingSymbols(posed(page, t, state), clampTime(t));
2978
+ function resolved(page, t, state = "initial", since = null, carried = {}) {
2979
+ return expandingSymbols(posed(page, t, state, since, carried), clampTime(t));
2206
2980
  }
2207
2981
  var EMPTY_TIMELINE = { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
2208
- function posed(page, t, state = "initial") {
2982
+ function posed(page, t, state = "initial", since = null, carried = {}) {
2983
+ return posedWithMetrics(page, t, state, since, carried).frame;
2984
+ }
2985
+ function posedWithMetrics(page, t, state = "initial", since = null, carried = {}) {
2209
2986
  const off = stateHidden(page, state);
2210
- const timeline = page.timeline ?? (off.size === 0 ? null : EMPTY_TIMELINE);
2211
- if (!timeline) return page;
2987
+ const values = stateValues(page, state);
2988
+ const active = /* @__PURE__ */ new Map();
2989
+ for (const e of page.elements) {
2990
+ const o = activeOverrides(e, values);
2991
+ if (o.length > 0) active.set(e.id, o);
2992
+ }
2993
+ const flowing = (page.flows ?? []).length > 0;
2994
+ const measuring = flowing || page.elements.some((e) => e.gauge || (e.overrides ?? []).some((o) => mentionsMetric(o.when)));
2995
+ const timeline = page.timeline ?? (off.size === 0 && active.size === 0 && !measuring ? null : EMPTY_TIMELINE);
2996
+ if (!timeline) return { frame: page, metrics: emptyMetrics() };
2212
2997
  t = clampTime(t);
2213
2998
  const tracks = trackMap(timeline);
2999
+ for (const [id, overrides] of active) {
3000
+ const track = tracks.get(id);
3001
+ if (!track?.follow || !overrides.some((o) => o.reroute)) continue;
3002
+ tracks.set(id, {
3003
+ ...track,
3004
+ follow: track.follow.map((f) => {
3005
+ let path = f.path;
3006
+ for (const o of overrides) path = o.reroute?.[path] ?? path;
3007
+ return { ...f, path };
3008
+ })
3009
+ });
3010
+ }
2214
3011
  const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
2215
3012
  const hidden = /* @__PURE__ */ new Set();
2216
3013
  for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
@@ -2230,6 +3027,14 @@ function posed(page, t, state = "initial") {
2230
3027
  let elements = page.elements.map((e) => {
2231
3028
  const track = tracks.get(e.id);
2232
3029
  let out = track ? applyTrack(track, e, t) : e;
3030
+ for (const o of active.get(e.id) ?? []) out = applyOverride(o, out);
3031
+ const list = active.get(e.id);
3032
+ if (list) {
3033
+ const caps = list.map((o) => o.capacity).filter((c) => c !== null && Number.isFinite(c));
3034
+ const delays = list.map((o) => o.delay).filter((d) => d !== null && Number.isFinite(d));
3035
+ if (caps.length > 0) out.capacity = Math.max(Math.min(...caps), 0);
3036
+ if (delays.length > 0 && out.kind !== "connector") out.delay = Math.max(Math.max(...delays), 0);
3037
+ }
2233
3038
  if (reseed !== null && reseed > 0 && boils(e)) {
2234
3039
  if (out === e) out = cloneElement(e);
2235
3040
  out.seed = boiled(e.seed, reseed);
@@ -2263,7 +3068,18 @@ function posed(page, t, state = "initial") {
2263
3068
  pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
2264
3069
  return pinned;
2265
3070
  });
2266
- return { ...page, elements: elements.filter((e) => !gone.has(e.id)), timeline: null };
3071
+ elements = elements.filter((e) => !gone.has(e.id));
3072
+ let metrics2 = emptyMetrics();
3073
+ if (measuring) {
3074
+ const offline = new Set([...active].filter(([, list]) => list.some((o) => o.offline === true)).map(([id]) => id));
3075
+ const frame2 = { ...page, elements, timeline: null };
3076
+ const out = flowFrame(page.flows, frame2, t, values, offline, since, carried);
3077
+ metrics2 = out.metrics;
3078
+ const finished = finishing(elements, out.metrics, values, frame2, page.flows ?? [], Math.max(0, Math.min(since ?? t, 1e6)), carried);
3079
+ Object.assign(metrics2.carry, finished.readings);
3080
+ elements = [...finished.elements, ...out.packets];
3081
+ }
3082
+ return { frame: { ...page, elements, timeline: null, flows: measuring ? null : page.flows }, metrics: metrics2 };
2267
3083
  }
2268
3084
  function posterTime(page) {
2269
3085
  return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
@@ -2378,7 +3194,7 @@ function filled(s, args) {
2378
3194
  }) };
2379
3195
  }
2380
3196
  function symbolPage(s) {
2381
- return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null };
3197
+ return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null, flows: null };
2382
3198
  }
2383
3199
  function hasInstances(page) {
2384
3200
  return page.elements.some((e) => e.kind === "symbol");
@@ -2485,8 +3301,8 @@ function mix2(x) {
2485
3301
  return z ^ z >> 31n;
2486
3302
  }
2487
3303
  function halves(id) {
2488
- const hex = id.replace(/-/g, "").padStart(32, "0").slice(-32);
2489
- return [BigInt(`0x${hex.slice(0, 16)}`), BigInt(`0x${hex.slice(16)}`)];
3304
+ const hex2 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
3305
+ return [BigInt(`0x${hex2.slice(0, 16)}`), BigInt(`0x${hex2.slice(16)}`)];
2490
3306
  }
2491
3307
  function derivedID(instance, inner) {
2492
3308
  const [a, b] = halves(instance), [c, d] = halves(inner);
@@ -2507,7 +3323,7 @@ function clickTargets(page, file) {
2507
3323
  let pageOf = null;
2508
3324
  for (const e of page.elements) {
2509
3325
  const c = e.click;
2510
- const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {} } : null;
3326
+ const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {}, ...c.reset ? { reset: true } : {} } : null;
2511
3327
  if (c?.scene) {
2512
3328
  const i = sceneNamed(c.scene, markers);
2513
3329
  if (i >= 0) {
@@ -2540,14 +3356,14 @@ function clickTargets(page, file) {
2540
3356
  function hoveredElement(e) {
2541
3357
  const h = e.hover;
2542
3358
  if (!h) return e;
2543
- const style = { ...e.style };
2544
- if (h.stroke) style.stroke = h.stroke;
2545
- if (h.fill) style.fill = h.fill;
3359
+ const style2 = { ...e.style };
3360
+ if (h.stroke) style2.stroke = h.stroke;
3361
+ if (h.fill) style2.fill = h.fill;
2546
3362
  if (h.opacity !== null && Number.isFinite(h.opacity)) {
2547
3363
  const o = Math.min(Math.max(h.opacity, 0), 1);
2548
- style.opacity = o >= 1 ? null : o;
3364
+ style2.opacity = o >= 1 ? null : o;
2549
3365
  }
2550
- return { ...e, style };
3366
+ return { ...e, style: style2 };
2551
3367
  }
2552
3368
  function hovered(page, ids) {
2553
3369
  return { ...page, elements: page.elements.map((e) => ids.has(e.id) ? hoveredElement(e) : e) };
@@ -2751,14 +3567,14 @@ function parseList(lines, start) {
2751
3567
  }
2752
3568
  return { block: { kind: "list", ordered, items }, next: i };
2753
3569
  }
2754
- function inline(src, style = {}) {
3570
+ function inline(src, style2 = {}) {
2755
3571
  const out = [];
2756
3572
  const push = (text, s) => {
2757
3573
  if (text) out.push({ ...s, text });
2758
3574
  };
2759
3575
  let i = 0, buf = "";
2760
3576
  const emit = () => {
2761
- push(buf, style);
3577
+ push(buf, style2);
2762
3578
  buf = "";
2763
3579
  };
2764
3580
  while (i < src.length) {
@@ -2786,43 +3602,43 @@ function inline(src, style = {}) {
2786
3602
  emit();
2787
3603
  let code = m[2].replace(/\n/g, " ");
2788
3604
  if (/^ .* $/.test(code)) code = code.slice(1, -1);
2789
- push(code, { ...style, mono: true });
3605
+ push(code, { ...style2, mono: true });
2790
3606
  i += m[0].length;
2791
3607
  continue;
2792
3608
  }
2793
3609
  if (m = /^!\[([^\]]*)\]\([^)]*\)/.exec(rest)) {
2794
3610
  emit();
2795
- out.push(...inline(m[1], style));
3611
+ out.push(...inline(m[1], style2));
2796
3612
  i += m[0].length;
2797
3613
  continue;
2798
3614
  }
2799
3615
  if (m = /^\[([^\]]+)\]\([^)]*\)/.exec(rest)) {
2800
3616
  emit();
2801
- out.push(...inline(m[1], { ...style, link: true }));
3617
+ out.push(...inline(m[1], { ...style2, link: true }));
2802
3618
  i += m[0].length;
2803
3619
  continue;
2804
3620
  }
2805
3621
  if (m = /^<((?:https?|ftp|mailto):[^>\s]+)>/.exec(rest)) {
2806
3622
  emit();
2807
- push(m[1], { ...style, link: true });
3623
+ push(m[1], { ...style2, link: true });
2808
3624
  i += m[0].length;
2809
3625
  continue;
2810
3626
  }
2811
3627
  if (m = /^~~(?=\S)([\s\S]*?\S)~~/.exec(rest)) {
2812
3628
  emit();
2813
- out.push(...inline(m[1], { ...style, strike: true }));
3629
+ out.push(...inline(m[1], { ...style2, strike: true }));
2814
3630
  i += m[0].length;
2815
3631
  continue;
2816
3632
  }
2817
3633
  if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
2818
3634
  emit();
2819
- out.push(...inline(m[2], { ...style, bold: true }));
3635
+ out.push(...inline(m[2], { ...style2, bold: true }));
2820
3636
  i += m[0].length;
2821
3637
  continue;
2822
3638
  }
2823
3639
  if ((m = /^(\*|_)(?=\S)([\s\S]*?\S)\1(?!\1)/.exec(rest)) && (m[1] === "*" || !/\w/.test(src[i - 1] ?? ""))) {
2824
3640
  emit();
2825
- out.push(...inline(m[2], { ...style, italic: true }));
3641
+ out.push(...inline(m[2], { ...style2, italic: true }));
2826
3642
  i += m[0].length;
2827
3643
  continue;
2828
3644
  }
@@ -3439,6 +4255,12 @@ var Playback = class {
3439
4255
  __publicField(this, "sceneLoops");
3440
4256
  /** The page's variables now: their starting values until clicks change them. */
3441
4257
  __publicField(this, "state");
4258
+ /** When the variables last changed (performance.now() milliseconds): backlog piles up from then (ADR 0003). */
4259
+ __publicField(this, "stateSince", typeof performance === "undefined" ? 0 : performance.now());
4260
+ /** A still (the `at` attribute): backlog is drawn for the page time, not the wall clock. */
4261
+ __publicField(this, "still", false);
4262
+ /** What each queue held when the variables last changed, which it drains from (FrameMetrics.carry). */
4263
+ __publicField(this, "carried", {});
3442
4264
  __publicField(this, "background");
3443
4265
  __publicField(this, "ink");
3444
4266
  /** What clicking each clickable element does, by element id. */
@@ -3510,6 +4332,10 @@ var Playback = class {
3510
4332
  this.svg.style.maxWidth = `${Math.ceil(this.plan.size.width)}px`;
3511
4333
  into.replaceChildren(this.svg);
3512
4334
  }
4335
+ /** Seconds since the variables last changed, or null for a still. */
4336
+ sinceChange(now = typeof performance === "undefined" ? 0 : performance.now()) {
4337
+ return this.still || typeof performance === "undefined" ? null : (now - this.stateSince) / 1e3;
4338
+ }
3513
4339
  get frameCount() {
3514
4340
  return this.plan.times.length;
3515
4341
  }
@@ -3550,7 +4376,7 @@ var Playback = class {
3550
4376
  const times = /* @__PURE__ */ new Map();
3551
4377
  for (const [id, start] of this.replayStarts) times.set(id, Math.min((now - start) / 1e3, this.replays.get(id) ?? 0));
3552
4378
  const tc = clampTime(t);
3553
- const pose = posed(this.page, tc, this.state);
4379
+ const pose = posed(this.page, tc, this.state, this.sinceChange(now), this.carried);
3554
4380
  const frame2 = expandingSymbols(pose, tc, true, times);
3555
4381
  const { parts } = svgParts(frame2, this.background, r);
3556
4382
  const owned = (m, parts2) => {
@@ -3807,6 +4633,7 @@ var DottiePlayer = class extends ElementBase {
3807
4633
  const file = this.file;
3808
4634
  const page = file.pages[pageIndex(file, this.getAttribute("page"))];
3809
4635
  const p = new Playback(page, this.stage, { clean: this.hasAttribute("clean"), file });
4636
+ p.still = this.hasAttribute("at");
3810
4637
  this.playback = p;
3811
4638
  this.style.setProperty("--dottie-page", color(p.background));
3812
4639
  this.style.setProperty("--dottie-ink", color(p.ink));
@@ -3900,7 +4727,13 @@ var DottiePlayer = class extends ElementBase {
3900
4727
  this.scheduleReplay();
3901
4728
  }
3902
4729
  if (!click) return;
3903
- if (click.set || click.toggle) this.setState(applyClick({ scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null }, p.state));
4730
+ if (click.set || click.toggle || click.reset) {
4731
+ this.setState(applyClick(
4732
+ { scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null, reset: click.reset ?? null },
4733
+ p.state,
4734
+ p.page.variables ?? {}
4735
+ ));
4736
+ }
3904
4737
  if ("state" in click) return;
3905
4738
  if ("url" in click) {
3906
4739
  if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
@@ -3926,7 +4759,10 @@ var DottiePlayer = class extends ElementBase {
3926
4759
  if (!p) return;
3927
4760
  const next = { ...p.state, ...values };
3928
4761
  if (Object.keys(next).every((k) => next[k] === p.state[k])) return;
4762
+ const t = clampTime(p.last.t);
4763
+ p.carried = posedWithMetrics(p.page, t, p.state, p.sinceChange(), p.carried).metrics.carry;
3929
4764
  p.state = next;
4765
+ p.stateSince = performance.now();
3930
4766
  p.redraw();
3931
4767
  this.dispatchEvent(new CustomEvent("state", { detail: { variables: { ...next } } }));
3932
4768
  }
@@ -4124,7 +4960,7 @@ function render(source, options = {}) {
4124
4960
  function isFile(v) {
4125
4961
  return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
4126
4962
  }
4127
- var VERSION = "0.7.0";
4963
+ var VERSION = "0.8.0";
4128
4964
 
4129
4965
  // src/player-entry.ts
4130
4966
  definePlayer();