morphicons 1.4.1 → 1.5.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.
@@ -1,3 +1,4 @@
1
+ import { n as iconToCubics } from "./normalize-CYnN3Npw.js";
1
2
  //#region src/core/interpolate.ts
2
3
  /** Preallocated output buffers for a plan (zero allocation per frame). */
3
4
  function allocOutputs(plan) {
@@ -44,545 +45,6 @@ function interpLinear(plan, t, out) {
44
45
  }
45
46
  }
46
47
  //#endregion
47
- //#region src/core/parse.ts
48
- const COMMANDS = "MmLlHhVvCcSsQqTtAaZz";
49
- function parsePath(d) {
50
- const subs = [];
51
- const n = d.length;
52
- let i = 0;
53
- let cx = 0;
54
- let cy = 0;
55
- let sx = 0;
56
- let sy = 0;
57
- let cur = null;
58
- let cmd = "";
59
- let px = 0;
60
- let py = 0;
61
- let prev = "";
62
- let started = false;
63
- const err = (msg) => {
64
- throw new Error(`morphicons: ${msg} at d[${i}]`);
65
- };
66
- const isDigit = (c) => c >= 48 && c <= 57;
67
- const skip = () => {
68
- while (i < n) {
69
- const c = d.charCodeAt(i);
70
- if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12 || c === 44) i++;
71
- else break;
72
- }
73
- };
74
- const num = () => {
75
- skip();
76
- const start = i;
77
- if (i < n && (d[i] === "+" || d[i] === "-")) i++;
78
- let dig = false;
79
- while (i < n && isDigit(d.charCodeAt(i))) {
80
- i++;
81
- dig = true;
82
- }
83
- if (i < n && d[i] === ".") {
84
- i++;
85
- while (i < n && isDigit(d.charCodeAt(i))) {
86
- i++;
87
- dig = true;
88
- }
89
- }
90
- if (!dig) err("expected number");
91
- if (i < n && (d[i] === "e" || d[i] === "E")) {
92
- const save = i;
93
- i++;
94
- if (i < n && (d[i] === "+" || d[i] === "-")) i++;
95
- let ed = false;
96
- while (i < n && isDigit(d.charCodeAt(i))) {
97
- i++;
98
- ed = true;
99
- }
100
- if (!ed) i = save;
101
- }
102
- return Number(d.slice(start, i));
103
- };
104
- const flag = () => {
105
- skip();
106
- const c = d[i];
107
- if (c === "0" || c === "1") {
108
- i++;
109
- return c === "1" ? 1 : 0;
110
- }
111
- return err("expected arc flag (0|1)");
112
- };
113
- const open = () => {
114
- if (!started) err("path must start with M/m");
115
- if (!cur) {
116
- cur = {
117
- x0: cx,
118
- y0: cy,
119
- segs: [],
120
- closed: false
121
- };
122
- subs.push(cur);
123
- }
124
- return cur;
125
- };
126
- let rel = false;
127
- const nx = () => num() + (rel ? cx : 0);
128
- const ny = () => num() + (rel ? cy : 0);
129
- while (true) {
130
- skip();
131
- if (i >= n) break;
132
- const ch = d[i];
133
- if (COMMANDS.includes(ch)) {
134
- cmd = ch;
135
- i++;
136
- } else if (cmd === "") err("path must start with M/m");
137
- else if (cmd === "M") cmd = "L";
138
- else if (cmd === "m") cmd = "l";
139
- else if (cmd === "Z" || cmd === "z") err("stray data after Z");
140
- rel = cmd >= "a";
141
- switch (rel ? cmd.toUpperCase() : cmd) {
142
- case "M": {
143
- started = true;
144
- const x = nx();
145
- const y = ny();
146
- cx = x;
147
- cy = y;
148
- sx = x;
149
- sy = y;
150
- cur = {
151
- x0: x,
152
- y0: y,
153
- segs: [],
154
- closed: false
155
- };
156
- subs.push(cur);
157
- prev = "";
158
- break;
159
- }
160
- case "L": {
161
- const x = nx();
162
- const y = ny();
163
- open().segs.push([
164
- "L",
165
- x,
166
- y
167
- ]);
168
- cx = x;
169
- cy = y;
170
- prev = "";
171
- break;
172
- }
173
- case "H": {
174
- const x = nx();
175
- open().segs.push([
176
- "L",
177
- x,
178
- cy
179
- ]);
180
- cx = x;
181
- prev = "";
182
- break;
183
- }
184
- case "V": {
185
- const y = ny();
186
- open().segs.push([
187
- "L",
188
- cx,
189
- y
190
- ]);
191
- cy = y;
192
- prev = "";
193
- break;
194
- }
195
- case "C":
196
- case "S": {
197
- let x1;
198
- let y1;
199
- if (cmd === "C" || cmd === "c") {
200
- x1 = nx();
201
- y1 = ny();
202
- } else {
203
- x1 = prev === "C" ? 2 * cx - px : cx;
204
- y1 = prev === "C" ? 2 * cy - py : cy;
205
- }
206
- const x2 = nx();
207
- const y2 = ny();
208
- const x = nx();
209
- const y = ny();
210
- open().segs.push([
211
- "C",
212
- x1,
213
- y1,
214
- x2,
215
- y2,
216
- x,
217
- y
218
- ]);
219
- px = x2;
220
- py = y2;
221
- cx = x;
222
- cy = y;
223
- prev = "C";
224
- break;
225
- }
226
- case "Q":
227
- case "T": {
228
- let x1;
229
- let y1;
230
- if (cmd === "Q" || cmd === "q") {
231
- x1 = nx();
232
- y1 = ny();
233
- } else {
234
- x1 = prev === "Q" ? 2 * cx - px : cx;
235
- y1 = prev === "Q" ? 2 * cy - py : cy;
236
- }
237
- const x = nx();
238
- const y = ny();
239
- open().segs.push([
240
- "Q",
241
- x1,
242
- y1,
243
- x,
244
- y
245
- ]);
246
- px = x1;
247
- py = y1;
248
- cx = x;
249
- cy = y;
250
- prev = "Q";
251
- break;
252
- }
253
- case "A": {
254
- const rx = num();
255
- const ry = num();
256
- const rot = num();
257
- const large = flag();
258
- const sweep = flag();
259
- const x = nx();
260
- const y = ny();
261
- open().segs.push([
262
- "A",
263
- rx,
264
- ry,
265
- rot,
266
- large,
267
- sweep,
268
- x,
269
- y
270
- ]);
271
- cx = x;
272
- cy = y;
273
- prev = "";
274
- break;
275
- }
276
- case "Z":
277
- if (cur) {
278
- cur.closed = true;
279
- cur = null;
280
- }
281
- cx = sx;
282
- cy = sy;
283
- prev = "";
284
- break;
285
- default: err(`unsupported command "${cmd}"`);
286
- }
287
- }
288
- return subs.filter((s) => s.segs.length > 0);
289
- }
290
- //#endregion
291
- //#region src/core/serialize.ts
292
- function fmt(v) {
293
- return String(Math.round(v * 100) / 100);
294
- }
295
- /** Sampled subpaths → polyline `d` attribute. `closed[k]` appends Z to
296
- * subpath k (closed loops in flight); without flags everything is open. */
297
- function serialize(subs, closed) {
298
- let d = "";
299
- for (let k = 0; k < subs.length; k++) {
300
- const o = subs[k];
301
- const n = o.length / 2;
302
- d += `M${fmt(o[0])} ${fmt(o[1])}`;
303
- for (let i = 1; i < n; i++) d += `L${fmt(o[2 * i])} ${fmt(o[2 * i + 1])}`;
304
- if (closed?.[k]) d += "Z";
305
- }
306
- return d;
307
- }
308
- function fmtCanon(v) {
309
- return String(Math.round(v * 1e4) / 1e4);
310
- }
311
- /** Cubic subpaths → canonical `d`, quantized to 4 decimals (engine-stable
312
- * bytes; see fmtCanon). */
313
- function cubicsToPathD(paths) {
314
- let d = "";
315
- for (const { pts, closed } of paths) {
316
- d += `M${fmtCanon(pts[0])} ${fmtCanon(pts[1])}`;
317
- for (let i = 2; i < pts.length; i += 6) d += `C${fmtCanon(pts[i])} ${fmtCanon(pts[i + 1])} ${fmtCanon(pts[i + 2])} ${fmtCanon(pts[i + 3])} ${fmtCanon(pts[i + 4])} ${fmtCanon(pts[i + 5])}`;
318
- if (closed) d += "Z";
319
- }
320
- return d;
321
- }
322
- //#endregion
323
- //#region src/core/normalize.ts
324
- /** Control-point offset for a quarter circle: (4/3)·tan(π/8) ≈ 0.5523. */
325
- const KAPPA = 4 / 3 * Math.tan(Math.PI / 8);
326
- const TAU = 2 * Math.PI;
327
- function builder(x0, y0) {
328
- const pts = [x0, y0];
329
- let cx = x0;
330
- let cy = y0;
331
- const cubic = (x1, y1, x2, y2, x, y) => {
332
- pts.push(x1, y1, x2, y2, x, y);
333
- cx = x;
334
- cy = y;
335
- };
336
- const line = (x, y) => {
337
- if (Math.abs(x - cx) < 1e-12 && Math.abs(y - cy) < 1e-12) return;
338
- cubic(cx + (x - cx) / 3, cy + (y - cy) / 3, cx + 2 * (x - cx) / 3, cy + 2 * (y - cy) / 3, x, y);
339
- };
340
- const quad = (x1, y1, x, y) => {
341
- cubic(cx + 2 / 3 * (x1 - cx), cy + 2 / 3 * (y1 - cy), x + 2 / 3 * (x1 - x), y + 2 / 3 * (y1 - y), x, y);
342
- };
343
- const arc = (rx0, ry0, rotDeg, large, sweep, x, y) => {
344
- const x1 = cx;
345
- const y1 = cy;
346
- if (Math.abs(x - x1) < 1e-12 && Math.abs(y - y1) < 1e-12) return;
347
- let rx = Math.abs(rx0);
348
- let ry = Math.abs(ry0);
349
- if (rx < 1e-12 || ry < 1e-12) {
350
- line(x, y);
351
- return;
352
- }
353
- const phi = rotDeg * Math.PI / 180;
354
- const cosP = Math.cos(phi);
355
- const sinP = Math.sin(phi);
356
- const hx = (x1 - x) / 2;
357
- const hy = (y1 - y) / 2;
358
- const x1p = cosP * hx + sinP * hy;
359
- const y1p = -sinP * hx + cosP * hy;
360
- const lam = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
361
- if (lam > 1) {
362
- const s = Math.sqrt(lam);
363
- rx *= s;
364
- ry *= s;
365
- }
366
- const rx2 = rx * rx;
367
- const ry2 = ry * ry;
368
- const xp2 = x1p * x1p;
369
- const yp2 = y1p * y1p;
370
- let rad = (rx2 * ry2 - rx2 * yp2 - ry2 * xp2) / (rx2 * yp2 + ry2 * xp2);
371
- if (rad < 0) rad = 0;
372
- const co = (large === sweep ? -1 : 1) * Math.sqrt(rad);
373
- const cxp = co * rx * y1p / ry;
374
- const cyp = -co * ry * x1p / rx;
375
- const ccx = cosP * cxp - sinP * cyp + (x1 + x) / 2;
376
- const ccy = sinP * cxp + cosP * cyp + (y1 + y) / 2;
377
- const th1 = Math.atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
378
- let dth = Math.atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx) - th1;
379
- if (sweep === 0 && dth > 0) dth -= TAU;
380
- else if (sweep === 1 && dth < 0) dth += TAU;
381
- const slices = Math.max(1, Math.ceil(Math.abs(dth) / (Math.PI / 2) - 1e-9));
382
- const delta = dth / slices;
383
- const alpha = 4 / 3 * Math.tan(delta / 4);
384
- const ex = (t) => ccx + rx * Math.cos(t) * cosP - ry * Math.sin(t) * sinP;
385
- const ey = (t) => ccy + rx * Math.cos(t) * sinP + ry * Math.sin(t) * cosP;
386
- const dx = (t) => -rx * Math.sin(t) * cosP - ry * Math.cos(t) * sinP;
387
- const dy = (t) => -rx * Math.sin(t) * sinP + ry * Math.cos(t) * cosP;
388
- let t0 = th1;
389
- let p0x = x1;
390
- let p0y = y1;
391
- for (let s = 1; s <= slices; s++) {
392
- const t1 = th1 + delta * s;
393
- const p1x = s === slices ? x : ex(t1);
394
- const p1y = s === slices ? y : ey(t1);
395
- cubic(p0x + alpha * dx(t0), p0y + alpha * dy(t0), p1x - alpha * dx(t1), p1y - alpha * dy(t1), p1x, p1y);
396
- t0 = t1;
397
- p0x = p1x;
398
- p0y = p1y;
399
- }
400
- };
401
- const finish = (closed) => {
402
- if (closed) line(pts[0], pts[1]);
403
- if (pts.length < 8) return null;
404
- return {
405
- pts: Float64Array.from(pts),
406
- closed
407
- };
408
- };
409
- return [
410
- cubic,
411
- line,
412
- quad,
413
- arc,
414
- finish
415
- ];
416
- }
417
- function lowerSubpath(raw) {
418
- const [cubic, line, quad, arc, finish] = builder(raw.x0, raw.y0);
419
- for (const s of raw.segs) switch (s[0]) {
420
- case "L":
421
- line(s[1], s[2]);
422
- break;
423
- case "C":
424
- cubic(s[1], s[2], s[3], s[4], s[5], s[6]);
425
- break;
426
- case "Q":
427
- quad(s[1], s[2], s[3], s[4]);
428
- break;
429
- case "A": arc(s[1], s[2], s[3], s[4], s[5], s[6], s[7]);
430
- }
431
- return finish(raw.closed);
432
- }
433
- function attrNum(attrs, key, fallback = 0) {
434
- const v = attrs[key];
435
- if (v === void 0) return fallback;
436
- const x = typeof v === "number" ? v : Number(v);
437
- return Number.isFinite(x) ? x : fallback;
438
- }
439
- function parsePoints(v) {
440
- const s = String(v ?? "").trim();
441
- if (!s) return [];
442
- const nums = s.split(/[\s,]+/).map(Number);
443
- if (nums.some((x) => !Number.isFinite(x))) throw new Error(`morphicons: invalid points: "${s}"`);
444
- return nums;
445
- }
446
- function polyPath(nums, closed) {
447
- if (nums.length < 4) return null;
448
- const [, line, , , finish] = builder(nums[0], nums[1]);
449
- for (let i = 2; i + 1 < nums.length; i += 2) line(nums[i], nums[i + 1]);
450
- return finish(closed);
451
- }
452
- function ellipsePath(cx, cy, rx, ry) {
453
- if (rx < 1e-12 || ry < 1e-12) return null;
454
- const kx = KAPPA * rx;
455
- const ky = KAPPA * ry;
456
- const e = cx + rx;
457
- const w = cx - rx;
458
- const s = cy + ry;
459
- const n = cy - ry;
460
- const [cubic, , , , finish] = builder(e, cy);
461
- cubic(e, cy + ky, cx + kx, s, cx, s);
462
- cubic(cx - kx, s, w, cy + ky, w, cy);
463
- cubic(w, cy - ky, cx - kx, n, cx, n);
464
- cubic(cx + kx, n, e, cy - ky, e, cy);
465
- return finish(true);
466
- }
467
- function rectPath(attrs) {
468
- const x = attrNum(attrs, "x");
469
- const y = attrNum(attrs, "y");
470
- const w = attrNum(attrs, "width");
471
- const h = attrNum(attrs, "height");
472
- if (w < 1e-12 || h < 1e-12) return null;
473
- let rx = attrNum(attrs, "rx", NaN);
474
- let ry = attrNum(attrs, "ry", NaN);
475
- if (Number.isNaN(rx)) rx = Number.isNaN(ry) ? 0 : ry;
476
- if (Number.isNaN(ry)) ry = rx;
477
- rx = Math.min(Math.max(rx, 0), w / 2);
478
- ry = Math.min(Math.max(ry, 0), h / 2);
479
- if (rx < 1e-12 || ry < 1e-12) return polyPath([
480
- x,
481
- y,
482
- x + w,
483
- y,
484
- x + w,
485
- y + h,
486
- x,
487
- y + h
488
- ], true);
489
- const xa = x + rx;
490
- const xb = x + w - rx;
491
- const xr = x + w;
492
- const ya = y + ry;
493
- const yb = y + h - ry;
494
- const yd = y + h;
495
- const kx = KAPPA * rx;
496
- const ky = KAPPA * ry;
497
- const [cubic, line, , , finish] = builder(xa, y);
498
- line(xb, y);
499
- cubic(xb + kx, y, xr, ya - ky, xr, ya);
500
- line(xr, yb);
501
- cubic(xr, yb + ky, xb + kx, yd, xb, yd);
502
- line(xa, yd);
503
- cubic(xa - kx, yd, x, yb + ky, x, yb);
504
- line(x, ya);
505
- cubic(x, ya - ky, xa - kx, y, xa, y);
506
- return finish(true);
507
- }
508
- /** Icon (IconNode or `d` string) → list of cubic subpaths. */
509
- function iconToCubics(input) {
510
- const out = [];
511
- const push = (p) => {
512
- if (p) out.push(p);
513
- };
514
- if (typeof input === "string") {
515
- for (const s of parsePath(input)) push(lowerSubpath(s));
516
- return out;
517
- }
518
- for (const [tag, attrs] of input) switch (tag) {
519
- case "path":
520
- for (const s of parsePath(String(attrs.d ?? ""))) push(lowerSubpath(s));
521
- break;
522
- case "line": {
523
- const [, line, , , finish] = builder(attrNum(attrs, "x1"), attrNum(attrs, "y1"));
524
- line(attrNum(attrs, "x2"), attrNum(attrs, "y2"));
525
- push(finish(false));
526
- break;
527
- }
528
- case "circle": {
529
- const r = attrNum(attrs, "r");
530
- push(ellipsePath(attrNum(attrs, "cx"), attrNum(attrs, "cy"), r, r));
531
- break;
532
- }
533
- case "ellipse":
534
- push(ellipsePath(attrNum(attrs, "cx"), attrNum(attrs, "cy"), attrNum(attrs, "rx"), attrNum(attrs, "ry")));
535
- break;
536
- case "rect":
537
- push(rectPath(attrs));
538
- break;
539
- case "polyline":
540
- push(polyPath(parsePoints(attrs.points), false));
541
- break;
542
- case "polygon":
543
- push(polyPath(parsePoints(attrs.points), true));
544
- break;
545
- default: throw new Error(`morphicons: unsupported tag <${tag}>`);
546
- }
547
- return out;
548
- }
549
- function parseViewBox(vb) {
550
- const v = typeof vb === "number" ? [
551
- 0,
552
- 0,
553
- vb,
554
- vb
555
- ] : typeof vb === "string" ? vb.trim().split(/[\s,]+/).map(Number) : vb;
556
- const [minX, minY, w, h] = v;
557
- if (v.length !== 4 || !(w > 0) || !(h > 0) || !Number.isFinite(minX) || !Number.isFinite(minY)) throw new Error(`morphicons: invalid viewBox "${String(vb)}"`);
558
- return [
559
- minX,
560
- minY,
561
- w,
562
- h
563
- ];
564
- }
565
- /** Re-grids an icon drawn on `viewBox` onto the shared `grid` (24 by default),
566
- * centred and preserving aspect ratio — the SVG `xMidYMid meet` rule.
567
- *
568
- * Both endpoints of a morph must live on the same coordinate space. Lucide and
569
- * Tabler already draw on 24×24; packs on 20 (Heroicons solid) or 32 (Carbon)
570
- * do not, and mixing them unfitted makes Procrustes read the scale/offset gap
571
- * as rotation. Apply once at module scope (not per render) and pass the
572
- * resulting `d` anywhere an icon is accepted. */
573
- function fitIcon(input, viewBox, grid = 24) {
574
- const [minX, minY, w, h] = parseViewBox(viewBox);
575
- const s = Math.min(grid / w, grid / h);
576
- const tx = (grid - w * s) / 2 - minX * s;
577
- const ty = (grid - h * s) / 2 - minY * s;
578
- const paths = iconToCubics(input);
579
- for (const { pts } of paths) for (let i = 0; i < pts.length; i += 2) {
580
- pts[i] = pts[i] * s + tx;
581
- pts[i + 1] = pts[i + 1] * s + ty;
582
- }
583
- return cubicsToPathD(paths);
584
- }
585
- //#endregion
586
48
  //#region src/core/plan.ts
587
49
  /** Weight of |ΔL| in the subpath pairing cost. */
588
50
  const LEN_WEIGHT = .35;
@@ -1158,4 +620,4 @@ const SPRING_PRESETS = {
1158
620
  }
1159
621
  };
1160
622
  //#endregion
1161
- export { buildPlan as a, cubicsToPathD as c, interpLinear as d, interpPolar as f, resamplePath as i, serialize as l, Spring as n, fitIcon as o, resampleIcon as r, iconToCubics as s, SPRING_PRESETS as t, allocOutputs as u };
623
+ export { buildPlan as a, interpPolar as c, resamplePath as i, Spring as n, allocOutputs as o, resampleIcon as r, interpLinear as s, SPRING_PRESETS as t };
@@ -1,5 +1,5 @@
1
1
  import { a as Sampled, c as SpringPreset, n as IconInput, r as IconNode } from "./types-JAF3s76Y.js";
2
- import { Morph, MorphOptions, PathEl } from "./dom.js";
2
+ import { a as ReducedMotionMode, i as PathEl, n as Morph, r as MorphOptions } from "./index-DXp5b6BU.js";
3
3
  import { SVGAttributes } from "svelte/elements";
4
4
  //#region src/svelte/shared.d.ts
5
5
  /** Imperative surface exposed on the component instance (`bind:this`). */
@@ -22,6 +22,10 @@ interface MorphIconProps extends Omit<SVGAttributes<SVGSVGElement>, "from" | "to
22
22
  progress?: number;
23
23
  /** Physics for uncontrolled/imperative mode: preset or custom spring. */
24
24
  spring?: SpringPreset | MorphOptions;
25
+ /** Reduced-motion policy: "never" (default) animates regardless of the OS
26
+ * setting, "user" honors prefers-reduced-motion (morphs degrade to an
27
+ * instant swap while it is on), "always" always jumps. */
28
+ reducedMotion?: ReducedMotionMode;
25
29
  size?: number | string;
26
30
  color?: string;
27
31
  strokeWidth?: number | string;
@@ -31,7 +35,8 @@ interface MorphIconProps extends Omit<SVGAttributes<SVGSVGElement>, "from" | "to
31
35
  label?: string;
32
36
  }
33
37
  type ModeProps = Pick<MorphIconProps, "icon" | "from" | "to" | "progress">;
34
- type WatchProps = ModeProps & {
38
+ type CtrlProps = ModeProps & Pick<MorphIconProps, "reducedMotion">;
39
+ type WatchProps = CtrlProps & {
35
40
  spring?: SpringPreset | MorphOptions;
36
41
  };
37
42
  /** The initial d is constant for Svelte: computed once from the mount-time
@@ -54,15 +59,15 @@ declare function computeInitialD({ icon, from, to, progress }: ModeProps): strin
54
59
  * path back to `icon`.
55
60
  * - Every exit from controlled mode (imperative call or icon takeover)
56
61
  * invalidates the frozen pair, so returning to it re-bases on `from`. */
57
- declare function createController({ icon, from, to, progress }: ModeProps): {
58
- mount(mountEl: PathEl, { icon, from, to, progress }: ModeProps): void;
62
+ declare function createController({ icon, from, to, progress, reducedMotion }: CtrlProps): {
63
+ mount(mountEl: PathEl, { icon, from, to, progress, reducedMotion }: CtrlProps): void;
59
64
  destroy(): void;
60
65
  /** Prop watcher: ONE owner decides per run — the controlled pair while it
61
66
  * is fully present, `icon` otherwise (mount doesn't fire thanks to the
62
67
  * init-time baselines). */
63
- watch({ icon, from, to, progress, spring }: WatchProps): void;
68
+ watch({ icon, from, to, progress, spring, reducedMotion }: WatchProps): void;
64
69
  morphTo(icon: IconInput, spring?: SpringPreset | MorphOptions): void;
65
70
  set(icon: IconInput): void;
66
71
  };
67
72
  //#endregion
68
- export { type IconInput, type IconNode, type Morph, MorphHandle, MorphIconProps, type MorphOptions, type PathEl, type Sampled, type SpringPreset, computeInitialD, createController };
73
+ export { type IconInput, type IconNode, type Morph, MorphHandle, MorphIconProps, type MorphOptions, type PathEl, type ReducedMotionMode, type Sampled, type SpringPreset, computeInitialD, createController };
@@ -1,4 +1,5 @@
1
- import { a as buildPlan, f as interpPolar, l as serialize, r as resampleIcon, u as allocOutputs } from "./spring-CBynweFS.js";
1
+ import { a as buildPlan, c as interpPolar, o as allocOutputs, r as resampleIcon } from "./spring-CFHloqPP.js";
2
+ import { i as serialize } from "./normalize-CYnN3Npw.js";
2
3
  import { canonicalD, createMorph } from "./dom.js";
3
4
  //#region src/svelte/shared.ts
4
5
  /** Frozen shape of the from→to pair at t, using the pure core (SSR-safe).
@@ -35,10 +36,11 @@ function computeInitialD({ icon, from, to, progress }) {
35
36
  * path back to `icon`.
36
37
  * - Every exit from controlled mode (imperative call or icon takeover)
37
38
  * invalidates the frozen pair, so returning to it re-bases on `from`. */
38
- function createController({ icon, from, to, progress }) {
39
+ function createController({ icon, from, to, progress, reducedMotion }) {
39
40
  let el = null;
40
41
  let dead = false;
41
42
  let morph = null;
43
+ let rm = reducedMotion ?? "never";
42
44
  let based = false;
43
45
  let pair = null;
44
46
  let prevIcon = icon;
@@ -50,7 +52,7 @@ function createController({ icon, from, to, progress }) {
50
52
  const ensure = (birth) => {
51
53
  if (morph) return morph;
52
54
  if (dead || !el) return null;
53
- morph = createMorph(el, birth);
55
+ morph = createMorph(el, birth, { reducedMotion: rm });
54
56
  return morph;
55
57
  };
56
58
  /** Controlled mode: freeze the pair at `progress` via seek (no spring). */
@@ -77,12 +79,13 @@ function createController({ icon, from, to, progress }) {
77
79
  }
78
80
  };
79
81
  return {
80
- mount(mountEl, { icon, from, to, progress }) {
82
+ mount(mountEl, { icon, from, to, progress, reducedMotion }) {
81
83
  el = mountEl;
84
+ rm = reducedMotion ?? rm;
82
85
  const controlled = from !== void 0 && to !== void 0;
83
86
  const initialIcon = icon ?? from ?? to;
84
87
  if (initialIcon === void 0) return;
85
- const m = createMorph(el, controlled ? from : initialIcon);
88
+ const m = createMorph(el, controlled ? from : initialIcon, { reducedMotion: rm });
86
89
  morph = m;
87
90
  if (controlled) {
88
91
  pair = [from, to];
@@ -106,7 +109,9 @@ function createController({ icon, from, to, progress }) {
106
109
  /** Prop watcher: ONE owner decides per run — the controlled pair while it
107
110
  * is fully present, `icon` otherwise (mount doesn't fire thanks to the
108
111
  * init-time baselines). */
109
- watch({ icon, from, to, progress, spring }) {
112
+ watch({ icon, from, to, progress, spring, reducedMotion }) {
113
+ rm = reducedMotion ?? "never";
114
+ if (morph) morph.reducedMotion = rm;
110
115
  const controlled = from !== void 0 && to !== void 0;
111
116
  const left = prevControlled && !controlled;
112
117
  const iconChanged = icon !== prevIcon;
package/dist/vue.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as Sampled, c as SpringPreset, n as IconInput, r as IconNode } from "./types-JAF3s76Y.js";
2
- import { Morph, MorphOptions, PathEl } from "./dom.js";
2
+ import { a as ReducedMotionMode, i as PathEl, n as Morph, r as MorphOptions } from "./index-DXp5b6BU.js";
3
3
  import { PropType } from "vue";
4
4
  //#region src/vue/index.d.ts
5
5
  /** Imperative surface exposed on the component instance (template ref). */
@@ -33,6 +33,13 @@ declare const MorphIcon: import("vue").DefineComponent<import("vue").ExtractProp
33
33
  type: PropType<SpringPreset | MorphOptions>;
34
34
  default: undefined;
35
35
  };
36
+ /** Reduced-motion policy: "never" (default) animates regardless of the OS
37
+ * setting, "user" honors prefers-reduced-motion (morphs degrade to an
38
+ * instant swap while it is on), "always" always jumps. */
39
+ reducedMotion: {
40
+ type: PropType<ReducedMotionMode>;
41
+ default: string;
42
+ };
36
43
  size: {
37
44
  type: (NumberConstructor | StringConstructor)[];
38
45
  default: number;
@@ -83,6 +90,13 @@ declare const MorphIcon: import("vue").DefineComponent<import("vue").ExtractProp
83
90
  type: PropType<SpringPreset | MorphOptions>;
84
91
  default: undefined;
85
92
  };
93
+ /** Reduced-motion policy: "never" (default) animates regardless of the OS
94
+ * setting, "user" honors prefers-reduced-motion (morphs degrade to an
95
+ * instant swap while it is on), "always" always jumps. */
96
+ reducedMotion: {
97
+ type: PropType<ReducedMotionMode>;
98
+ default: string;
99
+ };
86
100
  size: {
87
101
  type: (NumberConstructor | StringConstructor)[];
88
102
  default: number;
@@ -111,6 +125,7 @@ declare const MorphIcon: import("vue").DefineComponent<import("vue").ExtractProp
111
125
  to: IconInput;
112
126
  progress: number;
113
127
  spring: "bouncy" | "smooth" | "snappy" | MorphOptions;
128
+ reducedMotion: ReducedMotionMode;
114
129
  size: string | number;
115
130
  color: string;
116
131
  strokeWidth: string | number;
@@ -118,4 +133,4 @@ declare const MorphIcon: import("vue").DefineComponent<import("vue").ExtractProp
118
133
  label: string;
119
134
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
120
135
  //#endregion
121
- export { type IconInput, type IconNode, type Morph, MorphHandle, MorphIcon, type MorphOptions, type PathEl, type Sampled, type SpringPreset };
136
+ export { type IconInput, type IconNode, type Morph, MorphHandle, MorphIcon, type MorphOptions, type PathEl, type ReducedMotionMode, type Sampled, type SpringPreset };