@onlynative/inertia 0.0.1-alpha.1 → 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/README.md +30 -3
- package/dist/index.d.mts +5 -6
- package/dist/index.d.ts +5 -6
- package/dist/index.js +209 -64
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +210 -65
- package/dist/index.mjs.map +1 -1
- package/dist/motion/Image.d.mts +1 -1
- package/dist/motion/Image.d.ts +1 -1
- package/dist/motion/Image.js +209 -64
- package/dist/motion/Image.js.map +1 -1
- package/dist/motion/Image.mjs +210 -65
- package/dist/motion/Image.mjs.map +1 -1
- package/dist/motion/Pressable.d.mts +1 -1
- package/dist/motion/Pressable.d.ts +1 -1
- package/dist/motion/Pressable.js +209 -64
- package/dist/motion/Pressable.js.map +1 -1
- package/dist/motion/Pressable.mjs +210 -65
- package/dist/motion/Pressable.mjs.map +1 -1
- package/dist/motion/ScrollView.d.mts +1 -1
- package/dist/motion/ScrollView.d.ts +1 -1
- package/dist/motion/ScrollView.js +209 -64
- package/dist/motion/ScrollView.js.map +1 -1
- package/dist/motion/ScrollView.mjs +210 -65
- package/dist/motion/ScrollView.mjs.map +1 -1
- package/dist/motion/Text.d.mts +1 -1
- package/dist/motion/Text.d.ts +1 -1
- package/dist/motion/Text.js +209 -64
- package/dist/motion/Text.js.map +1 -1
- package/dist/motion/Text.mjs +210 -65
- package/dist/motion/Text.mjs.map +1 -1
- package/dist/motion/View.d.mts +1 -1
- package/dist/motion/View.d.ts +1 -1
- package/dist/motion/View.js +209 -64
- package/dist/motion/View.js.map +1 -1
- package/dist/motion/View.mjs +210 -65
- package/dist/motion/View.mjs.map +1 -1
- package/dist/{types-DeZZzE_e.d.mts → types-DAhX3fC2.d.mts} +40 -16
- package/dist/{types-DeZZzE_e.d.ts → types-DAhX3fC2.d.ts} +40 -16
- package/llms.txt +30 -3
- package/package.json +1 -1
- package/src/motion/createMotionComponent.tsx +342 -129
- package/src/motion/installCheck.ts +69 -0
- package/src/types.ts +44 -16
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ export function FadeIn() {
|
|
|
42
42
|
- **Primitives** — `Motion.View`, `Motion.Text`, `Motion.Image`, `Motion.Pressable`, `Motion.ScrollView`. Per-primitive style inference (no shared `ViewStyle & TextStyle & ImageStyle` fallback).
|
|
43
43
|
- **Sequences and keyframes** — `animate={{ x: [0, 100, 0] }}` with per-step transitions; unified `repeat: number | 'infinite' | { count, alternate }`.
|
|
44
44
|
- **Variants** — `variants={{ open, closed }}` with `animate="open"`. Programmatic control via `useVariants` + `controller={...}`.
|
|
45
|
-
- **Gestures** — single `gesture` prop on every primitive: `gesture={{ pressed, focused, focusVisible, hovered }}`. `focusVisible` engages only on keyboard focus (W3C `:focus-visible`) so click-focus on web doesn't flash a ring; on native it tracks `focused`. Zero overhead when omitted.
|
|
45
|
+
- **Gestures** — single `gesture` prop on every primitive: `gesture={{ pressed, focused, focusVisible, hovered }}`. Sub-states layer **additively** in priority order (`hovered → focused → focusVisible → pressed`); each layer fades in/out on its own progress so MD3 release-while-hovered cross-fades correctly. Per-layer transitions via `transition.<stateName>`. `focusVisible` engages only on keyboard focus (W3C `:focus-visible`) so click-focus on web doesn't flash a ring; on native it tracks `focused`. Zero overhead when omitted.
|
|
46
46
|
- **`<Presence>`** — mount/unmount transitions; exiting children automatically receive `pointerEvents: 'none'`.
|
|
47
47
|
- **`<MotionConfig reducedMotion>`** — OS reduce-motion honored end-to-end (`'user' | 'never' | 'always'`).
|
|
48
48
|
- **Per-primitive subpath imports** — `@onlynative/inertia/view`, `/text`, `/image`, `/pressable`, `/scroll-view`.
|
|
@@ -58,7 +58,13 @@ import { MotionPressable } from '@onlynative/inertia/pressable'
|
|
|
58
58
|
import { MotionScrollView } from '@onlynative/inertia/scroll-view'
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Or the barrel — same primitives, named imports tree-shake cleanly because the package is `sideEffects: false`:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { MotionView } from '@onlynative/inertia'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Both forms land at ~4.1–4.2 kB brotlied for a single primitive (peers excluded). The full namespace (`import { Motion } from '@onlynative/inertia'`, then `Motion.View`) cannot tree-shake — accessing one property of a literal object holds the whole object live — and lands at ~4.8 kB. CI checks all three forms via `size-limit` so the gap doesn't drift.
|
|
62
68
|
|
|
63
69
|
## Transitions
|
|
64
70
|
|
|
@@ -71,12 +77,33 @@ A `Motion.View`-only import currently bundles to ~3.2 kB brotlied (excluding pee
|
|
|
71
77
|
|
|
72
78
|
Plus, on any transition: `delay`, `repeat`. Per-property transitions take precedence over the top-level transition. Spring config uses **react-spring vocabulary** (`tension`/`friction`); Reanimated's raw `stiffness`/`damping` is never on the public surface.
|
|
73
79
|
|
|
80
|
+
## Caveats
|
|
81
|
+
|
|
82
|
+
- **`Motion.Pressable` does not support function-form `style`.** RN's `Pressable` accepts `style={({ pressed }) => ...}` and re-runs it per state change; Inertia inherits Reanimated's `createAnimatedComponent` wrapper, which silently drops that form (no error, no warning). Drive press/focus/hover styling through `gesture` instead, or compute conditional styles once in render. See [primitives/pressable](https://onlynative.github.io/inertia/docs/primitives/pressable#style-must-be-a-value-not-a-function).
|
|
83
|
+
- **`initial` is read once on mount.** Mutating `initial` after first render does nothing — change the component `key`, remount via `<Presence>`, or drive the value through a controller. Pass `initial={false}` to skip the initial-mount animation entirely.
|
|
84
|
+
|
|
74
85
|
## Animatable properties
|
|
75
86
|
|
|
76
|
-
`opacity`, `translateX`, `translateY`, `scale`, `scaleX`, `scaleY`, `rotate`, `rotateX`, `rotateY`, `
|
|
87
|
+
Numeric: `opacity`, `translateX`, `translateY`, `scale`, `scaleX`, `scaleY`, `rotate`, `rotateX`, `rotateY`, `width`, `height`, `borderRadius`. Color: `backgroundColor`, `borderColor`, `color`, `tintColor` (Image only — `Motion.View` rejects it at compile time). Layout transforms via `transform: [...]`. Color targets are forwarded straight through `withSpring` / `withTiming`; Reanimated's value setter packs the string to RGBA and interpolates on the UI thread.
|
|
77
88
|
|
|
78
89
|
Out of scope for `0.x`: SVG path morphing, gradient interpolation, shared-element transitions across screens.
|
|
79
90
|
|
|
91
|
+
## When not to use the core package alone
|
|
92
|
+
|
|
93
|
+
The `gesture` prop in `@onlynative/inertia` covers `pressed` / `focused` / `focusVisible` / `hovered` — the Pressable-shaped sub-states. **Continuous, value-bearing gestures live in the adapter package** [`@onlynative/inertia-gestures`](../gestures), which wraps `react-native-gesture-handler` and ships:
|
|
94
|
+
|
|
95
|
+
- `useDrag` — one- or two-axis drag with optional constraints and rubber-band elasticity
|
|
96
|
+
- `usePan` — camera-style pan with momentum on release
|
|
97
|
+
- `useSwipe` — directional commit-or-snap-back (distance + velocity thresholds)
|
|
98
|
+
|
|
99
|
+
If your screen needs a thumb that follows the finger, a sheet that flicks closed, a carousel with momentum, or any gesture that produces a value other than "active / inactive" — install the adapter:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
pnpm add @onlynative/inertia-gestures react-native-gesture-handler
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
A fully gesture-driven `Slider` (continuous thumb tracking + range clamping) is the canonical example: the core package can't build it on its own, the adapter can. This is intentional — keeping `react-native-gesture-handler` out of the core peer set means apps that animate buttons and sheets don't pay for a gesture engine they never invoke.
|
|
106
|
+
|
|
80
107
|
## Documentation
|
|
81
108
|
|
|
82
109
|
Full docs, every primitive's example screen, and an `llms-full.txt` reference live at:
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ComponentType, ReactNode } from 'react';
|
|
3
3
|
import * as react_native from 'react-native';
|
|
4
|
-
import { M as MotionComponent, A as AnimatableValue, T as TransitionConfig, V as VariantController } from './types-
|
|
5
|
-
export { a as AnimateStyle, b as AnimationCallbackInfo, D as DecayTransition, G as GestureSubStates, c as MotionProps, N as NoAnimationTransition, P as PerPropertyTransition, R as RepeatConfig, S as SequenceStep, d as SpringTransition, e as TimingTransition, f as Transition, g as VariantsMap } from './types-
|
|
4
|
+
import { M as MotionComponent, A as AnimatableValue, T as TransitionConfig, V as VariantController } from './types-DAhX3fC2.mjs';
|
|
5
|
+
export { a as AnimateStyle, b as AnimationCallbackInfo, D as DecayTransition, G as GestureSubStates, c as MotionProps, N as NoAnimationTransition, P as PerPropertyTransition, R as RepeatConfig, S as SequenceStep, d as SpringTransition, e as TimingTransition, f as Transition, g as VariantsMap } from './types-DAhX3fC2.mjs';
|
|
6
6
|
export { MotionImage } from './motion/Image.mjs';
|
|
7
7
|
export { MotionPressable } from './motion/Pressable.mjs';
|
|
8
8
|
export { MotionScrollView } from './motion/ScrollView.mjs';
|
|
@@ -16,10 +16,9 @@ export { MotionView } from './motion/View.mjs';
|
|
|
16
16
|
* `exit` / `transition` all infer from `C`'s `style` prop. There is no
|
|
17
17
|
* shared `ViewStyle & TextStyle & ImageStyle` fallback.
|
|
18
18
|
*
|
|
19
|
-
* Alpha scope: numeric properties
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* land in later phases.
|
|
19
|
+
* Alpha scope: numeric properties (transforms, opacity, width, height,
|
|
20
|
+
* borderRadius) and color properties (backgroundColor, borderColor, color,
|
|
21
|
+
* tintColor) applied via Reanimated shared values + `useAnimatedStyle`.
|
|
23
22
|
*/
|
|
24
23
|
declare function createMotionComponent<C extends ComponentType<any>>(Component: C): MotionComponent<C>;
|
|
25
24
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ComponentType, ReactNode } from 'react';
|
|
3
3
|
import * as react_native from 'react-native';
|
|
4
|
-
import { M as MotionComponent, A as AnimatableValue, T as TransitionConfig, V as VariantController } from './types-
|
|
5
|
-
export { a as AnimateStyle, b as AnimationCallbackInfo, D as DecayTransition, G as GestureSubStates, c as MotionProps, N as NoAnimationTransition, P as PerPropertyTransition, R as RepeatConfig, S as SequenceStep, d as SpringTransition, e as TimingTransition, f as Transition, g as VariantsMap } from './types-
|
|
4
|
+
import { M as MotionComponent, A as AnimatableValue, T as TransitionConfig, V as VariantController } from './types-DAhX3fC2.js';
|
|
5
|
+
export { a as AnimateStyle, b as AnimationCallbackInfo, D as DecayTransition, G as GestureSubStates, c as MotionProps, N as NoAnimationTransition, P as PerPropertyTransition, R as RepeatConfig, S as SequenceStep, d as SpringTransition, e as TimingTransition, f as Transition, g as VariantsMap } from './types-DAhX3fC2.js';
|
|
6
6
|
export { MotionImage } from './motion/Image.js';
|
|
7
7
|
export { MotionPressable } from './motion/Pressable.js';
|
|
8
8
|
export { MotionScrollView } from './motion/ScrollView.js';
|
|
@@ -16,10 +16,9 @@ export { MotionView } from './motion/View.js';
|
|
|
16
16
|
* `exit` / `transition` all infer from `C`'s `style` prop. There is no
|
|
17
17
|
* shared `ViewStyle & TextStyle & ImageStyle` fallback.
|
|
18
18
|
*
|
|
19
|
-
* Alpha scope: numeric properties
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* land in later phases.
|
|
19
|
+
* Alpha scope: numeric properties (transforms, opacity, width, height,
|
|
20
|
+
* borderRadius) and color properties (backgroundColor, borderColor, color,
|
|
21
|
+
* tintColor) applied via Reanimated shared values + `useAnimatedStyle`.
|
|
23
22
|
*/
|
|
24
23
|
declare function createMotionComponent<C extends ComponentType<any>>(Component: C): MotionComponent<C>;
|
|
25
24
|
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,12 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
9
9
|
|
|
10
10
|
var Animated__default = /*#__PURE__*/_interopDefault(Animated);
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
13
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
14
|
+
}) : x)(function(x) {
|
|
15
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
16
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
17
|
+
});
|
|
13
18
|
var DEFAULT_MOTION_CONFIG = {
|
|
14
19
|
reducedMotion: "user"
|
|
15
20
|
};
|
|
@@ -285,6 +290,40 @@ function mergeTransition(base, override) {
|
|
|
285
290
|
}
|
|
286
291
|
return { ...base ?? { type: "spring" }, ...override };
|
|
287
292
|
}
|
|
293
|
+
|
|
294
|
+
// src/motion/installCheck.ts
|
|
295
|
+
var alreadyChecked = false;
|
|
296
|
+
function ensureReanimatedInstalled() {
|
|
297
|
+
if (!__DEV__ || alreadyChecked) return;
|
|
298
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV === "test") {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
alreadyChecked = true;
|
|
302
|
+
let version;
|
|
303
|
+
try {
|
|
304
|
+
const pkg = __require("react-native-reanimated/package.json");
|
|
305
|
+
version = pkg.version;
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
if (version) {
|
|
309
|
+
const major = parseInt(version.split(".")[0] ?? "0", 10);
|
|
310
|
+
if (major < 4) {
|
|
311
|
+
console.error(
|
|
312
|
+
`[inertia] react-native-reanimated v${version} is installed, but @onlynative/inertia requires v4.0.0 or later. Upgrade with \`pnpm add react-native-reanimated@^4\` (or your package manager's equivalent).`
|
|
313
|
+
);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const probe = function probe2() {
|
|
318
|
+
"worklet";
|
|
319
|
+
return 0;
|
|
320
|
+
};
|
|
321
|
+
if (typeof probe.__workletHash !== "number") {
|
|
322
|
+
console.error(
|
|
323
|
+
`[inertia] The Reanimated worklets babel plugin is not configured. Add \`'react-native-worklets/plugin'\` as the LAST entry in the \`plugins\` array of your \`babel.config.js\`, then restart Metro with a fresh cache: \`npx expo start -c\` or \`npx react-native start --reset-cache\`.`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
288
327
|
var TRANSFORM_KEYS = [
|
|
289
328
|
"translateX",
|
|
290
329
|
"translateY",
|
|
@@ -293,9 +332,32 @@ var TRANSFORM_KEYS = [
|
|
|
293
332
|
"scaleY",
|
|
294
333
|
"rotate"
|
|
295
334
|
];
|
|
296
|
-
var
|
|
297
|
-
|
|
335
|
+
var NUMERIC_TOP_LEVEL_KEYS = [
|
|
336
|
+
"opacity",
|
|
337
|
+
"width",
|
|
338
|
+
"height",
|
|
339
|
+
"borderRadius"
|
|
340
|
+
];
|
|
341
|
+
var COLOR_KEYS = [
|
|
342
|
+
"backgroundColor",
|
|
343
|
+
"borderColor",
|
|
344
|
+
"color",
|
|
345
|
+
"tintColor"
|
|
346
|
+
];
|
|
347
|
+
var ALL_KEYS = [
|
|
348
|
+
...TRANSFORM_KEYS,
|
|
349
|
+
...NUMERIC_TOP_LEVEL_KEYS,
|
|
350
|
+
...COLOR_KEYS
|
|
351
|
+
];
|
|
298
352
|
var TRANSFORM_KEY_SET = new Set(TRANSFORM_KEYS);
|
|
353
|
+
var COLOR_KEY_SET = new Set(COLOR_KEYS);
|
|
354
|
+
var GESTURE_LAYER_NAMES = [
|
|
355
|
+
"hovered",
|
|
356
|
+
"focused",
|
|
357
|
+
"focusVisible",
|
|
358
|
+
"pressed"
|
|
359
|
+
];
|
|
360
|
+
var GESTURE_LAYER_NAME_SET = new Set(GESTURE_LAYER_NAMES);
|
|
299
361
|
var EXITING_POINTER_EVENTS_STYLE = { pointerEvents: "none" };
|
|
300
362
|
var DEFAULT_RESTING = {
|
|
301
363
|
translateX: 0,
|
|
@@ -307,7 +369,15 @@ var DEFAULT_RESTING = {
|
|
|
307
369
|
opacity: 1,
|
|
308
370
|
width: 0,
|
|
309
371
|
height: 0,
|
|
310
|
-
borderRadius: 0
|
|
372
|
+
borderRadius: 0,
|
|
373
|
+
// 'transparent' is the only safe universal default for colors: it works as
|
|
374
|
+
// an initial seed for any color animation (no jarring opaque flash on mount
|
|
375
|
+
// when `initial` is omitted) and rgba(0,0,0,0) interpolates cleanly into
|
|
376
|
+
// any opaque target via Reanimated's color util.
|
|
377
|
+
backgroundColor: "transparent",
|
|
378
|
+
borderColor: "transparent",
|
|
379
|
+
color: "transparent",
|
|
380
|
+
tintColor: "transparent"
|
|
311
381
|
};
|
|
312
382
|
var TRANSITION_KEYS = /* @__PURE__ */ new Set([
|
|
313
383
|
"type",
|
|
@@ -333,10 +403,18 @@ function isTopLevelTransition(t) {
|
|
|
333
403
|
function transitionFor(prop, transition) {
|
|
334
404
|
if (!transition) return void 0;
|
|
335
405
|
if (isTopLevelTransition(transition)) return transition;
|
|
406
|
+
if (GESTURE_LAYER_NAME_SET.has(prop)) return void 0;
|
|
336
407
|
return transition[prop];
|
|
337
408
|
}
|
|
409
|
+
function gestureLayerTransitionFor(layer, transition) {
|
|
410
|
+
if (!transition) return void 0;
|
|
411
|
+
if (isTopLevelTransition(transition)) return transition;
|
|
412
|
+
return transition[layer];
|
|
413
|
+
}
|
|
338
414
|
function createMotionComponent(Component) {
|
|
415
|
+
ensureReanimatedInstalled();
|
|
339
416
|
const AnimatedComponent = Animated__default.default.createAnimatedComponent(
|
|
417
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
340
418
|
Component
|
|
341
419
|
);
|
|
342
420
|
const Motion2 = react.forwardRef(function Motion3(props, ref) {
|
|
@@ -415,13 +493,19 @@ function createMotionComponent(Component) {
|
|
|
415
493
|
}
|
|
416
494
|
return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? DEFAULT_RESTING[key];
|
|
417
495
|
});
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
496
|
+
const pressedProgress = Animated.useSharedValue(0);
|
|
497
|
+
const focusedProgress = Animated.useSharedValue(0);
|
|
498
|
+
const focusVisibleProgress = Animated.useSharedValue(0);
|
|
499
|
+
const hoveredProgress = Animated.useSharedValue(0);
|
|
500
|
+
const gestureSV = Animated.useSharedValue(
|
|
501
|
+
resolveGestureLayers(gesture)
|
|
502
|
+
);
|
|
503
|
+
const gestureTargetsSig = stableSig(gesture);
|
|
504
|
+
react.useEffect(() => {
|
|
505
|
+
gestureSV.value = resolveGestureLayers(gesture);
|
|
506
|
+
}, [gestureTargetsSig]);
|
|
507
|
+
const baseRecord = isExiting && exitRecord ? { ...animateRecord, ...exitRecord } : animateRecord;
|
|
508
|
+
const baseSig = stableSig(baseRecord) + (isExiting ? "|exit" : "") + (shouldReduceMotion ? "|rm" : "");
|
|
425
509
|
const transitionSig = stableSig(transition);
|
|
426
510
|
const safeToRemoveRef = react.useRef(void 0);
|
|
427
511
|
safeToRemoveRef.current = presence?.safeToRemove;
|
|
@@ -442,13 +526,13 @@ function createMotionComponent(Component) {
|
|
|
442
526
|
};
|
|
443
527
|
let transformPending = 0;
|
|
444
528
|
for (const k of ALL_KEYS) {
|
|
445
|
-
if (TRANSFORM_KEY_SET.has(k) &&
|
|
529
|
+
if (TRANSFORM_KEY_SET.has(k) && baseRecord[k] !== void 0) {
|
|
446
530
|
transformPending++;
|
|
447
531
|
}
|
|
448
532
|
}
|
|
449
533
|
const transformGroup = transformPending > 0 ? { remaining: transformPending } : void 0;
|
|
450
534
|
for (const key of ALL_KEYS) {
|
|
451
|
-
const target =
|
|
535
|
+
const target = baseRecord[key];
|
|
452
536
|
if (target === void 0) continue;
|
|
453
537
|
const cfg = shouldReduceMotion ? { type: "no-animation" } : transitionFor(key, transition);
|
|
454
538
|
if (isExiting) pending++;
|
|
@@ -473,14 +557,76 @@ function createMotionComponent(Component) {
|
|
|
473
557
|
if (isExiting && pending === 0) {
|
|
474
558
|
safeToRemoveRef.current?.();
|
|
475
559
|
}
|
|
476
|
-
}, [
|
|
560
|
+
}, [baseSig, transitionSig]);
|
|
561
|
+
useGestureLayerProgress(
|
|
562
|
+
pressedProgress,
|
|
563
|
+
pressed,
|
|
564
|
+
gesture?.pressed != null,
|
|
565
|
+
"pressed",
|
|
566
|
+
transition,
|
|
567
|
+
isExiting,
|
|
568
|
+
shouldReduceMotion
|
|
569
|
+
);
|
|
570
|
+
useGestureLayerProgress(
|
|
571
|
+
focusedProgress,
|
|
572
|
+
focused,
|
|
573
|
+
gesture?.focused != null,
|
|
574
|
+
"focused",
|
|
575
|
+
transition,
|
|
576
|
+
isExiting,
|
|
577
|
+
shouldReduceMotion
|
|
578
|
+
);
|
|
579
|
+
useGestureLayerProgress(
|
|
580
|
+
focusVisibleProgress,
|
|
581
|
+
focusVisible,
|
|
582
|
+
gesture?.focusVisible != null,
|
|
583
|
+
"focusVisible",
|
|
584
|
+
transition,
|
|
585
|
+
isExiting,
|
|
586
|
+
shouldReduceMotion
|
|
587
|
+
);
|
|
588
|
+
useGestureLayerProgress(
|
|
589
|
+
hoveredProgress,
|
|
590
|
+
hovered,
|
|
591
|
+
gesture?.hovered != null,
|
|
592
|
+
"hovered",
|
|
593
|
+
transition,
|
|
594
|
+
isExiting,
|
|
595
|
+
shouldReduceMotion
|
|
596
|
+
);
|
|
477
597
|
const animatedStyle = Animated.useAnimatedStyle(() => {
|
|
478
598
|
const activeKeys = activeKeysRef.current;
|
|
479
599
|
const hasTransform = hasTransformRef.current;
|
|
480
600
|
const out = {};
|
|
481
601
|
const transform = [];
|
|
602
|
+
const ph = hoveredProgress.value;
|
|
603
|
+
const pf = focusedProgress.value;
|
|
604
|
+
const pfv = focusVisibleProgress.value;
|
|
605
|
+
const pp = pressedProgress.value;
|
|
606
|
+
const layers = gestureSV.value;
|
|
607
|
+
const hoveredLayer = layers ? layers.hovered : null;
|
|
608
|
+
const focusedLayer = layers ? layers.focused : null;
|
|
609
|
+
const focusVisibleLayer = layers ? layers.focusVisible : null;
|
|
610
|
+
const pressedLayer = layers ? layers.pressed : null;
|
|
482
611
|
for (const key of activeKeys) {
|
|
483
|
-
|
|
612
|
+
let v = sharedValues[key].value;
|
|
613
|
+
const isColor = COLOR_KEY_SET.has(key);
|
|
614
|
+
if (hoveredLayer && ph > 0 && hoveredLayer[key] !== void 0) {
|
|
615
|
+
const t = hoveredLayer[key];
|
|
616
|
+
v = isColor ? Animated.interpolateColor(ph, [0, 1], [v, t]) : v + (t - v) * ph;
|
|
617
|
+
}
|
|
618
|
+
if (focusedLayer && pf > 0 && focusedLayer[key] !== void 0) {
|
|
619
|
+
const t = focusedLayer[key];
|
|
620
|
+
v = isColor ? Animated.interpolateColor(pf, [0, 1], [v, t]) : v + (t - v) * pf;
|
|
621
|
+
}
|
|
622
|
+
if (focusVisibleLayer && pfv > 0 && focusVisibleLayer[key] !== void 0) {
|
|
623
|
+
const t = focusVisibleLayer[key];
|
|
624
|
+
v = isColor ? Animated.interpolateColor(pfv, [0, 1], [v, t]) : v + (t - v) * pfv;
|
|
625
|
+
}
|
|
626
|
+
if (pressedLayer && pp > 0 && pressedLayer[key] !== void 0) {
|
|
627
|
+
const t = pressedLayer[key];
|
|
628
|
+
v = isColor ? Animated.interpolateColor(pp, [0, 1], [v, t]) : v + (t - v) * pp;
|
|
629
|
+
}
|
|
484
630
|
if (TRANSFORM_KEY_SET.has(key)) {
|
|
485
631
|
transform.push(
|
|
486
632
|
key === "rotate" ? { rotate: `${v}deg` } : { [key]: v }
|
|
@@ -528,6 +674,12 @@ function useAnimatableSharedValues(init) {
|
|
|
528
674
|
const width = Animated.useSharedValue(init("width"));
|
|
529
675
|
const height = Animated.useSharedValue(init("height"));
|
|
530
676
|
const borderRadius = Animated.useSharedValue(init("borderRadius"));
|
|
677
|
+
const backgroundColor = Animated.useSharedValue(
|
|
678
|
+
init("backgroundColor")
|
|
679
|
+
);
|
|
680
|
+
const borderColor = Animated.useSharedValue(init("borderColor"));
|
|
681
|
+
const color = Animated.useSharedValue(init("color"));
|
|
682
|
+
const tintColor = Animated.useSharedValue(init("tintColor"));
|
|
531
683
|
const ref = react.useRef(null);
|
|
532
684
|
if (ref.current === null) {
|
|
533
685
|
ref.current = {
|
|
@@ -540,7 +692,11 @@ function useAnimatableSharedValues(init) {
|
|
|
540
692
|
opacity,
|
|
541
693
|
width,
|
|
542
694
|
height,
|
|
543
|
-
borderRadius
|
|
695
|
+
borderRadius,
|
|
696
|
+
backgroundColor,
|
|
697
|
+
borderColor,
|
|
698
|
+
color,
|
|
699
|
+
tintColor
|
|
544
700
|
};
|
|
545
701
|
}
|
|
546
702
|
return ref.current;
|
|
@@ -659,13 +815,13 @@ function resolveAnimateInput(animate, variants, controllerKey) {
|
|
|
659
815
|
}
|
|
660
816
|
function restValue(v) {
|
|
661
817
|
if (v === void 0) return void 0;
|
|
662
|
-
if (typeof v === "number") return v;
|
|
818
|
+
if (typeof v === "number" || typeof v === "string") return v;
|
|
663
819
|
if (Array.isArray(v)) {
|
|
664
820
|
return v.length > 0 ? restValue(v[0]) : void 0;
|
|
665
821
|
}
|
|
666
822
|
if (typeof v === "object" && v !== null && "to" in v) {
|
|
667
823
|
const to = v.to;
|
|
668
|
-
return typeof to === "number" ? to : void 0;
|
|
824
|
+
return typeof to === "number" || typeof to === "string" ? to : void 0;
|
|
669
825
|
}
|
|
670
826
|
return void 0;
|
|
671
827
|
}
|
|
@@ -689,52 +845,41 @@ function stableStringify(v) {
|
|
|
689
845
|
const keys = Object.keys(obj).sort();
|
|
690
846
|
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
691
847
|
}
|
|
692
|
-
function
|
|
693
|
-
if (!gesture) return
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
if (!sub) continue;
|
|
705
|
-
for (const k of ALL_KEYS) {
|
|
706
|
-
if (k in sub && !(k in merged)) {
|
|
707
|
-
merged[k] = DEFAULT_RESTING[k];
|
|
708
|
-
}
|
|
848
|
+
function resolveGestureLayers(gesture) {
|
|
849
|
+
if (!gesture) return null;
|
|
850
|
+
const out = {};
|
|
851
|
+
for (const layer of GESTURE_LAYER_NAMES) {
|
|
852
|
+
const subState = gesture[layer];
|
|
853
|
+
if (!subState) continue;
|
|
854
|
+
const resolved = {};
|
|
855
|
+
for (const key of ALL_KEYS) {
|
|
856
|
+
const raw = subState[key];
|
|
857
|
+
if (raw === void 0) continue;
|
|
858
|
+
const t = targetEndValue(raw);
|
|
859
|
+
if (t !== void 0) resolved[key] = t;
|
|
709
860
|
}
|
|
861
|
+
out[layer] = resolved;
|
|
710
862
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
merged,
|
|
726
|
-
gesture.focusVisible
|
|
727
|
-
);
|
|
728
|
-
}
|
|
729
|
-
if (active.pressed && gesture.pressed) {
|
|
730
|
-
Object.assign(
|
|
731
|
-
merged,
|
|
732
|
-
gesture.pressed
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
return merged;
|
|
863
|
+
return out;
|
|
864
|
+
}
|
|
865
|
+
function useGestureLayerProgress(progress, active, declared, layer, transition, isExiting, shouldReduceMotion) {
|
|
866
|
+
const layerCfgSig = stableSig(gestureLayerTransitionFor(layer, transition));
|
|
867
|
+
react.useEffect(() => {
|
|
868
|
+
if (!declared) return;
|
|
869
|
+
if (isExiting) {
|
|
870
|
+
progress.value = 0;
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const target = active ? 1 : 0;
|
|
874
|
+
const cfg = shouldReduceMotion ? { type: "no-animation" } : gestureLayerTransitionFor(layer, transition) ?? { type: "spring" };
|
|
875
|
+
progress.value = resolveTransition(cfg, target);
|
|
876
|
+
}, [active, declared, isExiting, shouldReduceMotion, layerCfgSig]);
|
|
736
877
|
}
|
|
737
878
|
function useGestureHandlers(gesture, rest, setPressed, setFocused, setFocusVisible, setHovered) {
|
|
879
|
+
const hasPressed = gesture?.pressed ? 1 : 0;
|
|
880
|
+
const hasFocused = gesture?.focused ? 1 : 0;
|
|
881
|
+
const hasFocusVisible = gesture?.focusVisible ? 1 : 0;
|
|
882
|
+
const hasHovered = gesture?.hovered ? 1 : 0;
|
|
738
883
|
return react.useMemo(() => {
|
|
739
884
|
if (!gesture) return {};
|
|
740
885
|
const handlers = {};
|
|
@@ -767,10 +912,10 @@ function useGestureHandlers(gesture, rest, setPressed, setFocused, setFocusVisib
|
|
|
767
912
|
}
|
|
768
913
|
return handlers;
|
|
769
914
|
}, [
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
915
|
+
hasPressed,
|
|
916
|
+
hasFocused,
|
|
917
|
+
hasFocusVisible,
|
|
918
|
+
hasHovered,
|
|
774
919
|
rest.onTouchStart,
|
|
775
920
|
rest.onTouchEnd,
|
|
776
921
|
rest.onTouchCancel,
|