@onlynative/inertia 0.0.1-alpha.2 → 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.
Files changed (44) hide show
  1. package/README.md +29 -2
  2. package/dist/index.d.mts +2 -2
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +170 -58
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +171 -59
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/motion/Image.d.mts +1 -1
  9. package/dist/motion/Image.d.ts +1 -1
  10. package/dist/motion/Image.js +170 -58
  11. package/dist/motion/Image.js.map +1 -1
  12. package/dist/motion/Image.mjs +171 -59
  13. package/dist/motion/Image.mjs.map +1 -1
  14. package/dist/motion/Pressable.d.mts +1 -1
  15. package/dist/motion/Pressable.d.ts +1 -1
  16. package/dist/motion/Pressable.js +170 -58
  17. package/dist/motion/Pressable.js.map +1 -1
  18. package/dist/motion/Pressable.mjs +171 -59
  19. package/dist/motion/Pressable.mjs.map +1 -1
  20. package/dist/motion/ScrollView.d.mts +1 -1
  21. package/dist/motion/ScrollView.d.ts +1 -1
  22. package/dist/motion/ScrollView.js +170 -58
  23. package/dist/motion/ScrollView.js.map +1 -1
  24. package/dist/motion/ScrollView.mjs +171 -59
  25. package/dist/motion/ScrollView.mjs.map +1 -1
  26. package/dist/motion/Text.d.mts +1 -1
  27. package/dist/motion/Text.d.ts +1 -1
  28. package/dist/motion/Text.js +170 -58
  29. package/dist/motion/Text.js.map +1 -1
  30. package/dist/motion/Text.mjs +171 -59
  31. package/dist/motion/Text.mjs.map +1 -1
  32. package/dist/motion/View.d.mts +1 -1
  33. package/dist/motion/View.d.ts +1 -1
  34. package/dist/motion/View.js +170 -58
  35. package/dist/motion/View.js.map +1 -1
  36. package/dist/motion/View.mjs +171 -59
  37. package/dist/motion/View.mjs.map +1 -1
  38. package/dist/{types-DeZZzE_e.d.mts → types-DAhX3fC2.d.mts} +40 -16
  39. package/dist/{types-DeZZzE_e.d.ts → types-DAhX3fC2.d.ts} +40 -16
  40. package/llms.txt +25 -2
  41. package/package.json +1 -1
  42. package/src/motion/createMotionComponent.tsx +258 -97
  43. package/src/motion/installCheck.ts +69 -0
  44. 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
- A `Motion.View`-only import currently bundles to ~3.2 kB brotlied (excluding peers).
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
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-DeZZzE_e.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-DeZZzE_e.mjs';
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';
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-DeZZzE_e.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-DeZZzE_e.js';
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';
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
- // src/motion/Image.tsx
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",
@@ -311,6 +350,14 @@ var ALL_KEYS = [
311
350
  ...COLOR_KEYS
312
351
  ];
313
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);
314
361
  var EXITING_POINTER_EVENTS_STYLE = { pointerEvents: "none" };
315
362
  var DEFAULT_RESTING = {
316
363
  translateX: 0,
@@ -356,10 +403,18 @@ function isTopLevelTransition(t) {
356
403
  function transitionFor(prop, transition) {
357
404
  if (!transition) return void 0;
358
405
  if (isTopLevelTransition(transition)) return transition;
406
+ if (GESTURE_LAYER_NAME_SET.has(prop)) return void 0;
359
407
  return transition[prop];
360
408
  }
409
+ function gestureLayerTransitionFor(layer, transition) {
410
+ if (!transition) return void 0;
411
+ if (isTopLevelTransition(transition)) return transition;
412
+ return transition[layer];
413
+ }
361
414
  function createMotionComponent(Component) {
415
+ ensureReanimatedInstalled();
362
416
  const AnimatedComponent = Animated__default.default.createAnimatedComponent(
417
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
363
418
  Component
364
419
  );
365
420
  const Motion2 = react.forwardRef(function Motion3(props, ref) {
@@ -438,13 +493,19 @@ function createMotionComponent(Component) {
438
493
  }
439
494
  return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? DEFAULT_RESTING[key];
440
495
  });
441
- const mergedRecord = isExiting && exitRecord ? { ...animateRecord, ...exitRecord } : mergeGestureTargets(animateRecord, gesture, {
442
- pressed,
443
- focused,
444
- focusVisible,
445
- hovered
446
- });
447
- const mergedSig = stableSig(mergedRecord) + (isExiting ? "|exit" : "") + (shouldReduceMotion ? "|rm" : "");
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" : "");
448
509
  const transitionSig = stableSig(transition);
449
510
  const safeToRemoveRef = react.useRef(void 0);
450
511
  safeToRemoveRef.current = presence?.safeToRemove;
@@ -465,13 +526,13 @@ function createMotionComponent(Component) {
465
526
  };
466
527
  let transformPending = 0;
467
528
  for (const k of ALL_KEYS) {
468
- if (TRANSFORM_KEY_SET.has(k) && mergedRecord[k] !== void 0) {
529
+ if (TRANSFORM_KEY_SET.has(k) && baseRecord[k] !== void 0) {
469
530
  transformPending++;
470
531
  }
471
532
  }
472
533
  const transformGroup = transformPending > 0 ? { remaining: transformPending } : void 0;
473
534
  for (const key of ALL_KEYS) {
474
- const target = mergedRecord[key];
535
+ const target = baseRecord[key];
475
536
  if (target === void 0) continue;
476
537
  const cfg = shouldReduceMotion ? { type: "no-animation" } : transitionFor(key, transition);
477
538
  if (isExiting) pending++;
@@ -496,14 +557,76 @@ function createMotionComponent(Component) {
496
557
  if (isExiting && pending === 0) {
497
558
  safeToRemoveRef.current?.();
498
559
  }
499
- }, [mergedSig, transitionSig]);
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
+ );
500
597
  const animatedStyle = Animated.useAnimatedStyle(() => {
501
598
  const activeKeys = activeKeysRef.current;
502
599
  const hasTransform = hasTransformRef.current;
503
600
  const out = {};
504
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;
505
611
  for (const key of activeKeys) {
506
- const v = sharedValues[key].value;
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
+ }
507
630
  if (TRANSFORM_KEY_SET.has(key)) {
508
631
  transform.push(
509
632
  key === "rotate" ? { rotate: `${v}deg` } : { [key]: v }
@@ -722,52 +845,41 @@ function stableStringify(v) {
722
845
  const keys = Object.keys(obj).sort();
723
846
  return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
724
847
  }
725
- function mergeGestureTargets(base, gesture, active) {
726
- if (!gesture) return base;
727
- const merged = {
728
- ...base
729
- };
730
- const subStates = [
731
- gesture.hovered,
732
- gesture.focused,
733
- gesture.focusVisible,
734
- gesture.pressed
735
- ];
736
- for (const sub of subStates) {
737
- if (!sub) continue;
738
- for (const k of ALL_KEYS) {
739
- if (k in sub && !(k in merged)) {
740
- merged[k] = DEFAULT_RESTING[k];
741
- }
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;
742
860
  }
861
+ out[layer] = resolved;
743
862
  }
744
- if (active.hovered && gesture.hovered) {
745
- Object.assign(
746
- merged,
747
- gesture.hovered
748
- );
749
- }
750
- if (active.focused && gesture.focused) {
751
- Object.assign(
752
- merged,
753
- gesture.focused
754
- );
755
- }
756
- if (active.focusVisible && gesture.focusVisible) {
757
- Object.assign(
758
- merged,
759
- gesture.focusVisible
760
- );
761
- }
762
- if (active.pressed && gesture.pressed) {
763
- Object.assign(
764
- merged,
765
- gesture.pressed
766
- );
767
- }
768
- 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]);
769
877
  }
770
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;
771
883
  return react.useMemo(() => {
772
884
  if (!gesture) return {};
773
885
  const handlers = {};
@@ -800,10 +912,10 @@ function useGestureHandlers(gesture, rest, setPressed, setFocused, setFocusVisib
800
912
  }
801
913
  return handlers;
802
914
  }, [
803
- gesture?.pressed ? 1 : 0,
804
- gesture?.focused ? 1 : 0,
805
- gesture?.focusVisible ? 1 : 0,
806
- gesture?.hovered ? 1 : 0,
915
+ hasPressed,
916
+ hasFocused,
917
+ hasFocusVisible,
918
+ hasHovered,
807
919
  rest.onTouchStart,
808
920
  rest.onTouchEnd,
809
921
  rest.onTouchCancel,