@phin-tech/dottie 0.6.2 → 0.7.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,7 +24,9 @@ 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
+ | 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. |
27
28
  | Arrowheads: arrow, triangle, dot, bar | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | |
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 |
28
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 |
29
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 |
30
32
  | Visibility, reveal (draw-on, typing), opacity | Supported | [edge/timeline](../fixtures/edge/timeline.dottie) | |
@@ -45,6 +47,9 @@ fixture, so the gap is visible rather than silent.
45
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 |
46
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 |
47
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
+ | 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 |
48
53
  | 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 |
49
54
  | 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 |
50
55
  | Editing | Missing | | |
package/README.md CHANGED
@@ -33,11 +33,16 @@ 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. A looping scene plays round until a click
41
+ or next leaves it, instead of stopping.
38
42
 
39
43
  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
44
+ (settable), `duration`, `paused`, `variables` and `setState({name: value})`, and fires `load`, `frame`, `action`,
45
+ `state` (with `{variables}`, after a change) and `error` events. Its page color comes from the drawing's theme; set
41
46
  `--dottie-radius` to round it, and style its parts with `::part(stage)` and `::part(controls)`.
42
47
 
43
48
  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.2 | Apache-2.0 | https://github.com/phin-tech/dottie */
1
+ /*! dottie.js 0.7.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,17 +44,23 @@ 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"],
54
60
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
55
61
  layer: ["id", "name", "visible", "locked"],
56
62
  rgba: ["r", "g", "b", "a"],
57
- style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset"],
63
+ style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
58
64
  element: [
59
65
  "id",
60
66
  "kind",
@@ -78,6 +84,7 @@ var KNOWN_FIELDS = {
78
84
  "anchor",
79
85
  "click",
80
86
  "hover",
87
+ "when",
81
88
  "symbol",
82
89
  "playback",
83
90
  "offset",
@@ -95,15 +102,16 @@ var KNOWN_FIELDS = {
95
102
  extensionRef: ["id", "shape", "version", "params"],
96
103
  normalizedPoint: ["x", "y"],
97
104
  normalizedRect: ["x", "y", "width", "height"],
98
- connector: ["start", "end", "routing", "startHead", "endHead"],
105
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
106
+ waypoint: ["x", "y"],
99
107
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
100
108
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
101
109
  keyframe: ["t", "x", "y", "width", "height", "rotation", "strokeWidth", "stroke", "fill", "visible", "reveal", "opacity", "ease"],
102
110
  follow: ["path", "start", "end", "ease", "reverse", "orient", "laps"],
103
111
  repeat: ["start", "end", "count"],
104
112
  cameraKey: ["t", "x", "y", "width", "height", "ease"],
105
- marker: ["t", "name"],
106
- click: ["scene", "url"],
113
+ marker: ["t", "name", "loop"],
114
+ click: ["scene", "url", "set", "toggle"],
107
115
  hover: ["stroke", "fill", "opacity"]
108
116
  };
109
117
  var MAX_DURATION = 3600;
@@ -172,7 +180,8 @@ var Reader = class {
172
180
  font: str(s.font) || null,
173
181
  gridSize: num(s.gridSize) ?? null,
174
182
  opacity: num(s.opacity) ?? null,
175
- dashOffset: num(s.dashOffset) ?? null
183
+ dashOffset: num(s.dashOffset) ?? null,
184
+ textColor: rgba(s.textColor) ?? null
176
185
  };
177
186
  }
178
187
  endpoint(v) {
@@ -200,7 +209,12 @@ var Reader = class {
200
209
  end: this.endpoint(v.end),
201
210
  routing: routing && ["straight", "curved", "elbow"].includes(routing) ? routing : "straight",
202
211
  startHead: head(v.startHead, "none"),
203
- endHead: head(v.endHead, "arrow")
212
+ endHead: head(v.endHead, "arrow"),
213
+ waypoints: Array.isArray(v.waypoints) ? v.waypoints.filter(isObj).map((w) => {
214
+ this.fields(w, "waypoint");
215
+ return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
216
+ }) : null,
217
+ hops: typeof v.hops === "boolean" ? v.hops : null
204
218
  };
205
219
  }
206
220
  image(v, table) {
@@ -251,6 +265,7 @@ var Reader = class {
251
265
  anchor: str(v.anchor) ?? null,
252
266
  click: this.click(v.click),
253
267
  hover: this.hover(v.hover),
268
+ when: str(v.when) ?? null,
254
269
  symbol: uuid(v.symbol) ?? null,
255
270
  playback: this.playback(v.playback),
256
271
  offset: num(v.offset) ?? null,
@@ -286,13 +301,27 @@ var Reader = class {
286
301
  }
287
302
  return { id, shape, version, params };
288
303
  }
304
+ variables(v) {
305
+ if (!isObj(v)) return null;
306
+ const out = {};
307
+ for (const [k, b] of Object.entries(v)) if (typeof b === "boolean") out[k] = b;
308
+ return Object.keys(out).length > 0 ? out : null;
309
+ }
289
310
  click(v) {
290
311
  if (!isObj(v)) return null;
291
312
  this.fields(v, "click");
292
313
  const url = str(v.url);
293
314
  const safe = url === void 0 ? null : allowedURL(url);
294
315
  if (url !== void 0 && safe === null) this.warn("Skipped a click link that isn't http, https or mailto");
295
- return { scene: str(v.scene) ?? null, url: safe };
316
+ let set = null;
317
+ if (isObj(v.set)) {
318
+ set = {};
319
+ for (const [k, b] of Object.entries(v.set)) if (typeof b === "boolean" && isName(k)) set[k] = b;
320
+ if (Object.keys(set).length === 0) set = null;
321
+ }
322
+ const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
323
+ const toggle = raw ? raw.filter(isName) : null;
324
+ return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
296
325
  }
297
326
  playback(v) {
298
327
  const s = str(v);
@@ -393,7 +422,7 @@ var Reader = class {
393
422
  if (camera) stableSortBy(camera, (c) => c.t);
394
423
  const markers = Array.isArray(v.markers) ? v.markers.filter(isObj).flatMap((m) => {
395
424
  const t = num(m.t);
396
- return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "" }] : [];
425
+ return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "", loop: bool(m.loop) ?? null }] : [];
397
426
  }) : null;
398
427
  if (markers) stableSortBy(markers, (m) => m.t);
399
428
  const poster = num(v.poster);
@@ -426,7 +455,8 @@ var Reader = class {
426
455
  elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
427
456
  theme: str(v.theme) ?? null,
428
457
  timeline: this.timeline(v.timeline),
429
- symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null
458
+ symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
459
+ variables: this.variables(v.variables)
430
460
  };
431
461
  }
432
462
  };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /*! dottie.js 0.6.2 | Apache-2.0 | https://github.com/phin-tech/dottie */
2
+ /*! dottie.js 0.7.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,17 +52,23 @@ 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"],
62
68
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
63
69
  layer: ["id", "name", "visible", "locked"],
64
70
  rgba: ["r", "g", "b", "a"],
65
- style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset"],
71
+ style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
66
72
  element: [
67
73
  "id",
68
74
  "kind",
@@ -86,6 +92,7 @@ var KNOWN_FIELDS = {
86
92
  "anchor",
87
93
  "click",
88
94
  "hover",
95
+ "when",
89
96
  "symbol",
90
97
  "playback",
91
98
  "offset",
@@ -103,15 +110,16 @@ var KNOWN_FIELDS = {
103
110
  extensionRef: ["id", "shape", "version", "params"],
104
111
  normalizedPoint: ["x", "y"],
105
112
  normalizedRect: ["x", "y", "width", "height"],
106
- connector: ["start", "end", "routing", "startHead", "endHead"],
113
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
114
+ waypoint: ["x", "y"],
107
115
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
108
116
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
109
117
  keyframe: ["t", "x", "y", "width", "height", "rotation", "strokeWidth", "stroke", "fill", "visible", "reveal", "opacity", "ease"],
110
118
  follow: ["path", "start", "end", "ease", "reverse", "orient", "laps"],
111
119
  repeat: ["start", "end", "count"],
112
120
  cameraKey: ["t", "x", "y", "width", "height", "ease"],
113
- marker: ["t", "name"],
114
- click: ["scene", "url"],
121
+ marker: ["t", "name", "loop"],
122
+ click: ["scene", "url", "set", "toggle"],
115
123
  hover: ["stroke", "fill", "opacity"]
116
124
  };
117
125
  var MAX_DURATION = 3600;
@@ -180,7 +188,8 @@ var Reader = class {
180
188
  font: str(s.font) || null,
181
189
  gridSize: num(s.gridSize) ?? null,
182
190
  opacity: num(s.opacity) ?? null,
183
- dashOffset: num(s.dashOffset) ?? null
191
+ dashOffset: num(s.dashOffset) ?? null,
192
+ textColor: rgba(s.textColor) ?? null
184
193
  };
185
194
  }
186
195
  endpoint(v) {
@@ -208,7 +217,12 @@ var Reader = class {
208
217
  end: this.endpoint(v.end),
209
218
  routing: routing && ["straight", "curved", "elbow"].includes(routing) ? routing : "straight",
210
219
  startHead: head(v.startHead, "none"),
211
- endHead: head(v.endHead, "arrow")
220
+ endHead: head(v.endHead, "arrow"),
221
+ waypoints: Array.isArray(v.waypoints) ? v.waypoints.filter(isObj).map((w) => {
222
+ this.fields(w, "waypoint");
223
+ return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
224
+ }) : null,
225
+ hops: typeof v.hops === "boolean" ? v.hops : null
212
226
  };
213
227
  }
214
228
  image(v, table) {
@@ -259,6 +273,7 @@ var Reader = class {
259
273
  anchor: str(v.anchor) ?? null,
260
274
  click: this.click(v.click),
261
275
  hover: this.hover(v.hover),
276
+ when: str(v.when) ?? null,
262
277
  symbol: uuid(v.symbol) ?? null,
263
278
  playback: this.playback(v.playback),
264
279
  offset: num(v.offset) ?? null,
@@ -294,13 +309,27 @@ var Reader = class {
294
309
  }
295
310
  return { id, shape, version, params };
296
311
  }
312
+ variables(v) {
313
+ if (!isObj(v)) return null;
314
+ const out = {};
315
+ for (const [k, b] of Object.entries(v)) if (typeof b === "boolean") out[k] = b;
316
+ return Object.keys(out).length > 0 ? out : null;
317
+ }
297
318
  click(v) {
298
319
  if (!isObj(v)) return null;
299
320
  this.fields(v, "click");
300
321
  const url = str(v.url);
301
322
  const safe = url === void 0 ? null : allowedURL(url);
302
323
  if (url !== void 0 && safe === null) this.warn("Skipped a click link that isn't http, https or mailto");
303
- return { scene: str(v.scene) ?? null, url: safe };
324
+ let set = null;
325
+ if (isObj(v.set)) {
326
+ set = {};
327
+ for (const [k, b] of Object.entries(v.set)) if (typeof b === "boolean" && isName(k)) set[k] = b;
328
+ if (Object.keys(set).length === 0) set = null;
329
+ }
330
+ const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
331
+ const toggle = raw ? raw.filter(isName) : null;
332
+ return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
304
333
  }
305
334
  playback(v) {
306
335
  const s = str(v);
@@ -401,7 +430,7 @@ var Reader = class {
401
430
  if (camera) stableSortBy(camera, (c) => c.t);
402
431
  const markers = Array.isArray(v.markers) ? v.markers.filter(isObj).flatMap((m) => {
403
432
  const t = num(m.t);
404
- return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "" }] : [];
433
+ return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "", loop: bool(m.loop) ?? null }] : [];
405
434
  }) : null;
406
435
  if (markers) stableSortBy(markers, (m) => m.t);
407
436
  const poster = num(v.poster);
@@ -434,7 +463,8 @@ var Reader = class {
434
463
  elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
435
464
  theme: str(v.theme) ?? null,
436
465
  timeline: this.timeline(v.timeline),
437
- symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null
466
+ symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
467
+ variables: this.variables(v.variables)
438
468
  };
439
469
  }
440
470
  };
@@ -581,7 +611,7 @@ function compile(source2, runtime, options2 = {}) {
581
611
  }
582
612
 
583
613
  // src/index.ts
584
- var VERSION = "0.6.2";
614
+ var VERSION = "0.7.0";
585
615
 
586
616
  // src/cli.ts
587
617
  var usage = `dottie-compile ${VERSION}: a Dottie drawing as one script that plays it on any page.