morphicons 1.4.1 → 1.4.2
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 +17 -2
- package/dist/MorphIcon.svelte +4 -3
- package/dist/dom.d.ts +13 -2
- package/dist/dom.js +18 -6
- package/dist/react-native.d.ts +7 -2
- package/dist/react-native.js +11 -4
- package/dist/react.d.ts +6 -2
- package/dist/react.js +9 -3
- package/dist/svelte-shared.d.ts +11 -6
- package/dist/svelte-shared.js +9 -5
- package/dist/vue.d.ts +17 -2
- package/dist/vue.js +16 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ ref.current?.morphTo(Check); // animates
|
|
|
65
65
|
ref.current?.set(X); // jumps without animating
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
Drop-in replacement for lucide-react: `size`, `strokeWidth`, `absoluteStrokeWidth`, `color`, `className` and the rest of the `<svg>` props pass straight through. Correct accessibility by default: `aria-hidden` unless you pass `label` (→ `role="img"` + `<title>`). Clean SSR: the server emits the exact static SVG (zero flash, zero layout shift); the runtime is born on hydration.
|
|
68
|
+
Drop-in replacement for lucide-react: `size`, `strokeWidth`, `absoluteStrokeWidth`, `color`, `className` and the rest of the `<svg>` props pass straight through. Correct accessibility by default: `aria-hidden` unless you pass `label` (→ `role="img"` + `<title>`). Clean SSR: the server emits the exact static SVG (zero flash, zero layout shift); the runtime is born on hydration. Morphs play regardless of the OS reduce-motion setting by default; opt into honoring it with `reducedMotion="user"` (see [Reduced motion](#reduced-motion-all-four-bindings)).
|
|
69
69
|
|
|
70
70
|
### Vue — same three modes
|
|
71
71
|
|
|
@@ -139,7 +139,7 @@ ref.current?.morphTo(Check); // animates
|
|
|
139
139
|
ref.current?.set(X); // jumps without animating
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
The DOM-free core beyond the browser: the dom driver is reused verbatim as the engine (React Native has a global `requestAnimationFrame`, and `PathEl` is structural), so the whole platform difference is a shim that forwards the per-frame `d` write to `Path.setNativeProps` of react-native-svg — outside the React render, exactly like the web mutation. Same surface as the React binding (`size`, `strokeWidth`, `absoluteStrokeWidth`, `color`, plus the native `Svg` props: `testID`, touch handlers…), same accessibility defaults (`aria-hidden` unless you pass `label` → `role="img"` + `aria-label`).
|
|
142
|
+
The DOM-free core beyond the browser: the dom driver is reused verbatim as the engine (React Native has a global `requestAnimationFrame`, and `PathEl` is structural), so the whole platform difference is a shim that forwards the per-frame `d` write to `Path.setNativeProps` of react-native-svg — outside the React render, exactly like the web mutation. Same surface as the React binding (`size`, `strokeWidth`, `absoluteStrokeWidth`, `color`, plus the native `Svg` props: `testID`, touch handlers…), same accessibility defaults (`aria-hidden` unless you pass `label` → `role="img"` + `aria-label`). With `reducedMotion="user"`, the OS setting comes from `AccessibilityInfo` (best-effort: the query is async; a `reduceMotionChanged` subscription keeps it exact from then on). Requires Metro with package `exports` resolution — default since React Native 0.79; on older versions enable `unstable_enablePackageExports`.
|
|
143
143
|
|
|
144
144
|
### Lifecycle contract (all four bindings)
|
|
145
145
|
|
|
@@ -149,6 +149,20 @@ The four components share one contract, pinned by mirrored client-mount tests:
|
|
|
149
149
|
- **Controlled wins.** While `from` AND `to` are both present, the pair owns the path: `icon` changes are ignored, no spring fires. Drop the pair and the current `icon` takes over (animated). Mixing the modes is not an error — the precedence is just explicit.
|
|
150
150
|
- **Clean re-entry.** Any exit from controlled mode (an imperative call or an icon takeover) invalidates the frozen pair, so returning to the same `from`/`to` re-bases on `from` and renders exactly like a clean mount at that `progress`.
|
|
151
151
|
|
|
152
|
+
### Reduced motion (all four bindings)
|
|
153
|
+
|
|
154
|
+
Icon morphs are small, short, communicative micro-transitions: the kind of motion the reduce-motion guidance considers generally acceptable, unlike parallax or full-screen movement. Auto-degrading them made the library look broken to every user with the OS setting on, so since 1.4.2 they play by default and the policy is an explicit prop (the same default Motion, formerly Framer Motion, ships):
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
<MorphIcon icon={open ? X : Menu} reducedMotion="user" />
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- `"never"` (default): morphs always animate; the OS setting is ignored.
|
|
161
|
+
- `"user"`: while the OS reduce-motion setting is on, every `morphTo` degrades to an instant swap (the pre-1.4.2 behavior). The web bindings read `prefers-reduced-motion`; React Native reads `AccessibilityInfo`, and only touches the accessibility bridge when some instance opts in.
|
|
162
|
+
- `"always"`: every `morphTo` jumps without animating (tests, screenshots).
|
|
163
|
+
|
|
164
|
+
The prop is live: changing it governs the next morph. The vanilla driver takes the same policy as an option, `createMorph(el, icon, { reducedMotion: "user" })`, and exposes it as a mutable `morph.reducedMotion`. Apps that must strictly follow the user's system preference should pass `reducedMotion="user"` across the board.
|
|
165
|
+
|
|
152
166
|
### Vanilla (no React)
|
|
153
167
|
|
|
154
168
|
```ts
|
|
@@ -159,6 +173,7 @@ m.morphTo(X, "snappy"); // preset or { stiffness, damping }
|
|
|
159
173
|
m.set(Check); // jump without animating (canonical d)
|
|
160
174
|
m.seek(X, 0.4); // morph frozen at t — scrubbing, gestures
|
|
161
175
|
m.progress = 0.4; // sugar: seek on the active target
|
|
176
|
+
m.reducedMotion = "user"; // live policy: "never" (default) | "user" | "always"
|
|
162
177
|
m.destroy();
|
|
163
178
|
```
|
|
164
179
|
|
package/dist/MorphIcon.svelte
CHANGED
|
@@ -25,6 +25,7 @@ let {
|
|
|
25
25
|
to,
|
|
26
26
|
progress,
|
|
27
27
|
spring,
|
|
28
|
+
reducedMotion,
|
|
28
29
|
size = 24,
|
|
29
30
|
color = "currentColor",
|
|
30
31
|
strokeWidth = 2,
|
|
@@ -40,17 +41,17 @@ const initialD = computeInitialD({ icon, from, to, progress });
|
|
|
40
41
|
let pathEl: SVGPathElement | undefined = $state();
|
|
41
42
|
// svelte-ignore state_referenced_locally — init-time values on purpose: they
|
|
42
43
|
// seed the controller's watch baselines so the first $effect run is a no-op.
|
|
43
|
-
const ctrl = createController({ icon, from, to, progress });
|
|
44
|
+
const ctrl = createController({ icon, from, to, progress, reducedMotion });
|
|
44
45
|
|
|
45
46
|
onMount(() => {
|
|
46
|
-
if (pathEl) ctrl.mount(pathEl, { icon, from, to, progress });
|
|
47
|
+
if (pathEl) ctrl.mount(pathEl, { icon, from, to, progress, reducedMotion });
|
|
47
48
|
return () => ctrl.destroy();
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
// Prop changes: one watcher owns the mode logic (controlled wins — change
|
|
51
52
|
// detection and precedence live in shared.ts).
|
|
52
53
|
$effect(() => {
|
|
53
|
-
const next = { icon, from, to, progress, spring };
|
|
54
|
+
const next = { icon, from, to, progress, spring, reducedMotion };
|
|
54
55
|
untrack(() => ctrl.watch(next));
|
|
55
56
|
});
|
|
56
57
|
|
package/dist/dom.d.ts
CHANGED
|
@@ -10,6 +10,15 @@ interface MorphOptions {
|
|
|
10
10
|
stiffness?: number;
|
|
11
11
|
damping?: number;
|
|
12
12
|
}
|
|
13
|
+
/** Reduced-motion policy. `"never"` (default): morphs always animate,
|
|
14
|
+
* ignoring the OS setting. `"user"`: while the OS reduce-motion setting is
|
|
15
|
+
* on, `morphTo` degrades to an instant `set`. `"always"`: every `morphTo`
|
|
16
|
+
* jumps (tests, screenshots). */
|
|
17
|
+
type ReducedMotionMode = "never" | "user" | "always";
|
|
18
|
+
interface CreateMorphOptions {
|
|
19
|
+
/** Reduced-motion policy; also live via the `reducedMotion` property. */
|
|
20
|
+
reducedMotion?: ReducedMotionMode;
|
|
21
|
+
}
|
|
13
22
|
interface Morph {
|
|
14
23
|
/** Animates toward the icon with spring physics. Interruptible: mid-flight
|
|
15
24
|
* it re-plans from the intermediate shape while preserving velocity. */
|
|
@@ -23,6 +32,8 @@ interface Morph {
|
|
|
23
32
|
/** Current progress (t of the last frame; 1 at rest). Assigning it is
|
|
24
33
|
* equivalent to seek(target, t) on the active target. */
|
|
25
34
|
progress: number;
|
|
35
|
+
/** Reduced-motion policy, live: assigning it applies to the next morphTo. */
|
|
36
|
+
reducedMotion: ReducedMotionMode;
|
|
26
37
|
/** Unregisters the instance; later calls are no-ops. */
|
|
27
38
|
destroy(): void;
|
|
28
39
|
}
|
|
@@ -32,6 +43,6 @@ interface Morph {
|
|
|
32
43
|
* is what a binding renders at SSR/rest before any runtime exists. */
|
|
33
44
|
declare function canonicalD(icon: IconInput): string;
|
|
34
45
|
/** Creates the morph instance over a `<path>` and paints the initial icon. */
|
|
35
|
-
declare function createMorph(el: PathEl, icon: IconInput): Morph;
|
|
46
|
+
declare function createMorph(el: PathEl, icon: IconInput, options?: CreateMorphOptions): Morph;
|
|
36
47
|
//#endregion
|
|
37
|
-
export { Morph, MorphOptions, PathEl, canonicalD, createMorph };
|
|
48
|
+
export { CreateMorphOptions, Morph, MorphOptions, PathEl, ReducedMotionMode, canonicalD, createMorph };
|
package/dist/dom.js
CHANGED
|
@@ -76,8 +76,9 @@ function resolveSpring(s) {
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
/** Creates the morph instance over a `<path>` and paints the initial icon. */
|
|
79
|
-
function createMorph(el, icon) {
|
|
79
|
+
function createMorph(el, icon, options) {
|
|
80
80
|
const spring = new Spring();
|
|
81
|
+
let reducedMotion = options?.reducedMotion ?? "never";
|
|
81
82
|
let target = icon;
|
|
82
83
|
let rest = true;
|
|
83
84
|
let plan = null;
|
|
@@ -142,6 +143,13 @@ function createMorph(el, icon) {
|
|
|
142
143
|
target = icon;
|
|
143
144
|
settle();
|
|
144
145
|
};
|
|
146
|
+
/** True when the policy says this morphTo must jump instead of flying. */
|
|
147
|
+
const motionOff = () => {
|
|
148
|
+
if (reducedMotion === "always") return true;
|
|
149
|
+
if (reducedMotion !== "user") return false;
|
|
150
|
+
if (typeof matchMedia === "undefined") return false;
|
|
151
|
+
return matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
|
|
152
|
+
};
|
|
145
153
|
const seek = (icon, tt) => {
|
|
146
154
|
if (dead) return;
|
|
147
155
|
const reuse = !rest && plan !== null && icon === target;
|
|
@@ -154,11 +162,9 @@ function createMorph(el, icon) {
|
|
|
154
162
|
morphTo(icon, sp) {
|
|
155
163
|
if (dead) return;
|
|
156
164
|
if (icon === target && (rest || flying)) return;
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
165
|
+
if (motionOff()) {
|
|
166
|
+
setNow(icon);
|
|
167
|
+
return;
|
|
162
168
|
}
|
|
163
169
|
const { k, c } = resolveSpring(sp);
|
|
164
170
|
spring.config(k, c);
|
|
@@ -180,6 +186,12 @@ function createMorph(el, icon) {
|
|
|
180
186
|
set progress(v) {
|
|
181
187
|
if (!dead) seek(target, v);
|
|
182
188
|
},
|
|
189
|
+
get reducedMotion() {
|
|
190
|
+
return reducedMotion;
|
|
191
|
+
},
|
|
192
|
+
set reducedMotion(v) {
|
|
193
|
+
reducedMotion = v;
|
|
194
|
+
},
|
|
183
195
|
destroy() {
|
|
184
196
|
stop();
|
|
185
197
|
dead = true;
|
package/dist/react-native.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 { Morph, MorphOptions, PathEl, ReducedMotionMode } from "./dom.js";
|
|
3
3
|
import { SvgProps } from "react-native-svg";
|
|
4
4
|
//#region src/react-native/index.d.ts
|
|
5
5
|
/** Imperative surface exposed via ref. */
|
|
@@ -18,6 +18,11 @@ interface MorphIconProps extends Omit<SvgProps, "from" | "to"> {
|
|
|
18
18
|
progress?: number;
|
|
19
19
|
/** Physics for uncontrolled/imperative mode: preset or custom spring. */
|
|
20
20
|
spring?: SpringPreset | MorphOptions;
|
|
21
|
+
/** Reduced-motion policy: "never" (default) animates regardless of the OS
|
|
22
|
+
* setting, "user" honors AccessibilityInfo's reduce motion (morphs degrade
|
|
23
|
+
* to an instant swap while it is on; best-effort, the query is async),
|
|
24
|
+
* "always" always jumps. */
|
|
25
|
+
reducedMotion?: ReducedMotionMode;
|
|
21
26
|
size?: number | string;
|
|
22
27
|
color?: string;
|
|
23
28
|
strokeWidth?: number | string;
|
|
@@ -28,4 +33,4 @@ interface MorphIconProps extends Omit<SvgProps, "from" | "to"> {
|
|
|
28
33
|
}
|
|
29
34
|
declare const MorphIcon: import("react").ForwardRefExoticComponent<MorphIconProps & import("react").RefAttributes<MorphHandle>>;
|
|
30
35
|
//#endregion
|
|
31
|
-
export { type IconInput, type IconNode, type Morph, MorphHandle, MorphIcon, MorphIconProps, type MorphOptions, type PathEl, type Sampled, type SpringPreset };
|
|
36
|
+
export { type IconInput, type IconNode, type Morph, MorphHandle, MorphIcon, MorphIconProps, type MorphOptions, type PathEl, type ReducedMotionMode, type Sampled, type SpringPreset };
|
package/dist/react-native.js
CHANGED
|
@@ -31,7 +31,7 @@ function frozenD(from, to, t) {
|
|
|
31
31
|
return serialize(out, plan.items.map((it) => it.closed));
|
|
32
32
|
}
|
|
33
33
|
const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
34
|
-
const { icon, from, to, progress, spring, size = 24, color = "currentColor", strokeWidth = 2, absoluteStrokeWidth, label, ...rest } = props;
|
|
34
|
+
const { icon, from, to, progress, spring, reducedMotion, size = 24, color = "currentColor", strokeWidth = 2, absoluteStrokeWidth, label, ...rest } = props;
|
|
35
35
|
const controlled = from !== void 0 && to !== void 0;
|
|
36
36
|
const initialIcon = icon ?? from ?? to;
|
|
37
37
|
const [initialD] = useState(() => {
|
|
@@ -42,6 +42,8 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
42
42
|
const morphRef = useRef(null);
|
|
43
43
|
const springRef = useRef(spring);
|
|
44
44
|
springRef.current = spring;
|
|
45
|
+
const rmRef = useRef(reducedMotion);
|
|
46
|
+
rmRef.current = reducedMotion;
|
|
45
47
|
const prevIcon = useRef(icon);
|
|
46
48
|
const prevControlled = useRef(controlled);
|
|
47
49
|
const dead = useRef(false);
|
|
@@ -52,9 +54,12 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
52
54
|
const el = useRef({ setAttribute: (name, value) => {
|
|
53
55
|
if (name === "d") pathRef.current?.setNativeProps({ d: value });
|
|
54
56
|
} });
|
|
55
|
-
/** morphTo that
|
|
57
|
+
/** morphTo that applies the reduced-motion policy ("user" reads the OS
|
|
58
|
+
* setting via AccessibilityInfo, best-effort). */
|
|
56
59
|
const fly = (m, i, s) => {
|
|
57
|
-
|
|
60
|
+
const mode = rmRef.current ?? "never";
|
|
61
|
+
if (mode === "user") initReducedMotion();
|
|
62
|
+
if (mode === "always" || mode === "user" && reduced) m.set(i);
|
|
58
63
|
else m.morphTo(i, s);
|
|
59
64
|
};
|
|
60
65
|
const flyRef = useRef(fly);
|
|
@@ -71,7 +76,6 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
71
76
|
}, []);
|
|
72
77
|
useIsoLayoutEffect(() => {
|
|
73
78
|
dead.current = false;
|
|
74
|
-
initReducedMotion();
|
|
75
79
|
if (pathRef.current && initialIcon !== void 0) {
|
|
76
80
|
const m = createMorph(el.current, controlled ? from : initialIcon);
|
|
77
81
|
morphRef.current = m;
|
|
@@ -94,6 +98,9 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
94
98
|
pair.current = null;
|
|
95
99
|
};
|
|
96
100
|
}, []);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (reducedMotion === "user") initReducedMotion();
|
|
103
|
+
}, [reducedMotion]);
|
|
97
104
|
useEffect(() => {
|
|
98
105
|
const left = prevControlled.current && !controlled;
|
|
99
106
|
prevControlled.current = controlled;
|
package/dist/react.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 { Morph, MorphOptions, PathEl, ReducedMotionMode } from "./dom.js";
|
|
3
3
|
import { SVGProps } from "react";
|
|
4
4
|
//#region src/react/index.d.ts
|
|
5
5
|
/** Imperative surface exposed via ref. */
|
|
@@ -18,6 +18,10 @@ interface MorphIconProps extends Omit<SVGProps<SVGSVGElement>, "from" | "to" | "
|
|
|
18
18
|
progress?: number;
|
|
19
19
|
/** Physics for uncontrolled/imperative mode: preset or custom spring. */
|
|
20
20
|
spring?: SpringPreset | MorphOptions;
|
|
21
|
+
/** Reduced-motion policy: "never" (default) animates regardless of the OS
|
|
22
|
+
* setting, "user" honors prefers-reduced-motion (morphs degrade to an
|
|
23
|
+
* instant swap while it is on), "always" always jumps. */
|
|
24
|
+
reducedMotion?: ReducedMotionMode;
|
|
21
25
|
size?: number | string;
|
|
22
26
|
color?: string;
|
|
23
27
|
strokeWidth?: number | string;
|
|
@@ -28,4 +32,4 @@ interface MorphIconProps extends Omit<SVGProps<SVGSVGElement>, "from" | "to" | "
|
|
|
28
32
|
}
|
|
29
33
|
declare const MorphIcon: import("react").ForwardRefExoticComponent<MorphIconProps & import("react").RefAttributes<MorphHandle>>;
|
|
30
34
|
//#endregion
|
|
31
|
-
export { type IconInput, type IconNode, type Morph, MorphHandle, MorphIcon, MorphIconProps, type MorphOptions, type PathEl, type Sampled, type SpringPreset };
|
|
35
|
+
export { type IconInput, type IconNode, type Morph, MorphHandle, MorphIcon, MorphIconProps, type MorphOptions, type PathEl, type ReducedMotionMode, type Sampled, type SpringPreset };
|
package/dist/react.js
CHANGED
|
@@ -15,7 +15,7 @@ function frozenD(from, to, t) {
|
|
|
15
15
|
return serialize(out, plan.items.map((it) => it.closed));
|
|
16
16
|
}
|
|
17
17
|
const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
18
|
-
const { icon, from, to, progress, spring, size = 24, color = "currentColor", strokeWidth = 2, absoluteStrokeWidth, label, ...rest } = props;
|
|
18
|
+
const { icon, from, to, progress, spring, reducedMotion, size = 24, color = "currentColor", strokeWidth = 2, absoluteStrokeWidth, label, ...rest } = props;
|
|
19
19
|
const controlled = from !== void 0 && to !== void 0;
|
|
20
20
|
const initialIcon = icon ?? from ?? to;
|
|
21
21
|
const [initialD] = useState(() => {
|
|
@@ -26,6 +26,8 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
26
26
|
const morphRef = useRef(null);
|
|
27
27
|
const springRef = useRef(spring);
|
|
28
28
|
springRef.current = spring;
|
|
29
|
+
const rmRef = useRef(reducedMotion);
|
|
30
|
+
rmRef.current = reducedMotion;
|
|
29
31
|
const prevIcon = useRef(icon);
|
|
30
32
|
const prevControlled = useRef(controlled);
|
|
31
33
|
const dead = useRef(false);
|
|
@@ -39,14 +41,14 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
39
41
|
if (morphRef.current) return morphRef.current;
|
|
40
42
|
const el = pathRef.current;
|
|
41
43
|
if (dead.current || !el) return null;
|
|
42
|
-
morphRef.current = createMorph(el, birth);
|
|
44
|
+
morphRef.current = createMorph(el, birth, { reducedMotion: rmRef.current });
|
|
43
45
|
return morphRef.current;
|
|
44
46
|
}, []);
|
|
45
47
|
useIsoLayoutEffect(() => {
|
|
46
48
|
dead.current = false;
|
|
47
49
|
const el = pathRef.current;
|
|
48
50
|
if (el && initialIcon !== void 0) {
|
|
49
|
-
const m = createMorph(el, controlled ? from : initialIcon);
|
|
51
|
+
const m = createMorph(el, controlled ? from : initialIcon, { reducedMotion: rmRef.current });
|
|
50
52
|
morphRef.current = m;
|
|
51
53
|
if (controlled) {
|
|
52
54
|
pair.current = [from, to];
|
|
@@ -67,6 +69,10 @@ const MorphIcon = forwardRef(function MorphIcon(props, ref) {
|
|
|
67
69
|
pair.current = null;
|
|
68
70
|
};
|
|
69
71
|
}, []);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const m = morphRef.current;
|
|
74
|
+
if (m) m.reducedMotion = reducedMotion ?? "never";
|
|
75
|
+
}, [reducedMotion]);
|
|
70
76
|
useEffect(() => {
|
|
71
77
|
const left = prevControlled.current && !controlled;
|
|
72
78
|
prevControlled.current = controlled;
|
package/dist/svelte-shared.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 { Morph, MorphOptions, PathEl, ReducedMotionMode } from "./dom.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
|
|
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 }:
|
|
58
|
-
mount(mountEl: PathEl, { icon, from, to, progress }:
|
|
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 };
|
package/dist/svelte-shared.js
CHANGED
|
@@ -35,10 +35,11 @@ function computeInitialD({ icon, from, to, progress }) {
|
|
|
35
35
|
* path back to `icon`.
|
|
36
36
|
* - Every exit from controlled mode (imperative call or icon takeover)
|
|
37
37
|
* invalidates the frozen pair, so returning to it re-bases on `from`. */
|
|
38
|
-
function createController({ icon, from, to, progress }) {
|
|
38
|
+
function createController({ icon, from, to, progress, reducedMotion }) {
|
|
39
39
|
let el = null;
|
|
40
40
|
let dead = false;
|
|
41
41
|
let morph = null;
|
|
42
|
+
let rm = reducedMotion ?? "never";
|
|
42
43
|
let based = false;
|
|
43
44
|
let pair = null;
|
|
44
45
|
let prevIcon = icon;
|
|
@@ -50,7 +51,7 @@ function createController({ icon, from, to, progress }) {
|
|
|
50
51
|
const ensure = (birth) => {
|
|
51
52
|
if (morph) return morph;
|
|
52
53
|
if (dead || !el) return null;
|
|
53
|
-
morph = createMorph(el, birth);
|
|
54
|
+
morph = createMorph(el, birth, { reducedMotion: rm });
|
|
54
55
|
return morph;
|
|
55
56
|
};
|
|
56
57
|
/** Controlled mode: freeze the pair at `progress` via seek (no spring). */
|
|
@@ -77,12 +78,13 @@ function createController({ icon, from, to, progress }) {
|
|
|
77
78
|
}
|
|
78
79
|
};
|
|
79
80
|
return {
|
|
80
|
-
mount(mountEl, { icon, from, to, progress }) {
|
|
81
|
+
mount(mountEl, { icon, from, to, progress, reducedMotion }) {
|
|
81
82
|
el = mountEl;
|
|
83
|
+
rm = reducedMotion ?? rm;
|
|
82
84
|
const controlled = from !== void 0 && to !== void 0;
|
|
83
85
|
const initialIcon = icon ?? from ?? to;
|
|
84
86
|
if (initialIcon === void 0) return;
|
|
85
|
-
const m = createMorph(el, controlled ? from : initialIcon);
|
|
87
|
+
const m = createMorph(el, controlled ? from : initialIcon, { reducedMotion: rm });
|
|
86
88
|
morph = m;
|
|
87
89
|
if (controlled) {
|
|
88
90
|
pair = [from, to];
|
|
@@ -106,7 +108,9 @@ function createController({ icon, from, to, progress }) {
|
|
|
106
108
|
/** Prop watcher: ONE owner decides per run — the controlled pair while it
|
|
107
109
|
* is fully present, `icon` otherwise (mount doesn't fire thanks to the
|
|
108
110
|
* init-time baselines). */
|
|
109
|
-
watch({ icon, from, to, progress, spring }) {
|
|
111
|
+
watch({ icon, from, to, progress, spring, reducedMotion }) {
|
|
112
|
+
rm = reducedMotion ?? "never";
|
|
113
|
+
if (morph) morph.reducedMotion = rm;
|
|
110
114
|
const controlled = from !== void 0 && to !== void 0;
|
|
111
115
|
const left = prevControlled && !controlled;
|
|
112
116
|
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 { Morph, MorphOptions, PathEl, ReducedMotionMode } from "./dom.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 };
|
package/dist/vue.js
CHANGED
|
@@ -40,6 +40,13 @@ const MorphIcon = defineComponent({
|
|
|
40
40
|
type: [String, Object],
|
|
41
41
|
default: void 0
|
|
42
42
|
},
|
|
43
|
+
/** Reduced-motion policy: "never" (default) animates regardless of the OS
|
|
44
|
+
* setting, "user" honors prefers-reduced-motion (morphs degrade to an
|
|
45
|
+
* instant swap while it is on), "always" always jumps. */
|
|
46
|
+
reducedMotion: {
|
|
47
|
+
type: String,
|
|
48
|
+
default: "never"
|
|
49
|
+
},
|
|
43
50
|
size: {
|
|
44
51
|
type: [Number, String],
|
|
45
52
|
default: 24
|
|
@@ -84,7 +91,7 @@ const MorphIcon = defineComponent({
|
|
|
84
91
|
if (morph) return morph;
|
|
85
92
|
const el = pathEl.value;
|
|
86
93
|
if (dead || !el) return null;
|
|
87
|
-
morph = createMorph(el, birth);
|
|
94
|
+
morph = createMorph(el, birth, { reducedMotion: props.reducedMotion });
|
|
88
95
|
return morph;
|
|
89
96
|
};
|
|
90
97
|
onMounted(() => {
|
|
@@ -93,7 +100,7 @@ const MorphIcon = defineComponent({
|
|
|
93
100
|
const controlled = from !== void 0 && to !== void 0;
|
|
94
101
|
const initialIcon = icon ?? from ?? to;
|
|
95
102
|
if (!el || initialIcon === void 0) return;
|
|
96
|
-
const m = createMorph(el, controlled ? from : initialIcon);
|
|
103
|
+
const m = createMorph(el, controlled ? from : initialIcon, { reducedMotion: props.reducedMotion });
|
|
97
104
|
morph = m;
|
|
98
105
|
if (controlled) {
|
|
99
106
|
pair = [from, to];
|
|
@@ -113,6 +120,9 @@ const MorphIcon = defineComponent({
|
|
|
113
120
|
based = false;
|
|
114
121
|
pair = null;
|
|
115
122
|
});
|
|
123
|
+
watch(() => props.reducedMotion, (v) => {
|
|
124
|
+
if (morph) morph.reducedMotion = v;
|
|
125
|
+
});
|
|
116
126
|
watch(() => [
|
|
117
127
|
props.icon,
|
|
118
128
|
props.from,
|
|
@@ -128,8 +138,10 @@ const MorphIcon = defineComponent({
|
|
|
128
138
|
if (icon === void 0 || !changed && !left) return;
|
|
129
139
|
pair = null;
|
|
130
140
|
based = false;
|
|
131
|
-
if (morph)
|
|
132
|
-
|
|
141
|
+
if (morph) {
|
|
142
|
+
morph.reducedMotion = props.reducedMotion;
|
|
143
|
+
morph.morphTo(icon, props.spring);
|
|
144
|
+
} else ensure(icon);
|
|
133
145
|
});
|
|
134
146
|
watch(() => [
|
|
135
147
|
props.from,
|
package/package.json
CHANGED