@onlynative/inertia 0.0.1-alpha.7 → 0.0.1-alpha.9

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 (72) hide show
  1. package/README.md +1 -1
  2. package/dist/gestureLayer/index.d.mts +119 -0
  3. package/dist/gestureLayer/index.d.ts +119 -0
  4. package/dist/gestureLayer/index.js +346 -0
  5. package/dist/gestureLayer/index.js.map +1 -0
  6. package/dist/gestureLayer/index.mjs +344 -0
  7. package/dist/gestureLayer/index.mjs.map +1 -0
  8. package/dist/index.d.mts +114 -74
  9. package/dist/index.d.ts +114 -74
  10. package/dist/index.js +388 -1542
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +388 -1545
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/motion/Image.d.mts +1 -1
  15. package/dist/motion/Image.d.ts +1 -1
  16. package/dist/motion/Image.js +244 -1462
  17. package/dist/motion/Image.js.map +1 -1
  18. package/dist/motion/Image.mjs +247 -1465
  19. package/dist/motion/Image.mjs.map +1 -1
  20. package/dist/motion/Pressable.d.mts +1 -1
  21. package/dist/motion/Pressable.d.ts +1 -1
  22. package/dist/motion/Pressable.js +244 -1462
  23. package/dist/motion/Pressable.js.map +1 -1
  24. package/dist/motion/Pressable.mjs +247 -1465
  25. package/dist/motion/Pressable.mjs.map +1 -1
  26. package/dist/motion/ScrollView.d.mts +1 -1
  27. package/dist/motion/ScrollView.d.ts +1 -1
  28. package/dist/motion/ScrollView.js +244 -1462
  29. package/dist/motion/ScrollView.js.map +1 -1
  30. package/dist/motion/ScrollView.mjs +247 -1465
  31. package/dist/motion/ScrollView.mjs.map +1 -1
  32. package/dist/motion/Text.d.mts +1 -1
  33. package/dist/motion/Text.d.ts +1 -1
  34. package/dist/motion/Text.js +244 -1462
  35. package/dist/motion/Text.js.map +1 -1
  36. package/dist/motion/Text.mjs +247 -1465
  37. package/dist/motion/Text.mjs.map +1 -1
  38. package/dist/motion/View.d.mts +1 -1
  39. package/dist/motion/View.d.ts +1 -1
  40. package/dist/motion/View.js +244 -1462
  41. package/dist/motion/View.js.map +1 -1
  42. package/dist/motion/View.mjs +247 -1465
  43. package/dist/motion/View.mjs.map +1 -1
  44. package/dist/touch/index.d.mts +146 -0
  45. package/dist/touch/index.d.ts +146 -0
  46. package/dist/touch/index.js +166 -0
  47. package/dist/touch/index.js.map +1 -0
  48. package/dist/touch/index.mjs +164 -0
  49. package/dist/touch/index.mjs.map +1 -0
  50. package/dist/{types-NmNeJjo1.d.mts → types-cU43dEmH.d.mts} +64 -17
  51. package/dist/{types-NmNeJjo1.d.ts → types-cU43dEmH.d.ts} +64 -17
  52. package/dist/useGesture-B7A_1DVg.d.ts +84 -0
  53. package/dist/useGesture-cimMrzC1.d.mts +84 -0
  54. package/jest-setup.js +4 -0
  55. package/llms.txt +12 -3
  56. package/package.json +22 -2
  57. package/src/__type-tests__/variants.test-d.tsx +67 -0
  58. package/src/gestureLayer/index.ts +21 -0
  59. package/src/gestureLayer/useGestureLayer.ts +285 -0
  60. package/src/index.ts +7 -0
  61. package/src/layout/index.ts +15 -0
  62. package/src/layout/sharedRegistry.ts +111 -0
  63. package/src/layout/useSharedLayout.ts +289 -0
  64. package/src/motion/createMotionComponent.tsx +123 -37
  65. package/src/motion/installCheck.ts +7 -11
  66. package/src/touch/index.ts +18 -0
  67. package/src/touch/useTouchDrag.ts +289 -0
  68. package/src/types.ts +79 -20
  69. package/src/values/index.ts +11 -0
  70. package/src/values/useBooleanSpring.ts +33 -0
  71. package/src/values/useColorTransition.ts +72 -0
  72. package/src/values/useShadow.ts +116 -0
@@ -0,0 +1,146 @@
1
+ import { PanResponderInstance } from 'react-native';
2
+ import { useAnimatedStyle, SharedValue } from 'react-native-reanimated';
3
+ import { T as TransitionConfig } from '../types-cU43dEmH.mjs';
4
+ import 'react';
5
+
6
+ /**
7
+ * Same drag-result shape as `useDrag` from `@onlynative/inertia-gestures`,
8
+ * minus the `gesture` field (PanResponder spreads handlers, no
9
+ * `<GestureDetector>` wrapper). The shared values + animatedStyle are
10
+ * interchangeable across both hooks; consumers can swap implementations
11
+ * without touching their `useAnimatedStyle` consumers.
12
+ */
13
+ interface UseTouchDragResult {
14
+ /** Spread onto a `View` / `Pressable` to install the pan responder. */
15
+ panHandlers: PanResponderInstance['panHandlers'];
16
+ /** Stable animated `transform` style. */
17
+ animatedStyle: ReturnType<typeof useAnimatedStyle>;
18
+ /** Live x translation, persistent across gestures. */
19
+ dragX: SharedValue<number>;
20
+ /** Live y translation, persistent across gestures. */
21
+ dragY: SharedValue<number>;
22
+ /** True while the gesture is active. */
23
+ isDragging: SharedValue<boolean>;
24
+ }
25
+ /**
26
+ * Release transition shape for PanResponder's JS-thread `onRelease`. Mirrors
27
+ * the gesture-handler adapter's `ReleaseTransition` but with `to` typed as
28
+ * required for spring/timing/no-animation (decay omits it).
29
+ */
30
+ type TouchReleaseTransition = (TransitionConfig & {
31
+ type: 'spring';
32
+ to: number;
33
+ }) | (TransitionConfig & {
34
+ type: 'timing';
35
+ to: number;
36
+ }) | (TransitionConfig & {
37
+ type: 'decay';
38
+ }) | (TransitionConfig & {
39
+ type: 'no-animation';
40
+ to: number;
41
+ });
42
+ interface TouchReleaseInfo {
43
+ x: number;
44
+ y: number;
45
+ velocity: {
46
+ x: number;
47
+ y: number;
48
+ };
49
+ }
50
+ interface TouchReleaseResult {
51
+ x?: TouchReleaseTransition;
52
+ y?: TouchReleaseTransition;
53
+ }
54
+ interface UseTouchDragOptions {
55
+ /**
56
+ * Restrict the drag to one axis. Defaults to `'both'`. When `'x'` is set
57
+ * the y-axis shared value never updates (and vice versa); velocity is
58
+ * still reported on both for `onDragEnd`.
59
+ */
60
+ axis?: 'x' | 'y' | 'both';
61
+ /**
62
+ * Travel bounds (px from resting). Each side is independently optional.
63
+ * Out-of-bounds values clamp to the limit unless `elastic > 0`.
64
+ */
65
+ constraints?: {
66
+ left?: number;
67
+ right?: number;
68
+ top?: number;
69
+ bottom?: number;
70
+ };
71
+ /**
72
+ * Rubber-band coefficient applied to overshoot past `constraints`. `0`
73
+ * (default) hard-clamps; `0.2`-`0.4` is a typical Framer-Motion feel.
74
+ */
75
+ elastic?: number;
76
+ /**
77
+ * Fires when the user starts dragging. JS thread.
78
+ */
79
+ onDragStart?: () => void;
80
+ /**
81
+ * Fires when the user releases or the gesture terminates. JS thread.
82
+ *
83
+ * Velocity is in px/sec to match the `@onlynative/inertia-gestures` API
84
+ * (PanResponder's native `vx` / `vy` are px/ms; the hook normalizes).
85
+ */
86
+ onDragEnd?: (info: TouchReleaseInfo) => void;
87
+ /**
88
+ * Optional release-animation callback. Return per-axis release transitions
89
+ * to animate the SVs to a settled position via Inertia's transition
90
+ * resolver — spring snap-to-tick, decay with bounds, timing settle.
91
+ *
92
+ * Unlike the gesture-handler version, this callback runs on the **JS
93
+ * thread** (PanResponder is JS-only). The returned transitions still drive
94
+ * UI-thread animations via Reanimated — only the decision logic is JS-side.
95
+ */
96
+ onRelease?: (info: TouchReleaseInfo) => TouchReleaseResult | void;
97
+ }
98
+ /**
99
+ * PanResponder-backed drag hook. Pointer-equivalent of `useDrag` from
100
+ * `@onlynative/inertia-gestures`, with two differences:
101
+ *
102
+ * 1. No `react-native-gesture-handler` peer dep required — PanResponder is
103
+ * built into React Native, so this lives in core.
104
+ * 2. Returns `panHandlers` to spread on a `View` / `Pressable` instead of
105
+ * a `gesture` to plug into `<GestureDetector>`.
106
+ *
107
+ * Use this when:
108
+ * - You need keyboard a11y alongside drag (a slider with arrow-key step,
109
+ * a scrollbar with `PageUp` / `PageDown`). PanResponder composes
110
+ * cleanly with `onKeyDown`; gesture-handler doesn't surface keyboard.
111
+ * - You don't want to take `react-native-gesture-handler` as a dependency
112
+ * (smaller bundle, simpler install).
113
+ *
114
+ * Skip this when:
115
+ * - You're already using `react-native-gesture-handler` elsewhere (use
116
+ * `useDrag` from `@onlynative/inertia-gestures` for consistency and
117
+ * better worklet-thread fidelity on release velocity).
118
+ * - You need momentum semantics like the gesture-handler `usePan` —
119
+ * PanResponder's release velocity is JS-thread and slightly less precise.
120
+ *
121
+ * @example
122
+ * ```tsx
123
+ * import { useTouchDrag } from '@onlynative/inertia/touch'
124
+ *
125
+ * function Slider({ ticks }: { ticks: number[] }) {
126
+ * const drag = useTouchDrag({
127
+ * axis: 'x',
128
+ * constraints: { left: 0, right: 280 },
129
+ * onRelease: (e) => {
130
+ * const snap = nearestTick(e.x, ticks)
131
+ * return { x: { type: 'spring', to: snap, velocity: e.velocity.x } }
132
+ * },
133
+ * })
134
+ *
135
+ * return (
136
+ * <Motion.View
137
+ * style={[styles.thumb, drag.animatedStyle]}
138
+ * {...drag.panHandlers}
139
+ * />
140
+ * )
141
+ * }
142
+ * ```
143
+ */
144
+ declare function useTouchDrag(options?: UseTouchDragOptions): UseTouchDragResult;
145
+
146
+ export { type TouchReleaseInfo, type TouchReleaseResult, type TouchReleaseTransition, type UseTouchDragOptions, type UseTouchDragResult, useTouchDrag };
@@ -0,0 +1,146 @@
1
+ import { PanResponderInstance } from 'react-native';
2
+ import { useAnimatedStyle, SharedValue } from 'react-native-reanimated';
3
+ import { T as TransitionConfig } from '../types-cU43dEmH.js';
4
+ import 'react';
5
+
6
+ /**
7
+ * Same drag-result shape as `useDrag` from `@onlynative/inertia-gestures`,
8
+ * minus the `gesture` field (PanResponder spreads handlers, no
9
+ * `<GestureDetector>` wrapper). The shared values + animatedStyle are
10
+ * interchangeable across both hooks; consumers can swap implementations
11
+ * without touching their `useAnimatedStyle` consumers.
12
+ */
13
+ interface UseTouchDragResult {
14
+ /** Spread onto a `View` / `Pressable` to install the pan responder. */
15
+ panHandlers: PanResponderInstance['panHandlers'];
16
+ /** Stable animated `transform` style. */
17
+ animatedStyle: ReturnType<typeof useAnimatedStyle>;
18
+ /** Live x translation, persistent across gestures. */
19
+ dragX: SharedValue<number>;
20
+ /** Live y translation, persistent across gestures. */
21
+ dragY: SharedValue<number>;
22
+ /** True while the gesture is active. */
23
+ isDragging: SharedValue<boolean>;
24
+ }
25
+ /**
26
+ * Release transition shape for PanResponder's JS-thread `onRelease`. Mirrors
27
+ * the gesture-handler adapter's `ReleaseTransition` but with `to` typed as
28
+ * required for spring/timing/no-animation (decay omits it).
29
+ */
30
+ type TouchReleaseTransition = (TransitionConfig & {
31
+ type: 'spring';
32
+ to: number;
33
+ }) | (TransitionConfig & {
34
+ type: 'timing';
35
+ to: number;
36
+ }) | (TransitionConfig & {
37
+ type: 'decay';
38
+ }) | (TransitionConfig & {
39
+ type: 'no-animation';
40
+ to: number;
41
+ });
42
+ interface TouchReleaseInfo {
43
+ x: number;
44
+ y: number;
45
+ velocity: {
46
+ x: number;
47
+ y: number;
48
+ };
49
+ }
50
+ interface TouchReleaseResult {
51
+ x?: TouchReleaseTransition;
52
+ y?: TouchReleaseTransition;
53
+ }
54
+ interface UseTouchDragOptions {
55
+ /**
56
+ * Restrict the drag to one axis. Defaults to `'both'`. When `'x'` is set
57
+ * the y-axis shared value never updates (and vice versa); velocity is
58
+ * still reported on both for `onDragEnd`.
59
+ */
60
+ axis?: 'x' | 'y' | 'both';
61
+ /**
62
+ * Travel bounds (px from resting). Each side is independently optional.
63
+ * Out-of-bounds values clamp to the limit unless `elastic > 0`.
64
+ */
65
+ constraints?: {
66
+ left?: number;
67
+ right?: number;
68
+ top?: number;
69
+ bottom?: number;
70
+ };
71
+ /**
72
+ * Rubber-band coefficient applied to overshoot past `constraints`. `0`
73
+ * (default) hard-clamps; `0.2`-`0.4` is a typical Framer-Motion feel.
74
+ */
75
+ elastic?: number;
76
+ /**
77
+ * Fires when the user starts dragging. JS thread.
78
+ */
79
+ onDragStart?: () => void;
80
+ /**
81
+ * Fires when the user releases or the gesture terminates. JS thread.
82
+ *
83
+ * Velocity is in px/sec to match the `@onlynative/inertia-gestures` API
84
+ * (PanResponder's native `vx` / `vy` are px/ms; the hook normalizes).
85
+ */
86
+ onDragEnd?: (info: TouchReleaseInfo) => void;
87
+ /**
88
+ * Optional release-animation callback. Return per-axis release transitions
89
+ * to animate the SVs to a settled position via Inertia's transition
90
+ * resolver — spring snap-to-tick, decay with bounds, timing settle.
91
+ *
92
+ * Unlike the gesture-handler version, this callback runs on the **JS
93
+ * thread** (PanResponder is JS-only). The returned transitions still drive
94
+ * UI-thread animations via Reanimated — only the decision logic is JS-side.
95
+ */
96
+ onRelease?: (info: TouchReleaseInfo) => TouchReleaseResult | void;
97
+ }
98
+ /**
99
+ * PanResponder-backed drag hook. Pointer-equivalent of `useDrag` from
100
+ * `@onlynative/inertia-gestures`, with two differences:
101
+ *
102
+ * 1. No `react-native-gesture-handler` peer dep required — PanResponder is
103
+ * built into React Native, so this lives in core.
104
+ * 2. Returns `panHandlers` to spread on a `View` / `Pressable` instead of
105
+ * a `gesture` to plug into `<GestureDetector>`.
106
+ *
107
+ * Use this when:
108
+ * - You need keyboard a11y alongside drag (a slider with arrow-key step,
109
+ * a scrollbar with `PageUp` / `PageDown`). PanResponder composes
110
+ * cleanly with `onKeyDown`; gesture-handler doesn't surface keyboard.
111
+ * - You don't want to take `react-native-gesture-handler` as a dependency
112
+ * (smaller bundle, simpler install).
113
+ *
114
+ * Skip this when:
115
+ * - You're already using `react-native-gesture-handler` elsewhere (use
116
+ * `useDrag` from `@onlynative/inertia-gestures` for consistency and
117
+ * better worklet-thread fidelity on release velocity).
118
+ * - You need momentum semantics like the gesture-handler `usePan` —
119
+ * PanResponder's release velocity is JS-thread and slightly less precise.
120
+ *
121
+ * @example
122
+ * ```tsx
123
+ * import { useTouchDrag } from '@onlynative/inertia/touch'
124
+ *
125
+ * function Slider({ ticks }: { ticks: number[] }) {
126
+ * const drag = useTouchDrag({
127
+ * axis: 'x',
128
+ * constraints: { left: 0, right: 280 },
129
+ * onRelease: (e) => {
130
+ * const snap = nearestTick(e.x, ticks)
131
+ * return { x: { type: 'spring', to: snap, velocity: e.velocity.x } }
132
+ * },
133
+ * })
134
+ *
135
+ * return (
136
+ * <Motion.View
137
+ * style={[styles.thumb, drag.animatedStyle]}
138
+ * {...drag.panHandlers}
139
+ * />
140
+ * )
141
+ * }
142
+ * ```
143
+ */
144
+ declare function useTouchDrag(options?: UseTouchDragOptions): UseTouchDragResult;
145
+
146
+ export { type TouchReleaseInfo, type TouchReleaseResult, type TouchReleaseTransition, type UseTouchDragOptions, type UseTouchDragResult, useTouchDrag };
@@ -0,0 +1,166 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var reactNative = require('react-native');
5
+ var reactNativeReanimated = require('react-native-reanimated');
6
+
7
+ // src/touch/useTouchDrag.ts
8
+
9
+ // src/transitions/spring.ts
10
+ var DEFAULT_SPRING = {
11
+ tension: 170,
12
+ friction: 26,
13
+ mass: 1
14
+ };
15
+ function springToReanimated(t) {
16
+ "worklet";
17
+ return {
18
+ stiffness: t.tension ?? DEFAULT_SPRING.tension,
19
+ damping: t.friction ?? DEFAULT_SPRING.friction,
20
+ mass: t.mass ?? DEFAULT_SPRING.mass,
21
+ velocity: t.velocity,
22
+ restSpeedThreshold: t.restSpeedThreshold,
23
+ restDisplacementThreshold: t.restDisplacementThreshold
24
+ };
25
+ }
26
+ var DEFAULT_TIMING_DURATION = 250;
27
+ function buildReleaseAnimation(transition, toValue) {
28
+ "worklet";
29
+ if (transition.type === "no-animation") return toValue;
30
+ if (transition.type === "decay") {
31
+ const cfg = { velocity: transition.velocity ?? 0 };
32
+ if (transition.deceleration !== void 0) {
33
+ cfg.deceleration = transition.deceleration;
34
+ }
35
+ if (transition.clamp !== void 0) cfg.clamp = transition.clamp;
36
+ return reactNativeReanimated.withDecay(cfg);
37
+ }
38
+ if (transition.type === "timing") {
39
+ const e = transition.easing;
40
+ const easingFn = e && typeof e === "object" && "factory" in e ? e.factory() : e ?? reactNativeReanimated.Easing.inOut(reactNativeReanimated.Easing.ease);
41
+ return reactNativeReanimated.withTiming(toValue, {
42
+ duration: transition.duration ?? DEFAULT_TIMING_DURATION,
43
+ easing: easingFn
44
+ });
45
+ }
46
+ return reactNativeReanimated.withSpring(toValue, springToReanimated(transition));
47
+ }
48
+
49
+ // src/touch/useTouchDrag.ts
50
+ function useTouchDrag(options = {}) {
51
+ const { axis = "both", constraints, elastic = 0 } = options;
52
+ const dragX = reactNativeReanimated.useSharedValue(0);
53
+ const dragY = reactNativeReanimated.useSharedValue(0);
54
+ const startX = reactNativeReanimated.useSharedValue(0);
55
+ const startY = reactNativeReanimated.useSharedValue(0);
56
+ const isDragging = reactNativeReanimated.useSharedValue(false);
57
+ const lockX = axis !== "y";
58
+ const lockY = axis !== "x";
59
+ const left = constraints?.left;
60
+ const right = constraints?.right;
61
+ const top = constraints?.top;
62
+ const bottom = constraints?.bottom;
63
+ const elasticCoef = elastic;
64
+ const { onDragStart, onDragEnd, onRelease } = options;
65
+ const responder = react.useMemo(
66
+ () => buildResponder(),
67
+ // eslint-disable-next-line react-hooks/exhaustive-deps
68
+ [
69
+ lockX,
70
+ lockY,
71
+ left,
72
+ right,
73
+ top,
74
+ bottom,
75
+ elasticCoef,
76
+ onDragStart,
77
+ onDragEnd,
78
+ onRelease
79
+ ]
80
+ );
81
+ function buildResponder() {
82
+ const handleEnd = (g) => {
83
+ isDragging.value = false;
84
+ const x = dragX.value;
85
+ const y = dragY.value;
86
+ const vx = g.vx * 1e3;
87
+ const vy = g.vy * 1e3;
88
+ if (onRelease) {
89
+ const result = onRelease({ x, y, velocity: { x: vx, y: vy } });
90
+ if (result) {
91
+ if (result.x && lockX) {
92
+ const toX = "to" in result.x ? result.x.to : x;
93
+ dragX.value = buildReleaseAnimation(
94
+ result.x,
95
+ toX
96
+ );
97
+ }
98
+ if (result.y && lockY) {
99
+ const toY = "to" in result.y ? result.y.to : y;
100
+ dragY.value = buildReleaseAnimation(
101
+ result.y,
102
+ toY
103
+ );
104
+ }
105
+ }
106
+ }
107
+ if (onDragEnd) onDragEnd({ x, y, velocity: { x: vx, y: vy } });
108
+ };
109
+ return reactNative.PanResponder.create({
110
+ // Always claim the start so taps that turn into drags don't slip
111
+ // through to a parent ScrollView. Consumers can compose their own
112
+ // capture predicates by wrapping the returned `panHandlers`.
113
+ onStartShouldSetPanResponder: () => true,
114
+ onMoveShouldSetPanResponder: () => true,
115
+ onPanResponderGrant: () => {
116
+ startX.value = dragX.value;
117
+ startY.value = dragY.value;
118
+ isDragging.value = true;
119
+ if (onDragStart) onDragStart();
120
+ },
121
+ onPanResponderMove: (_e, g) => {
122
+ if (lockX) {
123
+ dragX.value = applyBounds(
124
+ startX.value + g.dx,
125
+ left,
126
+ right,
127
+ elasticCoef
128
+ );
129
+ }
130
+ if (lockY) {
131
+ dragY.value = applyBounds(
132
+ startY.value + g.dy,
133
+ top,
134
+ bottom,
135
+ elasticCoef
136
+ );
137
+ }
138
+ },
139
+ onPanResponderRelease: (_e, g) => handleEnd(g),
140
+ onPanResponderTerminate: (_e, g) => handleEnd(g)
141
+ });
142
+ }
143
+ const animatedStyle = reactNativeReanimated.useAnimatedStyle(() => ({
144
+ transform: [{ translateX: dragX.value }, { translateY: dragY.value }]
145
+ }));
146
+ return {
147
+ panHandlers: responder.panHandlers,
148
+ animatedStyle,
149
+ dragX,
150
+ dragY,
151
+ isDragging
152
+ };
153
+ }
154
+ function applyBounds(value, min, max, elastic) {
155
+ if (min !== void 0 && value < min) {
156
+ return elastic > 0 ? min + (value - min) * elastic : min;
157
+ }
158
+ if (max !== void 0 && value > max) {
159
+ return elastic > 0 ? max + (value - max) * elastic : max;
160
+ }
161
+ return value;
162
+ }
163
+
164
+ exports.useTouchDrag = useTouchDrag;
165
+ //# sourceMappingURL=index.js.map
166
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/transitions/spring.ts","../../src/transitions/runtime.ts","../../src/touch/useTouchDrag.ts"],"names":["withDecay","Easing","withTiming","withSpring","useSharedValue","useMemo","PanResponder","useAnimatedStyle"],"mappings":"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","file":"index.js","sourcesContent":["import { type SpringTransition } from '../types'\n\n/**\n * Default spring physics, expressed in react-spring vocabulary.\n *\n * `tension: 170` / `friction: 26` / `mass: 1` was picked over Reanimated's\n * raw `stiffness: 100` / `damping: 10` default because the raw default\n * overshoots noticeably for the small (~100px) translates that dominate\n * UI work — buttons, sheets, popovers. These numbers settle in ~350ms with\n * a single, almost-imperceptible overshoot, which matches the perceptual\n * target the rest of the library is tuned against.\n */\nexport const DEFAULT_SPRING: Required<\n Pick<SpringTransition, 'tension' | 'friction' | 'mass'>\n> = {\n tension: 170,\n friction: 26,\n mass: 1,\n}\n\n/**\n * Convert public react-spring vocabulary (`tension` / `friction` / `mass`)\n * to Reanimated's raw `stiffness` / `damping` / `mass`. This is the single\n * place the mapping lives; resolvers, value hooks, and any future surface\n * that needs a Reanimated spring config import from here.\n *\n * The mapping is identity (tension ≡ stiffness, friction ≡ damping) — the\n * names differ but the underlying physics constants are the same. We don't\n * surface the raw names publicly because the react-spring vocabulary is\n * what designers and prior-art consumers expect.\n */\nexport function springToReanimated(t: SpringTransition) {\n 'worklet'\n return {\n stiffness: t.tension ?? DEFAULT_SPRING.tension,\n damping: t.friction ?? DEFAULT_SPRING.friction,\n mass: t.mass ?? DEFAULT_SPRING.mass,\n velocity: t.velocity,\n restSpeedThreshold: t.restSpeedThreshold,\n restDisplacementThreshold: t.restDisplacementThreshold,\n }\n}\n","import {\n Easing,\n withDecay,\n withSpring,\n withTiming,\n} from 'react-native-reanimated'\nimport { springToReanimated } from './spring'\nimport { type TransitionConfig } from '../types'\n\nconst DEFAULT_TIMING_DURATION = 250\n\n/**\n * Worklet-safe single-step animation builder. Mirrors a subset of\n * `resolveTransition` for the UI-thread path where the transition config is\n * picked at gesture-release time, not at render time.\n *\n * Supported: spring / timing / decay / no-animation, single-step only.\n * Not supported: sequences, top-level repeat, easing-function\n * auto-worklet-wrapping (pass an already-worklet easing if you need a custom\n * one — most release transitions don't).\n *\n * Use this from gesture worklets (`useDrag` / `usePan` release callbacks, or\n * any custom `Gesture.Pan().onEnd(() => ...)` worklet) to animate a shared\n * value with an Inertia transition without the JS round-trip that would lose\n * the release velocity.\n *\n * For decay transitions, `toValue` is ignored — decay decelerates from the\n * SV's current position via its own physics. Pass `0` if you don't have one.\n */\nexport function buildReleaseAnimation(\n transition: TransitionConfig,\n toValue: number,\n): unknown {\n 'worklet'\n if (transition.type === 'no-animation') return toValue\n if (transition.type === 'decay') {\n const cfg: {\n velocity: number\n deceleration?: number\n clamp?: [number, number]\n } = { velocity: transition.velocity ?? 0 }\n if (transition.deceleration !== undefined) {\n cfg.deceleration = transition.deceleration\n }\n if (transition.clamp !== undefined) cfg.clamp = transition.clamp\n return withDecay(cfg)\n }\n if (transition.type === 'timing') {\n // Reanimated 4's `Easing.bezier(...)` returns an `EasingFunctionFactory`\n // rather than the function itself. Unwrap inline so consumers calling\n // `buildReleaseAnimation` from a gesture worklet don't have to.\n const e = transition.easing\n const easingFn =\n e && typeof e === 'object' && 'factory' in e\n ? e.factory()\n : (e ?? Easing.inOut(Easing.ease))\n return withTiming(toValue, {\n duration: transition.duration ?? DEFAULT_TIMING_DURATION,\n easing: easingFn,\n })\n }\n return withSpring(toValue, springToReanimated(transition))\n}\n","import { useMemo } from 'react'\nimport {\n PanResponder,\n type PanResponderGestureState,\n type PanResponderInstance,\n} from 'react-native'\nimport {\n useAnimatedStyle,\n useSharedValue,\n type SharedValue,\n} from 'react-native-reanimated'\nimport { buildReleaseAnimation } from '../transitions'\nimport type { TransitionConfig } from '../types'\n\n/**\n * Same drag-result shape as `useDrag` from `@onlynative/inertia-gestures`,\n * minus the `gesture` field (PanResponder spreads handlers, no\n * `<GestureDetector>` wrapper). The shared values + animatedStyle are\n * interchangeable across both hooks; consumers can swap implementations\n * without touching their `useAnimatedStyle` consumers.\n */\nexport interface UseTouchDragResult {\n /** Spread onto a `View` / `Pressable` to install the pan responder. */\n panHandlers: PanResponderInstance['panHandlers']\n /** Stable animated `transform` style. */\n animatedStyle: ReturnType<typeof useAnimatedStyle>\n /** Live x translation, persistent across gestures. */\n dragX: SharedValue<number>\n /** Live y translation, persistent across gestures. */\n dragY: SharedValue<number>\n /** True while the gesture is active. */\n isDragging: SharedValue<boolean>\n}\n\n/**\n * Release transition shape for PanResponder's JS-thread `onRelease`. Mirrors\n * the gesture-handler adapter's `ReleaseTransition` but with `to` typed as\n * required for spring/timing/no-animation (decay omits it).\n */\nexport type TouchReleaseTransition =\n | (TransitionConfig & { type: 'spring'; to: number })\n | (TransitionConfig & { type: 'timing'; to: number })\n | (TransitionConfig & { type: 'decay' })\n | (TransitionConfig & { type: 'no-animation'; to: number })\n\nexport interface TouchReleaseInfo {\n x: number\n y: number\n velocity: { x: number; y: number }\n}\n\nexport interface TouchReleaseResult {\n x?: TouchReleaseTransition\n y?: TouchReleaseTransition\n}\n\nexport interface UseTouchDragOptions {\n /**\n * Restrict the drag to one axis. Defaults to `'both'`. When `'x'` is set\n * the y-axis shared value never updates (and vice versa); velocity is\n * still reported on both for `onDragEnd`.\n */\n axis?: 'x' | 'y' | 'both'\n /**\n * Travel bounds (px from resting). Each side is independently optional.\n * Out-of-bounds values clamp to the limit unless `elastic > 0`.\n */\n constraints?: {\n left?: number\n right?: number\n top?: number\n bottom?: number\n }\n /**\n * Rubber-band coefficient applied to overshoot past `constraints`. `0`\n * (default) hard-clamps; `0.2`-`0.4` is a typical Framer-Motion feel.\n */\n elastic?: number\n /**\n * Fires when the user starts dragging. JS thread.\n */\n onDragStart?: () => void\n /**\n * Fires when the user releases or the gesture terminates. JS thread.\n *\n * Velocity is in px/sec to match the `@onlynative/inertia-gestures` API\n * (PanResponder's native `vx` / `vy` are px/ms; the hook normalizes).\n */\n onDragEnd?: (info: TouchReleaseInfo) => void\n /**\n * Optional release-animation callback. Return per-axis release transitions\n * to animate the SVs to a settled position via Inertia's transition\n * resolver — spring snap-to-tick, decay with bounds, timing settle.\n *\n * Unlike the gesture-handler version, this callback runs on the **JS\n * thread** (PanResponder is JS-only). The returned transitions still drive\n * UI-thread animations via Reanimated — only the decision logic is JS-side.\n */\n onRelease?: (info: TouchReleaseInfo) => TouchReleaseResult | void\n}\n\n/**\n * PanResponder-backed drag hook. Pointer-equivalent of `useDrag` from\n * `@onlynative/inertia-gestures`, with two differences:\n *\n * 1. No `react-native-gesture-handler` peer dep required — PanResponder is\n * built into React Native, so this lives in core.\n * 2. Returns `panHandlers` to spread on a `View` / `Pressable` instead of\n * a `gesture` to plug into `<GestureDetector>`.\n *\n * Use this when:\n * - You need keyboard a11y alongside drag (a slider with arrow-key step,\n * a scrollbar with `PageUp` / `PageDown`). PanResponder composes\n * cleanly with `onKeyDown`; gesture-handler doesn't surface keyboard.\n * - You don't want to take `react-native-gesture-handler` as a dependency\n * (smaller bundle, simpler install).\n *\n * Skip this when:\n * - You're already using `react-native-gesture-handler` elsewhere (use\n * `useDrag` from `@onlynative/inertia-gestures` for consistency and\n * better worklet-thread fidelity on release velocity).\n * - You need momentum semantics like the gesture-handler `usePan` —\n * PanResponder's release velocity is JS-thread and slightly less precise.\n *\n * @example\n * ```tsx\n * import { useTouchDrag } from '@onlynative/inertia/touch'\n *\n * function Slider({ ticks }: { ticks: number[] }) {\n * const drag = useTouchDrag({\n * axis: 'x',\n * constraints: { left: 0, right: 280 },\n * onRelease: (e) => {\n * const snap = nearestTick(e.x, ticks)\n * return { x: { type: 'spring', to: snap, velocity: e.velocity.x } }\n * },\n * })\n *\n * return (\n * <Motion.View\n * style={[styles.thumb, drag.animatedStyle]}\n * {...drag.panHandlers}\n * />\n * )\n * }\n * ```\n */\nexport function useTouchDrag(\n options: UseTouchDragOptions = {},\n): UseTouchDragResult {\n const { axis = 'both', constraints, elastic = 0 } = options\n\n const dragX = useSharedValue(0)\n const dragY = useSharedValue(0)\n const startX = useSharedValue(0)\n const startY = useSharedValue(0)\n const isDragging = useSharedValue(false)\n\n // Snapshot scalars into local consts so the responder callbacks close over\n // primitives, not the `options` literal — a fresh `options` each render\n // would otherwise force the PanResponder identity to change.\n const lockX = axis !== 'y'\n const lockY = axis !== 'x'\n const left = constraints?.left\n const right = constraints?.right\n const top = constraints?.top\n const bottom = constraints?.bottom\n const elasticCoef = elastic\n const { onDragStart, onDragEnd, onRelease } = options\n\n const responder = useMemo(\n () => buildResponder(),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n lockX,\n lockY,\n left,\n right,\n top,\n bottom,\n elasticCoef,\n onDragStart,\n onDragEnd,\n onRelease,\n ],\n )\n\n // Hoisted out of the inline `useMemo` factory to keep the dep list readable\n // and avoid re-declaring closure helpers each render.\n function buildResponder(): PanResponderInstance {\n const handleEnd = (g: PanResponderGestureState) => {\n isDragging.value = false\n const x = dragX.value\n const y = dragY.value\n // PanResponder velocity is px/ms; multiply to match the\n // `@onlynative/inertia-gestures` API (px/sec from gesture-handler).\n const vx = g.vx * 1000\n const vy = g.vy * 1000\n if (onRelease) {\n const result = onRelease({ x, y, velocity: { x: vx, y: vy } })\n if (result) {\n if (result.x && lockX) {\n const toX = 'to' in result.x ? result.x.to : x\n dragX.value = buildReleaseAnimation(\n result.x,\n toX,\n ) as unknown as number\n }\n if (result.y && lockY) {\n const toY = 'to' in result.y ? result.y.to : y\n dragY.value = buildReleaseAnimation(\n result.y,\n toY,\n ) as unknown as number\n }\n }\n }\n if (onDragEnd) onDragEnd({ x, y, velocity: { x: vx, y: vy } })\n }\n\n return PanResponder.create({\n // Always claim the start so taps that turn into drags don't slip\n // through to a parent ScrollView. Consumers can compose their own\n // capture predicates by wrapping the returned `panHandlers`.\n onStartShouldSetPanResponder: () => true,\n onMoveShouldSetPanResponder: () => true,\n onPanResponderGrant: () => {\n startX.value = dragX.value\n startY.value = dragY.value\n isDragging.value = true\n if (onDragStart) onDragStart()\n },\n onPanResponderMove: (_e, g) => {\n if (lockX) {\n dragX.value = applyBounds(\n startX.value + g.dx,\n left,\n right,\n elasticCoef,\n )\n }\n if (lockY) {\n dragY.value = applyBounds(\n startY.value + g.dy,\n top,\n bottom,\n elasticCoef,\n )\n }\n },\n onPanResponderRelease: (_e, g) => handleEnd(g),\n onPanResponderTerminate: (_e, g) => handleEnd(g),\n })\n }\n\n const animatedStyle = useAnimatedStyle(() => ({\n transform: [{ translateX: dragX.value }, { translateY: dragY.value }],\n }))\n\n return {\n panHandlers: responder.panHandlers,\n animatedStyle,\n dragX,\n dragY,\n isDragging,\n }\n}\n\n/**\n * Clamp `value` to `[min, max]`. When `elastic > 0` the overshoot past a\n * bound is scaled by `elastic`, giving a rubber-band feel. `min` / `max`\n * may be `undefined` to leave that side unbounded.\n *\n * JS-thread (PanResponder callbacks are JS, not worklets).\n */\nfunction applyBounds(\n value: number,\n min: number | undefined,\n max: number | undefined,\n elastic: number,\n): number {\n if (min !== undefined && value < min) {\n return elastic > 0 ? min + (value - min) * elastic : min\n }\n if (max !== undefined && value > max) {\n return elastic > 0 ? max + (value - max) * elastic : max\n }\n return value\n}\n"]}
@@ -0,0 +1,164 @@
1
+ import { useMemo } from 'react';
2
+ import { PanResponder } from 'react-native';
3
+ import { useSharedValue, useAnimatedStyle, withDecay, Easing, withTiming, withSpring } from 'react-native-reanimated';
4
+
5
+ // src/touch/useTouchDrag.ts
6
+
7
+ // src/transitions/spring.ts
8
+ var DEFAULT_SPRING = {
9
+ tension: 170,
10
+ friction: 26,
11
+ mass: 1
12
+ };
13
+ function springToReanimated(t) {
14
+ "worklet";
15
+ return {
16
+ stiffness: t.tension ?? DEFAULT_SPRING.tension,
17
+ damping: t.friction ?? DEFAULT_SPRING.friction,
18
+ mass: t.mass ?? DEFAULT_SPRING.mass,
19
+ velocity: t.velocity,
20
+ restSpeedThreshold: t.restSpeedThreshold,
21
+ restDisplacementThreshold: t.restDisplacementThreshold
22
+ };
23
+ }
24
+ var DEFAULT_TIMING_DURATION = 250;
25
+ function buildReleaseAnimation(transition, toValue) {
26
+ "worklet";
27
+ if (transition.type === "no-animation") return toValue;
28
+ if (transition.type === "decay") {
29
+ const cfg = { velocity: transition.velocity ?? 0 };
30
+ if (transition.deceleration !== void 0) {
31
+ cfg.deceleration = transition.deceleration;
32
+ }
33
+ if (transition.clamp !== void 0) cfg.clamp = transition.clamp;
34
+ return withDecay(cfg);
35
+ }
36
+ if (transition.type === "timing") {
37
+ const e = transition.easing;
38
+ const easingFn = e && typeof e === "object" && "factory" in e ? e.factory() : e ?? Easing.inOut(Easing.ease);
39
+ return withTiming(toValue, {
40
+ duration: transition.duration ?? DEFAULT_TIMING_DURATION,
41
+ easing: easingFn
42
+ });
43
+ }
44
+ return withSpring(toValue, springToReanimated(transition));
45
+ }
46
+
47
+ // src/touch/useTouchDrag.ts
48
+ function useTouchDrag(options = {}) {
49
+ const { axis = "both", constraints, elastic = 0 } = options;
50
+ const dragX = useSharedValue(0);
51
+ const dragY = useSharedValue(0);
52
+ const startX = useSharedValue(0);
53
+ const startY = useSharedValue(0);
54
+ const isDragging = useSharedValue(false);
55
+ const lockX = axis !== "y";
56
+ const lockY = axis !== "x";
57
+ const left = constraints?.left;
58
+ const right = constraints?.right;
59
+ const top = constraints?.top;
60
+ const bottom = constraints?.bottom;
61
+ const elasticCoef = elastic;
62
+ const { onDragStart, onDragEnd, onRelease } = options;
63
+ const responder = useMemo(
64
+ () => buildResponder(),
65
+ // eslint-disable-next-line react-hooks/exhaustive-deps
66
+ [
67
+ lockX,
68
+ lockY,
69
+ left,
70
+ right,
71
+ top,
72
+ bottom,
73
+ elasticCoef,
74
+ onDragStart,
75
+ onDragEnd,
76
+ onRelease
77
+ ]
78
+ );
79
+ function buildResponder() {
80
+ const handleEnd = (g) => {
81
+ isDragging.value = false;
82
+ const x = dragX.value;
83
+ const y = dragY.value;
84
+ const vx = g.vx * 1e3;
85
+ const vy = g.vy * 1e3;
86
+ if (onRelease) {
87
+ const result = onRelease({ x, y, velocity: { x: vx, y: vy } });
88
+ if (result) {
89
+ if (result.x && lockX) {
90
+ const toX = "to" in result.x ? result.x.to : x;
91
+ dragX.value = buildReleaseAnimation(
92
+ result.x,
93
+ toX
94
+ );
95
+ }
96
+ if (result.y && lockY) {
97
+ const toY = "to" in result.y ? result.y.to : y;
98
+ dragY.value = buildReleaseAnimation(
99
+ result.y,
100
+ toY
101
+ );
102
+ }
103
+ }
104
+ }
105
+ if (onDragEnd) onDragEnd({ x, y, velocity: { x: vx, y: vy } });
106
+ };
107
+ return PanResponder.create({
108
+ // Always claim the start so taps that turn into drags don't slip
109
+ // through to a parent ScrollView. Consumers can compose their own
110
+ // capture predicates by wrapping the returned `panHandlers`.
111
+ onStartShouldSetPanResponder: () => true,
112
+ onMoveShouldSetPanResponder: () => true,
113
+ onPanResponderGrant: () => {
114
+ startX.value = dragX.value;
115
+ startY.value = dragY.value;
116
+ isDragging.value = true;
117
+ if (onDragStart) onDragStart();
118
+ },
119
+ onPanResponderMove: (_e, g) => {
120
+ if (lockX) {
121
+ dragX.value = applyBounds(
122
+ startX.value + g.dx,
123
+ left,
124
+ right,
125
+ elasticCoef
126
+ );
127
+ }
128
+ if (lockY) {
129
+ dragY.value = applyBounds(
130
+ startY.value + g.dy,
131
+ top,
132
+ bottom,
133
+ elasticCoef
134
+ );
135
+ }
136
+ },
137
+ onPanResponderRelease: (_e, g) => handleEnd(g),
138
+ onPanResponderTerminate: (_e, g) => handleEnd(g)
139
+ });
140
+ }
141
+ const animatedStyle = useAnimatedStyle(() => ({
142
+ transform: [{ translateX: dragX.value }, { translateY: dragY.value }]
143
+ }));
144
+ return {
145
+ panHandlers: responder.panHandlers,
146
+ animatedStyle,
147
+ dragX,
148
+ dragY,
149
+ isDragging
150
+ };
151
+ }
152
+ function applyBounds(value, min, max, elastic) {
153
+ if (min !== void 0 && value < min) {
154
+ return elastic > 0 ? min + (value - min) * elastic : min;
155
+ }
156
+ if (max !== void 0 && value > max) {
157
+ return elastic > 0 ? max + (value - max) * elastic : max;
158
+ }
159
+ return value;
160
+ }
161
+
162
+ export { useTouchDrag };
163
+ //# sourceMappingURL=index.mjs.map
164
+ //# sourceMappingURL=index.mjs.map