@phin-tech/dottie 0.7.0 → 0.9.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/PARITY.md CHANGED
@@ -27,7 +27,7 @@ fixture, so the gap is visible rather than silent.
27
27
  | Waypoints (bends) on every routing, and hops | Supported | [edge/waypoints](../fixtures/edge/waypoints.dottie), [edge/waypoints-symbol](../fixtures/edge/waypoints-symbol.dottie) | Hops need the page, so `connectorParts` takes it; without one a line draws none. |
28
28
  | Arrowheads: arrow, triangle, dot, bar | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | |
29
29
  | Label colors (`textColor`, apart from the stroke) | Supported | [edge/text-colors](../fixtures/edge/text-colors.dottie) | Text boxes and notes ignore it, as in Dottie |
30
- | Connector labels | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | Label boxes sized with CoreText's measurements. The player paints them on the page color, like the app; Dottie's flipbook export leaves them unpainted |
30
+ | Connector labels | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | Label boxes sized with CoreText's measurements. The player paints them on the page color, like the app |
31
31
  | Keys and eases (linear, in, out, in-out, hold, spring, bounce, elastic) | Supported | [edge/timeline](../fixtures/edge/timeline.dottie), [edge/eases](../fixtures/edge/eases.dottie) | Unknown ease names play linear, with a warning |
32
32
  | Visibility, reveal (draw-on, typing), opacity | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | |
33
33
  | Transitions | Supported | [edge/transitions](../fixtures/edge/transitions.dottie) | They are ordinary keys in the file |
@@ -37,19 +37,22 @@ fixture, so the gap is visible rather than silent.
37
37
  | Repeats (counted and forever) | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | |
38
38
  | Boil (three drawings) and settle | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | Boiled seeds match bit for bit |
39
39
  | Camera views | Supported | [edge/camera](../fixtures/edge/camera.dottie), [design/zoom](../fixtures/design/zoom.dottie) | |
40
- | Scenes (markers) and click-through | Supported | [design/tour](../fixtures/design/tour.dottie) | Stops at each scene, as the HTML export does |
40
+ | Scenes (markers) and click-through | Supported | [design/tour](../fixtures/design/tour.dottie) | Stops at each scene. The HTML export is this player |
41
41
  | Poster frame, reduced motion | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | |
42
42
  | Damaged timelines (clamped, filtered, sorted) | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | Read the way DottieKit's decoders read them |
43
43
  | Unknown kinds, fields and newer versions | Partial | [js-only/future](../fixtures/js-only/future.dottie) | Drawn as labelled placeholders, reported in `warnings`; Dottie refuses such files |
44
44
  | Text fonts | Partial | [edge/fonts](../fixtures/edge/fonts.dottie) | Measured with CoreText's metrics for Helvetica Neue and the bundled handwriting fonts (Caveat, Patrick Hand, Architects Daughter), which the player loads from `dist/fonts` and a compiled drawing carries; browsers without Helvetica Neue draw Helvetica or Arial in the same places, so runs of mixed styles may sit slightly apart. A font of one's own is laid out as Helvetica Neue and drawn in that font where the viewer has it |
45
45
  | Symbols: instances on their own clocks (loop, once, still, offset), nested, scaled and turned | Supported | [edge/symbols](../fixtures/edge/symbols.dottie) | Expanded as DottieKit expands them, with the same ids, seeds and limits ([js-only/hostile-symbols](../fixtures/js-only/hostile-symbols.dottie)) |
46
- | Button symbols (up, over, down, hit) and clickable copies | Supported | [edge/buttons](../fixtures/edge/buttons.dottie) | As the HTML export draws them |
46
+ | Button symbols (up, over, down, hit) and clickable copies | Supported | [edge/buttons](../fixtures/edge/buttons.dottie) | Up, over, down and hit, in the player and the HTML export alike |
47
47
  | Symbol parameters (`{{name}}` in a symbol's text, filled per copy) | Supported | [edge/params](../fixtures/edge/params.dottie) | Defaults, undeclared names left as written, values passed into nested copies, filled before text types out |
48
- | Copies that play when clicked | Supported | [edge/buttons](../fixtures/edge/buttons.dottie) | `<dottie-player>` only: the HTML export is drawn ahead of time and shows them at rest |
49
- | Clicks: play a scene, open a link, hover looks, keyboard | Supported | [edge/clicks](../fixtures/edge/clicks.dottie) | As the HTML export does; only http, https and mailto links are followed |
48
+ | Copies that play when clicked | Supported | [edge/buttons](../fixtures/edge/buttons.dottie) | In the player and the HTML export |
49
+ | Clicks: play a scene, open a link, hover looks, keyboard | Supported | [edge/clicks](../fixtures/edge/clicks.dottie) | Only http, https and mailto links are followed |
50
50
  | Page variables and conditions (`variables`, `when`) | Supported | [edge/state](../fixtures/edge/state.dottie) | Starting values in every still and export; connectors go with a shape the state hides |
51
- | Clicks that set and toggle variables | Supported | [edge/state](../fixtures/edge/state.dottie) | Change what shows without touching the playhead. The HTML export of a page with state is this player |
52
- | Looping scenes (`loop` on a marker) | Supported | [edge/state](../fixtures/edge/state.dottie) | As the HTML export plays them: no stop on entering, round from the scene's end to its start |
51
+ | Overrides (`overrides`: restyle and reroute under a condition) | Supported | [edge/state](../fixtures/edge/state.dottie) | Opacity multiplies; a reroute or key that isn't an id is dropped as DottieKit drops it |
52
+ | Traffic flows (`flows`) | Supported | [edge/flows](../fixtures/edge/flows.dottie) | Routed each frame over the connectors drawn, cheapest by connector `cost` (ADR 0002); packet ids derive from the flow's |
53
+ | Volume, capacity, delay, metrics, gauges | Supported | [edge/flows](../fixtures/edge/flows.dottie) (Capacity page) | One implementation: Dottie runs `measure.ts` in JavaScriptCore (ADRs 0003, 0004). Assignment in page order, backlog piles, conditions on metrics, gauges expanded to `.path` elements |
54
+ | Clicks that set and toggle variables | Supported | [edge/state](../fixtures/edge/state.dottie) | Change what shows without touching the playhead |
55
+ | Looping scenes (`loop` on a marker) | Supported | [edge/state](../fixtures/edge/state.dottie) | No stop on entering, round from the scene's end to its start |
53
56
  | Links out of the drawing (`url`) | Supported | [edge/links](../fixtures/edge/links.dottie) | A web or mail `url` opens when clicked if there is no `click`, and SVG export wraps it in `<a>`; a link to another drawing does nothing on the web |
54
57
  | Links between shapes and pages | Partial | [edge/clicks](../fixtures/edge/clicks.dottie) | A link to a shape on another page shows that page; one on the same page does nothing. The HTML export holds one page, so it has neither |
55
58
  | Editing | Missing | | |
package/README.md CHANGED
@@ -37,8 +37,9 @@ click first fires a cancelable `action` event whose `detail` is `{element, scene
37
37
  replay?}`; call `preventDefault()` to handle it yourself.
38
38
 
39
39
  A click can also set or toggle the page's variables, which show and hide the elements with a `when` while the
40
- animation keeps playing: a diagram you leave running that people poke. A looping scene plays round until a click
41
- or next leaves it, instead of stopping.
40
+ animation keeps playing: a diagram you leave running that people poke. Elements can restyle themselves under a
41
+ condition (`overrides`), and a page's `flows` send traffic over whatever connectors are drawn, so it goes round a
42
+ region a click took down. A looping scene plays round until a click or next leaves it, instead of stopping.
42
43
 
43
44
  The element has `play()`, `pause()`, `nextScene()`, `prevScene()`, `activate(elementId)`, `currentTime`
44
45
  (settable), `duration`, `paused`, `variables` and `setState({name: value})`, and fires `load`, `frame`, `action`,
@@ -48,6 +49,13 @@ The element has `play()`, `pause()`, `nextScene()`, `prevScene()`, `activate(ele
48
49
  Without modules, `dist/dottie-player.min.js` is a classic script that registers the element and sets
49
50
  `window.Dottie`.
50
51
 
52
+ **Big animations.** Before it shows an animation without a camera, the player works out the view of every frame,
53
+ which for a long, busy one takes long enough to hold up the page. So it does that in a worker, and the drawing
54
+ appears, exactly as it would have, when the worker is done; `setFile()` returns at once, and the `load` event fires
55
+ when it is showing. The classic script starts the worker from its own source (a `blob:` URL); the module player
56
+ loads `dottie-player.min.js` from beside itself, so host the two together. Where a worker can't start, as under a
57
+ `worker-src` policy that refuses it, the player works it out in place as before. Small drawings always do.
58
+
51
59
  **Fonts.** Text in Dottie's handwriting fonts (Caveat, Patrick Hand, Architects Daughter; SIL Open Font License)
52
60
  is drawn in them: the player loads the ones a drawing uses from `fonts/` beside its own script, as `dist/fonts` on
53
61
  npm and the CDNs. If you host the player yourself, copy that folder with it, or call `Dottie.setFontBase(url)`
package/dist/compile.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.9.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -56,7 +56,9 @@ var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand",
56
56
  var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
57
57
  var KNOWN_FIELDS = {
58
58
  file: ["version", "pages", "images"],
59
- page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables"],
59
+ page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
60
+ flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
61
+ flowOverride: ["when", "volume"],
60
62
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
61
63
  layer: ["id", "name", "visible", "locked"],
62
64
  rgba: ["r", "g", "b", "a"],
@@ -85,6 +87,7 @@ var KNOWN_FIELDS = {
85
87
  "click",
86
88
  "hover",
87
89
  "when",
90
+ "overrides",
88
91
  "symbol",
89
92
  "playback",
90
93
  "offset",
@@ -93,8 +96,13 @@ var KNOWN_FIELDS = {
93
96
  "reveal",
94
97
  "ports",
95
98
  "labelRect",
96
- "extension"
99
+ "extension",
100
+ "capacity",
101
+ "delay",
102
+ "queue",
103
+ "gauge"
97
104
  ],
105
+ gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
98
106
  strokePoint: ["x", "y"],
99
107
  subpath: ["closed", "coords"],
100
108
  inlineImage: ["data", "format"],
@@ -102,7 +110,7 @@ var KNOWN_FIELDS = {
102
110
  extensionRef: ["id", "shape", "version", "params"],
103
111
  normalizedPoint: ["x", "y"],
104
112
  normalizedRect: ["x", "y", "width", "height"],
105
- connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
113
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
106
114
  waypoint: ["x", "y"],
107
115
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
108
116
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -111,8 +119,9 @@ var KNOWN_FIELDS = {
111
119
  repeat: ["start", "end", "count"],
112
120
  cameraKey: ["t", "x", "y", "width", "height", "ease"],
113
121
  marker: ["t", "name", "loop"],
114
- click: ["scene", "url", "set", "toggle"],
115
- hover: ["stroke", "fill", "opacity"]
122
+ click: ["scene", "url", "set", "toggle", "reset"],
123
+ hover: ["stroke", "fill", "opacity"],
124
+ override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
116
125
  };
117
126
  var MAX_DURATION = 3600;
118
127
  var MAX_MARCH = 1e4;
@@ -126,6 +135,8 @@ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
126
135
  var str = (v) => typeof v === "string" ? v : void 0;
127
136
  var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
128
137
  var bool = (v) => typeof v === "boolean" ? v : void 0;
138
+ var NO_ID = "00000000-0000-0000-0000-000000000000";
139
+ 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}$/;
129
140
  var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
130
141
  function clampTime(t) {
131
142
  if (!Number.isFinite(t)) return t === Infinity ? MAX_DURATION : 0;
@@ -214,7 +225,8 @@ var Reader = class {
214
225
  this.fields(w, "waypoint");
215
226
  return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
216
227
  }) : null,
217
- hops: typeof v.hops === "boolean" ? v.hops : null
228
+ hops: typeof v.hops === "boolean" ? v.hops : null,
229
+ cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
218
230
  };
219
231
  }
220
232
  image(v, table) {
@@ -266,6 +278,7 @@ var Reader = class {
266
278
  click: this.click(v.click),
267
279
  hover: this.hover(v.hover),
268
280
  when: str(v.when) ?? null,
281
+ overrides: this.overrides(v.overrides),
269
282
  symbol: uuid(v.symbol) ?? null,
270
283
  playback: this.playback(v.playback),
271
284
  offset: num(v.offset) ?? null,
@@ -277,7 +290,26 @@ var Reader = class {
277
290
  return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
278
291
  }) : null,
279
292
  labelRect: this.labelRect(v.labelRect),
280
- extension: this.extension(v.extension)
293
+ extension: this.extension(v.extension),
294
+ capacity: num(v.capacity) ?? null,
295
+ delay: num(v.delay) ?? null,
296
+ queue: v.queue === true ? true : null,
297
+ gauge: this.gauge(v.gauge)
298
+ };
299
+ }
300
+ gauge(v) {
301
+ if (!isObj(v)) return null;
302
+ this.fields(v, "gauge");
303
+ const finite = (x) => typeof x === "number" && Number.isFinite(x) ? x : null;
304
+ const style = str(v.style);
305
+ return {
306
+ metric: str(v.metric) ?? "",
307
+ of: typeof v.of === "string" && UUID.test(v.of) ? v.of.toUpperCase() : NO_ID,
308
+ max: finite(v.max),
309
+ warn: finite(v.warn),
310
+ alarm: finite(v.alarm),
311
+ style: style === "number" || style === "bar" || style === "dial" ? style : null,
312
+ label: str(v.label) ?? null
281
313
  };
282
314
  }
283
315
  labelRect(v) {
@@ -321,7 +353,7 @@ var Reader = class {
321
353
  }
322
354
  const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
323
355
  const toggle = raw ? raw.filter(isName) : null;
324
- return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
356
+ return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null, reset: v.reset === true ? true : null };
325
357
  }
326
358
  playback(v) {
327
359
  const s = str(v);
@@ -357,6 +389,35 @@ var Reader = class {
357
389
  params: strings(v.params)
358
390
  };
359
391
  }
392
+ /** As DottieKit reads them: a reroute whose targets aren't all ids is dropped, as are keys that aren't ids. */
393
+ overrides(v) {
394
+ if (!Array.isArray(v)) return null;
395
+ return v.filter(isObj).map((o) => {
396
+ this.fields(o, "override");
397
+ const dash = str(o.dash);
398
+ let reroute = null;
399
+ if (isObj(o.reroute) && Object.values(o.reroute).every((x) => typeof x === "string" && UUID.test(x))) {
400
+ reroute = {};
401
+ for (const [k, x] of Object.entries(o.reroute)) if (UUID.test(k)) reroute[k.toUpperCase()] = x.toUpperCase();
402
+ if (Object.keys(reroute).length === 0) reroute = null;
403
+ }
404
+ return {
405
+ when: str(o.when) ?? "",
406
+ stroke: rgba(o.stroke) ?? null,
407
+ fill: rgba(o.fill) ?? null,
408
+ textColor: rgba(o.textColor) ?? null,
409
+ opacity: num(o.opacity) ?? null,
410
+ strokeWidth: num(o.strokeWidth) ?? null,
411
+ dash: dash !== void 0 && ["solid", "dashed", "dotted"].includes(dash) ? dash : null,
412
+ text: str(o.text) ?? null,
413
+ reroute,
414
+ offline: o.offline === true ? true : null,
415
+ cost: typeof o.cost === "number" && Number.isFinite(o.cost) ? o.cost : null,
416
+ capacity: typeof o.capacity === "number" && Number.isFinite(o.capacity) ? o.capacity : null,
417
+ delay: typeof o.delay === "number" && Number.isFinite(o.delay) ? o.delay : null
418
+ };
419
+ });
420
+ }
360
421
  hover(v) {
361
422
  if (!isObj(v)) return null;
362
423
  this.fields(v, "hover");
@@ -456,7 +517,33 @@ var Reader = class {
456
517
  theme: str(v.theme) ?? null,
457
518
  timeline: this.timeline(v.timeline),
458
519
  symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
459
- variables: this.variables(v.variables)
520
+ variables: this.variables(v.variables),
521
+ flows: Array.isArray(v.flows) ? v.flows.filter(isObj).map((f) => this.flow(f)) : null
522
+ };
523
+ }
524
+ /** As DottieKit reads one: a missing or bad id, source or destination sends nothing; bad destinations are dropped. */
525
+ flow(f) {
526
+ this.fields(f, "flow");
527
+ const id = (x) => typeof x === "string" && UUID.test(x) ? x.toUpperCase() : NO_ID;
528
+ const finite = (x) => {
529
+ const n = num(x);
530
+ return n !== void 0 && Number.isFinite(n) ? n : null;
531
+ };
532
+ return {
533
+ id: id(f.id),
534
+ from: id(f.from),
535
+ to: Array.isArray(f.to) && f.to.every((x) => typeof x === "string") ? f.to.filter((x) => UUID.test(x)).map((x) => x.toUpperCase()) : [],
536
+ every: finite(f.every),
537
+ speed: finite(f.speed),
538
+ size: finite(f.size),
539
+ color: isObj(f.color) && ["r", "g", "b", "a"].every((k) => typeof f.color[k] === "number") ? rgba(f.color) ?? null : null,
540
+ when: str(f.when) ?? null,
541
+ spread: f.spread === true ? true : null,
542
+ volume: finite(f.volume),
543
+ overrides: Array.isArray(f.overrides) && f.overrides.length > 0 ? f.overrides.filter(isObj).map((o) => {
544
+ this.fields(o, "flowOverride");
545
+ return { when: str(o.when) ?? "", volume: finite(o.volume) };
546
+ }) : null
460
547
  };
461
548
  }
462
549
  };
@@ -599,7 +686,12 @@ function compile(source, runtime, options = {}) {
599
686
  if (b64) (fonts[family] ?? (fonts[family] = {}))[face] = b64;
600
687
  }
601
688
  }
602
- return runtime.trimEnd() + "\n" + LOADER.replace("DOTTIE_DATA", () => scriptJSON(data)).replace("DOTTIE_OPTIONS", () => scriptJSON(playerAttributes(options))).replace("DOTTIE_FONTS", () => scriptJSON(fonts));
689
+ const fill = {
690
+ DOTTIE_DATA: () => scriptJSON(data),
691
+ DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(options)),
692
+ DOTTIE_FONTS: () => scriptJSON(fonts)
693
+ };
694
+ return runtime.trimEnd() + "\n" + LOADER.replace(/\bDOTTIE_(?:DATA|OPTIONS|FONTS)\b/g, (m) => fill[m]());
603
695
  }
604
696
  export {
605
697
  LOADER,
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /*! dottie.js 0.7.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
2
+ /*! dottie.js 0.9.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
3
3
  var __defProp = Object.defineProperty;
4
4
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
5
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -64,7 +64,9 @@ var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand",
64
64
  var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
65
65
  var KNOWN_FIELDS = {
66
66
  file: ["version", "pages", "images"],
67
- page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables"],
67
+ page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
68
+ flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
69
+ flowOverride: ["when", "volume"],
68
70
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
69
71
  layer: ["id", "name", "visible", "locked"],
70
72
  rgba: ["r", "g", "b", "a"],
@@ -93,6 +95,7 @@ var KNOWN_FIELDS = {
93
95
  "click",
94
96
  "hover",
95
97
  "when",
98
+ "overrides",
96
99
  "symbol",
97
100
  "playback",
98
101
  "offset",
@@ -101,8 +104,13 @@ var KNOWN_FIELDS = {
101
104
  "reveal",
102
105
  "ports",
103
106
  "labelRect",
104
- "extension"
107
+ "extension",
108
+ "capacity",
109
+ "delay",
110
+ "queue",
111
+ "gauge"
105
112
  ],
113
+ gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
106
114
  strokePoint: ["x", "y"],
107
115
  subpath: ["closed", "coords"],
108
116
  inlineImage: ["data", "format"],
@@ -110,7 +118,7 @@ var KNOWN_FIELDS = {
110
118
  extensionRef: ["id", "shape", "version", "params"],
111
119
  normalizedPoint: ["x", "y"],
112
120
  normalizedRect: ["x", "y", "width", "height"],
113
- connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
121
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
114
122
  waypoint: ["x", "y"],
115
123
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
116
124
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -119,8 +127,9 @@ var KNOWN_FIELDS = {
119
127
  repeat: ["start", "end", "count"],
120
128
  cameraKey: ["t", "x", "y", "width", "height", "ease"],
121
129
  marker: ["t", "name", "loop"],
122
- click: ["scene", "url", "set", "toggle"],
123
- hover: ["stroke", "fill", "opacity"]
130
+ click: ["scene", "url", "set", "toggle", "reset"],
131
+ hover: ["stroke", "fill", "opacity"],
132
+ override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
124
133
  };
125
134
  var MAX_DURATION = 3600;
126
135
  var MAX_MARCH = 1e4;
@@ -134,6 +143,8 @@ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
134
143
  var str = (v) => typeof v === "string" ? v : void 0;
135
144
  var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
136
145
  var bool = (v) => typeof v === "boolean" ? v : void 0;
146
+ var NO_ID = "00000000-0000-0000-0000-000000000000";
147
+ 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}$/;
137
148
  var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
138
149
  function clampTime(t) {
139
150
  if (!Number.isFinite(t)) return t === Infinity ? MAX_DURATION : 0;
@@ -222,7 +233,8 @@ var Reader = class {
222
233
  this.fields(w, "waypoint");
223
234
  return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
224
235
  }) : null,
225
- hops: typeof v.hops === "boolean" ? v.hops : null
236
+ hops: typeof v.hops === "boolean" ? v.hops : null,
237
+ cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
226
238
  };
227
239
  }
228
240
  image(v, table) {
@@ -274,6 +286,7 @@ var Reader = class {
274
286
  click: this.click(v.click),
275
287
  hover: this.hover(v.hover),
276
288
  when: str(v.when) ?? null,
289
+ overrides: this.overrides(v.overrides),
277
290
  symbol: uuid(v.symbol) ?? null,
278
291
  playback: this.playback(v.playback),
279
292
  offset: num(v.offset) ?? null,
@@ -285,7 +298,26 @@ var Reader = class {
285
298
  return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
286
299
  }) : null,
287
300
  labelRect: this.labelRect(v.labelRect),
288
- extension: this.extension(v.extension)
301
+ extension: this.extension(v.extension),
302
+ capacity: num(v.capacity) ?? null,
303
+ delay: num(v.delay) ?? null,
304
+ queue: v.queue === true ? true : null,
305
+ gauge: this.gauge(v.gauge)
306
+ };
307
+ }
308
+ gauge(v) {
309
+ if (!isObj(v)) return null;
310
+ this.fields(v, "gauge");
311
+ const finite = (x) => typeof x === "number" && Number.isFinite(x) ? x : null;
312
+ const style = str(v.style);
313
+ return {
314
+ metric: str(v.metric) ?? "",
315
+ of: typeof v.of === "string" && UUID.test(v.of) ? v.of.toUpperCase() : NO_ID,
316
+ max: finite(v.max),
317
+ warn: finite(v.warn),
318
+ alarm: finite(v.alarm),
319
+ style: style === "number" || style === "bar" || style === "dial" ? style : null,
320
+ label: str(v.label) ?? null
289
321
  };
290
322
  }
291
323
  labelRect(v) {
@@ -329,7 +361,7 @@ var Reader = class {
329
361
  }
330
362
  const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
331
363
  const toggle = raw ? raw.filter(isName) : null;
332
- return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
364
+ return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null, reset: v.reset === true ? true : null };
333
365
  }
334
366
  playback(v) {
335
367
  const s = str(v);
@@ -365,6 +397,35 @@ var Reader = class {
365
397
  params: strings(v.params)
366
398
  };
367
399
  }
400
+ /** As DottieKit reads them: a reroute whose targets aren't all ids is dropped, as are keys that aren't ids. */
401
+ overrides(v) {
402
+ if (!Array.isArray(v)) return null;
403
+ return v.filter(isObj).map((o) => {
404
+ this.fields(o, "override");
405
+ const dash = str(o.dash);
406
+ let reroute = null;
407
+ if (isObj(o.reroute) && Object.values(o.reroute).every((x) => typeof x === "string" && UUID.test(x))) {
408
+ reroute = {};
409
+ for (const [k, x] of Object.entries(o.reroute)) if (UUID.test(k)) reroute[k.toUpperCase()] = x.toUpperCase();
410
+ if (Object.keys(reroute).length === 0) reroute = null;
411
+ }
412
+ return {
413
+ when: str(o.when) ?? "",
414
+ stroke: rgba(o.stroke) ?? null,
415
+ fill: rgba(o.fill) ?? null,
416
+ textColor: rgba(o.textColor) ?? null,
417
+ opacity: num(o.opacity) ?? null,
418
+ strokeWidth: num(o.strokeWidth) ?? null,
419
+ dash: dash !== void 0 && ["solid", "dashed", "dotted"].includes(dash) ? dash : null,
420
+ text: str(o.text) ?? null,
421
+ reroute,
422
+ offline: o.offline === true ? true : null,
423
+ cost: typeof o.cost === "number" && Number.isFinite(o.cost) ? o.cost : null,
424
+ capacity: typeof o.capacity === "number" && Number.isFinite(o.capacity) ? o.capacity : null,
425
+ delay: typeof o.delay === "number" && Number.isFinite(o.delay) ? o.delay : null
426
+ };
427
+ });
428
+ }
368
429
  hover(v) {
369
430
  if (!isObj(v)) return null;
370
431
  this.fields(v, "hover");
@@ -464,7 +525,33 @@ var Reader = class {
464
525
  theme: str(v.theme) ?? null,
465
526
  timeline: this.timeline(v.timeline),
466
527
  symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
467
- variables: this.variables(v.variables)
528
+ variables: this.variables(v.variables),
529
+ flows: Array.isArray(v.flows) ? v.flows.filter(isObj).map((f) => this.flow(f)) : null
530
+ };
531
+ }
532
+ /** As DottieKit reads one: a missing or bad id, source or destination sends nothing; bad destinations are dropped. */
533
+ flow(f) {
534
+ this.fields(f, "flow");
535
+ const id = (x) => typeof x === "string" && UUID.test(x) ? x.toUpperCase() : NO_ID;
536
+ const finite = (x) => {
537
+ const n = num(x);
538
+ return n !== void 0 && Number.isFinite(n) ? n : null;
539
+ };
540
+ return {
541
+ id: id(f.id),
542
+ from: id(f.from),
543
+ to: Array.isArray(f.to) && f.to.every((x) => typeof x === "string") ? f.to.filter((x) => UUID.test(x)).map((x) => x.toUpperCase()) : [],
544
+ every: finite(f.every),
545
+ speed: finite(f.speed),
546
+ size: finite(f.size),
547
+ color: isObj(f.color) && ["r", "g", "b", "a"].every((k) => typeof f.color[k] === "number") ? rgba(f.color) ?? null : null,
548
+ when: str(f.when) ?? null,
549
+ spread: f.spread === true ? true : null,
550
+ volume: finite(f.volume),
551
+ overrides: Array.isArray(f.overrides) && f.overrides.length > 0 ? f.overrides.filter(isObj).map((o) => {
552
+ this.fields(o, "flowOverride");
553
+ return { when: str(o.when) ?? "", volume: finite(o.volume) };
554
+ }) : null
468
555
  };
469
556
  }
470
557
  };
@@ -607,11 +694,16 @@ function compile(source2, runtime, options2 = {}) {
607
694
  if (b64) (fonts[family] ?? (fonts[family] = {}))[face] = b64;
608
695
  }
609
696
  }
610
- return runtime.trimEnd() + "\n" + LOADER.replace("DOTTIE_DATA", () => scriptJSON(data)).replace("DOTTIE_OPTIONS", () => scriptJSON(playerAttributes(options2))).replace("DOTTIE_FONTS", () => scriptJSON(fonts));
697
+ const fill = {
698
+ DOTTIE_DATA: () => scriptJSON(data),
699
+ DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(options2)),
700
+ DOTTIE_FONTS: () => scriptJSON(fonts)
701
+ };
702
+ return runtime.trimEnd() + "\n" + LOADER.replace(/\bDOTTIE_(?:DATA|OPTIONS|FONTS)\b/g, (m) => fill[m]());
611
703
  }
612
704
 
613
705
  // src/index.ts
614
- var VERSION = "0.7.0";
706
+ var VERSION = "0.9.0";
615
707
 
616
708
  // src/cli.ts
617
709
  var usage = `dottie-compile ${VERSION}: a Dottie drawing as one script that plays it on any page.