morphicons 1.7.1 → 2.0.1

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/dist/index.d.ts CHANGED
@@ -1,50 +1,15 @@
1
- import { a as Sampled, c as SpringPreset, i as IconNodeAttrs, n as IconInput, o as SPRING_PRESETS, r as IconNode, s as Spring, t as CubicPath } from "./types-JAF3s76Y.js";
2
- //#region src/core/plan.d.ts
3
- interface PlanItem {
4
- /** Points of A with the chosen correspondence (if A is a closed loop it
5
- * may come circularly re-indexed: same points, different cut). */
6
- a: Float64Array;
7
- /** A centered on its centroid. */
8
- aC: Float64Array;
9
- /** B brought into A's frame: R(−θ)·(b − c_B)/σ. */
10
- bT: Float64Array;
11
- /** B oriented, raw (for linear mode and for exact t=1). */
12
- bO: Float64Array;
13
- ca: readonly [number, number];
14
- cb: readonly [number, number];
15
- theta: number;
16
- lnSigma: number;
17
- res: number;
18
- /** true if both endpoints are closed loops: the subpath flies with Z.
19
- * Closed → open flies open: the loop opens at the chosen cut. */
20
- closed: boolean;
21
- /** Block transport (set by the global hybrid, else null): mid-flight the
22
- * centroid rides the shared similarity around the global centroid instead
23
- * of lerping — off = c_A − g_A; drift closes c(1) = c_B exactly. */
24
- block: {
25
- off: readonly [number, number];
26
- drift: readonly [number, number];
27
- } | null;
28
- }
29
- interface MorphPlan {
30
- items: PlanItem[];
31
- n: number;
32
- }
33
- /** Builds the morph plan between two lists of sampled subpaths. The plan is
34
- * cacheable and serializable; it accepts any list — including intermediate
35
- * shapes (interruptions). */
36
- declare function buildPlan(srcSubs: readonly Sampled[], dstSubs: readonly Sampled[]): MorphPlan;
37
- //#endregion
1
+ import { a as IconInput, c as Sampled, i as CubicPath, n as PlanItem, o as IconNode, r as buildPlan, s as IconNodeAttrs, t as MorphPlan } from "./plan-DhaDhEVq.js";
2
+ import { n as Spring, r as SpringPreset, t as SPRING_PRESETS } from "./spring-BgZHrI2W.js";
38
3
  //#region src/core/interpolate.d.ts
39
4
  /** Preallocated output buffers for a plan (zero allocation per frame). */
40
- declare function allocOutputs(plan: MorphPlan): Float64Array[];
41
- declare function interpPolar(plan: MorphPlan, t: number, out: Float64Array[]): void;
5
+ export declare function allocOutputs(plan: MorphPlan): Float64Array[];
6
+ export declare function interpPolar(plan: MorphPlan, t: number, out: Float64Array[]): void;
42
7
  /** Raw coordinate lerp (same correspondence, no decomposition). */
43
- declare function interpLinear(plan: MorphPlan, t: number, out: Float64Array[]): void;
8
+ export declare function interpLinear(plan: MorphPlan, t: number, out: Float64Array[]): void;
44
9
  //#endregion
45
10
  //#region src/core/normalize.d.ts
46
11
  /** Icon (IconNode or `d` string) → list of cubic subpaths. */
47
- declare function iconToCubics(input: IconInput): CubicPath[];
12
+ export declare function iconToCubics(input: IconInput): CubicPath[];
48
13
  /** A source viewBox: `24`, `"0 0 20 20"` or `[minX, minY, w, h]`. */
49
14
  type ViewBox = number | string | readonly [number, number, number, number];
50
15
  /** Re-grids an icon drawn on `viewBox` onto the shared `grid` (24 by default),
@@ -55,7 +20,7 @@ type ViewBox = number | string | readonly [number, number, number, number];
55
20
  * do not, and mixing them unfitted makes Procrustes read the scale/offset gap
56
21
  * as rotation. Apply once at module scope (not per render) and pass the
57
22
  * resulting `d` anywhere an icon is accepted. */
58
- declare function fitIcon(input: IconInput, viewBox: ViewBox, grid?: number): string;
23
+ export declare function fitIcon(input: IconInput, viewBox: ViewBox, grid?: number): string;
59
24
  //#endregion
60
25
  //#region src/core/resample.d.ts
61
26
  /** Samples a cubic subpath at N points equidistant by arc length, anchoring
@@ -63,17 +28,17 @@ declare function fitIcon(input: IconInput, viewBox: ViewBox, grid?: number): str
63
28
  * paths distribute N intervals around the loop (without duplicating the
64
29
  * first point); the circular start-point freedom is resolved by the plan's
65
30
  * circular correspondence. */
66
- declare function resamplePath(path: CubicPath, N?: number, cornerThreshold?: number): Float64Array;
31
+ export declare function resamplePath(path: CubicPath, N?: number, cornerThreshold?: number): Float64Array;
67
32
  /** Full input pipeline: icon → cubics → sampled subpaths with their
68
33
  * topology (the plan needs to know which subpaths are closed loops). */
69
- declare function resampleIcon(input: IconInput, N?: number): Sampled[];
34
+ export declare function resampleIcon(input: IconInput, N?: number): Sampled[];
70
35
  //#endregion
71
36
  //#region src/core/serialize.d.ts
72
37
  /** Sampled subpaths → polyline `d` attribute. `closed[k]` appends Z to
73
38
  * subpath k (closed loops in flight); without flags everything is open. */
74
- declare function serialize(subs: readonly Float64Array[], closed?: readonly boolean[]): string;
39
+ export declare function serialize(subs: readonly Float64Array[], closed?: readonly boolean[]): string;
75
40
  /** Cubic subpaths → canonical `d`, quantized to 4 decimals (engine-stable
76
41
  * bytes; see fmtCanon). */
77
- declare function cubicsToPathD(paths: readonly CubicPath[]): string;
42
+ export declare function cubicsToPathD(paths: readonly CubicPath[]): string;
78
43
  //#endregion
79
- export { type CubicPath, type IconInput, type IconNode, type IconNodeAttrs, type MorphPlan, type PlanItem, SPRING_PRESETS, type Sampled, Spring, type SpringPreset, type ViewBox, allocOutputs, buildPlan, cubicsToPathD, fitIcon, iconToCubics, interpLinear, interpPolar, resampleIcon, resamplePath, serialize };
44
+ export { type CubicPath, type IconInput, type IconNode, type IconNodeAttrs, type MorphPlan, type PlanItem, SPRING_PRESETS, type Sampled, Spring, type SpringPreset, type ViewBox, buildPlan };
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- import { a as buildPlan, c as interpPolar, i as resamplePath, n as Spring, o as allocOutputs, r as resampleIcon, s as interpLinear, t as SPRING_PRESETS } from "./spring-CFHloqPP.js";
1
+ import { a as interpPolar, i as interpLinear, n as Spring, r as allocOutputs, t as SPRING_PRESETS } from "./spring-B_P92nf6.js";
2
2
  import { i as serialize, n as iconToCubics, r as cubicsToPathD, t as fitIcon } from "./normalize-CYnN3Npw.js";
3
+ import { a as buildPlan, n as resamplePath, t as resampleIcon } from "./resample-CXlaZ5ZQ.js";
3
4
  export { SPRING_PRESETS, Spring, allocOutputs, buildPlan, cubicsToPathD, fitIcon, iconToCubics, interpLinear, interpPolar, resampleIcon, resamplePath, serialize };
@@ -0,0 +1,76 @@
1
+ //#region src/core/types.d.ts
2
+ /** Attributes of an icon node (values as Lucide exports them: string or
3
+ * number). `undefined` is allowed on purpose — lucide's SVGProps includes it
4
+ * and the runtime treats an undefined attr as absent (fallback). */
5
+ type IconNodeAttrs = Record<string, string | number | undefined>;
6
+ /** Lucide-style icon data: a `[tag, attrs]` list. Structurally typed —
7
+ * Lucide is neither a dependency nor a peer; Feather/Tabler/custom paths
8
+ * work just the same. */
9
+ type IconNode = ReadonlyArray<readonly [string, IconNodeAttrs]>;
10
+ /** Input accepted by the core: an IconNode or a raw `d` attribute.
11
+ * (SVGElement is resolved in the dom layer — the core never touches DOM.) */
12
+ type IconInput = IconNode | string;
13
+ /** Normalized subpath: a chain of cubics packed as points
14
+ * [p0, c1, c2, p1, c1', c2', p2, …] → Float64Array of length 2·(3m+1),
15
+ * with m = number of segments. Consecutive segments share an endpoint. */
16
+ interface CubicPath {
17
+ pts: Float64Array;
18
+ closed: boolean;
19
+ }
20
+ /** Subpath sampled at N points by arc length + its topology. It is the
21
+ * currency between resample and plan; an intermediate shape (interruption)
22
+ * is also a list of Sampled. */
23
+ interface Sampled {
24
+ pts: Float64Array;
25
+ closed: boolean;
26
+ /** Region contours only: true for a hole (negative winding). Carried
27
+ * explicitly because a hole collapsing mid-flight has ~zero area and
28
+ * could not be re-classified from its points. */
29
+ hole?: boolean;
30
+ }
31
+ //#endregion
32
+ //#region src/core/plan.d.ts
33
+ interface PlanItem {
34
+ /** Points of A with the chosen correspondence (if A is a closed loop it
35
+ * may come circularly re-indexed: same points, different cut). */
36
+ a: Float64Array;
37
+ /** A centered on its centroid. */
38
+ aC: Float64Array;
39
+ /** B brought into A's frame: R(−θ)·(b − c_B)/σ. */
40
+ bT: Float64Array;
41
+ /** B oriented, raw (for linear mode and for exact t=1). */
42
+ bO: Float64Array;
43
+ ca: readonly [number, number];
44
+ cb: readonly [number, number];
45
+ theta: number;
46
+ lnSigma: number;
47
+ res: number;
48
+ /** true if both endpoints are closed loops: the subpath flies with Z.
49
+ * Closed → open flies open: the loop opens at the chosen cut. */
50
+ closed: boolean;
51
+ /** Region plans only: the item is a hole (negative winding). Collapsing
52
+ * and growing holes are items too — their far endpoint is one point. */
53
+ hole?: boolean;
54
+ /** Region plans only: a hole that vanishes or appears blends its shape on
55
+ * one half of the flight (0 = collapses on [0, ½], 1 = grows on [½, 1])
56
+ * while it keeps riding its outer the whole way. */
57
+ half?: 0 | 1;
58
+ /** Block transport (set by the global hybrid, else null): mid-flight the
59
+ * centroid rides the shared similarity around the global centroid instead
60
+ * of lerping — off = c_A − g_A; drift closes c(1) = c_B exactly. */
61
+ block: {
62
+ off: readonly [number, number];
63
+ drift: readonly [number, number];
64
+ } | null;
65
+ }
66
+ interface MorphPlan {
67
+ items: PlanItem[];
68
+ n: number;
69
+ }
70
+ /** Builds the morph plan between two lists of sampled subpaths. The plan is
71
+ * cacheable and serializable; it accepts any list — including intermediate
72
+ * shapes (interruptions). Filled regions use buildRegionPlan (lazy chunk,
73
+ * src/region/plan.ts) instead. */
74
+ declare function buildPlan(srcSubs: readonly Sampled[], dstSubs: readonly Sampled[]): MorphPlan;
75
+ //#endregion
76
+ export { IconInput as a, Sampled as c, CubicPath as i, PlanItem as n, IconNode as o, buildPlan as r, IconNodeAttrs as s, MorphPlan as t };
@@ -1,13 +1,14 @@
1
- import { a as Sampled, c as SpringPreset, n as IconInput, r as IconNode } from "./types-JAF3s76Y.js";
2
- import { a as ReducedMotionMode, i as PathEl, n as Morph, r as MorphOptions } from "./index-DXp5b6BU.js";
1
+ import { a as IconInput, c as Sampled, o as IconNode } from "./plan-DhaDhEVq.js";
2
+ import { r as SpringPreset } from "./spring-BgZHrI2W.js";
3
+ import { a as ReducedMotionMode, i as PathEl, n as Morph, r as MorphOptions } from "./index-DpvKR75N.js";
3
4
  import { SvgProps } from "react-native-svg";
4
5
  //#region src/react-native/index.d.ts
5
6
  /** Imperative surface exposed via ref. */
6
- interface MorphHandle {
7
+ export interface MorphHandle {
7
8
  morphTo(icon: IconInput, spring?: SpringPreset | MorphOptions): void;
8
9
  set(icon: IconInput): void;
9
10
  }
10
- interface MorphIconProps extends Omit<SvgProps, "from" | "to"> {
11
+ export interface MorphIconProps extends Omit<SvgProps, "from" | "to"> {
11
12
  /** Uncontrolled mode: the current icon; changing the prop animates. */
12
13
  icon?: IconInput;
13
14
  /** Controlled mode: source endpoint of the pair. */
@@ -31,6 +32,6 @@ interface MorphIconProps extends Omit<SvgProps, "from" | "to"> {
31
32
  /** Accessibility: with label → role="img" + aria-label; without → aria-hidden. */
32
33
  label?: string;
33
34
  }
34
- declare const MorphIcon: import("react").ForwardRefExoticComponent<MorphIconProps & import("react").RefAttributes<MorphHandle>>;
35
+ export declare const MorphIcon: import("react").ForwardRefExoticComponent<MorphIconProps & import("react").RefAttributes<MorphHandle>>;
35
36
  //#endregion
36
- export { type IconInput, type IconNode, type Morph, MorphHandle, MorphIcon, MorphIconProps, type MorphOptions, type PathEl, type ReducedMotionMode, type Sampled, type SpringPreset };
37
+ export type { IconInput, IconNode, Morph, MorphOptions, PathEl, ReducedMotionMode, Sampled, SpringPreset };
@@ -1,6 +1,8 @@
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 { canonicalD, createMorph, restAttrs } from "./dom.js";
5
+ import { i as frozenEnd } from "./controller--7SSBggk.js";
4
6
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
5
7
  import { jsx } from "react/jsx-runtime";
6
8
  import { AccessibilityInfo } from "react-native";
@@ -23,11 +25,23 @@ function initReducedMotion() {
23
25
  });
24
26
  } catch {}
25
27
  }
28
+ const PAINT_PROPS = {
29
+ fill: "fill",
30
+ stroke: "stroke",
31
+ "fill-rule": "fillRule"
32
+ };
33
+ /** The driver's paint in native terms: currentColor (fill) and inherit
34
+ * (stroke) are the `color` prop — there is no cascade to resolve them. */
35
+ function tint(p, color) {
36
+ const out = {};
37
+ for (const [k, v] of Object.entries(p)) out[k] = v === "currentColor" || v === "inherit" ? color : v;
38
+ return out;
39
+ }
26
40
  /** Frozen shape of the from→to pair at t, using the pure core. At exact
27
41
  * endpoints returns the canonical `d` (real curves, not polyline). */
28
42
  function frozenD(from, to, t) {
29
- if (t <= 0) return canonicalD(from);
30
- if (t >= 1) return canonicalD(to);
43
+ const end = frozenEnd(from, to, t);
44
+ if (end !== null) return canonicalD(end);
31
45
  const plan = buildPlan(resampleIcon(from), resampleIcon(to));
32
46
  const out = allocOutputs(plan);
33
47
  interpPolar(plan, t, out);
@@ -43,23 +57,48 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
43
57
  });
44
58
  const liveD = useRef(initialD);
45
59
  const declaredD = liveD.current;
60
+ const [initialPaint] = useState(() => {
61
+ const a = restAttrs(controlled ? frozenEnd(from, to, progress ?? 0) ?? ((progress ?? 0) < .5 ? from : to) : initialIcon);
62
+ return a ? {
63
+ fill: a.fill,
64
+ stroke: a.stroke,
65
+ fillRule: a["fill-rule"]
66
+ } : {};
67
+ });
68
+ const livePaint = useRef(initialPaint);
69
+ const declaredPaint = livePaint.current;
70
+ const colorRef = useRef(color);
71
+ colorRef.current = color;
46
72
  const pathRef = useRef(null);
47
73
  const morphRef = useRef(null);
48
74
  const springRef = useRef(spring);
49
75
  springRef.current = spring;
50
76
  const rmRef = useRef(reducedMotion);
51
77
  rmRef.current = reducedMotion;
78
+ const gridSw = Number(absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth);
79
+ const swRef = useRef(gridSw);
80
+ swRef.current = gridSw;
52
81
  const prevIcon = useRef(icon);
53
82
  const prevControlled = useRef(controlled);
54
83
  const dead = useRef(false);
55
84
  const based = useRef(false);
56
85
  const pair = useRef(null);
57
- /** The PathEl shim: createMorph only ever writes `d`, and Path forwards
58
- * it to the native view without re-rendering. Stable identity. */
86
+ /** The PathEl shim: createMorph writes `d` (every frame) and paint (on
87
+ * a stroke ↔ fill change), and Path forwards both to the native view
88
+ * without re-rendering. Stable identity. */
59
89
  const el = useRef({ setAttribute: (name, value) => {
60
- if (name !== "d") return;
61
- liveD.current = value;
62
- pathRef.current?.setNativeProps({ d: value });
90
+ if (name === "d") {
91
+ liveD.current = value;
92
+ pathRef.current?.setNativeProps({ d: value });
93
+ return;
94
+ }
95
+ const key = PAINT_PROPS[name];
96
+ if (!key) return;
97
+ livePaint.current = {
98
+ ...livePaint.current,
99
+ [key]: value
100
+ };
101
+ pathRef.current?.setNativeProps(tint({ [key]: value }, colorRef.current));
63
102
  } });
64
103
  /** morphTo that applies the reduced-motion policy ("user" reads the OS
65
104
  * setting via AccessibilityInfo, best-effort). */
@@ -78,13 +117,13 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
78
117
  const ensure = useCallback((birth) => {
79
118
  if (morphRef.current) return morphRef.current;
80
119
  if (dead.current || !pathRef.current) return null;
81
- morphRef.current = createMorph(el.current, birth);
120
+ morphRef.current = createMorph(el.current, birth, { strokeWidth: swRef.current });
82
121
  return morphRef.current;
83
122
  }, []);
84
123
  useIsoLayoutEffect(() => {
85
124
  dead.current = false;
86
125
  if (pathRef.current && initialIcon !== void 0) {
87
- const m = createMorph(el.current, controlled ? from : initialIcon);
126
+ const m = createMorph(el.current, controlled ? from : initialIcon, { strokeWidth: swRef.current });
88
127
  morphRef.current = m;
89
128
  if (controlled) {
90
129
  pair.current = [from, to];
@@ -107,10 +146,15 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
107
146
  }, []);
108
147
  useIsoLayoutEffect(() => {
109
148
  if (liveD.current !== declaredD) pathRef.current?.setNativeProps({ d: liveD.current });
149
+ if (livePaint.current !== declaredPaint) pathRef.current?.setNativeProps(tint(livePaint.current, colorRef.current));
110
150
  });
111
151
  useEffect(() => {
112
152
  if (reducedMotion === "user") initReducedMotion();
113
153
  }, [reducedMotion]);
154
+ useEffect(() => {
155
+ const m = morphRef.current;
156
+ if (m) m.strokeWidth = gridSw;
157
+ }, [gridSw]);
114
158
  useEffect(() => {
115
159
  const left = prevControlled.current && !controlled;
116
160
  prevControlled.current = controlled;
@@ -189,7 +233,8 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
189
233
  ...rest,
190
234
  children: /* @__PURE__ */ jsx(Path, {
191
235
  ref: pathRef,
192
- d: declaredD
236
+ d: declaredD,
237
+ ...tint(declaredPaint, color)
193
238
  })
194
239
  });
195
240
  });
package/dist/react.d.ts CHANGED
@@ -1,13 +1,14 @@
1
- import { a as Sampled, c as SpringPreset, n as IconInput, r as IconNode } from "./types-JAF3s76Y.js";
2
- import { a as ReducedMotionMode, i as PathEl, n as Morph, r as MorphOptions } from "./index-DXp5b6BU.js";
1
+ import { a as IconInput, c as Sampled, o as IconNode } from "./plan-DhaDhEVq.js";
2
+ import { r as SpringPreset } from "./spring-BgZHrI2W.js";
3
+ import { a as ReducedMotionMode, i as PathEl, n as Morph, r as MorphOptions } from "./index-DpvKR75N.js";
3
4
  import { SVGProps } from "react";
4
5
  //#region src/react/index.d.ts
5
6
  /** Imperative surface exposed via ref. */
6
- interface MorphHandle {
7
+ export interface MorphHandle {
7
8
  morphTo(icon: IconInput, spring?: SpringPreset | MorphOptions): void;
8
9
  set(icon: IconInput): void;
9
10
  }
10
- interface MorphIconProps extends Omit<SVGProps<SVGSVGElement>, "from" | "to" | "ref"> {
11
+ export interface MorphIconProps extends Omit<SVGProps<SVGSVGElement>, "from" | "to" | "ref"> {
11
12
  /** Uncontrolled mode: the current icon; changing the prop animates. */
12
13
  icon?: IconInput;
13
14
  /** Controlled mode: source endpoint of the pair. */
@@ -30,6 +31,6 @@ interface MorphIconProps extends Omit<SVGProps<SVGSVGElement>, "from" | "to" | "
30
31
  /** Accessibility: with label → role="img" + <title>; without → aria-hidden. */
31
32
  label?: string;
32
33
  }
33
- declare const MorphIcon: import("react").ForwardRefExoticComponent<MorphIconProps & import("react").RefAttributes<MorphHandle>>;
34
+ export declare const MorphIcon: import("react").ForwardRefExoticComponent<MorphIconProps & import("react").RefAttributes<MorphHandle>>;
34
35
  //#endregion
35
- export { type IconInput, type IconNode, type Morph, MorphHandle, MorphIcon, MorphIconProps, type MorphOptions, type PathEl, type ReducedMotionMode, type Sampled, type SpringPreset };
36
+ export type { IconInput, IconNode, Morph, MorphOptions, PathEl, ReducedMotionMode, Sampled, SpringPreset };
package/dist/react.js CHANGED
@@ -1,6 +1,8 @@
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 { canonicalD, createMorph, restAttrs } from "./dom.js";
5
+ import { i as frozenEnd } from "./controller--7SSBggk.js";
4
6
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
5
7
  import { jsx, jsxs } from "react/jsx-runtime";
6
8
  //#region src/react/index.tsx
@@ -8,8 +10,8 @@ const useIsoLayoutEffect = typeof document === "undefined" ? useEffect : useLayo
8
10
  /** Frozen shape of the from→to pair at t, using the pure core (SSR-safe).
9
11
  * At exact endpoints returns the canonical `d` (real curves, not polyline). */
10
12
  function frozenD(from, to, t) {
11
- if (t <= 0) return canonicalD(from);
12
- if (t >= 1) return canonicalD(to);
13
+ const end = frozenEnd(from, to, t);
14
+ if (end !== null) return canonicalD(end);
13
15
  const plan = buildPlan(resampleIcon(from), resampleIcon(to));
14
16
  const out = allocOutputs(plan);
15
17
  interpPolar(plan, t, out);
@@ -23,12 +25,23 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
23
25
  if (controlled) return frozenD(from, to, progress ?? 0);
24
26
  return initialIcon !== void 0 ? canonicalD(initialIcon) : "";
25
27
  });
28
+ const [initialPaint] = useState(() => {
29
+ const a = restAttrs(controlled ? frozenEnd(from, to, progress ?? 0) ?? ((progress ?? 0) < .5 ? from : to) : initialIcon);
30
+ return a && {
31
+ fill: a.fill,
32
+ stroke: a.stroke,
33
+ fillRule: a["fill-rule"]
34
+ };
35
+ });
26
36
  const pathRef = useRef(null);
27
37
  const morphRef = useRef(null);
28
38
  const springRef = useRef(spring);
29
39
  springRef.current = spring;
30
40
  const rmRef = useRef(reducedMotion);
31
41
  rmRef.current = reducedMotion;
42
+ const gridSw = Number(absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth);
43
+ const swRef = useRef(gridSw);
44
+ swRef.current = gridSw;
32
45
  const prevIcon = useRef(icon);
33
46
  const prevControlled = useRef(controlled);
34
47
  const dead = useRef(false);
@@ -42,14 +55,20 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
42
55
  if (morphRef.current) return morphRef.current;
43
56
  const el = pathRef.current;
44
57
  if (dead.current || !el) return null;
45
- morphRef.current = createMorph(el, birth, { reducedMotion: rmRef.current });
58
+ morphRef.current = createMorph(el, birth, {
59
+ reducedMotion: rmRef.current,
60
+ strokeWidth: swRef.current
61
+ });
46
62
  return morphRef.current;
47
63
  }, []);
48
64
  useIsoLayoutEffect(() => {
49
65
  dead.current = false;
50
66
  const el = pathRef.current;
51
67
  if (el && initialIcon !== void 0) {
52
- const m = createMorph(el, controlled ? from : initialIcon, { reducedMotion: rmRef.current });
68
+ const m = createMorph(el, controlled ? from : initialIcon, {
69
+ reducedMotion: rmRef.current,
70
+ strokeWidth: swRef.current
71
+ });
53
72
  morphRef.current = m;
54
73
  if (controlled) {
55
74
  pair.current = [from, to];
@@ -74,6 +93,10 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
74
93
  const m = morphRef.current;
75
94
  if (m) m.reducedMotion = reducedMotion ?? "never";
76
95
  }, [reducedMotion]);
96
+ useEffect(() => {
97
+ const m = morphRef.current;
98
+ if (m) m.strokeWidth = gridSw;
99
+ }, [gridSw]);
77
100
  useEffect(() => {
78
101
  const left = prevControlled.current && !controlled;
79
102
  prevControlled.current = controlled;
@@ -144,6 +167,7 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
144
167
  viewBox: "0 0 24 24",
145
168
  fill: "none",
146
169
  stroke: color,
170
+ color,
147
171
  strokeWidth: sw,
148
172
  strokeLinecap: "round",
149
173
  strokeLinejoin: "round",
@@ -152,7 +176,8 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
152
176
  ...rest,
153
177
  children: [label ? /* @__PURE__ */ jsx("title", { children: label }) : null, /* @__PURE__ */ jsx("path", {
154
178
  ref: pathRef,
155
- d: initialD
179
+ d: initialD,
180
+ ...initialPaint
156
181
  })]
157
182
  });
158
183
  });
@@ -0,0 +1,60 @@
1
+ //#region src/core/region.ts
2
+ /** Attribute that marks an IconNode as an already-normalized region (the
3
+ * output of `toRegion`): one `<path>` of non-overlapping closed contours,
4
+ * outers positive, holes negative. Normalization skips it. */
5
+ const REGION_ATTR = "data-region";
6
+ /** A paint attribute that paints (not absent, not `none`). */
7
+ const painted = (v) => v !== void 0 && String(v).trim().toLowerCase() !== "none";
8
+ /** Paint of an icon. Raw `d` strings and IconNodes that never declare a
9
+ * painted fill are stroke icons — the historical contract, so every
10
+ * existing input keeps its meaning. One element with a painted `fill` makes
11
+ * the whole icon a fill icon (mixed stroke+fill icons need the region path
12
+ * too: their silhouette is not a set of centerlines). */
13
+ function paintOf(icon) {
14
+ if (typeof icon === "string") return "stroke";
15
+ for (const [, a] of icon) if (painted(a.fill)) return "fill";
16
+ return "stroke";
17
+ }
18
+ /** A duotone background layer (Phosphor: opacity 0.2) is not part of a
19
+ * monochrome silhouette: one path can't paint it translucent, so it is
20
+ * dropped from a fill icon rather than painted opaque. */
21
+ const translucent = (a) => Number.parseFloat(String(a.opacity)) < .5 || Number.parseFloat(String(a["fill-opacity"])) < .5;
22
+ /** The elements a fill icon actually draws: its translucent layers out. */
23
+ function solid(icon) {
24
+ return typeof icon === "string" || paintOf(icon) === "stroke" ? icon : icon.filter(([, a]) => !translucent(a));
25
+ }
26
+ /** True when the icon is already a normalized region (no work left). */
27
+ function isRegion(icon) {
28
+ return typeof icon !== "string" && icon.length === 1 && "data-region" in icon[0][1];
29
+ }
30
+ /** Even-odd point-in-polygon against a closed point cloud. */
31
+ function inside(px, py, p) {
32
+ const n = p.length / 2;
33
+ let c = false;
34
+ for (let i = 0, j = n - 1; i < n; j = i++) {
35
+ const yi = p[2 * i + 1];
36
+ const yj = p[2 * j + 1];
37
+ if (yi > py !== yj > py && px < (p[2 * j] - p[2 * i]) * (py - yi) / (yj - yi) + p[2 * i]) c = !c;
38
+ }
39
+ return c;
40
+ }
41
+ /** Distance from (px, py) to the segment ab. */
42
+ function distSeg(px, py, ax, ay, bx, by) {
43
+ const dx = bx - ax;
44
+ const dy = by - ay;
45
+ const l2 = dx * dx + dy * dy;
46
+ let t = l2 > 0 ? ((px - ax) * dx + (py - ay) * dy) / l2 : 0;
47
+ t = t < 0 ? 0 : t > 1 ? 1 : t;
48
+ const ex = px - ax - t * dx;
49
+ const ey = py - ay - t * dy;
50
+ return Math.sqrt(ex * ex + ey * ey);
51
+ }
52
+ /** Shoelace signed area of a closed point cloud (SVG axes: y down). */
53
+ function signedArea(p) {
54
+ const n = p.length / 2;
55
+ let a = 0;
56
+ for (let i = 0, j = n - 1; i < n; j = i++) a += p[2 * j] * p[2 * i + 1] - p[2 * i] * p[2 * j + 1];
57
+ return a / 2;
58
+ }
59
+ //#endregion
60
+ export { paintOf as a, solid as c, isRegion as i, translucent as l, distSeg as n, painted as o, inside as r, signedArea as s, REGION_ATTR as t };
@@ -0,0 +1,2 @@
1
+ import { a as regionPlan, c as Paint, d as buildRegionPlan, f as resampleRegion, i as regionOf, l as isRegion, o as regionPlanFrom, r as regionContours, s as toRegion, t as RegionOptions, u as paintOf } from "./index-DDNCPQ4Z.js";
2
+ export { type Paint, RegionOptions, buildRegionPlan, isRegion, paintOf, regionContours, regionOf, regionPlan, regionPlanFrom, resampleRegion, toRegion };