morphicons 1.7.1 → 2.0.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.
@@ -12,6 +12,7 @@
12
12
  import { onMount, untrack } from "svelte";
13
13
  import {
14
14
  computeInitialD,
15
+ computeInitialPaint,
15
16
  createController,
16
17
  type IconInput,
17
18
  type MorphIconProps,
@@ -37,21 +38,34 @@ let {
37
38
  // svelte-ignore state_referenced_locally — mount-time value on purpose: the
38
39
  // initial d must stay a constant so Svelte never rewrites the attribute.
39
40
  const initialD = computeInitialD({ icon, from, to, progress });
41
+ const initialPaint = computeInitialPaint({ icon, from, to, progress });
40
42
 
41
43
  let pathEl: SVGPathElement | undefined = $state();
44
+ const sw = $derived(
45
+ absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
46
+ );
47
+
42
48
  // svelte-ignore state_referenced_locally — init-time values on purpose: they
43
49
  // seed the controller's watch baselines so the first $effect run is a no-op.
44
- const ctrl = createController({ icon, from, to, progress, reducedMotion });
50
+ const ctrl = createController({
51
+ icon,
52
+ from,
53
+ to,
54
+ progress,
55
+ reducedMotion,
56
+ strokeWidth: sw,
57
+ });
45
58
 
46
59
  onMount(() => {
47
- if (pathEl) ctrl.mount(pathEl, { icon, from, to, progress, reducedMotion });
60
+ if (pathEl)
61
+ ctrl.mount(pathEl, { icon, from, to, progress, reducedMotion, strokeWidth: sw });
48
62
  return () => ctrl.destroy();
49
63
  });
50
64
 
51
65
  // Prop changes: one watcher owns the mode logic (controlled wins — change
52
66
  // detection and precedence live in shared.ts).
53
67
  $effect(() => {
54
- const next = { icon, from, to, progress, spring, reducedMotion };
68
+ const next = { icon, from, to, progress, spring, reducedMotion, strokeWidth: sw };
55
69
  untrack(() => ctrl.watch(next));
56
70
  });
57
71
 
@@ -62,10 +76,6 @@ export function morphTo(i: IconInput, s?: SpringPreset | MorphOptions): void {
62
76
  export function set(i: IconInput): void {
63
77
  ctrl.set(i);
64
78
  }
65
-
66
- const sw = $derived(
67
- absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
68
- );
69
79
  </script>
70
80
 
71
81
  <svg
@@ -75,6 +85,7 @@ const sw = $derived(
75
85
  viewBox="0 0 24 24"
76
86
  fill="none"
77
87
  stroke={color}
88
+ {color}
78
89
  stroke-width={sw}
79
90
  stroke-linecap="round"
80
91
  stroke-linejoin="round"
@@ -83,5 +94,5 @@ const sw = $derived(
83
94
  {...rest}
84
95
  >
85
96
  {#if label}<title>{label}</title>{/if}
86
- <path bind:this={pathEl} d={initialD} />
97
+ <path bind:this={pathEl} d={initialD} {...initialPaint} />
87
98
  </svg>
@@ -1,5 +1,5 @@
1
- import { n as IconInput } from "./types-JAF3s76Y.js";
2
- import { i as PathEl } from "./index-DXp5b6BU.js";
1
+ import { a as IconInput } from "./plan-DhaDhEVq.js";
2
+ import { i as PathEl } from "./index-DpvKR75N.js";
3
3
  //#region src/adapters/canvas.d.ts
4
4
  /** The 2D context surface the target drives — structural, so a real
5
5
  * CanvasRenderingContext2D, an OffscreenCanvasRenderingContext2D or a test
@@ -42,7 +42,7 @@ interface CanvasTargetOptions {
42
42
  * the morph's geometry onto the canvas, so
43
43
  * `createMorph(canvasTarget(canvas), icon)` animates pixels instead of an
44
44
  * inline `<svg><path>`. */
45
- declare function canvasTarget(surface: CanvasSurface | Canvas2DContext, opts?: CanvasTargetOptions): PathEl;
45
+ export declare function canvasTarget(surface: CanvasSurface | Canvas2DContext, opts?: CanvasTargetOptions): PathEl;
46
46
  //#endregion
47
47
  //#region src/adapters/mask.d.ts
48
48
  /** The subset of an element's inline style the mask target writes — structural
@@ -85,15 +85,16 @@ interface MaskPathEl extends PathEl {
85
85
  * the back buffer and re-points `mask-image` at it: the property VALUE flips
86
86
  * (`url(#a)` ↔ `url(#b)`) each frame, which forces reference re-resolution
87
87
  * in every engine. Chromium/Gecko pay nothing extra for it. */
88
- declare function maskTarget(el: MaskEl, opts?: MaskTargetOptions): MaskPathEl;
88
+ export declare function maskTarget(el: MaskEl, opts?: MaskTargetOptions): MaskPathEl;
89
89
  //#endregion
90
90
  //#region src/adapters/svg.d.ts
91
91
  /** SVG markup → an IconInput the pipeline can lower. Strips non-rendered
92
- * containers, rejects what can't be honestly morphed (a fill-drawn icon with
93
- * no stroke, a transform, an unsupported element), and re-grids onto 24 via
94
- * the viewBox when one is present — so an off-grid collection lands on the
95
- * shared grid automatically. Returns a `d` (when a viewBox forced a fit) or
96
- * an IconNode. */
97
- declare function svgToIcon(markup: string): IconInput;
92
+ * containers, rejects what it can't read (an unsupported element, empty
93
+ * geometry) and re-grids onto 24 via the viewBox when one is present.
94
+ * Stroke icons return a `d` (when a viewBox forced a fit) or an IconNode,
95
+ * exactly as before; fill-drawn icons return an IconNode whose elements
96
+ * carry their resolved paint (fill/stroke/stroke-width/fill-rule), which
97
+ * is what makes the morph fly as a region. */
98
+ export declare function svgToIcon(markup: string): IconInput;
98
99
  //#endregion
99
- export { type Canvas2DContext, type CanvasSurface, type CanvasTargetOptions, type MaskEl, type MaskPathEl, type MaskStyle, type MaskTargetOptions, canvasTarget, maskTarget, svgToIcon };
100
+ export type { Canvas2DContext, CanvasSurface, CanvasTargetOptions, MaskEl, MaskPathEl, MaskStyle, MaskTargetOptions };
package/dist/adapters.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as fitIcon } from "./normalize-CYnN3Npw.js";
1
+ import { n as iconToCubics, r as cubicsToPathD, t as fitIcon } from "./normalize-CYnN3Npw.js";
2
2
  //#region src/adapters/canvas.ts
3
3
  /** Adapts a canvas (or its 2D context) into a `PathEl` whose writes stroke
4
4
  * the morph's geometry onto the canvas, so
@@ -118,7 +118,7 @@ function maskTarget(el, opts = {}) {
118
118
  //#endregion
119
119
  //#region src/adapters/svg.ts
120
120
  const SVG_CONTAINER = /<(defs|mask|clipPath|symbol)\b[^>]*>[\s\S]*?<\/\1>/gi;
121
- const SVG_ELEMENT = /<([a-zA-Z][\w:-]*)((?:[^>"']|"[^"]*"|'[^']*')*?)\/?>/g;
121
+ const SVG_TAG = /<(\/?)([a-zA-Z][\w:-]*)((?:[^>"']|"[^"]*"|'[^']*')*?)(\/?)>/g;
122
122
  const SVG_ATTR = /([\w:-]+)\s*=\s*"([^"]*)"|([\w:-]+)\s*=\s*'([^']*)'/g;
123
123
  const SVG_VIEWBOX = /<svg\b[^>]*\bviewBox\s*=\s*["']([^"']+)["']/i;
124
124
  const SVG_GEOMETRY = /* @__PURE__ */ new Set([
@@ -130,9 +130,12 @@ const SVG_GEOMETRY = /* @__PURE__ */ new Set([
130
130
  "polyline",
131
131
  "polygon"
132
132
  ]);
133
- const SVG_SKIP = /* @__PURE__ */ new Set([
133
+ const SVG_GROUP = /* @__PURE__ */ new Set([
134
134
  "svg",
135
135
  "g",
136
+ "a"
137
+ ]);
138
+ const SVG_SKIP = /* @__PURE__ */ new Set([
136
139
  "title",
137
140
  "desc",
138
141
  "defs",
@@ -140,6 +143,16 @@ const SVG_SKIP = /* @__PURE__ */ new Set([
140
143
  "clippath",
141
144
  "symbol"
142
145
  ]);
146
+ const PAINT = [
147
+ "fill",
148
+ "stroke",
149
+ "stroke-width",
150
+ "fill-rule",
151
+ "opacity",
152
+ "fill-opacity",
153
+ "visibility",
154
+ "display"
155
+ ];
143
156
  function readSvgAttrs(raw) {
144
157
  const attrs = {};
145
158
  SVG_ATTR.lastIndex = 0;
@@ -150,32 +163,219 @@ function readSvgAttrs(raw) {
150
163
  }
151
164
  return attrs;
152
165
  }
166
+ function paintOf(parent, attrs) {
167
+ const out = { ...parent };
168
+ for (const k of PAINT) if (attrs[k] !== void 0) out[k] = String(attrs[k]);
169
+ for (const decl of String(attrs.style ?? "").split(";")) {
170
+ const i = decl.indexOf(":");
171
+ const k = decl.slice(0, i).trim().toLowerCase();
172
+ if (i > 0 && PAINT.includes(k)) out[k] = decl.slice(i + 1).trim();
173
+ }
174
+ return out;
175
+ }
176
+ const mul = (m, n) => [
177
+ m[0] * n[0] + m[2] * n[1],
178
+ m[1] * n[0] + m[3] * n[1],
179
+ m[0] * n[2] + m[2] * n[3],
180
+ m[1] * n[2] + m[3] * n[3],
181
+ m[0] * n[4] + m[2] * n[5] + m[4],
182
+ m[1] * n[4] + m[3] * n[5] + m[5]
183
+ ];
184
+ function parseTransform(v) {
185
+ const s = String(v ?? "").trim();
186
+ if (!s) return null;
187
+ let m = [
188
+ 1,
189
+ 0,
190
+ 0,
191
+ 1,
192
+ 0,
193
+ 0
194
+ ];
195
+ for (const [, fn, args] of s.matchAll(/(\w+)\s*\(([^)]*)\)/g)) {
196
+ const a = args.trim().split(/[\s,]+/).map(Number);
197
+ const rad = (a[0] ?? 0) * Math.PI / 180;
198
+ let t;
199
+ switch (fn) {
200
+ case "matrix":
201
+ t = [
202
+ a[0],
203
+ a[1],
204
+ a[2],
205
+ a[3],
206
+ a[4],
207
+ a[5]
208
+ ];
209
+ break;
210
+ case "translate":
211
+ t = [
212
+ 1,
213
+ 0,
214
+ 0,
215
+ 1,
216
+ a[0],
217
+ a[1] ?? 0
218
+ ];
219
+ break;
220
+ case "scale":
221
+ t = [
222
+ a[0],
223
+ 0,
224
+ 0,
225
+ a[1] ?? a[0],
226
+ 0,
227
+ 0
228
+ ];
229
+ break;
230
+ case "rotate": {
231
+ const c = Math.cos(rad);
232
+ const n = Math.sin(rad);
233
+ const [cx = 0, cy = 0] = [a[1], a[2]];
234
+ t = [
235
+ c,
236
+ n,
237
+ -n,
238
+ c,
239
+ cx - c * cx + n * cy,
240
+ cy - n * cx - c * cy
241
+ ];
242
+ break;
243
+ }
244
+ case "skewX":
245
+ t = [
246
+ 1,
247
+ 0,
248
+ Math.tan(rad),
249
+ 1,
250
+ 0,
251
+ 0
252
+ ];
253
+ break;
254
+ case "skewY":
255
+ t = [
256
+ 1,
257
+ Math.tan(rad),
258
+ 0,
259
+ 1,
260
+ 0,
261
+ 0
262
+ ];
263
+ break;
264
+ default: throw new Error(`morphicons: unsupported transform "${fn}"`);
265
+ }
266
+ if (t.some((x) => !Number.isFinite(x))) throw new Error(`morphicons: invalid transform "${s}"`);
267
+ m = mul(m, t);
268
+ }
269
+ return m;
270
+ }
271
+ function bake(tag, attrs, m) {
272
+ const paths = iconToCubics([[tag, attrs]]);
273
+ for (const { pts } of paths) for (let i = 0; i < pts.length; i += 2) {
274
+ const x = pts[i];
275
+ const y = pts[i + 1];
276
+ pts[i] = m[0] * x + m[2] * y + m[4];
277
+ pts[i + 1] = m[1] * x + m[3] * y + m[5];
278
+ }
279
+ return cubicsToPathD(paths);
280
+ }
281
+ function stripComments(markup) {
282
+ let out = markup;
283
+ let prev;
284
+ do {
285
+ prev = out;
286
+ out = out.replace(/<!--[\s\S]*?-->/g, "");
287
+ } while (out !== prev);
288
+ const open = out.indexOf("<!--");
289
+ return open === -1 ? out : out.slice(0, open);
290
+ }
291
+ const painted = (v) => v !== void 0 && v.trim().toLowerCase() !== "none";
153
292
  /** SVG markup → an IconInput the pipeline can lower. Strips non-rendered
154
- * containers, rejects what can't be honestly morphed (a fill-drawn icon with
155
- * no stroke, a transform, an unsupported element), and re-grids onto 24 via
156
- * the viewBox when one is present — so an off-grid collection lands on the
157
- * shared grid automatically. Returns a `d` (when a viewBox forced a fit) or
158
- * an IconNode. */
293
+ * containers, rejects what it can't read (an unsupported element, empty
294
+ * geometry) and re-grids onto 24 via the viewBox when one is present.
295
+ * Stroke icons return a `d` (when a viewBox forced a fit) or an IconNode,
296
+ * exactly as before; fill-drawn icons return an IconNode whose elements
297
+ * carry their resolved paint (fill/stroke/stroke-width/fill-rule), which
298
+ * is what makes the morph fly as a region. */
159
299
  function svgToIcon(markup) {
160
300
  if (markup.trimStart().charCodeAt(0) !== 60) throw new Error("morphicons: svgToIcon expects SVG markup — a d string or IconNode is already an IconInput");
161
- const body = markup.replace(SVG_CONTAINER, "");
162
- const hasStroke = /stroke\s*=\s*["'](?!\s*none\s*["'])/i.test(body);
163
- const fillNone = /fill\s*=\s*["']\s*none\s*["']/i.test(body);
164
- if (!hasStroke && !fillNone) throw new Error("morphicons: SVG looks fill-drawn (no stroke) — only stroke-centerline icons morph");
165
- const node = [];
166
- SVG_ELEMENT.lastIndex = 0;
167
- let m;
168
- while ((m = SVG_ELEMENT.exec(body)) !== null) {
169
- const tag = m[1].toLowerCase();
301
+ const clean = stripComments(markup);
302
+ if (/<style\b/i.test(clean)) throw new Error("morphicons: unsupported SVG element <style>");
303
+ const body = clean.replace(SVG_CONTAINER, "");
304
+ const found = [];
305
+ const stack = [{
306
+ paint: {},
307
+ m: null
308
+ }];
309
+ SVG_TAG.lastIndex = 0;
310
+ let mt;
311
+ while ((mt = SVG_TAG.exec(body)) !== null) {
312
+ const tag = mt[2].toLowerCase();
313
+ const top = stack[stack.length - 1];
314
+ if (mt[1]) {
315
+ if (SVG_GROUP.has(tag) && stack.length > 1) stack.pop();
316
+ continue;
317
+ }
170
318
  if (SVG_SKIP.has(tag)) continue;
319
+ const attrs = readSvgAttrs(mt[3]);
320
+ const own = parseTransform(attrs.transform);
321
+ const m = own ? top.m ? mul(top.m, own) : own : top.m;
322
+ const paint = paintOf(top.paint, attrs);
323
+ if (top.paint.display === "none") paint.display = "none";
324
+ if (SVG_GROUP.has(tag)) {
325
+ if (!mt[4]) stack.push({
326
+ paint,
327
+ m
328
+ });
329
+ continue;
330
+ }
171
331
  if (!SVG_GEOMETRY.has(tag)) throw new Error(`morphicons: unsupported SVG element <${tag}>`);
172
- const attrs = readSvgAttrs(m[2]);
173
- if ("transform" in attrs) throw new Error(`morphicons: <${tag}> has a transform (unsupported)`);
174
- node.push([tag, attrs]);
332
+ if (paint.display === "none" || /^(hidden|collapse)$/.test(paint.visibility ?? "")) continue;
333
+ found.push({
334
+ tag,
335
+ attrs,
336
+ paint,
337
+ m
338
+ });
339
+ }
340
+ if (found.length === 0) throw new Error("morphicons: no morphable geometry in SVG");
341
+ const fills = found.map(({ paint }) => paint.fill !== void 0 ? painted(paint.fill) : !painted(paint.stroke));
342
+ const vb = SVG_VIEWBOX.exec(clean);
343
+ if (!fills.some(Boolean)) {
344
+ const node = found.map(({ tag, attrs, m }) => {
345
+ if (!m) return [tag, attrs];
346
+ const { transform: _, ...rest } = attrs;
347
+ return ["path", {
348
+ ...rest,
349
+ d: bake(tag, attrs, m)
350
+ }];
351
+ });
352
+ return vb ? fitIcon(node, vb[1]) : node;
175
353
  }
176
- if (node.length === 0) throw new Error("morphicons: no morphable geometry in SVG");
177
- const vb = SVG_VIEWBOX.exec(markup);
178
- return vb ? fitIcon(node, vb[1]) : node;
354
+ let scale = 1;
355
+ if (vb) {
356
+ const [, , w, h] = vb[1].trim().split(/[\s,]+/).map(Number);
357
+ scale = Math.min(24 / w, 24 / h);
358
+ }
359
+ return found.map(({ tag, attrs, paint, m }, i) => {
360
+ const d = m ? bake(tag, attrs, m) : tag === "path" ? String(attrs.d ?? "") : bake(tag, attrs, [
361
+ 1,
362
+ 0,
363
+ 0,
364
+ 1,
365
+ 0,
366
+ 0
367
+ ]);
368
+ const out = {
369
+ d: vb ? fitIcon(d, vb[1]) : d,
370
+ fill: fills[i] ? "currentColor" : "none",
371
+ stroke: painted(paint.stroke) ? "currentColor" : "none"
372
+ };
373
+ if (painted(paint.stroke)) out["stroke-width"] = String(Number.parseFloat(paint["stroke-width"] ?? "1") * scale);
374
+ if (paint["fill-rule"]) out["fill-rule"] = paint["fill-rule"];
375
+ if (paint.opacity) out.opacity = paint.opacity;
376
+ if (paint["fill-opacity"]) out["fill-opacity"] = paint["fill-opacity"];
377
+ return ["path", out];
378
+ });
179
379
  }
180
380
  //#endregion
181
381
  export { canvasTarget, maskTarget, svgToIcon };
@@ -1,12 +1,23 @@
1
- import { a as buildPlan, c as interpPolar, o as allocOutputs, r as resampleIcon } from "./spring-CFHloqPP.js";
1
+ import { a as interpPolar, r as allocOutputs } from "./spring-B_P92nf6.js";
2
2
  import { i as serialize } from "./normalize-CYnN3Npw.js";
3
- import { canonicalD, createMorph } from "./dom.js";
3
+ import { a as buildPlan, t as resampleIcon } from "./resample-CXlaZ5ZQ.js";
4
+ import { a as paintOf } from "./region-BjECuWZR.js";
5
+ import { canonicalD, createMorph, restAttrs } from "./dom.js";
4
6
  //#region src/dom/controller.ts
7
+ /** The endpoint a frozen controlled pair renders at rest at `t` (SSR
8
+ * included), or null mid-way for a stroke pair, interpolated with the pure
9
+ * core. A pair with a fill endpoint scrubs as a region morph, which only the
10
+ * lazy chunk draws: mid-way it renders `from` until the client's first seek. */
11
+ function frozenEnd(from, to, t) {
12
+ if (t >= 1) return to;
13
+ if (t <= 0 || paintOf(from) === "fill" || paintOf(to) === "fill") return from;
14
+ return null;
15
+ }
5
16
  /** Frozen shape of the from→to pair at t, using the pure core (SSR-safe).
6
17
  * At exact endpoints returns the canonical `d` (real curves, not polyline). */
7
18
  function frozenD(from, to, t) {
8
- if (t <= 0) return canonicalD(from);
9
- if (t >= 1) return canonicalD(to);
19
+ const end = frozenEnd(from, to, t);
20
+ if (end !== null) return canonicalD(end);
10
21
  const plan = buildPlan(resampleIcon(from), resampleIcon(to));
11
22
  const out = allocOutputs(plan);
12
23
  interpPolar(plan, t, out);
@@ -21,6 +32,17 @@ function computeInitialD({ icon, from, to, progress }) {
21
32
  const first = icon ?? from ?? to;
22
33
  return first !== void 0 ? canonicalD(first) : "";
23
34
  }
35
+ /** Paint attributes of the initial `<path>` (SSR included), a constant like
36
+ * the initial d: a fill icon is filled before any script runs. A frozen
37
+ * stroke pair paints as the endpoint it is nearer to; one with a fill
38
+ * endpoint rests on `from` mid-way, like its d (frozenEnd). */
39
+ function computeInitialPaint({ icon, from, to, progress }) {
40
+ if (from !== void 0 && to !== void 0) {
41
+ const t = progress ?? 0;
42
+ return restAttrs(frozenEnd(from, to, t) ?? (t < .5 ? from : to));
43
+ }
44
+ return restAttrs(icon ?? from ?? to);
45
+ }
24
46
  /** Per-instance driver state — the exact logic of the React/Vue bindings
25
47
  * (mount, mode watch, controlled seek with re-basing, imperative). Takes the
26
48
  * init-time props: they seed the watch baselines so a watcher that fires on
@@ -37,11 +59,12 @@ function computeInitialD({ icon, from, to, progress }) {
37
59
  * path back to `icon`.
38
60
  * - Every exit from controlled mode (imperative call or icon takeover)
39
61
  * invalidates the frozen pair, so returning to it re-bases on `from`. */
40
- function createController({ icon, from, to, progress, reducedMotion }) {
62
+ function createController({ icon, from, to, progress, reducedMotion, strokeWidth }) {
41
63
  let el = null;
42
64
  let dead = false;
43
65
  let morph = null;
44
66
  let rm = reducedMotion ?? "never";
67
+ let sw = Number(strokeWidth ?? 2);
45
68
  let based = false;
46
69
  let pair = null;
47
70
  let prevIcon = icon;
@@ -53,7 +76,10 @@ function createController({ icon, from, to, progress, reducedMotion }) {
53
76
  const ensure = (birth) => {
54
77
  if (morph) return morph;
55
78
  if (dead || !el) return null;
56
- morph = createMorph(el, birth, { reducedMotion: rm });
79
+ morph = createMorph(el, birth, {
80
+ reducedMotion: rm,
81
+ strokeWidth: sw
82
+ });
57
83
  return morph;
58
84
  };
59
85
  /** Controlled mode: freeze the pair at `progress` via seek (no spring). */
@@ -80,13 +106,17 @@ function createController({ icon, from, to, progress, reducedMotion }) {
80
106
  }
81
107
  };
82
108
  return {
83
- mount(mountEl, { icon, from, to, progress, reducedMotion }) {
109
+ mount(mountEl, { icon, from, to, progress, reducedMotion, strokeWidth }) {
84
110
  el = mountEl;
85
111
  rm = reducedMotion ?? rm;
112
+ sw = Number(strokeWidth ?? sw);
86
113
  const controlled = from !== void 0 && to !== void 0;
87
114
  const initialIcon = icon ?? from ?? to;
88
115
  if (initialIcon === void 0) return;
89
- const m = createMorph(el, controlled ? from : initialIcon, { reducedMotion: rm });
116
+ const m = createMorph(el, controlled ? from : initialIcon, {
117
+ reducedMotion: rm,
118
+ strokeWidth: sw
119
+ });
90
120
  morph = m;
91
121
  if (controlled) {
92
122
  pair = [from, to];
@@ -110,9 +140,13 @@ function createController({ icon, from, to, progress, reducedMotion }) {
110
140
  /** Prop watcher: ONE owner decides per run — the controlled pair while it
111
141
  * is fully present, `icon` otherwise (mount doesn't fire thanks to the
112
142
  * init-time baselines). */
113
- watch({ icon, from, to, progress, spring, reducedMotion }) {
143
+ watch({ icon, from, to, progress, spring, reducedMotion, strokeWidth }) {
114
144
  rm = reducedMotion ?? "never";
115
- if (morph) morph.reducedMotion = rm;
145
+ sw = Number(strokeWidth ?? 2);
146
+ if (morph) {
147
+ morph.reducedMotion = rm;
148
+ morph.strokeWidth = sw;
149
+ }
116
150
  const controlled = from !== void 0 && to !== void 0;
117
151
  const left = prevControlled && !controlled;
118
152
  const iconChanged = icon !== prevIcon;
@@ -149,4 +183,4 @@ function createController({ icon, from, to, progress, reducedMotion }) {
149
183
  };
150
184
  }
151
185
  //#endregion
152
- export { createController as n, computeInitialD as t };
186
+ export { frozenEnd as i, computeInitialPaint as n, createController as r, computeInitialD as t };
@@ -1,5 +1,6 @@
1
- import { c as SpringPreset, n as IconInput } from "./types-JAF3s76Y.js";
2
- import { a as ReducedMotionMode, i as PathEl, r as MorphOptions } from "./index-DXp5b6BU.js";
1
+ import { a as IconInput } from "./plan-DhaDhEVq.js";
2
+ import { r as SpringPreset } from "./spring-BgZHrI2W.js";
3
+ import { a as ReducedMotionMode, i as PathEl, r as MorphOptions } from "./index-DpvKR75N.js";
3
4
  //#region src/dom/controller.d.ts
4
5
  /** Imperative surface exposed by every binding (ref / bind:this / element). */
5
6
  interface MorphHandle {
@@ -15,6 +16,9 @@ interface MorphModeProps {
15
16
  }
16
17
  interface MorphCtrlProps extends MorphModeProps {
17
18
  reducedMotion?: ReducedMotionMode;
19
+ /** The stroke width on the 24 grid (absoluteStrokeWidth already resolved):
20
+ * what a stroke icon is outlined at when it morphs with a fill icon. */
21
+ strokeWidth?: number | string;
18
22
  }
19
23
  interface MorphWatchProps extends MorphCtrlProps {
20
24
  spring?: SpringPreset | MorphOptions;
@@ -24,6 +28,11 @@ interface MorphWatchProps extends MorphCtrlProps {
24
28
  * without mismatch) and from then on only the driver mutates it outside the
25
29
  * template. */
26
30
  declare function computeInitialD({ icon, from, to, progress }: MorphModeProps): string;
31
+ /** Paint attributes of the initial `<path>` (SSR included), a constant like
32
+ * the initial d: a fill icon is filled before any script runs. A frozen
33
+ * stroke pair paints as the endpoint it is nearer to; one with a fill
34
+ * endpoint rests on `from` mid-way, like its d (frozenEnd). */
35
+ declare function computeInitialPaint({ icon, from, to, progress }: MorphModeProps): Record<string, string> | undefined;
27
36
  /** Per-instance driver state — the exact logic of the React/Vue bindings
28
37
  * (mount, mode watch, controlled seek with re-basing, imperative). Takes the
29
38
  * init-time props: they seed the watch baselines so a watcher that fires on
@@ -40,15 +49,15 @@ declare function computeInitialD({ icon, from, to, progress }: MorphModeProps):
40
49
  * path back to `icon`.
41
50
  * - Every exit from controlled mode (imperative call or icon takeover)
42
51
  * invalidates the frozen pair, so returning to it re-bases on `from`. */
43
- declare function createController({ icon, from, to, progress, reducedMotion }: MorphCtrlProps): {
44
- mount(mountEl: PathEl, { icon, from, to, progress, reducedMotion }: MorphCtrlProps): void;
52
+ declare function createController({ icon, from, to, progress, reducedMotion, strokeWidth }: MorphCtrlProps): {
53
+ mount(mountEl: PathEl, { icon, from, to, progress, reducedMotion, strokeWidth }: MorphCtrlProps): void;
45
54
  destroy(): void;
46
55
  /** Prop watcher: ONE owner decides per run — the controlled pair while it
47
56
  * is fully present, `icon` otherwise (mount doesn't fire thanks to the
48
57
  * init-time baselines). */
49
- watch({ icon, from, to, progress, spring, reducedMotion }: MorphWatchProps): void;
58
+ watch({ icon, from, to, progress, spring, reducedMotion, strokeWidth }: MorphWatchProps): void;
50
59
  morphTo(icon: IconInput, spring?: SpringPreset | MorphOptions): void;
51
60
  set(icon: IconInput): void;
52
61
  };
53
62
  //#endregion
54
- export { computeInitialD as n, createController as r, MorphHandle as t };
63
+ export { createController as i, computeInitialD as n, computeInitialPaint as r, MorphHandle as t };
package/dist/dom.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ReducedMotionMode, i as PathEl, n as Morph, o as canonicalD, r as MorphOptions, s as createMorph, t as CreateMorphOptions } from "./index-DXp5b6BU.js";
2
- export { CreateMorphOptions, Morph, MorphOptions, PathEl, ReducedMotionMode, canonicalD, createMorph };
1
+ import { a as ReducedMotionMode, c as preloadRegion, i as PathEl, l as restAttrs, n as Morph, o as canonicalD, r as MorphOptions, s as createMorph, t as CreateMorphOptions, u as setRegionLoader } from "./index-DpvKR75N.js";
2
+ export { CreateMorphOptions, Morph, MorphOptions, PathEl, ReducedMotionMode, canonicalD, createMorph, preloadRegion, restAttrs, setRegionLoader };