@strategicprojects/rpic 0.9.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/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  export interface Anim {
4
4
  id: string;
5
- /** "fade" | "pop" | "draw" | "slide" | "move" | "highlight" | "morph" */
5
+ /** "fade" | "pop" | "draw" | "slide" | "move" | "highlight" | "morph" | "type" | "scramble" | "wiggle" */
6
6
  effect: string;
7
7
  /** absolute start time in seconds */
8
8
  start: number;
@@ -23,6 +23,16 @@ export interface Anim {
23
23
  from?: string;
24
24
  /** id of the shape a `morph` tweens into. Present only for `morph`. */
25
25
  morph?: string;
26
+ /** `type` reveal granularity: "word" splits by word, absent means by char. */
27
+ unit?: string;
28
+ /** custom scramble charset for `scramble` (`by "…"`). Present only when set. */
29
+ chars?: string;
30
+ /** oscillation count for `wiggle` (`wiggles <n>`). Present only when set. */
31
+ wiggles?: number;
32
+ /** `draw` reveal start as a stroke fraction (`from 40%` → 0.4). Present only when set. */
33
+ drawFrom?: number;
34
+ /** `draw` reveal end as a stroke fraction (`to 60%` → 0.6). Present only when set. */
35
+ drawTo?: number;
26
36
  }
27
37
 
28
38
  export interface Diagnostic {
@@ -59,9 +69,23 @@ export interface ObjectGeometry {
59
69
  file?: string;
60
70
  }
61
71
 
72
+ export interface Interaction {
73
+ id: string;
74
+ /** currently always "drag" */
75
+ kind: string;
76
+ /** throw with momentum (needs InertiaPlugin). Present only when set. */
77
+ inertia?: boolean;
78
+ /** id of the shape whose box constrains dragging. Present only when set. */
79
+ bounds?: string;
80
+ /** axis lock: "x" | "y". Present only when set (absent = free drag). */
81
+ axis?: string;
82
+ }
83
+
62
84
  export interface Bundle {
63
85
  svg: string;
64
86
  animations: Anim[];
87
+ /** `draggable` targets for the host to wire GSAP Draggable. Present only when non-empty. */
88
+ interactions?: Interaction[];
65
89
  /** lines emitted by pic `print` statements */
66
90
  diagnostics: string[];
67
91
  /** non-fatal compiler warnings for accepted but suspicious input */
@@ -126,3 +150,11 @@ export function renderSvg(src: string, opts?: CompileOptions): string;
126
150
  * `root`. Browser-only.
127
151
  */
128
152
  export function animate(root: Element, animations: Anim[], gsap: unknown): unknown;
153
+
154
+ /**
155
+ * Make objects draggable from the `interactions` manifest (the `draggable`
156
+ * directive). Pass GSAP's `Draggable` class (register it — and `InertiaPlugin`
157
+ * if any interaction uses `inertia`). Returns the created Draggable instances.
158
+ * Browser-only.
159
+ */
160
+ export function interactive(root: Element, interactions: Interaction[], Draggable: unknown): unknown[];
package/index.js CHANGED
@@ -138,6 +138,15 @@ export function animate(root, animations, gsap) {
138
138
  case 'highlight':
139
139
  highlightWith(el, a, tl);
140
140
  break;
141
+ case 'type':
142
+ typeReveal(el, a, tl);
143
+ break;
144
+ case 'scramble':
145
+ scrambleReveal(el, a, tl);
146
+ break;
147
+ case 'wiggle':
148
+ wiggleShake(el, a, tl);
149
+ break;
141
150
  default:
142
151
  enterExit(tl, el, withOverrides({ opacity: 0, duration: a.duration }, a), a);
143
152
  }
@@ -145,6 +154,36 @@ export function animate(root, animations, gsap) {
145
154
  return tl;
146
155
  }
147
156
 
157
+ /**
158
+ * Make objects draggable from the compile bundle's `interactions` array (the
159
+ * `draggable` directive). Pass GSAP's `Draggable` class (import it and
160
+ * `registerPlugin(Draggable)` — and `InertiaPlugin` if any interaction uses
161
+ * `inertia`). Returns the created Draggable instances.
162
+ * @param {Element} root
163
+ * @param {Array<{id:string,kind:string,inertia?:boolean,bounds?:string,axis?:string}>} interactions
164
+ * @param {*} Draggable
165
+ * @returns {Array}
166
+ */
167
+ export function interactive(root, interactions, Draggable) {
168
+ if (!Draggable || !interactions || !interactions.length) return [];
169
+ const pick = (id) =>
170
+ root.querySelector(
171
+ typeof CSS !== 'undefined' && CSS.escape ? '#' + CSS.escape(id) : `[id="${id}"]`
172
+ );
173
+ const out = [];
174
+ for (const it of interactions) {
175
+ const el = pick(it.id);
176
+ if (!el) continue;
177
+ const vars = { type: it.axis || 'x,y', inertia: !!it.inertia };
178
+ if (it.bounds) {
179
+ const b = pick(it.bounds);
180
+ if (b) vars.bounds = b;
181
+ }
182
+ out.push(...Draggable.create(el, vars));
183
+ }
184
+ return out;
185
+ }
186
+
148
187
  // Entrances tween FROM the hidden `vars` to the natural state; `out` reverses
149
188
  // it into an exit (TO the hidden state). Shared by fade/pop/slide.
150
189
  function enterExit(tl, el, vars, a) {
@@ -169,6 +208,49 @@ function slideIn(tl, el, a) {
169
208
  enterExit(tl, el, withOverrides({ ...off, opacity: 0, duration: a.duration, ease: 'power2.out' }, a), a);
170
209
  }
171
210
 
211
+ // The `type` effect: reveal a label a glyph (or word) at a time — a
212
+ // typewriter. The emitter split the label into `.rpic-ch` tspans; stagger
213
+ // their opacity so the whole reveal spans the effect's duration (each unit
214
+ // fades over one stagger step, the last finishing at `duration`). `out`
215
+ // reverses it into a staggered fade-out.
216
+ function typeReveal(el, a, tl) {
217
+ const units = el.querySelectorAll('.rpic-ch');
218
+ if (!units.length) return;
219
+ const step = a.duration / units.length;
220
+ const vars = withOverrides({ opacity: 0, duration: step, stagger: step, ease: 'none' }, a);
221
+ return a.out ? tl.to(units, vars, a.start) : tl.from(units, vars, a.start);
222
+ }
223
+
224
+ // The `scramble` effect: the label's glyphs cycle through random characters and
225
+ // resolve to the real text — a decode reveal. Needs GSAP's ScrambleTextPlugin
226
+ // registered. `{original}` tells the plugin to scramble the element's existing
227
+ // text, so no capture is needed; `out` scrambles it away to nothing.
228
+ function scrambleReveal(el, a, tl) {
229
+ const text = el.querySelector('text');
230
+ if (!text) return;
231
+ const chars = a.chars || 'upperCase';
232
+ const target = a.out ? '' : '{original}';
233
+ const vars = withOverrides(
234
+ { duration: a.duration, scrambleText: { text: target, chars, speed: 1 }, ease: 'none' },
235
+ a
236
+ );
237
+ return tl.to(text, vars, a.start);
238
+ }
239
+
240
+ // The `wiggle` effect: a quick oscillating shake that returns to rest, to draw
241
+ // the eye to an object without moving it. Uses GSAP's CustomWiggle ease via its
242
+ // string syntax `wiggle(n)` (register CustomWiggle first); the tween rotates
243
+ // about the element's own centre and the ease settles it back to 0. An explicit
244
+ // `ease` overrides the wiggle.
245
+ function wiggleShake(el, a, tl) {
246
+ const n = a.wiggles || 6;
247
+ const vars = withOverrides(
248
+ { rotation: 8, transformOrigin: '50% 50%', duration: a.duration, ease: `wiggle(${n})` },
249
+ a
250
+ );
251
+ return tl.fromTo(el, { rotation: 0 }, vars, a.start);
252
+ }
253
+
172
254
  // Fold the optional GSAP overrides (repeat/yoyo/ease) into a tween's vars.
173
255
  // `ease` replaces the effect's default easing; repeat/yoyo loop the tween.
174
256
  function withOverrides(vars, a) {
@@ -317,12 +399,19 @@ function moveAlong(root, el, a, tl) {
317
399
  }
318
400
 
319
401
  function drawOn(group, a, tl) {
402
+ // `draw from 40% to 60%` reveals only a sub-segment; absent ends default to
403
+ // 0 and 1. A range switches the trace to a dash-window animation (no plugin).
404
+ const rangeStart = a.drawFrom != null ? a.drawFrom : 0;
405
+ const rangeEnd = a.drawTo != null ? a.drawTo : 1;
406
+ const hasRange = a.drawFrom != null || a.drawTo != null;
320
407
  const els = group.querySelectorAll('path, polyline, line, rect, circle, ellipse, polygon');
321
408
  els.forEach((el) => {
322
409
  // Filled, unstroked elements (arrowheads) can't be dash-traced — pop
323
- // them in as the shaft reaches the tip instead.
410
+ // them in as the shaft reaches the tip instead. A partial draw that stops
411
+ // short of the end never reaches the tip, so leave the arrowhead hidden.
324
412
  const fill = el.getAttribute('fill');
325
413
  if (el.getAttribute('stroke-width') === '0' || (fill && fill !== 'none' && !el.getAttribute('stroke'))) {
414
+ if (hasRange && rangeEnd < 1) return;
326
415
  tl.from(
327
416
  el,
328
417
  { opacity: 0, scale: 0, transformOrigin: '50% 50%', duration: Math.min(0.2, a.duration * 0.4), ease: 'back.out(1.7)' },
@@ -336,7 +425,22 @@ function drawOn(group, a, tl) {
336
425
  } catch {
337
426
  len = 0;
338
427
  }
339
- if (len > 0) {
428
+ if (len > 0 && hasRange) {
429
+ // Reveal only [p0,p1] of the stroke by growing a dash window: a leading
430
+ // gap of p0, then a dash that stretches from 0 to (p1-p0). `out` retracts
431
+ // it back to nothing. dasharray `[0, p0, w, len]` shows exactly [p0,p0+w].
432
+ const p0 = rangeStart * len;
433
+ const p1 = rangeEnd * len;
434
+ const [w0, w1] = a.out ? [p1 - p0, 0] : [0, p1 - p0];
435
+ const set = (w) => el.setAttribute('stroke-dasharray', `0 ${p0} ${Math.max(0, w)} ${len}`);
436
+ set(w0);
437
+ const proxy = { w: w0 };
438
+ tl.to(
439
+ proxy,
440
+ withOverrides({ w: w1, duration: a.duration, ease: 'none', onUpdate: () => set(proxy.w) }, a),
441
+ a.start
442
+ );
443
+ } else if (len > 0) {
340
444
  // `out` reverses the trace: the stroke retracts (dashoffset 0 → len).
341
445
  const [begin, end] = a.out ? [0, len] : [len, 0];
342
446
  tl.fromTo(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strategicprojects/rpic",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "The pic graphics language compiled to SVG with animation manifests, via WebAssembly. Browser + Node.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
Binary file
Binary file