morphicons 1.7.0 → 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.
- package/README.md +61 -428
- package/dist/MorphIcon.astro +4 -2
- package/dist/MorphIcon.svelte +19 -8
- package/dist/adapters.d.ts +12 -11
- package/dist/adapters.js +223 -23
- package/dist/{controller-CXZuwJ_M.js → controller--7SSBggk.js} +45 -11
- package/dist/{controller-Dkruvf3Y.d.ts → controller-DKm8s2ni.d.ts} +15 -6
- package/dist/dom.d.ts +2 -2
- package/dist/dom.js +292 -31
- package/dist/element.d.ts +7 -6
- package/dist/element.js +18 -5
- package/dist/index-DDNCPQ4Z.d.ts +55 -0
- package/dist/{index-DXp5b6BU.d.ts → index-DpvKR75N.d.ts} +26 -3
- package/dist/index.d.ts +12 -47
- package/dist/index.js +2 -1
- package/dist/plan-DhaDhEVq.d.ts +76 -0
- package/dist/react-native.d.ts +7 -6
- package/dist/react-native.js +65 -11
- package/dist/react.d.ts +7 -6
- package/dist/react.js +32 -7
- package/dist/region-BjECuWZR.js +60 -0
- package/dist/region.d.ts +2 -0
- package/dist/region.js +819 -0
- package/dist/{spring-CFHloqPP.js → resample-CXlaZ5ZQ.js} +56 -142
- package/dist/spring-B_P92nf6.js +95 -0
- package/dist/spring-BgZHrI2W.d.ts +33 -0
- package/dist/svelte-shared.d.ts +6 -5
- package/dist/svelte-shared.js +1 -2
- package/dist/vue.d.ts +6 -5
- package/dist/vue.js +31 -7
- package/package.json +62 -36
- package/dist/types-JAF3s76Y.d.ts +0 -60
package/dist/index.d.ts
CHANGED
|
@@ -1,50 +1,15 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
|
|
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,
|
|
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
|
|
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 };
|
package/dist/react-native.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
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 {
|
|
37
|
+
export type { IconInput, IconNode, Morph, MorphOptions, PathEl, ReducedMotionMode, Sampled, SpringPreset };
|
package/dist/react-native.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { a as
|
|
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 {
|
|
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";
|
|
7
9
|
import Svg, { Path } from "react-native-svg";
|
|
10
|
+
//#region src/react-native/iso-layout-effect.ts
|
|
11
|
+
const useIsoLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
12
|
+
//#endregion
|
|
8
13
|
//#region src/react-native/index.tsx
|
|
9
|
-
const useIsoLayoutEffect = typeof document === "undefined" ? useEffect : useLayoutEffect;
|
|
10
14
|
let reduced = false;
|
|
11
15
|
let rmInit = false;
|
|
12
16
|
function initReducedMotion() {
|
|
@@ -21,11 +25,23 @@ function initReducedMotion() {
|
|
|
21
25
|
});
|
|
22
26
|
} catch {}
|
|
23
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
|
+
}
|
|
24
40
|
/** Frozen shape of the from→to pair at t, using the pure core. At exact
|
|
25
41
|
* endpoints returns the canonical `d` (real curves, not polyline). */
|
|
26
42
|
function frozenD(from, to, t) {
|
|
27
|
-
|
|
28
|
-
if (
|
|
43
|
+
const end = frozenEnd(from, to, t);
|
|
44
|
+
if (end !== null) return canonicalD(end);
|
|
29
45
|
const plan = buildPlan(resampleIcon(from), resampleIcon(to));
|
|
30
46
|
const out = allocOutputs(plan);
|
|
31
47
|
interpPolar(plan, t, out);
|
|
@@ -39,21 +55,50 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
39
55
|
if (controlled) return frozenD(from, to, progress ?? 0);
|
|
40
56
|
return initialIcon !== void 0 ? canonicalD(initialIcon) : "";
|
|
41
57
|
});
|
|
58
|
+
const liveD = useRef(initialD);
|
|
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;
|
|
42
72
|
const pathRef = useRef(null);
|
|
43
73
|
const morphRef = useRef(null);
|
|
44
74
|
const springRef = useRef(spring);
|
|
45
75
|
springRef.current = spring;
|
|
46
76
|
const rmRef = useRef(reducedMotion);
|
|
47
77
|
rmRef.current = reducedMotion;
|
|
78
|
+
const gridSw = Number(absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth);
|
|
79
|
+
const swRef = useRef(gridSw);
|
|
80
|
+
swRef.current = gridSw;
|
|
48
81
|
const prevIcon = useRef(icon);
|
|
49
82
|
const prevControlled = useRef(controlled);
|
|
50
83
|
const dead = useRef(false);
|
|
51
84
|
const based = useRef(false);
|
|
52
85
|
const pair = useRef(null);
|
|
53
|
-
/** The PathEl shim: createMorph
|
|
54
|
-
*
|
|
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. */
|
|
55
89
|
const el = useRef({ setAttribute: (name, value) => {
|
|
56
|
-
if (name === "d")
|
|
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));
|
|
57
102
|
} });
|
|
58
103
|
/** morphTo that applies the reduced-motion policy ("user" reads the OS
|
|
59
104
|
* setting via AccessibilityInfo, best-effort). */
|
|
@@ -72,13 +117,13 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
72
117
|
const ensure = useCallback((birth) => {
|
|
73
118
|
if (morphRef.current) return morphRef.current;
|
|
74
119
|
if (dead.current || !pathRef.current) return null;
|
|
75
|
-
morphRef.current = createMorph(el.current, birth);
|
|
120
|
+
morphRef.current = createMorph(el.current, birth, { strokeWidth: swRef.current });
|
|
76
121
|
return morphRef.current;
|
|
77
122
|
}, []);
|
|
78
123
|
useIsoLayoutEffect(() => {
|
|
79
124
|
dead.current = false;
|
|
80
125
|
if (pathRef.current && initialIcon !== void 0) {
|
|
81
|
-
const m = createMorph(el.current, controlled ? from : initialIcon);
|
|
126
|
+
const m = createMorph(el.current, controlled ? from : initialIcon, { strokeWidth: swRef.current });
|
|
82
127
|
morphRef.current = m;
|
|
83
128
|
if (controlled) {
|
|
84
129
|
pair.current = [from, to];
|
|
@@ -99,9 +144,17 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
99
144
|
pair.current = null;
|
|
100
145
|
};
|
|
101
146
|
}, []);
|
|
147
|
+
useIsoLayoutEffect(() => {
|
|
148
|
+
if (liveD.current !== declaredD) pathRef.current?.setNativeProps({ d: liveD.current });
|
|
149
|
+
if (livePaint.current !== declaredPaint) pathRef.current?.setNativeProps(tint(livePaint.current, colorRef.current));
|
|
150
|
+
});
|
|
102
151
|
useEffect(() => {
|
|
103
152
|
if (reducedMotion === "user") initReducedMotion();
|
|
104
153
|
}, [reducedMotion]);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
const m = morphRef.current;
|
|
156
|
+
if (m) m.strokeWidth = gridSw;
|
|
157
|
+
}, [gridSw]);
|
|
105
158
|
useEffect(() => {
|
|
106
159
|
const left = prevControlled.current && !controlled;
|
|
107
160
|
prevControlled.current = controlled;
|
|
@@ -180,7 +233,8 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
180
233
|
...rest,
|
|
181
234
|
children: /* @__PURE__ */ jsx(Path, {
|
|
182
235
|
ref: pathRef,
|
|
183
|
-
d:
|
|
236
|
+
d: declaredD,
|
|
237
|
+
...tint(declaredPaint, color)
|
|
184
238
|
})
|
|
185
239
|
});
|
|
186
240
|
});
|
package/dist/react.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
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 {
|
|
36
|
+
export type { IconInput, IconNode, Morph, MorphOptions, PathEl, ReducedMotionMode, Sampled, SpringPreset };
|
package/dist/react.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { a as
|
|
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 {
|
|
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
|
-
|
|
12
|
-
if (
|
|
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, {
|
|
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, {
|
|
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 };
|
package/dist/region.d.ts
ADDED
|
@@ -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 };
|