@phin-tech/dottie 0.8.0 → 0.10.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
@@ -24,10 +24,13 @@ fixture, so the gap is visible rather than silent.
24
24
  | Ports and edge-following, rotated shapes, free ends | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | |
25
25
  | Connectors and labels on outlines (ADR 0001) | Supported | [edge/extensions](../fixtures/edge/extensions.dottie) | Edge-following meets a `.path`'s actual outline, not its box (ray against 16-step flattened curves). Endpoints pinned with `portIndex` attach to the element's `ports`. A path's label goes in its `labelRect`. |
26
26
  | Extension shapes as stored (ADR 0001) | Supported | [edge/extensions](../fixtures/edge/extensions.dottie) | Drawn from the stored path with no extension installed. The `extension` field is kept as metadata and nothing runs. Parameters, commands and handles are Dottie-only. |
27
+ | Containers and swimlanes | Supported | [edge/containers](../fixtures/edge/containers.dottie) | Drawn from the stored frame and title band (`labelRect`); the app keeps them fitted (ADR 0006). |
28
+ | Themes of a page's own, and palette colors by name | Supported | [edge/palette](../fixtures/edge/palette.dottie) | Names are resolved when a file is read, by the same rules in `Theme.swift` and `theme.ts` (ADR 0005). |
29
+ | Elbows that go round shapes (`avoid`) | Supported | [edge/elbow-around](../fixtures/edge/elbow-around.dottie) | The same grid router, point for point (`ElbowRouter.swift`, `elbow.ts`). |
27
30
  | 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
31
  | Arrowheads: arrow, triangle, dot, bar | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | |
29
32
  | 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 |
33
+ | 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
34
  | 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
35
  | Visibility, reveal (draw-on, typing), opacity | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | |
33
36
  | Transitions | Supported | [edge/transitions](../fixtures/edge/transitions.dottie) | They are ordinary keys in the file |
@@ -37,22 +40,22 @@ fixture, so the gap is visible rather than silent.
37
40
  | Repeats (counted and forever) | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | |
38
41
  | Boil (three drawings) and settle | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | Boiled seeds match bit for bit |
39
42
  | 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 |
43
+ | Scenes (markers) and click-through | Supported | [design/tour](../fixtures/design/tour.dottie) | Stops at each scene. The HTML export is this player |
41
44
  | Poster frame, reduced motion | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | |
42
45
  | Damaged timelines (clamped, filtered, sorted) | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | Read the way DottieKit's decoders read them |
43
46
  | 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
47
  | 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
48
  | 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 |
49
+ | 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
50
  | 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 |
51
+ | Copies that play when clicked | Supported | [edge/buttons](../fixtures/edge/buttons.dottie) | In the player and the HTML export |
52
+ | 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
53
  | 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
54
  | 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
55
  | 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 |
56
+ | 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 |
57
+ | Clicks that set and toggle variables | Supported | [edge/state](../fixtures/edge/state.dottie) | Change what shows without touching the playhead |
58
+ | 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 |
56
59
  | 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 |
57
60
  | 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 |
58
61
  | Editing | Missing | | |
package/README.md CHANGED
@@ -49,6 +49,13 @@ The element has `play()`, `pause()`, `nextScene()`, `prevScene()`, `activate(ele
49
49
  Without modules, `dist/dottie-player.min.js` is a classic script that registers the element and sets
50
50
  `window.Dottie`.
51
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
+
52
59
  **Fonts.** Text in Dottie's handwriting fonts (Caveat, Patrick Hand, Architects Daughter; SIL Open Font License)
53
60
  is drawn in them: the player loads the ones a drawing uses from `fonts/` beside its own script, as `dist/fonts` on
54
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.8.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
1
+ /*! dottie.js 0.10.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);
@@ -50,19 +50,80 @@ function isName(name) {
50
50
  return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
51
51
  }
52
52
 
53
+ // src/theme.ts
54
+ var rgb = (r, g, b) => ({ r, g, b, a: 1 });
55
+ function hex(h) {
56
+ const n = parseInt(h.slice(1), 16);
57
+ return { r: (n >> 16 & 255) / 255, g: (n >> 8 & 255) / 255, b: (n & 255) / 255, a: 1 };
58
+ }
59
+ var PALETTE_NAMES = ["accent", "muted", "danger", "warning", "success", "info"];
60
+ var palette = (colors) => PALETTE_NAMES.map((name, i) => ({ name, color: hex(colors[i]) }));
61
+ var LIGHT_PALETTE = palette(["#2563eb", "#6b7280", "#dc2626", "#d97706", "#16a34a", "#0891b2"]);
62
+ var DARK_PALETTE = palette(["#93c5fd", "#cbd5e1", "#fca5a5", "#fcd34d", "#86efac", "#67e8f9"]);
63
+ var themes = [
64
+ { id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
65
+ { id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
66
+ {
67
+ id: "sepia",
68
+ name: "Sepia",
69
+ background: rgb(0.953, 0.914, 0.824),
70
+ ink: rgb(0.23, 0.18, 0.13),
71
+ palette: palette(["#9a3412", "#78716c", "#b91c1c", "#b45309", "#4d7c0f", "#0e7490"])
72
+ },
73
+ { id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984), palette: DARK_PALETTE },
74
+ { id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918), palette: DARK_PALETTE },
75
+ { id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94), palette: DARK_PALETTE }
76
+ ];
77
+ function themeNamed(id) {
78
+ return themes.find((t) => t.id === id) ?? null;
79
+ }
80
+ function pageTheme(page) {
81
+ const c = page.customTheme;
82
+ if (c) return { id: "custom", name: c.name, background: c.background, ink: c.ink, palette: c.palette };
83
+ return themeNamed(page.theme);
84
+ }
85
+ function paletteColor(page, name) {
86
+ const n = name.trim().toLowerCase();
87
+ return (pageTheme(page)?.palette ?? LIGHT_PALETTE).find((p) => p.name.toLowerCase() === n)?.color ?? null;
88
+ }
89
+ function resolvingPalette(page) {
90
+ const named = (e) => !!e.style.named;
91
+ if (!page.elements.some(named) && !(page.symbols ?? []).some((s) => s.elements.some(named))) return page;
92
+ const resolve = (e) => {
93
+ const n = e.style.named;
94
+ if (!n) return e;
95
+ const style = { ...e.style };
96
+ const f = n.fill !== null ? paletteColor(page, n.fill) : null;
97
+ const s = n.stroke !== null ? paletteColor(page, n.stroke) : null;
98
+ const t = n.textColor !== null ? paletteColor(page, n.textColor) : null;
99
+ if (f) style.fill = { ...f };
100
+ if (s) style.stroke = { ...s };
101
+ if (t) style.textColor = { ...t };
102
+ return { ...e, style };
103
+ };
104
+ return {
105
+ ...page,
106
+ elements: page.elements.map(resolve),
107
+ symbols: page.symbols ? page.symbols.map((s) => ({ ...s, elements: s.elements.map(resolve) })) : page.symbols
108
+ };
109
+ }
110
+
53
111
  // src/model.ts
54
112
  var SUPPORTED_VERSION = 2;
55
113
  var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
56
114
  var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
57
115
  var KNOWN_FIELDS = {
58
116
  file: ["version", "pages", "images"],
59
- page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
117
+ page: ["id", "name", "layers", "elements", "theme", "customTheme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
60
118
  flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
61
119
  flowOverride: ["when", "volume"],
62
120
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
63
121
  layer: ["id", "name", "visible", "locked"],
64
122
  rgba: ["r", "g", "b", "a"],
65
- style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
123
+ style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor", "named"],
124
+ colorNames: ["fill", "stroke", "textColor"],
125
+ customTheme: ["name", "background", "ink", "palette", "base"],
126
+ paletteColor: ["name", "color"],
66
127
  element: [
67
128
  "id",
68
129
  "kind",
@@ -100,8 +161,11 @@ var KNOWN_FIELDS = {
100
161
  "capacity",
101
162
  "delay",
102
163
  "queue",
103
- "gauge"
164
+ "gauge",
165
+ "containerID",
166
+ "container"
104
167
  ],
168
+ container: ["fixed", "lanes"],
105
169
  gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
106
170
  strokePoint: ["x", "y"],
107
171
  subpath: ["closed", "coords"],
@@ -110,7 +174,7 @@ var KNOWN_FIELDS = {
110
174
  extensionRef: ["id", "shape", "version", "params"],
111
175
  normalizedPoint: ["x", "y"],
112
176
  normalizedRect: ["x", "y", "width", "height"],
113
- connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
177
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "avoid", "cost"],
114
178
  waypoint: ["x", "y"],
115
179
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
116
180
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -192,9 +256,36 @@ var Reader = class {
192
256
  gridSize: num(s.gridSize) ?? null,
193
257
  opacity: num(s.opacity) ?? null,
194
258
  dashOffset: num(s.dashOffset) ?? null,
195
- textColor: rgba(s.textColor) ?? null
259
+ textColor: rgba(s.textColor) ?? null,
260
+ named: this.colorNames(s.named)
196
261
  };
197
262
  }
263
+ container(v) {
264
+ if (!isObj(v)) return null;
265
+ this.fields(v, "container");
266
+ return { fixed: v.fixed === true ? true : null, lanes: v.lanes === "rows" || v.lanes === "columns" ? v.lanes : null };
267
+ }
268
+ colorNames(v) {
269
+ if (!isObj(v)) return null;
270
+ this.fields(v, "colorNames");
271
+ const n = { fill: str(v.fill) ?? null, stroke: str(v.stroke) ?? null, textColor: str(v.textColor) ?? null };
272
+ return n.fill === null && n.stroke === null && n.textColor === null ? null : n;
273
+ }
274
+ customTheme(v) {
275
+ if (!isObj(v)) return null;
276
+ this.fields(v, "customTheme");
277
+ const background = rgba(v.background), ink = rgba(v.ink);
278
+ if (!background || !ink) {
279
+ this.warn("A custom theme needs a background and an ink; drawn without it");
280
+ return null;
281
+ }
282
+ const palette2 = (Array.isArray(v.palette) ? v.palette : []).filter(isObj).flatMap((p) => {
283
+ this.fields(p, "paletteColor");
284
+ const name = str(p.name), color = rgba(p.color);
285
+ return name && color ? [{ name, color }] : [];
286
+ });
287
+ return { name: str(v.name) ?? "Custom", background, ink, palette: palette2, base: str(v.base) ?? null };
288
+ }
198
289
  endpoint(v) {
199
290
  const e = isObj(v) ? v : {};
200
291
  const port = str(e.port);
@@ -226,6 +317,7 @@ var Reader = class {
226
317
  return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
227
318
  }) : null,
228
319
  hops: typeof v.hops === "boolean" ? v.hops : null,
320
+ avoid: typeof v.avoid === "boolean" ? v.avoid : null,
229
321
  cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
230
322
  };
231
323
  }
@@ -294,7 +386,9 @@ var Reader = class {
294
386
  capacity: num(v.capacity) ?? null,
295
387
  delay: num(v.delay) ?? null,
296
388
  queue: v.queue === true ? true : null,
297
- gauge: this.gauge(v.gauge)
389
+ gauge: this.gauge(v.gauge),
390
+ containerID: uuid(v.containerID) ?? null,
391
+ container: this.container(v.container)
298
392
  };
299
393
  }
300
394
  gauge(v) {
@@ -515,6 +609,7 @@ var Reader = class {
515
609
  layers,
516
610
  elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
517
611
  theme: str(v.theme) ?? null,
612
+ customTheme: this.customTheme(v.customTheme),
518
613
  timeline: this.timeline(v.timeline),
519
614
  symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
520
615
  variables: this.variables(v.variables),
@@ -578,7 +673,7 @@ function parseDottie(source) {
578
673
  if (isObj(v) && typeof v.data === "string") images[k] = reader.picture(v.data, str(v.format));
579
674
  }
580
675
  }
581
- const pages = json.pages.map((p, i) => reader.page(p, images, i + 1));
676
+ const pages = json.pages.map((p, i) => resolvingPalette(reader.page(p, images, i + 1)));
582
677
  if (new Set(pages.map((p) => p.id)).size !== pages.length) throw new DottieFormatError("Two pages share an id");
583
678
  return { version, pages, warnings: reader.warnings };
584
679
  }
@@ -686,7 +781,12 @@ function compile(source, runtime, options = {}) {
686
781
  if (b64) (fonts[family] ?? (fonts[family] = {}))[face] = b64;
687
782
  }
688
783
  }
689
- return runtime.trimEnd() + "\n" + LOADER.replace("DOTTIE_DATA", () => scriptJSON(data)).replace("DOTTIE_OPTIONS", () => scriptJSON(playerAttributes(options))).replace("DOTTIE_FONTS", () => scriptJSON(fonts));
784
+ const fill = {
785
+ DOTTIE_DATA: () => scriptJSON(data),
786
+ DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(options)),
787
+ DOTTIE_FONTS: () => scriptJSON(fonts)
788
+ };
789
+ return runtime.trimEnd() + "\n" + LOADER.replace(/\bDOTTIE_(?:DATA|OPTIONS|FONTS)\b/g, (m) => fill[m]());
690
790
  }
691
791
  export {
692
792
  LOADER,
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /*! dottie.js 0.8.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
2
+ /*! dottie.js 0.10.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);
@@ -58,19 +58,80 @@ function isName(name) {
58
58
  return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
59
59
  }
60
60
 
61
+ // src/theme.ts
62
+ var rgb = (r, g, b) => ({ r, g, b, a: 1 });
63
+ function hex(h) {
64
+ const n = parseInt(h.slice(1), 16);
65
+ return { r: (n >> 16 & 255) / 255, g: (n >> 8 & 255) / 255, b: (n & 255) / 255, a: 1 };
66
+ }
67
+ var PALETTE_NAMES = ["accent", "muted", "danger", "warning", "success", "info"];
68
+ var palette = (colors) => PALETTE_NAMES.map((name, i) => ({ name, color: hex(colors[i]) }));
69
+ var LIGHT_PALETTE = palette(["#2563eb", "#6b7280", "#dc2626", "#d97706", "#16a34a", "#0891b2"]);
70
+ var DARK_PALETTE = palette(["#93c5fd", "#cbd5e1", "#fca5a5", "#fcd34d", "#86efac", "#67e8f9"]);
71
+ var themes = [
72
+ { id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
73
+ { id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
74
+ {
75
+ id: "sepia",
76
+ name: "Sepia",
77
+ background: rgb(0.953, 0.914, 0.824),
78
+ ink: rgb(0.23, 0.18, 0.13),
79
+ palette: palette(["#9a3412", "#78716c", "#b91c1c", "#b45309", "#4d7c0f", "#0e7490"])
80
+ },
81
+ { id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984), palette: DARK_PALETTE },
82
+ { id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918), palette: DARK_PALETTE },
83
+ { id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94), palette: DARK_PALETTE }
84
+ ];
85
+ function themeNamed(id) {
86
+ return themes.find((t) => t.id === id) ?? null;
87
+ }
88
+ function pageTheme(page) {
89
+ const c = page.customTheme;
90
+ if (c) return { id: "custom", name: c.name, background: c.background, ink: c.ink, palette: c.palette };
91
+ return themeNamed(page.theme);
92
+ }
93
+ function paletteColor(page, name) {
94
+ const n = name.trim().toLowerCase();
95
+ return (pageTheme(page)?.palette ?? LIGHT_PALETTE).find((p) => p.name.toLowerCase() === n)?.color ?? null;
96
+ }
97
+ function resolvingPalette(page) {
98
+ const named = (e) => !!e.style.named;
99
+ if (!page.elements.some(named) && !(page.symbols ?? []).some((s) => s.elements.some(named))) return page;
100
+ const resolve = (e) => {
101
+ const n = e.style.named;
102
+ if (!n) return e;
103
+ const style = { ...e.style };
104
+ const f = n.fill !== null ? paletteColor(page, n.fill) : null;
105
+ const s = n.stroke !== null ? paletteColor(page, n.stroke) : null;
106
+ const t = n.textColor !== null ? paletteColor(page, n.textColor) : null;
107
+ if (f) style.fill = { ...f };
108
+ if (s) style.stroke = { ...s };
109
+ if (t) style.textColor = { ...t };
110
+ return { ...e, style };
111
+ };
112
+ return {
113
+ ...page,
114
+ elements: page.elements.map(resolve),
115
+ symbols: page.symbols ? page.symbols.map((s) => ({ ...s, elements: s.elements.map(resolve) })) : page.symbols
116
+ };
117
+ }
118
+
61
119
  // src/model.ts
62
120
  var SUPPORTED_VERSION = 2;
63
121
  var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
64
122
  var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
65
123
  var KNOWN_FIELDS = {
66
124
  file: ["version", "pages", "images"],
67
- page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
125
+ page: ["id", "name", "layers", "elements", "theme", "customTheme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
68
126
  flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
69
127
  flowOverride: ["when", "volume"],
70
128
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
71
129
  layer: ["id", "name", "visible", "locked"],
72
130
  rgba: ["r", "g", "b", "a"],
73
- style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
131
+ style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor", "named"],
132
+ colorNames: ["fill", "stroke", "textColor"],
133
+ customTheme: ["name", "background", "ink", "palette", "base"],
134
+ paletteColor: ["name", "color"],
74
135
  element: [
75
136
  "id",
76
137
  "kind",
@@ -108,8 +169,11 @@ var KNOWN_FIELDS = {
108
169
  "capacity",
109
170
  "delay",
110
171
  "queue",
111
- "gauge"
172
+ "gauge",
173
+ "containerID",
174
+ "container"
112
175
  ],
176
+ container: ["fixed", "lanes"],
113
177
  gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
114
178
  strokePoint: ["x", "y"],
115
179
  subpath: ["closed", "coords"],
@@ -118,7 +182,7 @@ var KNOWN_FIELDS = {
118
182
  extensionRef: ["id", "shape", "version", "params"],
119
183
  normalizedPoint: ["x", "y"],
120
184
  normalizedRect: ["x", "y", "width", "height"],
121
- connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
185
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "avoid", "cost"],
122
186
  waypoint: ["x", "y"],
123
187
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
124
188
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -200,9 +264,36 @@ var Reader = class {
200
264
  gridSize: num(s.gridSize) ?? null,
201
265
  opacity: num(s.opacity) ?? null,
202
266
  dashOffset: num(s.dashOffset) ?? null,
203
- textColor: rgba(s.textColor) ?? null
267
+ textColor: rgba(s.textColor) ?? null,
268
+ named: this.colorNames(s.named)
204
269
  };
205
270
  }
271
+ container(v) {
272
+ if (!isObj(v)) return null;
273
+ this.fields(v, "container");
274
+ return { fixed: v.fixed === true ? true : null, lanes: v.lanes === "rows" || v.lanes === "columns" ? v.lanes : null };
275
+ }
276
+ colorNames(v) {
277
+ if (!isObj(v)) return null;
278
+ this.fields(v, "colorNames");
279
+ const n = { fill: str(v.fill) ?? null, stroke: str(v.stroke) ?? null, textColor: str(v.textColor) ?? null };
280
+ return n.fill === null && n.stroke === null && n.textColor === null ? null : n;
281
+ }
282
+ customTheme(v) {
283
+ if (!isObj(v)) return null;
284
+ this.fields(v, "customTheme");
285
+ const background = rgba(v.background), ink = rgba(v.ink);
286
+ if (!background || !ink) {
287
+ this.warn("A custom theme needs a background and an ink; drawn without it");
288
+ return null;
289
+ }
290
+ const palette2 = (Array.isArray(v.palette) ? v.palette : []).filter(isObj).flatMap((p) => {
291
+ this.fields(p, "paletteColor");
292
+ const name = str(p.name), color = rgba(p.color);
293
+ return name && color ? [{ name, color }] : [];
294
+ });
295
+ return { name: str(v.name) ?? "Custom", background, ink, palette: palette2, base: str(v.base) ?? null };
296
+ }
206
297
  endpoint(v) {
207
298
  const e = isObj(v) ? v : {};
208
299
  const port = str(e.port);
@@ -234,6 +325,7 @@ var Reader = class {
234
325
  return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
235
326
  }) : null,
236
327
  hops: typeof v.hops === "boolean" ? v.hops : null,
328
+ avoid: typeof v.avoid === "boolean" ? v.avoid : null,
237
329
  cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
238
330
  };
239
331
  }
@@ -302,7 +394,9 @@ var Reader = class {
302
394
  capacity: num(v.capacity) ?? null,
303
395
  delay: num(v.delay) ?? null,
304
396
  queue: v.queue === true ? true : null,
305
- gauge: this.gauge(v.gauge)
397
+ gauge: this.gauge(v.gauge),
398
+ containerID: uuid(v.containerID) ?? null,
399
+ container: this.container(v.container)
306
400
  };
307
401
  }
308
402
  gauge(v) {
@@ -523,6 +617,7 @@ var Reader = class {
523
617
  layers,
524
618
  elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
525
619
  theme: str(v.theme) ?? null,
620
+ customTheme: this.customTheme(v.customTheme),
526
621
  timeline: this.timeline(v.timeline),
527
622
  symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
528
623
  variables: this.variables(v.variables),
@@ -586,7 +681,7 @@ function parseDottie(source2) {
586
681
  if (isObj(v) && typeof v.data === "string") images[k] = reader.picture(v.data, str(v.format));
587
682
  }
588
683
  }
589
- const pages = json.pages.map((p, i) => reader.page(p, images, i + 1));
684
+ const pages = json.pages.map((p, i) => resolvingPalette(reader.page(p, images, i + 1)));
590
685
  if (new Set(pages.map((p) => p.id)).size !== pages.length) throw new DottieFormatError("Two pages share an id");
591
686
  return { version, pages, warnings: reader.warnings };
592
687
  }
@@ -694,11 +789,16 @@ function compile(source2, runtime, options2 = {}) {
694
789
  if (b64) (fonts[family] ?? (fonts[family] = {}))[face] = b64;
695
790
  }
696
791
  }
697
- return runtime.trimEnd() + "\n" + LOADER.replace("DOTTIE_DATA", () => scriptJSON(data)).replace("DOTTIE_OPTIONS", () => scriptJSON(playerAttributes(options2))).replace("DOTTIE_FONTS", () => scriptJSON(fonts));
792
+ const fill = {
793
+ DOTTIE_DATA: () => scriptJSON(data),
794
+ DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(options2)),
795
+ DOTTIE_FONTS: () => scriptJSON(fonts)
796
+ };
797
+ return runtime.trimEnd() + "\n" + LOADER.replace(/\bDOTTIE_(?:DATA|OPTIONS|FONTS)\b/g, (m) => fill[m]());
698
798
  }
699
799
 
700
800
  // src/index.ts
701
- var VERSION = "0.8.0";
801
+ var VERSION = "0.10.0";
702
802
 
703
803
  // src/cli.ts
704
804
  var usage = `dottie-compile ${VERSION}: a Dottie drawing as one script that plays it on any page.