@phin-tech/dottie 0.6.3 → 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/PARITY.md CHANGED
@@ -47,6 +47,12 @@ fixture, so the gap is visible rather than silent.
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
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
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 |
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
+ | 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) | `measure.ts` mirrors `Metrics.swift` (ADR 0003): 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. The HTML export of a page with state is this player |
55
+ | 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 |
50
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 |
51
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 |
52
58
  | Editing | Missing | | |
package/README.md CHANGED
@@ -33,11 +33,17 @@ Elements the drawing makes clickable work with the mouse and the keyboard (Tab,
33
33
  a named scene on to the next one, opens a link in a new tab (only `http`, `https` and `mailto`), or shows the page a
34
34
  shape's link points at. Each shows its hover look under the pointer; button symbols show their over and down looks
35
35
  and take clicks on their hit area, and a symbol's copy set to play when clicked plays its animation through. Every
36
- click first fires a cancelable `action` event whose `detail` is `{element, scene | url | page, replay?}`; call
37
- `preventDefault()` to handle it yourself.
36
+ click first fires a cancelable `action` event whose `detail` is `{element, scene | url | page | state, set?, toggle?,
37
+ replay?}`; call `preventDefault()` to handle it yourself.
38
+
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. 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.
38
43
 
39
44
  The element has `play()`, `pause()`, `nextScene()`, `prevScene()`, `activate(elementId)`, `currentTime`
40
- (settable), `duration`, `paused`, and fires `load`, `frame`, `action` and `error` events. Its page color comes from the drawing's theme; set
45
+ (settable), `duration`, `paused`, `variables` and `setState({name: value})`, and fires `load`, `frame`, `action`,
46
+ `state` (with `{variables}`, after a change) and `error` events. Its page color comes from the drawing's theme; set
41
47
  `--dottie-radius` to round it, and style its parts with `::part(stage)` and `::part(controls)`.
42
48
 
43
49
  Without modules, `dist/dottie-player.min.js` is a classic script that registers the element and sets
package/dist/compile.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! dottie.js 0.6.3 | 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, 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);
@@ -44,13 +44,21 @@ function defaultBase() {
44
44
  }
45
45
  var base = defaultBase();
46
46
 
47
+ // src/state.ts
48
+ var MAX_NAME = 64;
49
+ function isName(name) {
50
+ return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
51
+ }
52
+
47
53
  // src/model.ts
48
54
  var SUPPORTED_VERSION = 2;
49
55
  var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
50
56
  var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
51
57
  var KNOWN_FIELDS = {
52
58
  file: ["version", "pages", "images"],
53
- page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols"],
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"],
54
62
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
55
63
  layer: ["id", "name", "visible", "locked"],
56
64
  rgba: ["r", "g", "b", "a"],
@@ -78,6 +86,8 @@ var KNOWN_FIELDS = {
78
86
  "anchor",
79
87
  "click",
80
88
  "hover",
89
+ "when",
90
+ "overrides",
81
91
  "symbol",
82
92
  "playback",
83
93
  "offset",
@@ -86,8 +96,13 @@ var KNOWN_FIELDS = {
86
96
  "reveal",
87
97
  "ports",
88
98
  "labelRect",
89
- "extension"
99
+ "extension",
100
+ "capacity",
101
+ "delay",
102
+ "queue",
103
+ "gauge"
90
104
  ],
105
+ gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
91
106
  strokePoint: ["x", "y"],
92
107
  subpath: ["closed", "coords"],
93
108
  inlineImage: ["data", "format"],
@@ -95,7 +110,7 @@ var KNOWN_FIELDS = {
95
110
  extensionRef: ["id", "shape", "version", "params"],
96
111
  normalizedPoint: ["x", "y"],
97
112
  normalizedRect: ["x", "y", "width", "height"],
98
- connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
113
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
99
114
  waypoint: ["x", "y"],
100
115
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
101
116
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -103,9 +118,10 @@ var KNOWN_FIELDS = {
103
118
  follow: ["path", "start", "end", "ease", "reverse", "orient", "laps"],
104
119
  repeat: ["start", "end", "count"],
105
120
  cameraKey: ["t", "x", "y", "width", "height", "ease"],
106
- marker: ["t", "name"],
107
- click: ["scene", "url"],
108
- hover: ["stroke", "fill", "opacity"]
121
+ marker: ["t", "name", "loop"],
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"]
109
125
  };
110
126
  var MAX_DURATION = 3600;
111
127
  var MAX_MARCH = 1e4;
@@ -119,6 +135,8 @@ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
119
135
  var str = (v) => typeof v === "string" ? v : void 0;
120
136
  var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
121
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}$/;
122
140
  var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
123
141
  function clampTime(t) {
124
142
  if (!Number.isFinite(t)) return t === Infinity ? MAX_DURATION : 0;
@@ -207,7 +225,8 @@ var Reader = class {
207
225
  this.fields(w, "waypoint");
208
226
  return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
209
227
  }) : null,
210
- 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
211
230
  };
212
231
  }
213
232
  image(v, table) {
@@ -258,6 +277,8 @@ var Reader = class {
258
277
  anchor: str(v.anchor) ?? null,
259
278
  click: this.click(v.click),
260
279
  hover: this.hover(v.hover),
280
+ when: str(v.when) ?? null,
281
+ overrides: this.overrides(v.overrides),
261
282
  symbol: uuid(v.symbol) ?? null,
262
283
  playback: this.playback(v.playback),
263
284
  offset: num(v.offset) ?? null,
@@ -269,7 +290,26 @@ var Reader = class {
269
290
  return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
270
291
  }) : null,
271
292
  labelRect: this.labelRect(v.labelRect),
272
- 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
273
313
  };
274
314
  }
275
315
  labelRect(v) {
@@ -293,13 +333,27 @@ var Reader = class {
293
333
  }
294
334
  return { id, shape, version, params };
295
335
  }
336
+ variables(v) {
337
+ if (!isObj(v)) return null;
338
+ const out = {};
339
+ for (const [k, b] of Object.entries(v)) if (typeof b === "boolean") out[k] = b;
340
+ return Object.keys(out).length > 0 ? out : null;
341
+ }
296
342
  click(v) {
297
343
  if (!isObj(v)) return null;
298
344
  this.fields(v, "click");
299
345
  const url = str(v.url);
300
346
  const safe = url === void 0 ? null : allowedURL(url);
301
347
  if (url !== void 0 && safe === null) this.warn("Skipped a click link that isn't http, https or mailto");
302
- return { scene: str(v.scene) ?? null, url: safe };
348
+ let set = null;
349
+ if (isObj(v.set)) {
350
+ set = {};
351
+ for (const [k, b] of Object.entries(v.set)) if (typeof b === "boolean" && isName(k)) set[k] = b;
352
+ if (Object.keys(set).length === 0) set = null;
353
+ }
354
+ const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
355
+ const toggle = raw ? raw.filter(isName) : null;
356
+ return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null, reset: v.reset === true ? true : null };
303
357
  }
304
358
  playback(v) {
305
359
  const s = str(v);
@@ -335,6 +389,35 @@ var Reader = class {
335
389
  params: strings(v.params)
336
390
  };
337
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
+ }
338
421
  hover(v) {
339
422
  if (!isObj(v)) return null;
340
423
  this.fields(v, "hover");
@@ -400,7 +483,7 @@ var Reader = class {
400
483
  if (camera) stableSortBy(camera, (c) => c.t);
401
484
  const markers = Array.isArray(v.markers) ? v.markers.filter(isObj).flatMap((m) => {
402
485
  const t = num(m.t);
403
- return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "" }] : [];
486
+ return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "", loop: bool(m.loop) ?? null }] : [];
404
487
  }) : null;
405
488
  if (markers) stableSortBy(markers, (m) => m.t);
406
489
  const poster = num(v.poster);
@@ -433,7 +516,34 @@ var Reader = class {
433
516
  elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
434
517
  theme: str(v.theme) ?? null,
435
518
  timeline: this.timeline(v.timeline),
436
- symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null
519
+ symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
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
437
547
  };
438
548
  }
439
549
  };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /*! dottie.js 0.6.3 | Apache-2.0 | https://github.com/phin-tech/dottie */
2
+ /*! dottie.js 0.8.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);
@@ -52,13 +52,21 @@ function defaultBase() {
52
52
  }
53
53
  var base = defaultBase();
54
54
 
55
+ // src/state.ts
56
+ var MAX_NAME = 64;
57
+ function isName(name) {
58
+ return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
59
+ }
60
+
55
61
  // src/model.ts
56
62
  var SUPPORTED_VERSION = 2;
57
63
  var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
58
64
  var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
59
65
  var KNOWN_FIELDS = {
60
66
  file: ["version", "pages", "images"],
61
- page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols"],
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"],
62
70
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
63
71
  layer: ["id", "name", "visible", "locked"],
64
72
  rgba: ["r", "g", "b", "a"],
@@ -86,6 +94,8 @@ var KNOWN_FIELDS = {
86
94
  "anchor",
87
95
  "click",
88
96
  "hover",
97
+ "when",
98
+ "overrides",
89
99
  "symbol",
90
100
  "playback",
91
101
  "offset",
@@ -94,8 +104,13 @@ var KNOWN_FIELDS = {
94
104
  "reveal",
95
105
  "ports",
96
106
  "labelRect",
97
- "extension"
107
+ "extension",
108
+ "capacity",
109
+ "delay",
110
+ "queue",
111
+ "gauge"
98
112
  ],
113
+ gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
99
114
  strokePoint: ["x", "y"],
100
115
  subpath: ["closed", "coords"],
101
116
  inlineImage: ["data", "format"],
@@ -103,7 +118,7 @@ var KNOWN_FIELDS = {
103
118
  extensionRef: ["id", "shape", "version", "params"],
104
119
  normalizedPoint: ["x", "y"],
105
120
  normalizedRect: ["x", "y", "width", "height"],
106
- connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
121
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
107
122
  waypoint: ["x", "y"],
108
123
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
109
124
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -111,9 +126,10 @@ var KNOWN_FIELDS = {
111
126
  follow: ["path", "start", "end", "ease", "reverse", "orient", "laps"],
112
127
  repeat: ["start", "end", "count"],
113
128
  cameraKey: ["t", "x", "y", "width", "height", "ease"],
114
- marker: ["t", "name"],
115
- click: ["scene", "url"],
116
- hover: ["stroke", "fill", "opacity"]
129
+ marker: ["t", "name", "loop"],
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"]
117
133
  };
118
134
  var MAX_DURATION = 3600;
119
135
  var MAX_MARCH = 1e4;
@@ -127,6 +143,8 @@ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
127
143
  var str = (v) => typeof v === "string" ? v : void 0;
128
144
  var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
129
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}$/;
130
148
  var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
131
149
  function clampTime(t) {
132
150
  if (!Number.isFinite(t)) return t === Infinity ? MAX_DURATION : 0;
@@ -215,7 +233,8 @@ var Reader = class {
215
233
  this.fields(w, "waypoint");
216
234
  return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
217
235
  }) : null,
218
- 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
219
238
  };
220
239
  }
221
240
  image(v, table) {
@@ -266,6 +285,8 @@ var Reader = class {
266
285
  anchor: str(v.anchor) ?? null,
267
286
  click: this.click(v.click),
268
287
  hover: this.hover(v.hover),
288
+ when: str(v.when) ?? null,
289
+ overrides: this.overrides(v.overrides),
269
290
  symbol: uuid(v.symbol) ?? null,
270
291
  playback: this.playback(v.playback),
271
292
  offset: num(v.offset) ?? null,
@@ -277,7 +298,26 @@ var Reader = class {
277
298
  return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
278
299
  }) : null,
279
300
  labelRect: this.labelRect(v.labelRect),
280
- 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
281
321
  };
282
322
  }
283
323
  labelRect(v) {
@@ -301,13 +341,27 @@ var Reader = class {
301
341
  }
302
342
  return { id, shape, version, params };
303
343
  }
344
+ variables(v) {
345
+ if (!isObj(v)) return null;
346
+ const out = {};
347
+ for (const [k, b] of Object.entries(v)) if (typeof b === "boolean") out[k] = b;
348
+ return Object.keys(out).length > 0 ? out : null;
349
+ }
304
350
  click(v) {
305
351
  if (!isObj(v)) return null;
306
352
  this.fields(v, "click");
307
353
  const url = str(v.url);
308
354
  const safe = url === void 0 ? null : allowedURL(url);
309
355
  if (url !== void 0 && safe === null) this.warn("Skipped a click link that isn't http, https or mailto");
310
- return { scene: str(v.scene) ?? null, url: safe };
356
+ let set = null;
357
+ if (isObj(v.set)) {
358
+ set = {};
359
+ for (const [k, b] of Object.entries(v.set)) if (typeof b === "boolean" && isName(k)) set[k] = b;
360
+ if (Object.keys(set).length === 0) set = null;
361
+ }
362
+ const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
363
+ const toggle = raw ? raw.filter(isName) : null;
364
+ return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null, reset: v.reset === true ? true : null };
311
365
  }
312
366
  playback(v) {
313
367
  const s = str(v);
@@ -343,6 +397,35 @@ var Reader = class {
343
397
  params: strings(v.params)
344
398
  };
345
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
+ }
346
429
  hover(v) {
347
430
  if (!isObj(v)) return null;
348
431
  this.fields(v, "hover");
@@ -408,7 +491,7 @@ var Reader = class {
408
491
  if (camera) stableSortBy(camera, (c) => c.t);
409
492
  const markers = Array.isArray(v.markers) ? v.markers.filter(isObj).flatMap((m) => {
410
493
  const t = num(m.t);
411
- return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "" }] : [];
494
+ return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "", loop: bool(m.loop) ?? null }] : [];
412
495
  }) : null;
413
496
  if (markers) stableSortBy(markers, (m) => m.t);
414
497
  const poster = num(v.poster);
@@ -441,7 +524,34 @@ var Reader = class {
441
524
  elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
442
525
  theme: str(v.theme) ?? null,
443
526
  timeline: this.timeline(v.timeline),
444
- symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null
527
+ symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
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
445
555
  };
446
556
  }
447
557
  };
@@ -588,7 +698,7 @@ function compile(source2, runtime, options2 = {}) {
588
698
  }
589
699
 
590
700
  // src/index.ts
591
- var VERSION = "0.6.3";
701
+ var VERSION = "0.8.0";
592
702
 
593
703
  // src/cli.ts
594
704
  var usage = `dottie-compile ${VERSION}: a Dottie drawing as one script that plays it on any page.