@webority-technologies/mobile 0.0.23 → 0.0.25

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 (171) hide show
  1. package/lib/commonjs/components/Accordion/Accordion.js +5 -5
  2. package/lib/commonjs/components/AnimatePresence/AnimatePresence.js +69 -0
  3. package/lib/commonjs/components/AnimatePresence/index.js +13 -0
  4. package/lib/commonjs/components/AppBar/AppBar.js +9 -6
  5. package/lib/commonjs/components/Autocomplete/Autocomplete.js +204 -0
  6. package/lib/commonjs/components/Autocomplete/index.js +13 -0
  7. package/lib/commonjs/components/Banner/Banner.js +12 -2
  8. package/lib/commonjs/components/BottomNavigation/BottomNavigation.js +1 -1
  9. package/lib/commonjs/components/Card/Card.js +3 -3
  10. package/lib/commonjs/components/Checkbox/Checkbox.js +3 -2
  11. package/lib/commonjs/components/Chip/Chip.js +4 -2
  12. package/lib/commonjs/components/Confetti/Confetti.js +170 -0
  13. package/lib/commonjs/components/Confetti/index.js +13 -0
  14. package/lib/commonjs/components/DatePicker/DatePicker.js +23 -18
  15. package/lib/commonjs/components/DateRangePicker/DateRangePicker.js +11 -9
  16. package/lib/commonjs/components/Dialog/Dialog.js +4 -2
  17. package/lib/commonjs/components/Drawer/Drawer.js +4 -2
  18. package/lib/commonjs/components/FieldBase/FieldBase.js +0 -2
  19. package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +10 -8
  20. package/lib/commonjs/components/IconButton/IconButton.js +176 -0
  21. package/lib/commonjs/components/IconButton/index.js +13 -0
  22. package/lib/commonjs/components/ImageGallery/ImageGallery.js +17 -15
  23. package/lib/commonjs/components/ListItem/ListItem.js +4 -3
  24. package/lib/commonjs/components/Modal/Modal.js +4 -4
  25. package/lib/commonjs/components/NumberInput/NumberInput.js +7 -5
  26. package/lib/commonjs/components/OTPInput/OTPInput.js +7 -7
  27. package/lib/commonjs/components/ProgressBar/ProgressBar.js +32 -4
  28. package/lib/commonjs/components/Radio/Radio.js +2 -3
  29. package/lib/commonjs/components/Rating/Rating.js +4 -3
  30. package/lib/commonjs/components/SearchBar/SearchBar.js +7 -4
  31. package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +4 -3
  32. package/lib/commonjs/components/Select/Select.js +7 -4
  33. package/lib/commonjs/components/SlideToConfirm/SlideToConfirm.js +224 -0
  34. package/lib/commonjs/components/SlideToConfirm/index.js +13 -0
  35. package/lib/commonjs/components/Slider/Slider.js +228 -228
  36. package/lib/commonjs/components/Stepper/Stepper.js +6 -5
  37. package/lib/commonjs/components/Swipeable/Swipeable.js +8 -9
  38. package/lib/commonjs/components/Tabs/Tabs.js +4 -3
  39. package/lib/commonjs/components/TimePicker/TimePicker.js +14 -9
  40. package/lib/commonjs/components/index.js +149 -114
  41. package/lib/commonjs/hooks/usePressAnimation.js +0 -1
  42. package/lib/commonjs/utils/hapticUtils.js +11 -1
  43. package/lib/commonjs/utils/index.js +6 -0
  44. package/lib/module/components/Accordion/Accordion.js +6 -6
  45. package/lib/module/components/AnimatePresence/AnimatePresence.js +63 -0
  46. package/lib/module/components/AnimatePresence/index.js +4 -0
  47. package/lib/module/components/AppBar/AppBar.js +10 -7
  48. package/lib/module/components/Autocomplete/Autocomplete.js +199 -0
  49. package/lib/module/components/Autocomplete/index.js +4 -0
  50. package/lib/module/components/Banner/Banner.js +12 -2
  51. package/lib/module/components/BottomNavigation/BottomNavigation.js +1 -1
  52. package/lib/module/components/Card/Card.js +4 -4
  53. package/lib/module/components/Checkbox/Checkbox.js +4 -3
  54. package/lib/module/components/Chip/Chip.js +5 -3
  55. package/lib/module/components/Confetti/Confetti.js +166 -0
  56. package/lib/module/components/Confetti/index.js +4 -0
  57. package/lib/module/components/DatePicker/DatePicker.js +24 -19
  58. package/lib/module/components/DateRangePicker/DateRangePicker.js +12 -10
  59. package/lib/module/components/Dialog/Dialog.js +5 -3
  60. package/lib/module/components/Drawer/Drawer.js +5 -3
  61. package/lib/module/components/FieldBase/FieldBase.js +0 -2
  62. package/lib/module/components/FloatingActionButton/FloatingActionButton.js +11 -9
  63. package/lib/module/components/IconButton/IconButton.js +172 -0
  64. package/lib/module/components/IconButton/index.js +4 -0
  65. package/lib/module/components/ImageGallery/ImageGallery.js +18 -16
  66. package/lib/module/components/ListItem/ListItem.js +5 -4
  67. package/lib/module/components/Modal/Modal.js +5 -5
  68. package/lib/module/components/NumberInput/NumberInput.js +8 -6
  69. package/lib/module/components/OTPInput/OTPInput.js +8 -8
  70. package/lib/module/components/ProgressBar/ProgressBar.js +33 -5
  71. package/lib/module/components/Radio/Radio.js +3 -4
  72. package/lib/module/components/Rating/Rating.js +5 -4
  73. package/lib/module/components/SearchBar/SearchBar.js +8 -5
  74. package/lib/module/components/SegmentedControl/SegmentedControl.js +5 -4
  75. package/lib/module/components/Select/Select.js +8 -5
  76. package/lib/module/components/SlideToConfirm/SlideToConfirm.js +220 -0
  77. package/lib/module/components/SlideToConfirm/index.js +4 -0
  78. package/lib/module/components/Slider/Slider.js +231 -231
  79. package/lib/module/components/Stepper/Stepper.js +7 -6
  80. package/lib/module/components/Swipeable/Swipeable.js +9 -10
  81. package/lib/module/components/Tabs/Tabs.js +5 -4
  82. package/lib/module/components/TimePicker/TimePicker.js +15 -10
  83. package/lib/module/components/index.js +5 -0
  84. package/lib/module/hooks/usePressAnimation.js +0 -1
  85. package/lib/module/utils/hapticUtils.js +9 -0
  86. package/lib/module/utils/index.js +1 -1
  87. package/lib/typescript/commonjs/components/Accordion/Accordion.d.ts +3 -0
  88. package/lib/typescript/commonjs/components/AnimatePresence/AnimatePresence.d.ts +30 -0
  89. package/lib/typescript/commonjs/components/AnimatePresence/index.d.ts +3 -0
  90. package/lib/typescript/commonjs/components/AppBar/AppBar.d.ts +6 -0
  91. package/lib/typescript/commonjs/components/Autocomplete/Autocomplete.d.ts +53 -0
  92. package/lib/typescript/commonjs/components/Autocomplete/index.d.ts +3 -0
  93. package/lib/typescript/commonjs/components/Banner/Banner.d.ts +3 -0
  94. package/lib/typescript/commonjs/components/Card/Card.d.ts +3 -0
  95. package/lib/typescript/commonjs/components/Checkbox/Checkbox.d.ts +1 -0
  96. package/lib/typescript/commonjs/components/Chip/Chip.d.ts +3 -0
  97. package/lib/typescript/commonjs/components/Confetti/Confetti.d.ts +41 -0
  98. package/lib/typescript/commonjs/components/Confetti/index.d.ts +3 -0
  99. package/lib/typescript/commonjs/components/DatePicker/DatePicker.d.ts +3 -0
  100. package/lib/typescript/commonjs/components/DateRangePicker/DateRangePicker.d.ts +6 -0
  101. package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +3 -0
  102. package/lib/typescript/commonjs/components/Drawer/Drawer.d.ts +3 -0
  103. package/lib/typescript/commonjs/components/FloatingActionButton/FloatingActionButton.d.ts +5 -0
  104. package/lib/typescript/commonjs/components/IconButton/IconButton.d.ts +34 -0
  105. package/lib/typescript/commonjs/components/IconButton/index.d.ts +3 -0
  106. package/lib/typescript/commonjs/components/ImageGallery/ImageGallery.d.ts +6 -0
  107. package/lib/typescript/commonjs/components/ListItem/ListItem.d.ts +3 -0
  108. package/lib/typescript/commonjs/components/Modal/Modal.d.ts +6 -0
  109. package/lib/typescript/commonjs/components/NumberInput/NumberInput.d.ts +3 -0
  110. package/lib/typescript/commonjs/components/OTPInput/OTPInput.d.ts +6 -0
  111. package/lib/typescript/commonjs/components/ProgressBar/ProgressBar.d.ts +12 -0
  112. package/lib/typescript/commonjs/components/ProgressBar/index.d.ts +1 -1
  113. package/lib/typescript/commonjs/components/Rating/Rating.d.ts +6 -0
  114. package/lib/typescript/commonjs/components/SearchBar/SearchBar.d.ts +3 -0
  115. package/lib/typescript/commonjs/components/SegmentedControl/SegmentedControl.d.ts +3 -0
  116. package/lib/typescript/commonjs/components/Select/Select.d.ts +6 -0
  117. package/lib/typescript/commonjs/components/SlideToConfirm/SlideToConfirm.d.ts +34 -0
  118. package/lib/typescript/commonjs/components/SlideToConfirm/index.d.ts +3 -0
  119. package/lib/typescript/commonjs/components/Slider/Slider.d.ts +3 -0
  120. package/lib/typescript/commonjs/components/Stepper/Stepper.d.ts +6 -0
  121. package/lib/typescript/commonjs/components/Swipeable/Swipeable.d.ts +3 -0
  122. package/lib/typescript/commonjs/components/Tabs/Tabs.d.ts +3 -0
  123. package/lib/typescript/commonjs/components/TimePicker/TimePicker.d.ts +3 -0
  124. package/lib/typescript/commonjs/components/index.d.ts +11 -1
  125. package/lib/typescript/commonjs/hooks/usePressAnimation.d.ts +1 -2
  126. package/lib/typescript/commonjs/theme/types.d.ts +2 -67
  127. package/lib/typescript/commonjs/utils/hapticUtils.d.ts +8 -0
  128. package/lib/typescript/commonjs/utils/index.d.ts +1 -1
  129. package/lib/typescript/module/components/Accordion/Accordion.d.ts +3 -0
  130. package/lib/typescript/module/components/AnimatePresence/AnimatePresence.d.ts +30 -0
  131. package/lib/typescript/module/components/AnimatePresence/index.d.ts +3 -0
  132. package/lib/typescript/module/components/AppBar/AppBar.d.ts +6 -0
  133. package/lib/typescript/module/components/Autocomplete/Autocomplete.d.ts +53 -0
  134. package/lib/typescript/module/components/Autocomplete/index.d.ts +3 -0
  135. package/lib/typescript/module/components/Banner/Banner.d.ts +3 -0
  136. package/lib/typescript/module/components/Card/Card.d.ts +3 -0
  137. package/lib/typescript/module/components/Checkbox/Checkbox.d.ts +1 -0
  138. package/lib/typescript/module/components/Chip/Chip.d.ts +3 -0
  139. package/lib/typescript/module/components/Confetti/Confetti.d.ts +41 -0
  140. package/lib/typescript/module/components/Confetti/index.d.ts +3 -0
  141. package/lib/typescript/module/components/DatePicker/DatePicker.d.ts +3 -0
  142. package/lib/typescript/module/components/DateRangePicker/DateRangePicker.d.ts +6 -0
  143. package/lib/typescript/module/components/Dialog/Dialog.d.ts +3 -0
  144. package/lib/typescript/module/components/Drawer/Drawer.d.ts +3 -0
  145. package/lib/typescript/module/components/FloatingActionButton/FloatingActionButton.d.ts +5 -0
  146. package/lib/typescript/module/components/IconButton/IconButton.d.ts +34 -0
  147. package/lib/typescript/module/components/IconButton/index.d.ts +3 -0
  148. package/lib/typescript/module/components/ImageGallery/ImageGallery.d.ts +6 -0
  149. package/lib/typescript/module/components/ListItem/ListItem.d.ts +3 -0
  150. package/lib/typescript/module/components/Modal/Modal.d.ts +6 -0
  151. package/lib/typescript/module/components/NumberInput/NumberInput.d.ts +3 -0
  152. package/lib/typescript/module/components/OTPInput/OTPInput.d.ts +6 -0
  153. package/lib/typescript/module/components/ProgressBar/ProgressBar.d.ts +12 -0
  154. package/lib/typescript/module/components/ProgressBar/index.d.ts +1 -1
  155. package/lib/typescript/module/components/Rating/Rating.d.ts +6 -0
  156. package/lib/typescript/module/components/SearchBar/SearchBar.d.ts +3 -0
  157. package/lib/typescript/module/components/SegmentedControl/SegmentedControl.d.ts +3 -0
  158. package/lib/typescript/module/components/Select/Select.d.ts +6 -0
  159. package/lib/typescript/module/components/SlideToConfirm/SlideToConfirm.d.ts +34 -0
  160. package/lib/typescript/module/components/SlideToConfirm/index.d.ts +3 -0
  161. package/lib/typescript/module/components/Slider/Slider.d.ts +3 -0
  162. package/lib/typescript/module/components/Stepper/Stepper.d.ts +6 -0
  163. package/lib/typescript/module/components/Swipeable/Swipeable.d.ts +3 -0
  164. package/lib/typescript/module/components/Tabs/Tabs.d.ts +3 -0
  165. package/lib/typescript/module/components/TimePicker/TimePicker.d.ts +3 -0
  166. package/lib/typescript/module/components/index.d.ts +11 -1
  167. package/lib/typescript/module/hooks/usePressAnimation.d.ts +1 -2
  168. package/lib/typescript/module/theme/types.d.ts +2 -67
  169. package/lib/typescript/module/utils/hapticUtils.d.ts +8 -0
  170. package/lib/typescript/module/utils/index.d.ts +1 -1
  171. package/package.json +1 -1
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
 
3
- import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
- import { Animated, Easing, PanResponder, Platform, Pressable, StyleSheet, Text, View } from 'react-native';
5
- import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
- import { triggerHaptic } from "../../utils/index.js";
3
+ import React, { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
4
+ import { Platform, Pressable, StyleSheet, Text, View } from 'react-native';
5
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
6
+ import Animated, { Easing, cancelAnimation, runOnJS, useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
7
+ import { useTheme } from "../../theme/index.js";
8
+ import { resolveHaptic, triggerHaptic } from "../../utils/index.js";
7
9
  import { useControllableState } from "../../hooks/index.js";
8
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
11
  const SIZE_MAP = {
@@ -30,7 +32,6 @@ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
30
32
  const snapToStep = (value, min, max, step) => {
31
33
  if (step <= 0) return clamp(value, min, max);
32
34
  const stepped = Math.round((value - min) / step) * step + min;
33
- // Guard against floating-point drift.
34
35
  const decimals = String(step).split('.')[1]?.length ?? 0;
35
36
  const fixed = decimals > 0 ? Number(stepped.toFixed(decimals)) : stepped;
36
37
  return clamp(fixed, min, max);
@@ -47,6 +48,7 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
47
48
  showLabel = false,
48
49
  formatLabel = defaultFormatLabel,
49
50
  accessibilityLabel,
51
+ haptic,
50
52
  style,
51
53
  containerStyle,
52
54
  trackStyle,
@@ -61,17 +63,20 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
61
63
  const theme = useTheme();
62
64
  const sliderTheme = theme.components.slider;
63
65
  const sliderSizeTheme = sliderTheme?.[size];
64
- const sizeCfg = {
66
+ const sizeCfg = useMemo(() => ({
65
67
  trackHeight: sliderSizeTheme?.trackHeight ?? SIZE_MAP[size].trackHeight,
66
68
  thumbDiameter: sliderSizeTheme?.thumbDiameter ?? SIZE_MAP[size].thumbDiameter
67
- };
69
+ }), [sliderSizeTheme, size]);
68
70
  const thumbPressAnimationEnabled = sliderTheme?.thumbPressAnimation ?? false;
69
71
  const resolvedPressScale = thumbPressScale ?? sliderTheme?.thumbPressScale ?? 1.1;
70
72
  const resolvedLabelDuration = labelShowDuration ?? sliderTheme?.labelShowDuration ?? theme.motion.duration.fast;
71
73
  const resolvedLabelOpacity = labelOpacityProp ?? sliderTheme?.labelOpacity ?? 1;
72
74
  const styles = useMemo(() => buildStyles(theme, sizeCfg), [theme, sizeCfg]);
73
-
74
- // Track layout (width + x offset within container)
75
+ const springCfg = useMemo(() => ({
76
+ damping: theme.motion.spring.snappy.damping,
77
+ stiffness: theme.motion.spring.snappy.stiffness,
78
+ mass: theme.motion.spring.snappy.mass
79
+ }), [theme.motion.spring.snappy]);
75
80
  const [trackWidth, setTrackWidth] = useState(0);
76
81
  const fillColor = theme.colors[TONE_TO_COLOR_KEY[tone]];
77
82
  const isRange = props.range === true;
@@ -81,217 +86,218 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
81
86
  onChange: props.onChange
82
87
  });
83
88
 
84
- // Current value is mirrored in refs so PanResponder closures get fresh values.
85
- const lowRef = useRef(isRange ? currentValue[0] : currentValue);
86
- const highRef = useRef(isRange ? currentValue[1] : currentValue);
87
- const lastReportedLow = useRef(lowRef.current);
88
- const lastReportedHigh = useRef(highRef.current);
89
-
90
- // Animated x positions (px) for thumbs.
91
- const lowX = useRef(createAnimatedValue(0)).current;
92
- const highX = useRef(createAnimatedValue(0)).current;
93
-
94
- // Press / drag scale animations.
95
- const lowScale = useRef(createAnimatedValue(1)).current;
96
- const highScale = useRef(createAnimatedValue(1)).current;
97
-
98
- // Label opacity per thumb.
99
- const lowLabelOpacity = useRef(createAnimatedValue(0)).current;
100
- const highLabelOpacity = useRef(createAnimatedValue(0)).current;
89
+ // ───────── Shared values (UI thread) ─────────
90
+ // lowX/highX: thumb px positions. lowVal/highVal: their snapped numeric values
91
+ // (mirrored on the UI thread so worklets can clamp the range + detect changes).
92
+ const lowX = useSharedValue(0);
93
+ const highX = useSharedValue(0);
94
+ const lowVal = useSharedValue(isRange ? currentValue[0] : currentValue);
95
+ const highVal = useSharedValue(isRange ? currentValue[1] : currentValue);
96
+ const dragStartX = useSharedValue(0);
97
+ const trackW = useSharedValue(0);
98
+ const lowScale = useSharedValue(1);
99
+ const highScale = useSharedValue(1);
100
+ const lowLabelOpacity = useSharedValue(0);
101
+ const highLabelOpacity = useSharedValue(0);
101
102
 
102
- // Track which thumb is actively dragging (to anchor gesture math).
103
- const dragOffsetRef = useRef(0);
104
-
105
- // Derived helper: convert value -> px on track.
103
+ // Float-drift guard precision for step snapping (e.g. step 0.1 -> precision 10).
104
+ const precision = useMemo(() => {
105
+ const decimals = String(step).split('.')[1]?.length ?? 0;
106
+ return Math.pow(10, decimals);
107
+ }, [step]);
108
+ useEffect(() => {
109
+ return () => {
110
+ cancelAnimation(lowX);
111
+ cancelAnimation(highX);
112
+ cancelAnimation(lowScale);
113
+ cancelAnimation(highScale);
114
+ cancelAnimation(lowLabelOpacity);
115
+ cancelAnimation(highLabelOpacity);
116
+ };
117
+ }, [lowX, highX, lowScale, highScale, lowLabelOpacity, highLabelOpacity]);
106
118
  const valueToPx = useCallback((value, width) => {
107
119
  if (max === min || width <= 0) return 0;
108
- const ratio = (value - min) / (max - min);
109
- return ratio * width;
120
+ return (value - min) / (max - min) * width;
110
121
  }, [min, max]);
111
-
112
- // Derived helper: convert px -> snapped value.
113
122
  const pxToValue = useCallback((px, width) => {
114
123
  if (width <= 0) return min;
115
124
  const ratio = clamp(px / width, 0, 1);
116
- const raw = min + ratio * (max - min);
117
- return snapToStep(raw, min, max, step);
125
+ return snapToStep(min + ratio * (max - min), min, max, step);
118
126
  }, [min, max, step]);
119
127
 
120
- // Sync external value -> animated values.
128
+ // Sync external value -> shared values (controlled updates, value prop changes).
121
129
  useEffect(() => {
122
130
  if (trackWidth <= 0) return;
123
131
  if (isRange) {
124
132
  const [v0, v1] = currentValue;
125
133
  const lo = Math.min(v0, v1);
126
134
  const hi = Math.max(v0, v1);
127
- lowRef.current = lo;
128
- highRef.current = hi;
129
- lastReportedLow.current = lo;
130
- lastReportedHigh.current = hi;
131
- setNativeValue(lowX, valueToPx(lo, trackWidth));
132
- setNativeValue(highX, valueToPx(hi, trackWidth));
135
+ lowVal.value = lo;
136
+ highVal.value = hi;
137
+ lowX.value = valueToPx(lo, trackWidth);
138
+ highX.value = valueToPx(hi, trackWidth);
133
139
  } else {
134
140
  const v = currentValue;
135
- lowRef.current = v;
136
- highRef.current = v;
137
- lastReportedLow.current = v;
138
- setNativeValue(lowX, valueToPx(v, trackWidth));
141
+ lowVal.value = v;
142
+ highVal.value = v;
143
+ lowX.value = valueToPx(v, trackWidth);
139
144
  }
140
- // We intentionally listen to the value across both shapes via JSON; ESLint is fine.
141
145
  // eslint-disable-next-line react-hooks/exhaustive-deps
142
146
  }, [JSON.stringify(currentValue), trackWidth, isRange, valueToPx]);
143
147
  const onTrackLayout = useCallback(e => {
144
148
  const w = e.nativeEvent.layout.width;
149
+ trackW.value = w;
145
150
  setTrackWidth(w);
146
- // Initialize positions immediately.
147
- if (isRange) {
148
- const [v0, v1] = currentValue;
149
- const lo = Math.min(v0, v1);
150
- const hi = Math.max(v0, v1);
151
- setNativeValue(lowX, valueToPx(lo, w));
152
- setNativeValue(highX, valueToPx(hi, w));
153
- } else {
154
- setNativeValue(lowX, valueToPx(currentValue, w));
155
- }
156
- },
157
- // eslint-disable-next-line react-hooks/exhaustive-deps
158
- [isRange, valueToPx]);
151
+ }, [trackW]);
159
152
  const fireChange = useCallback((lo, hi) => {
160
153
  setCurrentValue(isRange ? [lo, hi] : lo);
161
154
  }, [isRange, setCurrentValue]);
162
- const animateThumbPress = useCallback((target, opacity, pressed) => {
163
- if (thumbPressAnimationEnabled) {
164
- Animated.spring(target, {
165
- toValue: pressed ? resolvedPressScale : 1,
166
- damping: theme.motion.spring.snappy.damping,
167
- stiffness: theme.motion.spring.snappy.stiffness,
168
- mass: theme.motion.spring.snappy.mass,
169
- useNativeDriver: true
170
- }).start();
171
- }
172
- if (showLabel) {
173
- Animated.timing(opacity, {
174
- toValue: pressed ? resolvedLabelOpacity : 0,
175
- duration: resolvedLabelDuration,
176
- easing: Easing.out(Easing.cubic),
177
- useNativeDriver: true
178
- }).start();
179
- }
180
- }, [showLabel, theme.motion, thumbPressAnimationEnabled, resolvedPressScale, resolvedLabelDuration, resolvedLabelOpacity]);
155
+ const fireHaptic = useCallback(() => {
156
+ const h = resolveHaptic(haptic, 'selection');
157
+ if (h) triggerHaptic(h);
158
+ }, [haptic]);
181
159
 
182
- // Build PanResponder for a thumb.
183
- const buildResponder = useCallback(which => {
184
- return PanResponder.create({
185
- onStartShouldSetPanResponder: () => !disabled,
186
- onMoveShouldSetPanResponder: () => !disabled,
187
- onPanResponderGrant: () => {
188
- dragOffsetRef.current = which === 'low' ? valueToPx(lowRef.current, trackWidth) : valueToPx(highRef.current, trackWidth);
189
- animateThumbPress(which === 'low' ? lowScale : highScale, which === 'low' ? lowLabelOpacity : highLabelOpacity, true);
190
- if (sliderTheme?.dragHaptic ?? false) triggerHaptic('selection');
191
- },
192
- onPanResponderMove: (_evt, gesture) => {
193
- if (trackWidth <= 0) return;
194
- const nextPx = clamp(dragOffsetRef.current + gesture.dx, 0, trackWidth);
195
- let nextValue = pxToValue(nextPx, trackWidth);
160
+ // ───────── Pan gesture per thumb (UI thread) ─────────
161
+ const buildPan = useCallback(which => {
162
+ const xSV = which === 'low' ? lowX : highX;
163
+ const valSV = which === 'low' ? lowVal : highVal;
164
+ const otherSV = which === 'low' ? highVal : lowVal;
165
+ const scaleSV = which === 'low' ? lowScale : highScale;
166
+ const labelSV = which === 'low' ? lowLabelOpacity : highLabelOpacity;
167
+ const isLow = which === 'low';
168
+ const _min = min;
169
+ const _max = max;
170
+ const _step = step;
171
+ const _precision = precision;
172
+ const _range = isRange;
173
+ const _pressScale = resolvedPressScale;
174
+ const _pressAnim = thumbPressAnimationEnabled;
175
+ const _showLabel = showLabel;
176
+ const _labelOpacity = resolvedLabelOpacity;
177
+ const _labelDur = resolvedLabelDuration;
178
+ const _spring = springCfg;
179
+ return Gesture.Pan().enabled(!disabled).minDistance(0).onStart(() => {
180
+ 'worklet';
196
181
 
197
- // Only touch the native driver + fire onChange when the snapped value
198
- // actually changes between step boundaries the drag is a visual
199
- // no-op (px is derived from the snapped value), so this drops the
200
- // per-frame native round-trip and per-frame consumer re-render.
201
- if (which === 'low') {
202
- if (isRange && nextValue > highRef.current) nextValue = highRef.current;
203
- if (nextValue !== lastReportedLow.current) {
204
- if (sliderTheme?.stepHaptic ?? false) triggerHaptic('selection');
205
- lastReportedLow.current = nextValue;
206
- lowRef.current = nextValue;
207
- setNativeValue(lowX, valueToPx(nextValue, trackWidth));
208
- fireChange(nextValue, highRef.current);
209
- }
210
- } else {
211
- if (isRange && nextValue < lowRef.current) nextValue = lowRef.current;
212
- if (nextValue !== lastReportedHigh.current) {
213
- if (sliderTheme?.stepHaptic ?? false) triggerHaptic('selection');
214
- lastReportedHigh.current = nextValue;
215
- highRef.current = nextValue;
216
- setNativeValue(highX, valueToPx(nextValue, trackWidth));
217
- fireChange(lowRef.current, nextValue);
218
- }
219
- }
220
- },
221
- onPanResponderRelease: () => {
222
- animateThumbPress(which === 'low' ? lowScale : highScale, which === 'low' ? lowLabelOpacity : highLabelOpacity, false);
223
- },
224
- onPanResponderTerminate: () => {
225
- animateThumbPress(which === 'low' ? lowScale : highScale, which === 'low' ? lowLabelOpacity : highLabelOpacity, false);
182
+ dragStartX.value = xSV.value;
183
+ if (_pressAnim) scaleSV.value = withSpring(_pressScale, _spring);
184
+ if (_showLabel) {
185
+ labelSV.value = withTiming(_labelOpacity, {
186
+ duration: _labelDur,
187
+ easing: Easing.out(Easing.cubic)
188
+ });
226
189
  }
227
- });
228
- }, [disabled, trackWidth, isRange, valueToPx, pxToValue, fireChange, animateThumbPress, lowX, highX, lowScale, highScale, lowLabelOpacity, highLabelOpacity]);
190
+ runOnJS(fireHaptic)();
191
+ }).onUpdate(event => {
192
+ 'worklet';
193
+
194
+ if (trackW.value <= 0) return;
195
+ const px = Math.min(Math.max(dragStartX.value + event.translationX, 0), trackW.value);
196
+ const ratio = px / trackW.value;
197
+ let v = _min + ratio * (_max - _min);
198
+ if (_step > 0) {
199
+ v = Math.round((v - _min) / _step) * _step + _min;
200
+ v = Math.round(v * _precision) / _precision;
201
+ }
202
+ v = Math.min(Math.max(v, _min), _max);
203
+ if (_range) {
204
+ if (isLow && v > otherSV.value) v = otherSV.value;
205
+ if (!isLow && v < otherSV.value) v = otherSV.value;
206
+ }
207
+ // Thumb sits at the snapped value's position.
208
+ xSV.value = _max === _min ? 0 : (v - _min) / (_max - _min) * trackW.value;
209
+ if (v !== valSV.value) {
210
+ valSV.value = v;
211
+ runOnJS(fireHaptic)();
212
+ runOnJS(fireChange)(lowVal.value, highVal.value);
213
+ }
214
+ }).onEnd(() => {
215
+ 'worklet';
229
216
 
230
- // Re-create responders when deps change so closures stay current.
231
- const lowResponder = useMemo(() => buildResponder('low'), [buildResponder]);
232
- const highResponder = useMemo(() => buildResponder('high'), [buildResponder]);
217
+ if (_pressAnim) scaleSV.value = withSpring(1, _spring);
218
+ if (_showLabel) {
219
+ labelSV.value = withTiming(0, {
220
+ duration: _labelDur,
221
+ easing: Easing.out(Easing.cubic)
222
+ });
223
+ }
224
+ });
225
+ }, [disabled, min, max, step, precision, isRange, resolvedPressScale, thumbPressAnimationEnabled, showLabel, resolvedLabelOpacity, resolvedLabelDuration, springCfg, fireChange, fireHaptic, lowX, highX, lowVal, highVal, lowScale, highScale, lowLabelOpacity, highLabelOpacity, dragStartX, trackW]);
226
+ const lowPan = useMemo(() => buildPan('low'), [buildPan]);
227
+ const highPan = useMemo(() => buildPan('high'), [buildPan]);
233
228
 
234
229
  // Tap on track jumps to position (single mode only).
235
230
  const handleTrackPress = useCallback(e => {
236
231
  if (disabled || isRange || trackWidth <= 0) return;
237
232
  const px = clamp(e.nativeEvent.locationX, 0, trackWidth);
238
233
  const next = pxToValue(px, trackWidth);
239
- if (next !== lowRef.current) {
240
- if (sliderTheme?.tapHaptic ?? false) triggerHaptic('selection');
241
- lowRef.current = next;
242
- lastReportedLow.current = next;
243
- Animated.timing(lowX, {
244
- toValue: valueToPx(next, trackWidth),
234
+ if (next !== lowVal.value) {
235
+ fireHaptic();
236
+ lowVal.value = next;
237
+ lowX.value = withTiming(valueToPx(next, trackWidth), {
245
238
  duration: theme.motion.duration.fast,
246
- easing: Easing.out(Easing.cubic),
247
- useNativeDriver: true
248
- }).start();
239
+ easing: Easing.out(Easing.cubic)
240
+ });
249
241
  fireChange(next, next);
250
242
  }
251
- }, [disabled, isRange, trackWidth, pxToValue, valueToPx, fireChange, lowX, theme.motion.duration.fast]);
243
+ }, [disabled, isRange, trackWidth, pxToValue, valueToPx, fireChange, fireHaptic, lowX, lowVal, theme.motion.duration.fast]);
252
244
 
253
- // Accessibility actions: increment / decrement
245
+ // Accessibility actions: increment / decrement.
254
246
  const handleAccessibilityAction = useCallback(event => {
255
247
  if (disabled) return;
256
248
  const delta = event.nativeEvent.actionName === 'increment' ? step : -step;
257
249
  if (isRange) {
258
- const nextLow = clamp(lowRef.current + delta, min, highRef.current);
259
- if (nextLow !== lowRef.current) {
260
- lowRef.current = nextLow;
261
- lastReportedLow.current = nextLow;
262
- setNativeValue(lowX, valueToPx(nextLow, trackWidth));
263
- if (sliderTheme?.a11yHaptic ?? false) triggerHaptic('selection');
264
- fireChange(nextLow, highRef.current);
250
+ const nextLow = clamp(lowVal.value + delta, min, highVal.value);
251
+ if (nextLow !== lowVal.value) {
252
+ lowVal.value = nextLow;
253
+ lowX.value = valueToPx(nextLow, trackWidth);
254
+ fireHaptic();
255
+ fireChange(nextLow, highVal.value);
265
256
  }
266
257
  } else {
267
- const next = clamp(lowRef.current + delta, min, max);
268
- if (next !== lowRef.current) {
269
- lowRef.current = next;
270
- lastReportedLow.current = next;
271
- setNativeValue(lowX, valueToPx(next, trackWidth));
272
- if (sliderTheme?.a11yHaptic ?? false) triggerHaptic('selection');
258
+ const next = clamp(lowVal.value + delta, min, max);
259
+ if (next !== lowVal.value) {
260
+ lowVal.value = next;
261
+ lowX.value = valueToPx(next, trackWidth);
262
+ fireHaptic();
273
263
  fireChange(next, next);
274
264
  }
275
265
  }
276
- }, [disabled, step, isRange, min, max, valueToPx, trackWidth, lowX, fireChange, sliderTheme]);
266
+ }, [disabled, step, isRange, min, max, valueToPx, trackWidth, lowX, lowVal, highVal, fireChange, fireHaptic]);
277
267
  const thumbRadius = sizeCfg.thumbDiameter / 2;
278
268
 
279
- // Stable Animated nodes for the fill bar and thumb offsets — created once and
280
- // memoised so a render doesn't rebuild `new Animated.Value(...)` /
281
- // Animated.subtract nodes and re-attach the animation graph every pass.
282
- const zeroX = useRef(createAnimatedValue(0)).current;
283
- const thumbRadiusX = useRef(createAnimatedValue(thumbRadius)).current;
284
- useEffect(() => {
285
- setNativeValue(thumbRadiusX, thumbRadius);
286
- }, [thumbRadius, thumbRadiusX]);
287
- const fillLeft = isRange ? lowX : zeroX;
288
- const fillWidth = useMemo(() => isRange ? Animated.subtract(highX, lowX) : lowX, [isRange, highX, lowX]);
289
- const lowTranslateX = useMemo(() => Animated.subtract(lowX, thumbRadiusX), [lowX, thumbRadiusX]);
290
- const highTranslateX = useMemo(() => Animated.subtract(highX, thumbRadiusX), [highX, thumbRadiusX]);
269
+ // ───────── Animated styles ─────────
270
+ const fillAnimStyle = useAnimatedStyle(() => isRange ? {
271
+ left: lowX.value,
272
+ width: Math.max(0, highX.value - lowX.value)
273
+ } : {
274
+ left: 0,
275
+ width: lowX.value
276
+ });
277
+ const lowThumbStyle = useAnimatedStyle(() => ({
278
+ transform: [{
279
+ translateX: lowX.value - thumbRadius
280
+ }, {
281
+ scale: lowScale.value
282
+ }]
283
+ }));
284
+ const highThumbStyle = useAnimatedStyle(() => ({
285
+ transform: [{
286
+ translateX: highX.value - thumbRadius
287
+ }, {
288
+ scale: highScale.value
289
+ }]
290
+ }));
291
+ const lowLabelStyle = useAnimatedStyle(() => ({
292
+ opacity: lowLabelOpacity.value
293
+ }));
294
+ const highLabelStyle = useAnimatedStyle(() => ({
295
+ opacity: highLabelOpacity.value
296
+ }));
291
297
 
292
- // Display values for labels.
293
- const lowDisplayValue = isRange ? lowRef.current : lowRef.current;
294
- const highDisplayValue = highRef.current;
298
+ // Display values (from committed state) for labels + a11y.
299
+ const lowDisplayValue = isRange ? Math.min(...currentValue) : currentValue;
300
+ const highDisplayValue = isRange ? Math.max(...currentValue) : currentValue;
295
301
  return /*#__PURE__*/_jsx(View, {
296
302
  ref: ref,
297
303
  style: [styles.container, containerStyle, style],
@@ -301,10 +307,15 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
301
307
  accessibilityState: {
302
308
  disabled
303
309
  },
304
- accessibilityValue: {
310
+ accessibilityValue: isRange ? {
305
311
  min,
306
312
  max,
307
- now: isRange ? lowDisplayValue : lowDisplayValue
313
+ now: lowDisplayValue,
314
+ text: `${lowDisplayValue} to ${highDisplayValue}`
315
+ } : {
316
+ min,
317
+ max,
318
+ now: lowDisplayValue
308
319
  },
309
320
  accessibilityActions: [{
310
321
  name: 'increment'
@@ -327,67 +338,57 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
327
338
  }, trackStyle]
328
339
  }), /*#__PURE__*/_jsx(Animated.View, {
329
340
  style: [styles.fill, {
330
- left: fillLeft,
331
- width: fillWidth,
332
341
  backgroundColor: disabled ? theme.colors.surface.disabled : fillColor,
333
- opacity: disabled ? 0.6 : 1
334
- }, fillStyle]
335
- }), /*#__PURE__*/_jsx(Animated.View, {
336
- ...lowResponder.panHandlers,
337
- style: [styles.thumb, {
338
- width: sizeCfg.thumbDiameter,
339
- height: sizeCfg.thumbDiameter,
340
- borderRadius: thumbRadius,
341
- backgroundColor: theme.colors.background.elevated,
342
- borderColor: disabled ? theme.colors.surface.disabled : fillColor,
343
- transform: [{
344
- translateX: lowTranslateX
345
- }, {
346
- scale: lowScale
347
- }],
348
- opacity: disabled ? 0.6 : 1
349
- }, thumbStyle],
350
- accessibilityElementsHidden: true,
351
- importantForAccessibility: "no-hide-descendants",
352
- children: showLabel ? /*#__PURE__*/_jsx(Animated.View, {
353
- style: [styles.bubble, {
354
- opacity: lowLabelOpacity,
355
- backgroundColor: fillColor
356
- }],
357
- pointerEvents: "none",
358
- children: /*#__PURE__*/_jsx(Text, {
359
- style: [styles.bubbleText, labelStyle],
360
- children: formatLabel(lowDisplayValue)
361
- })
362
- }) : null
363
- }), isRange ? /*#__PURE__*/_jsx(Animated.View, {
364
- ...highResponder.panHandlers,
365
- style: [styles.thumb, {
366
- width: sizeCfg.thumbDiameter,
367
- height: sizeCfg.thumbDiameter,
368
- borderRadius: thumbRadius,
369
- backgroundColor: theme.colors.background.elevated,
370
- borderColor: disabled ? theme.colors.surface.disabled : fillColor,
371
- transform: [{
372
- translateX: highTranslateX
373
- }, {
374
- scale: highScale
375
- }],
376
- opacity: disabled ? 0.6 : 1
377
- }, thumbStyle],
378
- accessibilityElementsHidden: true,
379
- importantForAccessibility: "no-hide-descendants",
380
- children: showLabel ? /*#__PURE__*/_jsx(Animated.View, {
381
- style: [styles.bubble, {
382
- opacity: highLabelOpacity,
383
- backgroundColor: fillColor
384
- }],
385
- pointerEvents: "none",
386
- children: /*#__PURE__*/_jsx(Text, {
387
- style: [styles.bubbleText, labelStyle],
388
- children: formatLabel(highDisplayValue)
389
- })
390
- }) : null
342
+ opacity: disabled ? 0.55 : 1
343
+ }, fillStyle, fillAnimStyle]
344
+ }), /*#__PURE__*/_jsx(GestureDetector, {
345
+ gesture: lowPan,
346
+ children: /*#__PURE__*/_jsx(Animated.View, {
347
+ style: [styles.thumb, {
348
+ width: sizeCfg.thumbDiameter,
349
+ height: sizeCfg.thumbDiameter,
350
+ borderRadius: thumbRadius,
351
+ backgroundColor: theme.colors.background.elevated,
352
+ borderColor: disabled ? theme.colors.surface.disabled : fillColor,
353
+ opacity: disabled ? 0.55 : 1
354
+ }, thumbStyle, lowThumbStyle],
355
+ accessibilityElementsHidden: true,
356
+ importantForAccessibility: "no-hide-descendants",
357
+ children: showLabel ? /*#__PURE__*/_jsx(Animated.View, {
358
+ style: [styles.bubble, {
359
+ backgroundColor: fillColor
360
+ }, lowLabelStyle],
361
+ pointerEvents: "none",
362
+ children: /*#__PURE__*/_jsx(Text, {
363
+ style: [styles.bubbleText, labelStyle],
364
+ children: formatLabel(lowDisplayValue)
365
+ })
366
+ }) : null
367
+ })
368
+ }), isRange ? /*#__PURE__*/_jsx(GestureDetector, {
369
+ gesture: highPan,
370
+ children: /*#__PURE__*/_jsx(Animated.View, {
371
+ style: [styles.thumb, {
372
+ width: sizeCfg.thumbDiameter,
373
+ height: sizeCfg.thumbDiameter,
374
+ borderRadius: thumbRadius,
375
+ backgroundColor: theme.colors.background.elevated,
376
+ borderColor: disabled ? theme.colors.surface.disabled : fillColor,
377
+ opacity: disabled ? 0.55 : 1
378
+ }, thumbStyle, highThumbStyle],
379
+ accessibilityElementsHidden: true,
380
+ importantForAccessibility: "no-hide-descendants",
381
+ children: showLabel ? /*#__PURE__*/_jsx(Animated.View, {
382
+ style: [styles.bubble, {
383
+ backgroundColor: fillColor
384
+ }, highLabelStyle],
385
+ pointerEvents: "none",
386
+ children: /*#__PURE__*/_jsx(Text, {
387
+ style: [styles.bubbleText, labelStyle],
388
+ children: formatLabel(highDisplayValue)
389
+ })
390
+ }) : null
391
+ })
391
392
  }) : null]
392
393
  })
393
394
  })
@@ -448,7 +449,6 @@ const buildStyles = (theme, sizeCfg) => {
448
449
  minWidth: 36,
449
450
  alignItems: 'center',
450
451
  justifyContent: 'center',
451
- // A subtle shadow for elevation.
452
452
  ...Platform.select({
453
453
  ios: {
454
454
  shadowColor: theme.shadows.sm.shadowColor,
@@ -3,7 +3,7 @@
3
3
  import React, { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { Animated, Easing, Pressable, StyleSheet, Text, View } from 'react-native';
5
5
  import { useTheme, createAnimatedValue } from "../../theme/index.js";
6
- import { triggerHaptic } from "../../utils/hapticUtils.js";
6
+ import { triggerHaptic, resolveHaptic } from "../../utils/hapticUtils.js";
7
7
  import { Skeleton } from "../Skeleton/index.js";
8
8
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
9
  const CIRCLE_SIZE = 24;
@@ -34,7 +34,7 @@ const StepCircle = ({
34
34
  pulseSize,
35
35
  pulseEnabled,
36
36
  pulseDuration,
37
- pressHapticEnabled,
37
+ pressHaptic,
38
38
  circleSlotStyle
39
39
  }) => {
40
40
  const isActive = status === 'active';
@@ -81,7 +81,7 @@ const StepCircle = ({
81
81
  const canPress = interactive && status === 'complete';
82
82
  const handlePress = () => {
83
83
  if (!canPress) return;
84
- if (pressHapticEnabled) triggerHaptic('selection');
84
+ if (pressHaptic) triggerHaptic(pressHaptic);
85
85
  onPress?.(index);
86
86
  };
87
87
  const inner = /*#__PURE__*/_jsxs(View, {
@@ -176,6 +176,7 @@ const Stepper = /*#__PURE__*/forwardRef((props, ref) => {
176
176
  circleStyle,
177
177
  labelStyle,
178
178
  connectorStyle,
179
+ haptic,
179
180
  testID
180
181
  } = props;
181
182
  const theme = useTheme();
@@ -184,7 +185,7 @@ const Stepper = /*#__PURE__*/forwardRef((props, ref) => {
184
185
  const pulseSize = stepperTheme?.pulseSize ?? PULSE_SIZE;
185
186
  const pulseEnabled = stepperTheme?.pulseAnimation ?? false;
186
187
  const pulseDuration = stepperTheme?.pulseDuration ?? PULSE_DURATION;
187
- const pressHapticEnabled = stepperTheme?.pressHaptic ?? false;
188
+ const pressHaptic = resolveHaptic(haptic, 'selection');
188
189
  const styles = useMemo(() => buildStyles(theme), [theme]);
189
190
  const toneColor = toneColorFor(theme, tone);
190
191
  const upcomingColor = theme.colors.border.primary;
@@ -265,7 +266,7 @@ const Stepper = /*#__PURE__*/forwardRef((props, ref) => {
265
266
  pulseSize: pulseSize,
266
267
  pulseEnabled: pulseEnabled,
267
268
  pulseDuration: pulseDuration,
268
- pressHapticEnabled: pressHapticEnabled,
269
+ pressHaptic: pressHaptic,
269
270
  circleSlotStyle: circleStyle
270
271
  }), /*#__PURE__*/_jsx(Text, {
271
272
  style: [styles.hLabel, {
@@ -304,7 +305,7 @@ const Stepper = /*#__PURE__*/forwardRef((props, ref) => {
304
305
  pulseSize: pulseSize,
305
306
  pulseEnabled: pulseEnabled,
306
307
  pulseDuration: pulseDuration,
307
- pressHapticEnabled: pressHapticEnabled,
308
+ pressHaptic: pressHaptic,
308
309
  circleSlotStyle: circleStyle
309
310
  }), !isLast ? /*#__PURE__*/_jsxs(View, {
310
311
  style: [styles.vConnectorContainer, connectorStyle],