@onlynative/inertia-gradients 0.0.1-alpha.3
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.mts +107 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +142 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +136 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +87 -0
- package/src/MotionLinearGradient.tsx +261 -0
- package/src/index.ts +21 -0
- package/src/types.ts +54 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { LinearGradientProps } from 'expo-linear-gradient';
|
|
3
|
+
import { TransitionConfig } from '@onlynative/inertia';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A 2D point on the gradient's `[0, 1]` square. `{ x: 0, y: 0 }` is the
|
|
7
|
+
* top-left corner; `{ x: 1, y: 1 }` is the bottom-right.
|
|
8
|
+
*/
|
|
9
|
+
interface GradientPoint {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Animatable target snapshot for a linear gradient. Every field is optional —
|
|
15
|
+
* include only the dimensions you want to animate; the rest fall back to the
|
|
16
|
+
* static props on the component.
|
|
17
|
+
*
|
|
18
|
+
* `colors` and `locations` arrays must keep the same length as the static
|
|
19
|
+
* `colors` prop. Slot count is locked at first render so the shared-value
|
|
20
|
+
* table is stable across the animation's lifetime.
|
|
21
|
+
*/
|
|
22
|
+
interface LinearGradientAnimate {
|
|
23
|
+
colors?: readonly string[];
|
|
24
|
+
start?: GradientPoint;
|
|
25
|
+
end?: GradientPoint;
|
|
26
|
+
locations?: readonly number[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The four animatable dimensions of a linear gradient. Per-key transitions on
|
|
30
|
+
* `transition` are keyed against this shape.
|
|
31
|
+
*/
|
|
32
|
+
interface LinearGradientStateShape {
|
|
33
|
+
colors: readonly string[];
|
|
34
|
+
start: GradientPoint;
|
|
35
|
+
end: GradientPoint;
|
|
36
|
+
locations: readonly number[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Per-property transition map. Top-level entries on `transition` apply to all
|
|
40
|
+
* properties unless overridden by a per-key entry here.
|
|
41
|
+
*/
|
|
42
|
+
type LinearGradientPerPropertyTransition = {
|
|
43
|
+
[K in keyof LinearGradientStateShape]?: TransitionConfig;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Transition shape accepted by `MotionLinearGradient`. Either a single
|
|
47
|
+
* top-level transition applied to every animated dimension, or a per-property
|
|
48
|
+
* map.
|
|
49
|
+
*/
|
|
50
|
+
type LinearGradientTransition = TransitionConfig | LinearGradientPerPropertyTransition;
|
|
51
|
+
|
|
52
|
+
type AtLeastTwoStrings = readonly [string, string, ...string[]];
|
|
53
|
+
interface MotionLinearGradientProps extends Omit<LinearGradientProps, 'colors' | 'start' | 'end' | 'locations'> {
|
|
54
|
+
/**
|
|
55
|
+
* Initial color stops, in order. At least two are required. The array's
|
|
56
|
+
* length is **locked at first render** — to change the number of stops,
|
|
57
|
+
* remount with a new `key`.
|
|
58
|
+
*/
|
|
59
|
+
colors: AtLeastTwoStrings;
|
|
60
|
+
/** Start point in normalized `[0, 1]` coordinates. Defaults to `{x:0,y:0}`. */
|
|
61
|
+
start?: GradientPoint;
|
|
62
|
+
/** End point in normalized `[0, 1]` coordinates. Defaults to `{x:1,y:0}`. */
|
|
63
|
+
end?: GradientPoint;
|
|
64
|
+
/**
|
|
65
|
+
* Optional stop positions. If supplied at mount, must remain supplied (and
|
|
66
|
+
* the same length as `colors`) for the lifetime of the component.
|
|
67
|
+
*/
|
|
68
|
+
locations?: readonly number[];
|
|
69
|
+
/**
|
|
70
|
+
* Initial frame override. When present, the component mounts displaying
|
|
71
|
+
* these values, then animates to `animate` on the next effect. Pass `false`
|
|
72
|
+
* to skip the initial-mount animation entirely.
|
|
73
|
+
*/
|
|
74
|
+
initial?: LinearGradientAnimate | false;
|
|
75
|
+
/** Target animation state. */
|
|
76
|
+
animate?: LinearGradientAnimate;
|
|
77
|
+
/**
|
|
78
|
+
* Transition config — either a single `TransitionConfig` applied to every
|
|
79
|
+
* animated dimension, or a per-property map (`{ colors, start, end,
|
|
80
|
+
* locations }`). Per-property entries win over the top-level transition.
|
|
81
|
+
*/
|
|
82
|
+
transition?: LinearGradientTransition;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Animatable `LinearGradient`. Wraps `expo-linear-gradient`'s `LinearGradient`
|
|
86
|
+
* with declarative `initial` / `animate` / `transition` props.
|
|
87
|
+
*
|
|
88
|
+
* Animatable dimensions:
|
|
89
|
+
* - `colors` — array of color strings, element-wise interpolated. Slot count
|
|
90
|
+
* is locked at mount.
|
|
91
|
+
* - `start` / `end` — `{ x, y }` points; x and y animate independently.
|
|
92
|
+
* - `locations` — array of stop positions, element-wise interpolated. Locked
|
|
93
|
+
* to the same length as `colors` (and to its presence at mount).
|
|
94
|
+
*
|
|
95
|
+
* Example:
|
|
96
|
+
* ```tsx
|
|
97
|
+
* <MotionLinearGradient
|
|
98
|
+
* colors={['#0f172a', '#1e293b']}
|
|
99
|
+
* animate={{ colors: ['#7c3aed', '#0ea5e9'] }}
|
|
100
|
+
* transition={{ type: 'timing', duration: 600 }}
|
|
101
|
+
* style={StyleSheet.absoluteFill}
|
|
102
|
+
* />
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
declare function MotionLinearGradient(props: MotionLinearGradientProps): react.JSX.Element;
|
|
106
|
+
|
|
107
|
+
export { type GradientPoint, type LinearGradientAnimate, type LinearGradientPerPropertyTransition, type LinearGradientStateShape, type LinearGradientTransition, MotionLinearGradient, type MotionLinearGradientProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { LinearGradientProps } from 'expo-linear-gradient';
|
|
3
|
+
import { TransitionConfig } from '@onlynative/inertia';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A 2D point on the gradient's `[0, 1]` square. `{ x: 0, y: 0 }` is the
|
|
7
|
+
* top-left corner; `{ x: 1, y: 1 }` is the bottom-right.
|
|
8
|
+
*/
|
|
9
|
+
interface GradientPoint {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Animatable target snapshot for a linear gradient. Every field is optional —
|
|
15
|
+
* include only the dimensions you want to animate; the rest fall back to the
|
|
16
|
+
* static props on the component.
|
|
17
|
+
*
|
|
18
|
+
* `colors` and `locations` arrays must keep the same length as the static
|
|
19
|
+
* `colors` prop. Slot count is locked at first render so the shared-value
|
|
20
|
+
* table is stable across the animation's lifetime.
|
|
21
|
+
*/
|
|
22
|
+
interface LinearGradientAnimate {
|
|
23
|
+
colors?: readonly string[];
|
|
24
|
+
start?: GradientPoint;
|
|
25
|
+
end?: GradientPoint;
|
|
26
|
+
locations?: readonly number[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The four animatable dimensions of a linear gradient. Per-key transitions on
|
|
30
|
+
* `transition` are keyed against this shape.
|
|
31
|
+
*/
|
|
32
|
+
interface LinearGradientStateShape {
|
|
33
|
+
colors: readonly string[];
|
|
34
|
+
start: GradientPoint;
|
|
35
|
+
end: GradientPoint;
|
|
36
|
+
locations: readonly number[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Per-property transition map. Top-level entries on `transition` apply to all
|
|
40
|
+
* properties unless overridden by a per-key entry here.
|
|
41
|
+
*/
|
|
42
|
+
type LinearGradientPerPropertyTransition = {
|
|
43
|
+
[K in keyof LinearGradientStateShape]?: TransitionConfig;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Transition shape accepted by `MotionLinearGradient`. Either a single
|
|
47
|
+
* top-level transition applied to every animated dimension, or a per-property
|
|
48
|
+
* map.
|
|
49
|
+
*/
|
|
50
|
+
type LinearGradientTransition = TransitionConfig | LinearGradientPerPropertyTransition;
|
|
51
|
+
|
|
52
|
+
type AtLeastTwoStrings = readonly [string, string, ...string[]];
|
|
53
|
+
interface MotionLinearGradientProps extends Omit<LinearGradientProps, 'colors' | 'start' | 'end' | 'locations'> {
|
|
54
|
+
/**
|
|
55
|
+
* Initial color stops, in order. At least two are required. The array's
|
|
56
|
+
* length is **locked at first render** — to change the number of stops,
|
|
57
|
+
* remount with a new `key`.
|
|
58
|
+
*/
|
|
59
|
+
colors: AtLeastTwoStrings;
|
|
60
|
+
/** Start point in normalized `[0, 1]` coordinates. Defaults to `{x:0,y:0}`. */
|
|
61
|
+
start?: GradientPoint;
|
|
62
|
+
/** End point in normalized `[0, 1]` coordinates. Defaults to `{x:1,y:0}`. */
|
|
63
|
+
end?: GradientPoint;
|
|
64
|
+
/**
|
|
65
|
+
* Optional stop positions. If supplied at mount, must remain supplied (and
|
|
66
|
+
* the same length as `colors`) for the lifetime of the component.
|
|
67
|
+
*/
|
|
68
|
+
locations?: readonly number[];
|
|
69
|
+
/**
|
|
70
|
+
* Initial frame override. When present, the component mounts displaying
|
|
71
|
+
* these values, then animates to `animate` on the next effect. Pass `false`
|
|
72
|
+
* to skip the initial-mount animation entirely.
|
|
73
|
+
*/
|
|
74
|
+
initial?: LinearGradientAnimate | false;
|
|
75
|
+
/** Target animation state. */
|
|
76
|
+
animate?: LinearGradientAnimate;
|
|
77
|
+
/**
|
|
78
|
+
* Transition config — either a single `TransitionConfig` applied to every
|
|
79
|
+
* animated dimension, or a per-property map (`{ colors, start, end,
|
|
80
|
+
* locations }`). Per-property entries win over the top-level transition.
|
|
81
|
+
*/
|
|
82
|
+
transition?: LinearGradientTransition;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Animatable `LinearGradient`. Wraps `expo-linear-gradient`'s `LinearGradient`
|
|
86
|
+
* with declarative `initial` / `animate` / `transition` props.
|
|
87
|
+
*
|
|
88
|
+
* Animatable dimensions:
|
|
89
|
+
* - `colors` — array of color strings, element-wise interpolated. Slot count
|
|
90
|
+
* is locked at mount.
|
|
91
|
+
* - `start` / `end` — `{ x, y }` points; x and y animate independently.
|
|
92
|
+
* - `locations` — array of stop positions, element-wise interpolated. Locked
|
|
93
|
+
* to the same length as `colors` (and to its presence at mount).
|
|
94
|
+
*
|
|
95
|
+
* Example:
|
|
96
|
+
* ```tsx
|
|
97
|
+
* <MotionLinearGradient
|
|
98
|
+
* colors={['#0f172a', '#1e293b']}
|
|
99
|
+
* animate={{ colors: ['#7c3aed', '#0ea5e9'] }}
|
|
100
|
+
* transition={{ type: 'timing', duration: 600 }}
|
|
101
|
+
* style={StyleSheet.absoluteFill}
|
|
102
|
+
* />
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
declare function MotionLinearGradient(props: MotionLinearGradientProps): react.JSX.Element;
|
|
106
|
+
|
|
107
|
+
export { type GradientPoint, type LinearGradientAnimate, type LinearGradientPerPropertyTransition, type LinearGradientStateShape, type LinearGradientTransition, MotionLinearGradient, type MotionLinearGradientProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var expoLinearGradient = require('expo-linear-gradient');
|
|
5
|
+
var Animated = require('react-native-reanimated');
|
|
6
|
+
var inertia = require('@onlynative/inertia');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var Animated__default = /*#__PURE__*/_interopDefault(Animated);
|
|
12
|
+
|
|
13
|
+
// src/MotionLinearGradient.tsx
|
|
14
|
+
var AnimatedLinearGradient = Animated__default.default.createAnimatedComponent(expoLinearGradient.LinearGradient);
|
|
15
|
+
var NO_ANIMATION = { type: "no-animation" };
|
|
16
|
+
var DEFAULT_START = { x: 0, y: 0 };
|
|
17
|
+
var DEFAULT_END = { x: 1, y: 0 };
|
|
18
|
+
function pickTransition(per, key) {
|
|
19
|
+
if (!per) return void 0;
|
|
20
|
+
if ("type" in per) return per;
|
|
21
|
+
return per[key];
|
|
22
|
+
}
|
|
23
|
+
function MotionLinearGradient(props) {
|
|
24
|
+
const {
|
|
25
|
+
colors,
|
|
26
|
+
start = DEFAULT_START,
|
|
27
|
+
end = DEFAULT_END,
|
|
28
|
+
locations,
|
|
29
|
+
initial,
|
|
30
|
+
animate,
|
|
31
|
+
transition,
|
|
32
|
+
...rest
|
|
33
|
+
} = props;
|
|
34
|
+
const slotCountRef = react.useRef(colors.length);
|
|
35
|
+
const hasLocationsRef = react.useRef(locations !== void 0);
|
|
36
|
+
if (__DEV__) {
|
|
37
|
+
if (slotCountRef.current !== colors.length) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`[inertia-gradients] colors length changed from ${slotCountRef.current} to ${colors.length} \u2014 colors length is locked at mount; remount via key={...} to resize.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (hasLocationsRef.current !== (locations !== void 0)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`[inertia-gradients] locations presence changed \u2014 locations must be either always present or always absent (locked at mount).`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (locations !== void 0 && locations.length !== slotCountRef.current) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`[inertia-gradients] locations length (${locations.length}) must match colors length (${slotCountRef.current}).`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const seedSource = initial === false ? animate : initial ?? void 0;
|
|
54
|
+
const seedColors = seedSource?.colors ?? colors;
|
|
55
|
+
const seedStart = seedSource?.start ?? start;
|
|
56
|
+
const seedEnd = seedSource?.end ?? end;
|
|
57
|
+
const seedLocations = seedSource?.locations ?? locations;
|
|
58
|
+
const colorSvs = [];
|
|
59
|
+
for (let i = 0; i < slotCountRef.current; i++) {
|
|
60
|
+
const seed = seedColors[i] ?? colors[i] ?? "";
|
|
61
|
+
colorSvs.push(Animated.useSharedValue(seed));
|
|
62
|
+
}
|
|
63
|
+
const startX = Animated.useSharedValue(seedStart.x);
|
|
64
|
+
const startY = Animated.useSharedValue(seedStart.y);
|
|
65
|
+
const endX = Animated.useSharedValue(seedEnd.x);
|
|
66
|
+
const endY = Animated.useSharedValue(seedEnd.y);
|
|
67
|
+
const locationSvs = [];
|
|
68
|
+
for (let i = 0; i < slotCountRef.current; i++) {
|
|
69
|
+
locationSvs.push(Animated.useSharedValue(seedLocations?.[i] ?? 0));
|
|
70
|
+
}
|
|
71
|
+
const reduce = inertia.useShouldReduceMotion();
|
|
72
|
+
const colorsKey = animate?.colors ? animate.colors.join("|") : "";
|
|
73
|
+
const startKey = animate?.start ? `${animate.start.x},${animate.start.y}` : "";
|
|
74
|
+
const endKey = animate?.end ? `${animate.end.x},${animate.end.y}` : "";
|
|
75
|
+
const locationsKey = animate?.locations ? animate.locations.join("|") : "";
|
|
76
|
+
const animateColors = animate?.colors;
|
|
77
|
+
const animateStart = animate?.start;
|
|
78
|
+
const animateEnd = animate?.end;
|
|
79
|
+
const animateLocations = animate?.locations;
|
|
80
|
+
react.useEffect(() => {
|
|
81
|
+
if (!animateColors) return;
|
|
82
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "colors");
|
|
83
|
+
for (let i = 0; i < colorSvs.length; i++) {
|
|
84
|
+
const target = animateColors[i] ?? colors[i] ?? "";
|
|
85
|
+
colorSvs[i].value = inertia.resolveTransition(cfg, target);
|
|
86
|
+
}
|
|
87
|
+
}, [colorsKey, reduce, transition]);
|
|
88
|
+
react.useEffect(() => {
|
|
89
|
+
if (!animateStart) return;
|
|
90
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "start");
|
|
91
|
+
startX.value = inertia.resolveTransition(cfg, animateStart.x);
|
|
92
|
+
startY.value = inertia.resolveTransition(cfg, animateStart.y);
|
|
93
|
+
}, [startKey, reduce, transition]);
|
|
94
|
+
react.useEffect(() => {
|
|
95
|
+
if (!animateEnd) return;
|
|
96
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "end");
|
|
97
|
+
endX.value = inertia.resolveTransition(cfg, animateEnd.x);
|
|
98
|
+
endY.value = inertia.resolveTransition(cfg, animateEnd.y);
|
|
99
|
+
}, [endKey, reduce, transition]);
|
|
100
|
+
react.useEffect(() => {
|
|
101
|
+
if (!animateLocations) return;
|
|
102
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "locations");
|
|
103
|
+
for (let i = 0; i < locationSvs.length; i++) {
|
|
104
|
+
const target = animateLocations[i];
|
|
105
|
+
if (target !== void 0) {
|
|
106
|
+
locationSvs[i].value = inertia.resolveTransition(cfg, target);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}, [locationsKey, reduce, transition]);
|
|
110
|
+
const animatedProps = Animated.useAnimatedProps(() => {
|
|
111
|
+
"worklet";
|
|
112
|
+
const colorsOut = new Array(colorSvs.length);
|
|
113
|
+
for (let i = 0; i < colorSvs.length; i++) colorsOut[i] = colorSvs[i].value;
|
|
114
|
+
const out = {
|
|
115
|
+
colors: colorsOut,
|
|
116
|
+
start: { x: startX.value, y: startY.value },
|
|
117
|
+
end: { x: endX.value, y: endY.value }
|
|
118
|
+
};
|
|
119
|
+
if (hasLocationsRef.current) {
|
|
120
|
+
const locsOut = new Array(locationSvs.length);
|
|
121
|
+
for (let i = 0; i < locationSvs.length; i++)
|
|
122
|
+
locsOut[i] = locationSvs[i].value;
|
|
123
|
+
out.locations = locsOut;
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
});
|
|
127
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
128
|
+
AnimatedLinearGradient,
|
|
129
|
+
{
|
|
130
|
+
animatedProps,
|
|
131
|
+
colors,
|
|
132
|
+
start,
|
|
133
|
+
end,
|
|
134
|
+
locations,
|
|
135
|
+
...rest
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
exports.MotionLinearGradient = MotionLinearGradient;
|
|
141
|
+
//# sourceMappingURL=index.js.map
|
|
142
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/MotionLinearGradient.tsx"],"names":["Animated","LinearGradient","useRef","useSharedValue","useShouldReduceMotion","useEffect","resolveTransition","useAnimatedProps","jsx"],"mappings":";;;;;;;;;;;;;AAmBA,IAAM,sBAAA,GAAyBA,yBAAA,CAAS,uBAAA,CAAwBC,iCAAc,CAAA;AAE9E,IAAM,YAAA,GAAiC,EAAE,IAAA,EAAM,cAAA,EAAe;AAC9D,IAAM,aAAA,GAA+B,EAAE,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA,EAAE;AAClD,IAAM,WAAA,GAA6B,EAAE,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA,EAAE;AAQhD,SAAS,cAAA,CACP,KACA,GAAA,EAC8B;AAC9B,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,MAAA,IAAU,KAAK,OAAO,GAAA;AAC1B,EAAA,OAAQ,IAA4C,GAAG,CAAA;AACzD;AA4DO,SAAS,qBAAqB,KAAA,EAAkC;AACrE,EAAA,MAAM;AAAA,IACJ,MAAA;AAAA,IACA,KAAA,GAAQ,aAAA;AAAA,IACR,GAAA,GAAM,WAAA;AAAA,IACN,SAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,GAAG;AAAA,GACL,GAAI,KAAA;AAMJ,EAAA,MAAM,YAAA,GAAeC,YAAA,CAAO,MAAA,CAAO,MAAM,CAAA;AACzC,EAAA,MAAM,eAAA,GAAkBA,YAAA,CAAO,SAAA,KAAc,MAAS,CAAA;AAEtD,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,IAAI,YAAA,CAAa,OAAA,KAAY,MAAA,CAAO,MAAA,EAAQ;AAC1C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,+CAAA,EAAkD,YAAA,CAAa,OAAO,CAAA,IAAA,EAAO,OAAO,MAAM,CAAA,0EAAA;AAAA,OAC5F;AAAA,IACF;AACA,IAAA,IAAI,eAAA,CAAgB,OAAA,MAAa,SAAA,KAAc,MAAA,CAAA,EAAY;AACzD,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,iIAAA;AAAA,OACF;AAAA,IACF;AACA,IAAA,IAAI,SAAA,KAAc,MAAA,IAAa,SAAA,CAAU,MAAA,KAAW,aAAa,OAAA,EAAS;AACxE,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,sCAAA,EAAyC,SAAA,CAAU,MAAM,CAAA,4BAAA,EAA+B,aAAa,OAAO,CAAA,EAAA;AAAA,OAC9G;AAAA,IACF;AAAA,EACF;AAKA,EAAA,MAAM,UAAA,GAAa,OAAA,KAAY,KAAA,GAAQ,OAAA,GAAW,OAAA,IAAW,MAAA;AAC7D,EAAA,MAAM,UAAA,GAAa,YAAY,MAAA,IAAU,MAAA;AACzC,EAAA,MAAM,SAAA,GAAY,YAAY,KAAA,IAAS,KAAA;AACvC,EAAA,MAAM,OAAA,GAAU,YAAY,GAAA,IAAO,GAAA;AACnC,EAAA,MAAM,aAAA,GAAgB,YAAY,SAAA,IAAa,SAAA;AAI/C,EAAA,MAAM,WAAkC,EAAC;AACzC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,YAAA,CAAa,SAAS,CAAA,EAAA,EAAK;AAG7C,IAAA,MAAM,OAAO,UAAA,CAAW,CAAC,CAAA,IAAK,MAAA,CAAO,CAAC,CAAA,IAAK,EAAA;AAE3C,IAAA,QAAA,CAAS,IAAA,CAAKC,uBAAA,CAAuB,IAAI,CAAC,CAAA;AAAA,EAC5C;AAEA,EAAA,MAAM,MAAA,GAASA,uBAAA,CAAe,SAAA,CAAU,CAAC,CAAA;AACzC,EAAA,MAAM,MAAA,GAASA,uBAAA,CAAe,SAAA,CAAU,CAAC,CAAA;AACzC,EAAA,MAAM,IAAA,GAAOA,uBAAA,CAAe,OAAA,CAAQ,CAAC,CAAA;AACrC,EAAA,MAAM,IAAA,GAAOA,uBAAA,CAAe,OAAA,CAAQ,CAAC,CAAA;AAErC,EAAA,MAAM,cAAqC,EAAC;AAC5C,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,YAAA,CAAa,SAAS,CAAA,EAAA,EAAK;AAE7C,IAAA,WAAA,CAAY,KAAKA,uBAAA,CAAuB,aAAA,GAAgB,CAAC,CAAA,IAAK,CAAC,CAAC,CAAA;AAAA,EAClE;AAEA,EAAA,MAAM,SAASC,6BAAA,EAAsB;AAKrC,EAAA,MAAM,YAAY,OAAA,EAAS,MAAA,GAAS,QAAQ,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,GAAI,EAAA;AAC/D,EAAA,MAAM,QAAA,GAAW,OAAA,EAAS,KAAA,GAAQ,CAAA,EAAG,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAA,CAAA,EAAI,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAA,CAAA,GAAK,EAAA;AAC5E,EAAA,MAAM,MAAA,GAAS,OAAA,EAAS,GAAA,GAAM,CAAA,EAAG,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,CAAA,EAAI,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,CAAA,GAAK,EAAA;AACpE,EAAA,MAAM,eAAe,OAAA,EAAS,SAAA,GAAY,QAAQ,SAAA,CAAU,IAAA,CAAK,GAAG,CAAA,GAAI,EAAA;AAExE,EAAA,MAAM,gBAAgB,OAAA,EAAS,MAAA;AAC/B,EAAA,MAAM,eAAe,OAAA,EAAS,KAAA;AAC9B,EAAA,MAAM,aAAa,OAAA,EAAS,GAAA;AAC5B,EAAA,MAAM,mBAAmB,OAAA,EAAS,SAAA;AAElC,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,aAAA,EAAe;AACpB,IAAA,MAAM,GAAA,GAAM,MAAA,GAAS,YAAA,GAAe,cAAA,CAAe,YAAY,QAAQ,CAAA;AACvE,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,MAAM,SAAS,aAAA,CAAc,CAAC,CAAA,IAAK,MAAA,CAAO,CAAC,CAAA,IAAK,EAAA;AAChD,MAAA,QAAA,CAAS,CAAC,CAAA,CAAG,KAAA,GAAQC,yBAAA,CAAkB,KAAK,MAAM,CAAA;AAAA,IACpD;AAAA,EAGF,CAAA,EAAG,CAAC,SAAA,EAAW,MAAA,EAAQ,UAAU,CAAC,CAAA;AAElC,EAAAD,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,YAAA,EAAc;AACnB,IAAA,MAAM,GAAA,GAAM,MAAA,GAAS,YAAA,GAAe,cAAA,CAAe,YAAY,OAAO,CAAA;AACtE,IAAA,MAAA,CAAO,KAAA,GAAQC,yBAAA,CAAkB,GAAA,EAAK,YAAA,CAAa,CAAC,CAAA;AACpD,IAAA,MAAA,CAAO,KAAA,GAAQA,yBAAA,CAAkB,GAAA,EAAK,YAAA,CAAa,CAAC,CAAA;AAAA,EAEtD,CAAA,EAAG,CAAC,QAAA,EAAU,MAAA,EAAQ,UAAU,CAAC,CAAA;AAEjC,EAAAD,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,UAAA,EAAY;AACjB,IAAA,MAAM,GAAA,GAAM,MAAA,GAAS,YAAA,GAAe,cAAA,CAAe,YAAY,KAAK,CAAA;AACpE,IAAA,IAAA,CAAK,KAAA,GAAQC,yBAAA,CAAkB,GAAA,EAAK,UAAA,CAAW,CAAC,CAAA;AAChD,IAAA,IAAA,CAAK,KAAA,GAAQA,yBAAA,CAAkB,GAAA,EAAK,UAAA,CAAW,CAAC,CAAA;AAAA,EAElD,CAAA,EAAG,CAAC,MAAA,EAAQ,MAAA,EAAQ,UAAU,CAAC,CAAA;AAE/B,EAAAD,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,gBAAA,EAAkB;AACvB,IAAA,MAAM,GAAA,GAAM,MAAA,GAAS,YAAA,GAAe,cAAA,CAAe,YAAY,WAAW,CAAA;AAC1E,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,WAAA,CAAY,QAAQ,CAAA,EAAA,EAAK;AAC3C,MAAA,MAAM,MAAA,GAAS,iBAAiB,CAAC,CAAA;AACjC,MAAA,IAAI,WAAW,MAAA,EAAW;AACxB,QAAA,WAAA,CAAY,CAAC,CAAA,CAAG,KAAA,GAAQC,yBAAA,CAAkB,KAAK,MAAM,CAAA;AAAA,MACvD;AAAA,IACF;AAAA,EAEF,CAAA,EAAG,CAAC,YAAA,EAAc,MAAA,EAAQ,UAAU,CAAC,CAAA;AAErC,EAAA,MAAM,aAAA,GAAgBC,0BAAiB,MAAM;AAC3C,IAAA,SAAA;AACA,IAAA,MAAM,SAAA,GAAY,IAAI,KAAA,CAAc,QAAA,CAAS,MAAM,CAAA;AACnD,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,MAAA,EAAQ,CAAA,EAAA,EAAK,SAAA,CAAU,CAAC,CAAA,GAAI,QAAA,CAAS,CAAC,CAAA,CAAG,KAAA;AACtE,IAAA,MAAM,GAAA,GAKF;AAAA,MACF,MAAA,EAAQ,SAAA;AAAA,MACR,OAAO,EAAE,CAAA,EAAG,OAAO,KAAA,EAAO,CAAA,EAAG,OAAO,KAAA,EAAM;AAAA,MAC1C,KAAK,EAAE,CAAA,EAAG,KAAK,KAAA,EAAO,CAAA,EAAG,KAAK,KAAA;AAAM,KACtC;AACA,IAAA,IAAI,gBAAgB,OAAA,EAAS;AAC3B,MAAA,MAAM,OAAA,GAAU,IAAI,KAAA,CAAc,WAAA,CAAY,MAAM,CAAA;AACpD,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,WAAA,CAAY,MAAA,EAAQ,CAAA,EAAA;AACtC,QAAA,OAAA,CAAQ,CAAC,CAAA,GAAI,WAAA,CAAY,CAAC,CAAA,CAAG,KAAA;AAC/B,MAAA,GAAA,CAAI,SAAA,GAAY,OAAA;AAAA,IAClB;AACA,IAAA,OAAO,GAAA;AAAA,EACT,CAAC,CAAA;AAED,EAAA,uBACEC,cAAA;AAAA,IAAC,sBAAA;AAAA,IAAA;AAAA,MAMC,aAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA;AAAA,MACA,GAAA;AAAA,MACA,SAAA;AAAA,MACC,GAAG;AAAA;AAAA,GACN;AAEJ","file":"index.js","sourcesContent":["import { useEffect, useRef } from 'react'\nimport { LinearGradient, type LinearGradientProps } from 'expo-linear-gradient'\nimport Animated, {\n useAnimatedProps,\n useSharedValue,\n type SharedValue,\n} from 'react-native-reanimated'\nimport {\n resolveTransition,\n useShouldReduceMotion,\n type TransitionConfig,\n} from '@onlynative/inertia'\nimport type {\n GradientPoint,\n LinearGradientAnimate,\n LinearGradientPerPropertyTransition,\n LinearGradientTransition,\n} from './types'\n\nconst AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient)\n\nconst NO_ANIMATION: TransitionConfig = { type: 'no-animation' }\nconst DEFAULT_START: GradientPoint = { x: 0, y: 0 }\nconst DEFAULT_END: GradientPoint = { x: 1, y: 0 }\n\n/**\n * Extract the per-key transition for `key`, falling back to the top-level\n * transition if the user passed `transition` as a single `TransitionConfig`\n * rather than a per-property map. Mirrors the precedence rule used by the\n * core factory: per-key wins, top-level fills, library default below that.\n */\nfunction pickTransition(\n per: LinearGradientTransition | undefined,\n key: keyof LinearGradientPerPropertyTransition,\n): TransitionConfig | undefined {\n if (!per) return undefined\n if ('type' in per) return per as TransitionConfig\n return (per as LinearGradientPerPropertyTransition)[key]\n}\n\ntype AtLeastTwoStrings = readonly [string, string, ...string[]]\n\nexport interface MotionLinearGradientProps extends Omit<\n LinearGradientProps,\n 'colors' | 'start' | 'end' | 'locations'\n> {\n /**\n * Initial color stops, in order. At least two are required. The array's\n * length is **locked at first render** — to change the number of stops,\n * remount with a new `key`.\n */\n colors: AtLeastTwoStrings\n /** Start point in normalized `[0, 1]` coordinates. Defaults to `{x:0,y:0}`. */\n start?: GradientPoint\n /** End point in normalized `[0, 1]` coordinates. Defaults to `{x:1,y:0}`. */\n end?: GradientPoint\n /**\n * Optional stop positions. If supplied at mount, must remain supplied (and\n * the same length as `colors`) for the lifetime of the component.\n */\n locations?: readonly number[]\n /**\n * Initial frame override. When present, the component mounts displaying\n * these values, then animates to `animate` on the next effect. Pass `false`\n * to skip the initial-mount animation entirely.\n */\n initial?: LinearGradientAnimate | false\n /** Target animation state. */\n animate?: LinearGradientAnimate\n /**\n * Transition config — either a single `TransitionConfig` applied to every\n * animated dimension, or a per-property map (`{ colors, start, end,\n * locations }`). Per-property entries win over the top-level transition.\n */\n transition?: LinearGradientTransition\n}\n\n/**\n * Animatable `LinearGradient`. Wraps `expo-linear-gradient`'s `LinearGradient`\n * with declarative `initial` / `animate` / `transition` props.\n *\n * Animatable dimensions:\n * - `colors` — array of color strings, element-wise interpolated. Slot count\n * is locked at mount.\n * - `start` / `end` — `{ x, y }` points; x and y animate independently.\n * - `locations` — array of stop positions, element-wise interpolated. Locked\n * to the same length as `colors` (and to its presence at mount).\n *\n * Example:\n * ```tsx\n * <MotionLinearGradient\n * colors={['#0f172a', '#1e293b']}\n * animate={{ colors: ['#7c3aed', '#0ea5e9'] }}\n * transition={{ type: 'timing', duration: 600 }}\n * style={StyleSheet.absoluteFill}\n * />\n * ```\n */\nexport function MotionLinearGradient(props: MotionLinearGradientProps) {\n const {\n colors,\n start = DEFAULT_START,\n end = DEFAULT_END,\n locations,\n initial,\n animate,\n transition,\n ...rest\n } = props\n\n // Slot count is locked at first render; subsequent renders must keep\n // `colors.length` constant so the hook order (one `useSharedValue` per slot\n // below) stays stable. `locations` presence is similarly locked because we\n // allocate its shared-value table on the same path.\n const slotCountRef = useRef(colors.length)\n const hasLocationsRef = useRef(locations !== undefined)\n\n if (__DEV__) {\n if (slotCountRef.current !== colors.length) {\n throw new Error(\n `[inertia-gradients] colors length changed from ${slotCountRef.current} to ${colors.length} — colors length is locked at mount; remount via key={...} to resize.`,\n )\n }\n if (hasLocationsRef.current !== (locations !== undefined)) {\n throw new Error(\n `[inertia-gradients] locations presence changed — locations must be either always present or always absent (locked at mount).`,\n )\n }\n if (locations !== undefined && locations.length !== slotCountRef.current) {\n throw new Error(\n `[inertia-gradients] locations length (${locations.length}) must match colors length (${slotCountRef.current}).`,\n )\n }\n }\n\n // `initial: false` means \"start at the animate target — no mount animation\".\n // `initial: {...}` overrides the static prop with explicit initial values.\n // `initial: undefined` (default) seeds from the static props.\n const seedSource = initial === false ? animate : (initial ?? undefined)\n const seedColors = seedSource?.colors ?? colors\n const seedStart = seedSource?.start ?? start\n const seedEnd = seedSource?.end ?? end\n const seedLocations = seedSource?.locations ?? locations\n\n // Loop-of-hooks pattern: safe because slotCountRef enforces a constant\n // length. ESLint can't see the invariant, so we suppress per call site.\n const colorSvs: SharedValue<string>[] = []\n for (let i = 0; i < slotCountRef.current; i++) {\n // Slot `i` is in-bounds for `colors` by the length lock, but TS sees a\n // generic `readonly string[]` index so coerce via a non-null fallback.\n const seed = seedColors[i] ?? colors[i] ?? ''\n // eslint-disable-next-line react-hooks/rules-of-hooks\n colorSvs.push(useSharedValue<string>(seed))\n }\n\n const startX = useSharedValue(seedStart.x)\n const startY = useSharedValue(seedStart.y)\n const endX = useSharedValue(seedEnd.x)\n const endY = useSharedValue(seedEnd.y)\n\n const locationSvs: SharedValue<number>[] = []\n for (let i = 0; i < slotCountRef.current; i++) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n locationSvs.push(useSharedValue<number>(seedLocations?.[i] ?? 0))\n }\n\n const reduce = useShouldReduceMotion()\n\n // Serialize targets into scalar keys so effects re-run on value change, not\n // on every parent re-render (a fresh `animate` literal each render is the\n // common case in callers).\n const colorsKey = animate?.colors ? animate.colors.join('|') : ''\n const startKey = animate?.start ? `${animate.start.x},${animate.start.y}` : ''\n const endKey = animate?.end ? `${animate.end.x},${animate.end.y}` : ''\n const locationsKey = animate?.locations ? animate.locations.join('|') : ''\n\n const animateColors = animate?.colors\n const animateStart = animate?.start\n const animateEnd = animate?.end\n const animateLocations = animate?.locations\n\n useEffect(() => {\n if (!animateColors) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'colors')\n for (let i = 0; i < colorSvs.length; i++) {\n const target = animateColors[i] ?? colors[i] ?? ''\n colorSvs[i]!.value = resolveTransition(cfg, target) as string\n }\n // colorSvs / colors are stable across renders by the length-lock above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [colorsKey, reduce, transition])\n\n useEffect(() => {\n if (!animateStart) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'start')\n startX.value = resolveTransition(cfg, animateStart.x) as number\n startY.value = resolveTransition(cfg, animateStart.y) as number\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [startKey, reduce, transition])\n\n useEffect(() => {\n if (!animateEnd) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'end')\n endX.value = resolveTransition(cfg, animateEnd.x) as number\n endY.value = resolveTransition(cfg, animateEnd.y) as number\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [endKey, reduce, transition])\n\n useEffect(() => {\n if (!animateLocations) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'locations')\n for (let i = 0; i < locationSvs.length; i++) {\n const target = animateLocations[i]\n if (target !== undefined) {\n locationSvs[i]!.value = resolveTransition(cfg, target) as number\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [locationsKey, reduce, transition])\n\n const animatedProps = useAnimatedProps(() => {\n 'worklet'\n const colorsOut = new Array<string>(colorSvs.length)\n for (let i = 0; i < colorSvs.length; i++) colorsOut[i] = colorSvs[i]!.value\n const out: {\n colors: string[]\n start: GradientPoint\n end: GradientPoint\n locations?: number[]\n } = {\n colors: colorsOut,\n start: { x: startX.value, y: startY.value },\n end: { x: endX.value, y: endY.value },\n }\n if (hasLocationsRef.current) {\n const locsOut = new Array<number>(locationSvs.length)\n for (let i = 0; i < locationSvs.length; i++)\n locsOut[i] = locationSvs[i]!.value\n out.locations = locsOut\n }\n return out\n })\n\n return (\n <AnimatedLinearGradient\n // `animatedProps` overrides the static `colors` / `start` / `end` /\n // `locations` each frame; the static props below are the first-render\n // seeds so the gradient renders before the first effect tick. The cast\n // sheds Reanimated's strict-tuple constraint that the worklet's return\n // type can't express — the runtime value is the same shape.\n animatedProps={animatedProps as never}\n colors={colors}\n start={start}\n end={end}\n locations={locations as never}\n {...rest}\n />\n )\n}\n\ndeclare const __DEV__: boolean\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { useRef, useEffect } from 'react';
|
|
2
|
+
import { LinearGradient } from 'expo-linear-gradient';
|
|
3
|
+
import Animated, { useSharedValue, useAnimatedProps } from 'react-native-reanimated';
|
|
4
|
+
import { useShouldReduceMotion, resolveTransition } from '@onlynative/inertia';
|
|
5
|
+
import { jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/MotionLinearGradient.tsx
|
|
8
|
+
var AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient);
|
|
9
|
+
var NO_ANIMATION = { type: "no-animation" };
|
|
10
|
+
var DEFAULT_START = { x: 0, y: 0 };
|
|
11
|
+
var DEFAULT_END = { x: 1, y: 0 };
|
|
12
|
+
function pickTransition(per, key) {
|
|
13
|
+
if (!per) return void 0;
|
|
14
|
+
if ("type" in per) return per;
|
|
15
|
+
return per[key];
|
|
16
|
+
}
|
|
17
|
+
function MotionLinearGradient(props) {
|
|
18
|
+
const {
|
|
19
|
+
colors,
|
|
20
|
+
start = DEFAULT_START,
|
|
21
|
+
end = DEFAULT_END,
|
|
22
|
+
locations,
|
|
23
|
+
initial,
|
|
24
|
+
animate,
|
|
25
|
+
transition,
|
|
26
|
+
...rest
|
|
27
|
+
} = props;
|
|
28
|
+
const slotCountRef = useRef(colors.length);
|
|
29
|
+
const hasLocationsRef = useRef(locations !== void 0);
|
|
30
|
+
if (__DEV__) {
|
|
31
|
+
if (slotCountRef.current !== colors.length) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`[inertia-gradients] colors length changed from ${slotCountRef.current} to ${colors.length} \u2014 colors length is locked at mount; remount via key={...} to resize.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (hasLocationsRef.current !== (locations !== void 0)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`[inertia-gradients] locations presence changed \u2014 locations must be either always present or always absent (locked at mount).`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (locations !== void 0 && locations.length !== slotCountRef.current) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`[inertia-gradients] locations length (${locations.length}) must match colors length (${slotCountRef.current}).`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const seedSource = initial === false ? animate : initial ?? void 0;
|
|
48
|
+
const seedColors = seedSource?.colors ?? colors;
|
|
49
|
+
const seedStart = seedSource?.start ?? start;
|
|
50
|
+
const seedEnd = seedSource?.end ?? end;
|
|
51
|
+
const seedLocations = seedSource?.locations ?? locations;
|
|
52
|
+
const colorSvs = [];
|
|
53
|
+
for (let i = 0; i < slotCountRef.current; i++) {
|
|
54
|
+
const seed = seedColors[i] ?? colors[i] ?? "";
|
|
55
|
+
colorSvs.push(useSharedValue(seed));
|
|
56
|
+
}
|
|
57
|
+
const startX = useSharedValue(seedStart.x);
|
|
58
|
+
const startY = useSharedValue(seedStart.y);
|
|
59
|
+
const endX = useSharedValue(seedEnd.x);
|
|
60
|
+
const endY = useSharedValue(seedEnd.y);
|
|
61
|
+
const locationSvs = [];
|
|
62
|
+
for (let i = 0; i < slotCountRef.current; i++) {
|
|
63
|
+
locationSvs.push(useSharedValue(seedLocations?.[i] ?? 0));
|
|
64
|
+
}
|
|
65
|
+
const reduce = useShouldReduceMotion();
|
|
66
|
+
const colorsKey = animate?.colors ? animate.colors.join("|") : "";
|
|
67
|
+
const startKey = animate?.start ? `${animate.start.x},${animate.start.y}` : "";
|
|
68
|
+
const endKey = animate?.end ? `${animate.end.x},${animate.end.y}` : "";
|
|
69
|
+
const locationsKey = animate?.locations ? animate.locations.join("|") : "";
|
|
70
|
+
const animateColors = animate?.colors;
|
|
71
|
+
const animateStart = animate?.start;
|
|
72
|
+
const animateEnd = animate?.end;
|
|
73
|
+
const animateLocations = animate?.locations;
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!animateColors) return;
|
|
76
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "colors");
|
|
77
|
+
for (let i = 0; i < colorSvs.length; i++) {
|
|
78
|
+
const target = animateColors[i] ?? colors[i] ?? "";
|
|
79
|
+
colorSvs[i].value = resolveTransition(cfg, target);
|
|
80
|
+
}
|
|
81
|
+
}, [colorsKey, reduce, transition]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!animateStart) return;
|
|
84
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "start");
|
|
85
|
+
startX.value = resolveTransition(cfg, animateStart.x);
|
|
86
|
+
startY.value = resolveTransition(cfg, animateStart.y);
|
|
87
|
+
}, [startKey, reduce, transition]);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!animateEnd) return;
|
|
90
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "end");
|
|
91
|
+
endX.value = resolveTransition(cfg, animateEnd.x);
|
|
92
|
+
endY.value = resolveTransition(cfg, animateEnd.y);
|
|
93
|
+
}, [endKey, reduce, transition]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!animateLocations) return;
|
|
96
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "locations");
|
|
97
|
+
for (let i = 0; i < locationSvs.length; i++) {
|
|
98
|
+
const target = animateLocations[i];
|
|
99
|
+
if (target !== void 0) {
|
|
100
|
+
locationSvs[i].value = resolveTransition(cfg, target);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}, [locationsKey, reduce, transition]);
|
|
104
|
+
const animatedProps = useAnimatedProps(() => {
|
|
105
|
+
"worklet";
|
|
106
|
+
const colorsOut = new Array(colorSvs.length);
|
|
107
|
+
for (let i = 0; i < colorSvs.length; i++) colorsOut[i] = colorSvs[i].value;
|
|
108
|
+
const out = {
|
|
109
|
+
colors: colorsOut,
|
|
110
|
+
start: { x: startX.value, y: startY.value },
|
|
111
|
+
end: { x: endX.value, y: endY.value }
|
|
112
|
+
};
|
|
113
|
+
if (hasLocationsRef.current) {
|
|
114
|
+
const locsOut = new Array(locationSvs.length);
|
|
115
|
+
for (let i = 0; i < locationSvs.length; i++)
|
|
116
|
+
locsOut[i] = locationSvs[i].value;
|
|
117
|
+
out.locations = locsOut;
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
});
|
|
121
|
+
return /* @__PURE__ */ jsx(
|
|
122
|
+
AnimatedLinearGradient,
|
|
123
|
+
{
|
|
124
|
+
animatedProps,
|
|
125
|
+
colors,
|
|
126
|
+
start,
|
|
127
|
+
end,
|
|
128
|
+
locations,
|
|
129
|
+
...rest
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export { MotionLinearGradient };
|
|
135
|
+
//# sourceMappingURL=index.mjs.map
|
|
136
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/MotionLinearGradient.tsx"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import { useEffect, useRef } from 'react'\nimport { LinearGradient, type LinearGradientProps } from 'expo-linear-gradient'\nimport Animated, {\n useAnimatedProps,\n useSharedValue,\n type SharedValue,\n} from 'react-native-reanimated'\nimport {\n resolveTransition,\n useShouldReduceMotion,\n type TransitionConfig,\n} from '@onlynative/inertia'\nimport type {\n GradientPoint,\n LinearGradientAnimate,\n LinearGradientPerPropertyTransition,\n LinearGradientTransition,\n} from './types'\n\nconst AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient)\n\nconst NO_ANIMATION: TransitionConfig = { type: 'no-animation' }\nconst DEFAULT_START: GradientPoint = { x: 0, y: 0 }\nconst DEFAULT_END: GradientPoint = { x: 1, y: 0 }\n\n/**\n * Extract the per-key transition for `key`, falling back to the top-level\n * transition if the user passed `transition` as a single `TransitionConfig`\n * rather than a per-property map. Mirrors the precedence rule used by the\n * core factory: per-key wins, top-level fills, library default below that.\n */\nfunction pickTransition(\n per: LinearGradientTransition | undefined,\n key: keyof LinearGradientPerPropertyTransition,\n): TransitionConfig | undefined {\n if (!per) return undefined\n if ('type' in per) return per as TransitionConfig\n return (per as LinearGradientPerPropertyTransition)[key]\n}\n\ntype AtLeastTwoStrings = readonly [string, string, ...string[]]\n\nexport interface MotionLinearGradientProps extends Omit<\n LinearGradientProps,\n 'colors' | 'start' | 'end' | 'locations'\n> {\n /**\n * Initial color stops, in order. At least two are required. The array's\n * length is **locked at first render** — to change the number of stops,\n * remount with a new `key`.\n */\n colors: AtLeastTwoStrings\n /** Start point in normalized `[0, 1]` coordinates. Defaults to `{x:0,y:0}`. */\n start?: GradientPoint\n /** End point in normalized `[0, 1]` coordinates. Defaults to `{x:1,y:0}`. */\n end?: GradientPoint\n /**\n * Optional stop positions. If supplied at mount, must remain supplied (and\n * the same length as `colors`) for the lifetime of the component.\n */\n locations?: readonly number[]\n /**\n * Initial frame override. When present, the component mounts displaying\n * these values, then animates to `animate` on the next effect. Pass `false`\n * to skip the initial-mount animation entirely.\n */\n initial?: LinearGradientAnimate | false\n /** Target animation state. */\n animate?: LinearGradientAnimate\n /**\n * Transition config — either a single `TransitionConfig` applied to every\n * animated dimension, or a per-property map (`{ colors, start, end,\n * locations }`). Per-property entries win over the top-level transition.\n */\n transition?: LinearGradientTransition\n}\n\n/**\n * Animatable `LinearGradient`. Wraps `expo-linear-gradient`'s `LinearGradient`\n * with declarative `initial` / `animate` / `transition` props.\n *\n * Animatable dimensions:\n * - `colors` — array of color strings, element-wise interpolated. Slot count\n * is locked at mount.\n * - `start` / `end` — `{ x, y }` points; x and y animate independently.\n * - `locations` — array of stop positions, element-wise interpolated. Locked\n * to the same length as `colors` (and to its presence at mount).\n *\n * Example:\n * ```tsx\n * <MotionLinearGradient\n * colors={['#0f172a', '#1e293b']}\n * animate={{ colors: ['#7c3aed', '#0ea5e9'] }}\n * transition={{ type: 'timing', duration: 600 }}\n * style={StyleSheet.absoluteFill}\n * />\n * ```\n */\nexport function MotionLinearGradient(props: MotionLinearGradientProps) {\n const {\n colors,\n start = DEFAULT_START,\n end = DEFAULT_END,\n locations,\n initial,\n animate,\n transition,\n ...rest\n } = props\n\n // Slot count is locked at first render; subsequent renders must keep\n // `colors.length` constant so the hook order (one `useSharedValue` per slot\n // below) stays stable. `locations` presence is similarly locked because we\n // allocate its shared-value table on the same path.\n const slotCountRef = useRef(colors.length)\n const hasLocationsRef = useRef(locations !== undefined)\n\n if (__DEV__) {\n if (slotCountRef.current !== colors.length) {\n throw new Error(\n `[inertia-gradients] colors length changed from ${slotCountRef.current} to ${colors.length} — colors length is locked at mount; remount via key={...} to resize.`,\n )\n }\n if (hasLocationsRef.current !== (locations !== undefined)) {\n throw new Error(\n `[inertia-gradients] locations presence changed — locations must be either always present or always absent (locked at mount).`,\n )\n }\n if (locations !== undefined && locations.length !== slotCountRef.current) {\n throw new Error(\n `[inertia-gradients] locations length (${locations.length}) must match colors length (${slotCountRef.current}).`,\n )\n }\n }\n\n // `initial: false` means \"start at the animate target — no mount animation\".\n // `initial: {...}` overrides the static prop with explicit initial values.\n // `initial: undefined` (default) seeds from the static props.\n const seedSource = initial === false ? animate : (initial ?? undefined)\n const seedColors = seedSource?.colors ?? colors\n const seedStart = seedSource?.start ?? start\n const seedEnd = seedSource?.end ?? end\n const seedLocations = seedSource?.locations ?? locations\n\n // Loop-of-hooks pattern: safe because slotCountRef enforces a constant\n // length. ESLint can't see the invariant, so we suppress per call site.\n const colorSvs: SharedValue<string>[] = []\n for (let i = 0; i < slotCountRef.current; i++) {\n // Slot `i` is in-bounds for `colors` by the length lock, but TS sees a\n // generic `readonly string[]` index so coerce via a non-null fallback.\n const seed = seedColors[i] ?? colors[i] ?? ''\n // eslint-disable-next-line react-hooks/rules-of-hooks\n colorSvs.push(useSharedValue<string>(seed))\n }\n\n const startX = useSharedValue(seedStart.x)\n const startY = useSharedValue(seedStart.y)\n const endX = useSharedValue(seedEnd.x)\n const endY = useSharedValue(seedEnd.y)\n\n const locationSvs: SharedValue<number>[] = []\n for (let i = 0; i < slotCountRef.current; i++) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n locationSvs.push(useSharedValue<number>(seedLocations?.[i] ?? 0))\n }\n\n const reduce = useShouldReduceMotion()\n\n // Serialize targets into scalar keys so effects re-run on value change, not\n // on every parent re-render (a fresh `animate` literal each render is the\n // common case in callers).\n const colorsKey = animate?.colors ? animate.colors.join('|') : ''\n const startKey = animate?.start ? `${animate.start.x},${animate.start.y}` : ''\n const endKey = animate?.end ? `${animate.end.x},${animate.end.y}` : ''\n const locationsKey = animate?.locations ? animate.locations.join('|') : ''\n\n const animateColors = animate?.colors\n const animateStart = animate?.start\n const animateEnd = animate?.end\n const animateLocations = animate?.locations\n\n useEffect(() => {\n if (!animateColors) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'colors')\n for (let i = 0; i < colorSvs.length; i++) {\n const target = animateColors[i] ?? colors[i] ?? ''\n colorSvs[i]!.value = resolveTransition(cfg, target) as string\n }\n // colorSvs / colors are stable across renders by the length-lock above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [colorsKey, reduce, transition])\n\n useEffect(() => {\n if (!animateStart) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'start')\n startX.value = resolveTransition(cfg, animateStart.x) as number\n startY.value = resolveTransition(cfg, animateStart.y) as number\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [startKey, reduce, transition])\n\n useEffect(() => {\n if (!animateEnd) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'end')\n endX.value = resolveTransition(cfg, animateEnd.x) as number\n endY.value = resolveTransition(cfg, animateEnd.y) as number\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [endKey, reduce, transition])\n\n useEffect(() => {\n if (!animateLocations) return\n const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'locations')\n for (let i = 0; i < locationSvs.length; i++) {\n const target = animateLocations[i]\n if (target !== undefined) {\n locationSvs[i]!.value = resolveTransition(cfg, target) as number\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [locationsKey, reduce, transition])\n\n const animatedProps = useAnimatedProps(() => {\n 'worklet'\n const colorsOut = new Array<string>(colorSvs.length)\n for (let i = 0; i < colorSvs.length; i++) colorsOut[i] = colorSvs[i]!.value\n const out: {\n colors: string[]\n start: GradientPoint\n end: GradientPoint\n locations?: number[]\n } = {\n colors: colorsOut,\n start: { x: startX.value, y: startY.value },\n end: { x: endX.value, y: endY.value },\n }\n if (hasLocationsRef.current) {\n const locsOut = new Array<number>(locationSvs.length)\n for (let i = 0; i < locationSvs.length; i++)\n locsOut[i] = locationSvs[i]!.value\n out.locations = locsOut\n }\n return out\n })\n\n return (\n <AnimatedLinearGradient\n // `animatedProps` overrides the static `colors` / `start` / `end` /\n // `locations` each frame; the static props below are the first-render\n // seeds so the gradient renders before the first effect tick. The cast\n // sheds Reanimated's strict-tuple constraint that the worklet's return\n // type can't express — the runtime value is the same shape.\n animatedProps={animatedProps as never}\n colors={colors}\n start={start}\n end={end}\n locations={locations as never}\n {...rest}\n />\n )\n}\n\ndeclare const __DEV__: boolean\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@onlynative/inertia-gradients",
|
|
3
|
+
"version": "0.0.1-alpha.3",
|
|
4
|
+
"description": "Animated linear gradient primitive for @onlynative/inertia, built on expo-linear-gradient.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "OnlyNative",
|
|
7
|
+
"homepage": "https://github.com/onlynative/inertia",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/onlynative/inertia.git",
|
|
11
|
+
"directory": "packages/gradients"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/onlynative/inertia/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react-native",
|
|
18
|
+
"reanimated",
|
|
19
|
+
"gradient",
|
|
20
|
+
"linear-gradient",
|
|
21
|
+
"animation",
|
|
22
|
+
"inertia",
|
|
23
|
+
"expo"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"module": "./dist/index.mjs",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"react-native": "./src/index.ts",
|
|
30
|
+
"source": "./src/index.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"react-native": "./src/index.ts",
|
|
35
|
+
"source": "./src/index.ts",
|
|
36
|
+
"import": "./dist/index.mjs",
|
|
37
|
+
"require": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"src",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"CHANGELOG.md",
|
|
47
|
+
"!**/__tests__",
|
|
48
|
+
"!**/*.test.*"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"dev": "tsup --watch",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"test": "jest",
|
|
55
|
+
"size": "size-limit",
|
|
56
|
+
"size:why": "size-limit --why",
|
|
57
|
+
"lint": "eslint src",
|
|
58
|
+
"clean": "rm -rf dist .turbo *.tsbuildinfo"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"@onlynative/inertia": "workspace:*",
|
|
62
|
+
"expo-linear-gradient": ">=14.0.0",
|
|
63
|
+
"react": ">=19.0.0",
|
|
64
|
+
"react-native": ">=0.81.0",
|
|
65
|
+
"react-native-reanimated": ">=4.0.0"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@onlynative/inertia": "workspace:*",
|
|
69
|
+
"@react-native/babel-preset": "^0.81.5",
|
|
70
|
+
"@size-limit/preset-small-lib": "^11.1.0",
|
|
71
|
+
"@testing-library/react-native": "^13.3.3",
|
|
72
|
+
"@types/jest": "^29.5.14",
|
|
73
|
+
"@types/react": "^19.1.0",
|
|
74
|
+
"expo-linear-gradient": "~14.0.2",
|
|
75
|
+
"jest": "^29.7.0",
|
|
76
|
+
"react": "19.1.0",
|
|
77
|
+
"react-native": "0.81.5",
|
|
78
|
+
"react-native-reanimated": "~4.1.1",
|
|
79
|
+
"react-test-renderer": "19.1.0",
|
|
80
|
+
"size-limit": "^11.1.0",
|
|
81
|
+
"tsup": "^8.3.5",
|
|
82
|
+
"typescript": "^5.7.3"
|
|
83
|
+
},
|
|
84
|
+
"publishConfig": {
|
|
85
|
+
"access": "public"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import { LinearGradient, type LinearGradientProps } from 'expo-linear-gradient'
|
|
3
|
+
import Animated, {
|
|
4
|
+
useAnimatedProps,
|
|
5
|
+
useSharedValue,
|
|
6
|
+
type SharedValue,
|
|
7
|
+
} from 'react-native-reanimated'
|
|
8
|
+
import {
|
|
9
|
+
resolveTransition,
|
|
10
|
+
useShouldReduceMotion,
|
|
11
|
+
type TransitionConfig,
|
|
12
|
+
} from '@onlynative/inertia'
|
|
13
|
+
import type {
|
|
14
|
+
GradientPoint,
|
|
15
|
+
LinearGradientAnimate,
|
|
16
|
+
LinearGradientPerPropertyTransition,
|
|
17
|
+
LinearGradientTransition,
|
|
18
|
+
} from './types'
|
|
19
|
+
|
|
20
|
+
const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient)
|
|
21
|
+
|
|
22
|
+
const NO_ANIMATION: TransitionConfig = { type: 'no-animation' }
|
|
23
|
+
const DEFAULT_START: GradientPoint = { x: 0, y: 0 }
|
|
24
|
+
const DEFAULT_END: GradientPoint = { x: 1, y: 0 }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extract the per-key transition for `key`, falling back to the top-level
|
|
28
|
+
* transition if the user passed `transition` as a single `TransitionConfig`
|
|
29
|
+
* rather than a per-property map. Mirrors the precedence rule used by the
|
|
30
|
+
* core factory: per-key wins, top-level fills, library default below that.
|
|
31
|
+
*/
|
|
32
|
+
function pickTransition(
|
|
33
|
+
per: LinearGradientTransition | undefined,
|
|
34
|
+
key: keyof LinearGradientPerPropertyTransition,
|
|
35
|
+
): TransitionConfig | undefined {
|
|
36
|
+
if (!per) return undefined
|
|
37
|
+
if ('type' in per) return per as TransitionConfig
|
|
38
|
+
return (per as LinearGradientPerPropertyTransition)[key]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type AtLeastTwoStrings = readonly [string, string, ...string[]]
|
|
42
|
+
|
|
43
|
+
export interface MotionLinearGradientProps extends Omit<
|
|
44
|
+
LinearGradientProps,
|
|
45
|
+
'colors' | 'start' | 'end' | 'locations'
|
|
46
|
+
> {
|
|
47
|
+
/**
|
|
48
|
+
* Initial color stops, in order. At least two are required. The array's
|
|
49
|
+
* length is **locked at first render** — to change the number of stops,
|
|
50
|
+
* remount with a new `key`.
|
|
51
|
+
*/
|
|
52
|
+
colors: AtLeastTwoStrings
|
|
53
|
+
/** Start point in normalized `[0, 1]` coordinates. Defaults to `{x:0,y:0}`. */
|
|
54
|
+
start?: GradientPoint
|
|
55
|
+
/** End point in normalized `[0, 1]` coordinates. Defaults to `{x:1,y:0}`. */
|
|
56
|
+
end?: GradientPoint
|
|
57
|
+
/**
|
|
58
|
+
* Optional stop positions. If supplied at mount, must remain supplied (and
|
|
59
|
+
* the same length as `colors`) for the lifetime of the component.
|
|
60
|
+
*/
|
|
61
|
+
locations?: readonly number[]
|
|
62
|
+
/**
|
|
63
|
+
* Initial frame override. When present, the component mounts displaying
|
|
64
|
+
* these values, then animates to `animate` on the next effect. Pass `false`
|
|
65
|
+
* to skip the initial-mount animation entirely.
|
|
66
|
+
*/
|
|
67
|
+
initial?: LinearGradientAnimate | false
|
|
68
|
+
/** Target animation state. */
|
|
69
|
+
animate?: LinearGradientAnimate
|
|
70
|
+
/**
|
|
71
|
+
* Transition config — either a single `TransitionConfig` applied to every
|
|
72
|
+
* animated dimension, or a per-property map (`{ colors, start, end,
|
|
73
|
+
* locations }`). Per-property entries win over the top-level transition.
|
|
74
|
+
*/
|
|
75
|
+
transition?: LinearGradientTransition
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Animatable `LinearGradient`. Wraps `expo-linear-gradient`'s `LinearGradient`
|
|
80
|
+
* with declarative `initial` / `animate` / `transition` props.
|
|
81
|
+
*
|
|
82
|
+
* Animatable dimensions:
|
|
83
|
+
* - `colors` — array of color strings, element-wise interpolated. Slot count
|
|
84
|
+
* is locked at mount.
|
|
85
|
+
* - `start` / `end` — `{ x, y }` points; x and y animate independently.
|
|
86
|
+
* - `locations` — array of stop positions, element-wise interpolated. Locked
|
|
87
|
+
* to the same length as `colors` (and to its presence at mount).
|
|
88
|
+
*
|
|
89
|
+
* Example:
|
|
90
|
+
* ```tsx
|
|
91
|
+
* <MotionLinearGradient
|
|
92
|
+
* colors={['#0f172a', '#1e293b']}
|
|
93
|
+
* animate={{ colors: ['#7c3aed', '#0ea5e9'] }}
|
|
94
|
+
* transition={{ type: 'timing', duration: 600 }}
|
|
95
|
+
* style={StyleSheet.absoluteFill}
|
|
96
|
+
* />
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function MotionLinearGradient(props: MotionLinearGradientProps) {
|
|
100
|
+
const {
|
|
101
|
+
colors,
|
|
102
|
+
start = DEFAULT_START,
|
|
103
|
+
end = DEFAULT_END,
|
|
104
|
+
locations,
|
|
105
|
+
initial,
|
|
106
|
+
animate,
|
|
107
|
+
transition,
|
|
108
|
+
...rest
|
|
109
|
+
} = props
|
|
110
|
+
|
|
111
|
+
// Slot count is locked at first render; subsequent renders must keep
|
|
112
|
+
// `colors.length` constant so the hook order (one `useSharedValue` per slot
|
|
113
|
+
// below) stays stable. `locations` presence is similarly locked because we
|
|
114
|
+
// allocate its shared-value table on the same path.
|
|
115
|
+
const slotCountRef = useRef(colors.length)
|
|
116
|
+
const hasLocationsRef = useRef(locations !== undefined)
|
|
117
|
+
|
|
118
|
+
if (__DEV__) {
|
|
119
|
+
if (slotCountRef.current !== colors.length) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`[inertia-gradients] colors length changed from ${slotCountRef.current} to ${colors.length} — colors length is locked at mount; remount via key={...} to resize.`,
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
if (hasLocationsRef.current !== (locations !== undefined)) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`[inertia-gradients] locations presence changed — locations must be either always present or always absent (locked at mount).`,
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
if (locations !== undefined && locations.length !== slotCountRef.current) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`[inertia-gradients] locations length (${locations.length}) must match colors length (${slotCountRef.current}).`,
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// `initial: false` means "start at the animate target — no mount animation".
|
|
137
|
+
// `initial: {...}` overrides the static prop with explicit initial values.
|
|
138
|
+
// `initial: undefined` (default) seeds from the static props.
|
|
139
|
+
const seedSource = initial === false ? animate : (initial ?? undefined)
|
|
140
|
+
const seedColors = seedSource?.colors ?? colors
|
|
141
|
+
const seedStart = seedSource?.start ?? start
|
|
142
|
+
const seedEnd = seedSource?.end ?? end
|
|
143
|
+
const seedLocations = seedSource?.locations ?? locations
|
|
144
|
+
|
|
145
|
+
// Loop-of-hooks pattern: safe because slotCountRef enforces a constant
|
|
146
|
+
// length. ESLint can't see the invariant, so we suppress per call site.
|
|
147
|
+
const colorSvs: SharedValue<string>[] = []
|
|
148
|
+
for (let i = 0; i < slotCountRef.current; i++) {
|
|
149
|
+
// Slot `i` is in-bounds for `colors` by the length lock, but TS sees a
|
|
150
|
+
// generic `readonly string[]` index so coerce via a non-null fallback.
|
|
151
|
+
const seed = seedColors[i] ?? colors[i] ?? ''
|
|
152
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
153
|
+
colorSvs.push(useSharedValue<string>(seed))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const startX = useSharedValue(seedStart.x)
|
|
157
|
+
const startY = useSharedValue(seedStart.y)
|
|
158
|
+
const endX = useSharedValue(seedEnd.x)
|
|
159
|
+
const endY = useSharedValue(seedEnd.y)
|
|
160
|
+
|
|
161
|
+
const locationSvs: SharedValue<number>[] = []
|
|
162
|
+
for (let i = 0; i < slotCountRef.current; i++) {
|
|
163
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
164
|
+
locationSvs.push(useSharedValue<number>(seedLocations?.[i] ?? 0))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const reduce = useShouldReduceMotion()
|
|
168
|
+
|
|
169
|
+
// Serialize targets into scalar keys so effects re-run on value change, not
|
|
170
|
+
// on every parent re-render (a fresh `animate` literal each render is the
|
|
171
|
+
// common case in callers).
|
|
172
|
+
const colorsKey = animate?.colors ? animate.colors.join('|') : ''
|
|
173
|
+
const startKey = animate?.start ? `${animate.start.x},${animate.start.y}` : ''
|
|
174
|
+
const endKey = animate?.end ? `${animate.end.x},${animate.end.y}` : ''
|
|
175
|
+
const locationsKey = animate?.locations ? animate.locations.join('|') : ''
|
|
176
|
+
|
|
177
|
+
const animateColors = animate?.colors
|
|
178
|
+
const animateStart = animate?.start
|
|
179
|
+
const animateEnd = animate?.end
|
|
180
|
+
const animateLocations = animate?.locations
|
|
181
|
+
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (!animateColors) return
|
|
184
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'colors')
|
|
185
|
+
for (let i = 0; i < colorSvs.length; i++) {
|
|
186
|
+
const target = animateColors[i] ?? colors[i] ?? ''
|
|
187
|
+
colorSvs[i]!.value = resolveTransition(cfg, target) as string
|
|
188
|
+
}
|
|
189
|
+
// colorSvs / colors are stable across renders by the length-lock above.
|
|
190
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
191
|
+
}, [colorsKey, reduce, transition])
|
|
192
|
+
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (!animateStart) return
|
|
195
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'start')
|
|
196
|
+
startX.value = resolveTransition(cfg, animateStart.x) as number
|
|
197
|
+
startY.value = resolveTransition(cfg, animateStart.y) as number
|
|
198
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
199
|
+
}, [startKey, reduce, transition])
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (!animateEnd) return
|
|
203
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'end')
|
|
204
|
+
endX.value = resolveTransition(cfg, animateEnd.x) as number
|
|
205
|
+
endY.value = resolveTransition(cfg, animateEnd.y) as number
|
|
206
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
207
|
+
}, [endKey, reduce, transition])
|
|
208
|
+
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (!animateLocations) return
|
|
211
|
+
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'locations')
|
|
212
|
+
for (let i = 0; i < locationSvs.length; i++) {
|
|
213
|
+
const target = animateLocations[i]
|
|
214
|
+
if (target !== undefined) {
|
|
215
|
+
locationSvs[i]!.value = resolveTransition(cfg, target) as number
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
219
|
+
}, [locationsKey, reduce, transition])
|
|
220
|
+
|
|
221
|
+
const animatedProps = useAnimatedProps(() => {
|
|
222
|
+
'worklet'
|
|
223
|
+
const colorsOut = new Array<string>(colorSvs.length)
|
|
224
|
+
for (let i = 0; i < colorSvs.length; i++) colorsOut[i] = colorSvs[i]!.value
|
|
225
|
+
const out: {
|
|
226
|
+
colors: string[]
|
|
227
|
+
start: GradientPoint
|
|
228
|
+
end: GradientPoint
|
|
229
|
+
locations?: number[]
|
|
230
|
+
} = {
|
|
231
|
+
colors: colorsOut,
|
|
232
|
+
start: { x: startX.value, y: startY.value },
|
|
233
|
+
end: { x: endX.value, y: endY.value },
|
|
234
|
+
}
|
|
235
|
+
if (hasLocationsRef.current) {
|
|
236
|
+
const locsOut = new Array<number>(locationSvs.length)
|
|
237
|
+
for (let i = 0; i < locationSvs.length; i++)
|
|
238
|
+
locsOut[i] = locationSvs[i]!.value
|
|
239
|
+
out.locations = locsOut
|
|
240
|
+
}
|
|
241
|
+
return out
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<AnimatedLinearGradient
|
|
246
|
+
// `animatedProps` overrides the static `colors` / `start` / `end` /
|
|
247
|
+
// `locations` each frame; the static props below are the first-render
|
|
248
|
+
// seeds so the gradient renders before the first effect tick. The cast
|
|
249
|
+
// sheds Reanimated's strict-tuple constraint that the worklet's return
|
|
250
|
+
// type can't express — the runtime value is the same shape.
|
|
251
|
+
animatedProps={animatedProps as never}
|
|
252
|
+
colors={colors}
|
|
253
|
+
start={start}
|
|
254
|
+
end={end}
|
|
255
|
+
locations={locations as never}
|
|
256
|
+
{...rest}
|
|
257
|
+
/>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
declare const __DEV__: boolean
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@onlynative/inertia-gradients` — animated gradient primitives for
|
|
3
|
+
* `@onlynative/inertia`.
|
|
4
|
+
*
|
|
5
|
+
* v0.2 surface:
|
|
6
|
+
* - `MotionLinearGradient` — animatable linear gradient over
|
|
7
|
+
* `expo-linear-gradient`. Animates `colors`, `start`, `end`, and
|
|
8
|
+
* `locations` with the same `initial` / `animate` / `transition` shape
|
|
9
|
+
* as the core Motion primitives.
|
|
10
|
+
*
|
|
11
|
+
* Radial / conic gradients land in v0.3 once the linear API is validated.
|
|
12
|
+
*/
|
|
13
|
+
export { MotionLinearGradient } from './MotionLinearGradient'
|
|
14
|
+
export type { MotionLinearGradientProps } from './MotionLinearGradient'
|
|
15
|
+
export type {
|
|
16
|
+
GradientPoint,
|
|
17
|
+
LinearGradientAnimate,
|
|
18
|
+
LinearGradientPerPropertyTransition,
|
|
19
|
+
LinearGradientStateShape,
|
|
20
|
+
LinearGradientTransition,
|
|
21
|
+
} from './types'
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { TransitionConfig } from '@onlynative/inertia'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A 2D point on the gradient's `[0, 1]` square. `{ x: 0, y: 0 }` is the
|
|
5
|
+
* top-left corner; `{ x: 1, y: 1 }` is the bottom-right.
|
|
6
|
+
*/
|
|
7
|
+
export interface GradientPoint {
|
|
8
|
+
x: number
|
|
9
|
+
y: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Animatable target snapshot for a linear gradient. Every field is optional —
|
|
14
|
+
* include only the dimensions you want to animate; the rest fall back to the
|
|
15
|
+
* static props on the component.
|
|
16
|
+
*
|
|
17
|
+
* `colors` and `locations` arrays must keep the same length as the static
|
|
18
|
+
* `colors` prop. Slot count is locked at first render so the shared-value
|
|
19
|
+
* table is stable across the animation's lifetime.
|
|
20
|
+
*/
|
|
21
|
+
export interface LinearGradientAnimate {
|
|
22
|
+
colors?: readonly string[]
|
|
23
|
+
start?: GradientPoint
|
|
24
|
+
end?: GradientPoint
|
|
25
|
+
locations?: readonly number[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The four animatable dimensions of a linear gradient. Per-key transitions on
|
|
30
|
+
* `transition` are keyed against this shape.
|
|
31
|
+
*/
|
|
32
|
+
export interface LinearGradientStateShape {
|
|
33
|
+
colors: readonly string[]
|
|
34
|
+
start: GradientPoint
|
|
35
|
+
end: GradientPoint
|
|
36
|
+
locations: readonly number[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Per-property transition map. Top-level entries on `transition` apply to all
|
|
41
|
+
* properties unless overridden by a per-key entry here.
|
|
42
|
+
*/
|
|
43
|
+
export type LinearGradientPerPropertyTransition = {
|
|
44
|
+
[K in keyof LinearGradientStateShape]?: TransitionConfig
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Transition shape accepted by `MotionLinearGradient`. Either a single
|
|
49
|
+
* top-level transition applied to every animated dimension, or a per-property
|
|
50
|
+
* map.
|
|
51
|
+
*/
|
|
52
|
+
export type LinearGradientTransition =
|
|
53
|
+
| TransitionConfig
|
|
54
|
+
| LinearGradientPerPropertyTransition
|