@webority-technologies/mobile 0.0.22 → 0.0.24

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 (249) hide show
  1. package/lib/commonjs/components/Accordion/Accordion.js +9 -7
  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/Avatar/Avatar.js +4 -2
  6. package/lib/commonjs/components/Badge/Badge.js +5 -5
  7. package/lib/commonjs/components/Banner/Banner.js +20 -6
  8. package/lib/commonjs/components/BottomNavigation/BottomNavigation.js +6 -4
  9. package/lib/commonjs/components/BottomSheet/BottomSheet.js +8 -9
  10. package/lib/commonjs/components/Box/Box.js +162 -0
  11. package/lib/commonjs/components/Box/index.js +37 -0
  12. package/lib/commonjs/components/Button/Button.js +7 -7
  13. package/lib/commonjs/components/Card/Card.js +3 -3
  14. package/lib/commonjs/components/Carousel/Carousel.js +4 -2
  15. package/lib/commonjs/components/Checkbox/Checkbox.js +17 -7
  16. package/lib/commonjs/components/Chip/Chip.js +4 -2
  17. package/lib/commonjs/components/DatePicker/DatePicker.js +31 -24
  18. package/lib/commonjs/components/DateRangePicker/DateRangePicker.js +16 -11
  19. package/lib/commonjs/components/Dialog/Dialog.js +6 -4
  20. package/lib/commonjs/components/Drawer/Drawer.js +4 -2
  21. package/lib/commonjs/components/FieldBase/FieldBase.js +8 -4
  22. package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +23 -13
  23. package/lib/commonjs/components/FormField/FormField.js +61 -25
  24. package/lib/commonjs/components/ImageGallery/ImageGallery.js +17 -15
  25. package/lib/commonjs/components/Input/Input.js +41 -29
  26. package/lib/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +102 -0
  27. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +13 -0
  28. package/lib/commonjs/components/KeyboardToolbar/KeyboardToolbar.js +130 -0
  29. package/lib/commonjs/components/KeyboardToolbar/index.js +13 -0
  30. package/lib/commonjs/components/ListItem/ListItem.js +4 -3
  31. package/lib/commonjs/components/Modal/Modal.js +21 -9
  32. package/lib/commonjs/components/NumberInput/NumberInput.js +38 -29
  33. package/lib/commonjs/components/OTPInput/OTPInput.js +37 -22
  34. package/lib/commonjs/components/Radio/Radio.js +9 -8
  35. package/lib/commonjs/components/Radio/RadioGroup.js +10 -3
  36. package/lib/commonjs/components/Rating/Rating.js +4 -3
  37. package/lib/commonjs/components/SearchBar/SearchBar.js +11 -6
  38. package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +23 -12
  39. package/lib/commonjs/components/Select/Select.js +40 -36
  40. package/lib/commonjs/components/Skeleton/SkeletonContent.js +5 -2
  41. package/lib/commonjs/components/Slider/Slider.js +241 -225
  42. package/lib/commonjs/components/Spinner/Spinner.js +5 -5
  43. package/lib/commonjs/components/Stepper/Stepper.js +6 -5
  44. package/lib/commonjs/components/Swipeable/Swipeable.js +8 -9
  45. package/lib/commonjs/components/Switch/Switch.js +29 -16
  46. package/lib/commonjs/components/Tabs/Tabs.js +8 -5
  47. package/lib/commonjs/components/Text/Text.js +142 -0
  48. package/lib/commonjs/components/Text/index.js +13 -0
  49. package/lib/commonjs/components/TimePicker/TimePicker.js +23 -15
  50. package/lib/commonjs/components/Toast/Toast.js +22 -10
  51. package/lib/commonjs/components/Tooltip/Tooltip.js +6 -2
  52. package/lib/commonjs/components/index.js +156 -103
  53. package/lib/commonjs/form/FormContext.js +40 -0
  54. package/lib/commonjs/form/index.js +68 -0
  55. package/lib/commonjs/form/path.js +79 -0
  56. package/lib/commonjs/form/rules.js +67 -0
  57. package/lib/commonjs/form/types.js +2 -0
  58. package/lib/commonjs/form/useField.js +54 -0
  59. package/lib/commonjs/form/useForm.js +316 -0
  60. package/lib/commonjs/hooks/index.js +14 -0
  61. package/lib/commonjs/hooks/useControllableState.js +30 -0
  62. package/lib/commonjs/hooks/useReducedMotion.js +31 -0
  63. package/lib/commonjs/index.js +96 -11
  64. package/lib/commonjs/theme/ThemeContext.js +30 -2
  65. package/lib/commonjs/theme/tokens.js +12 -0
  66. package/lib/commonjs/utils/hapticUtils.js +11 -1
  67. package/lib/commonjs/utils/index.js +6 -0
  68. package/lib/module/components/Accordion/Accordion.js +10 -8
  69. package/lib/module/components/AnimatePresence/AnimatePresence.js +63 -0
  70. package/lib/module/components/AnimatePresence/index.js +4 -0
  71. package/lib/module/components/AppBar/AppBar.js +10 -7
  72. package/lib/module/components/Avatar/Avatar.js +4 -2
  73. package/lib/module/components/Badge/Badge.js +5 -5
  74. package/lib/module/components/Banner/Banner.js +20 -6
  75. package/lib/module/components/BottomNavigation/BottomNavigation.js +6 -4
  76. package/lib/module/components/BottomSheet/BottomSheet.js +8 -9
  77. package/lib/module/components/Box/Box.js +156 -0
  78. package/lib/module/components/Box/index.js +4 -0
  79. package/lib/module/components/Button/Button.js +7 -7
  80. package/lib/module/components/Card/Card.js +4 -4
  81. package/lib/module/components/Carousel/Carousel.js +4 -2
  82. package/lib/module/components/Checkbox/Checkbox.js +18 -8
  83. package/lib/module/components/Chip/Chip.js +5 -3
  84. package/lib/module/components/DatePicker/DatePicker.js +32 -25
  85. package/lib/module/components/DateRangePicker/DateRangePicker.js +17 -12
  86. package/lib/module/components/Dialog/Dialog.js +7 -5
  87. package/lib/module/components/Drawer/Drawer.js +5 -3
  88. package/lib/module/components/FieldBase/FieldBase.js +8 -4
  89. package/lib/module/components/FloatingActionButton/FloatingActionButton.js +24 -14
  90. package/lib/module/components/FormField/FormField.js +62 -26
  91. package/lib/module/components/ImageGallery/ImageGallery.js +18 -16
  92. package/lib/module/components/Input/Input.js +41 -29
  93. package/lib/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +98 -0
  94. package/lib/module/components/KeyboardAwareScrollView/index.js +4 -0
  95. package/lib/module/components/KeyboardToolbar/KeyboardToolbar.js +125 -0
  96. package/lib/module/components/KeyboardToolbar/index.js +4 -0
  97. package/lib/module/components/ListItem/ListItem.js +5 -4
  98. package/lib/module/components/Modal/Modal.js +22 -10
  99. package/lib/module/components/NumberInput/NumberInput.js +36 -27
  100. package/lib/module/components/OTPInput/OTPInput.js +37 -22
  101. package/lib/module/components/Radio/Radio.js +10 -9
  102. package/lib/module/components/Radio/RadioGroup.js +10 -3
  103. package/lib/module/components/Rating/Rating.js +5 -4
  104. package/lib/module/components/SearchBar/SearchBar.js +12 -7
  105. package/lib/module/components/SegmentedControl/SegmentedControl.js +24 -13
  106. package/lib/module/components/Select/Select.js +41 -37
  107. package/lib/module/components/Skeleton/SkeletonContent.js +5 -2
  108. package/lib/module/components/Slider/Slider.js +244 -228
  109. package/lib/module/components/Spinner/Spinner.js +5 -5
  110. package/lib/module/components/Stepper/Stepper.js +7 -6
  111. package/lib/module/components/Swipeable/Swipeable.js +9 -10
  112. package/lib/module/components/Switch/Switch.js +29 -16
  113. package/lib/module/components/Tabs/Tabs.js +9 -6
  114. package/lib/module/components/Text/Text.js +138 -0
  115. package/lib/module/components/Text/index.js +4 -0
  116. package/lib/module/components/TimePicker/TimePicker.js +24 -16
  117. package/lib/module/components/Toast/Toast.js +22 -10
  118. package/lib/module/components/Tooltip/Tooltip.js +6 -2
  119. package/lib/module/components/index.js +5 -0
  120. package/lib/module/form/FormContext.js +32 -0
  121. package/lib/module/form/index.js +12 -0
  122. package/lib/module/form/path.js +72 -0
  123. package/lib/module/form/rules.js +52 -0
  124. package/lib/module/form/types.js +2 -0
  125. package/lib/module/form/useField.js +49 -0
  126. package/lib/module/form/useForm.js +312 -0
  127. package/lib/module/hooks/index.js +2 -0
  128. package/lib/module/hooks/useControllableState.js +26 -0
  129. package/lib/module/hooks/useReducedMotion.js +27 -0
  130. package/lib/module/index.js +3 -1
  131. package/lib/module/theme/ThemeContext.js +30 -2
  132. package/lib/module/theme/tokens.js +12 -0
  133. package/lib/module/utils/hapticUtils.js +9 -0
  134. package/lib/module/utils/index.js +1 -1
  135. package/lib/typescript/commonjs/components/Accordion/Accordion.d.ts +3 -0
  136. package/lib/typescript/commonjs/components/AnimatePresence/AnimatePresence.d.ts +30 -0
  137. package/lib/typescript/commonjs/components/AnimatePresence/index.d.ts +3 -0
  138. package/lib/typescript/commonjs/components/AppBar/AppBar.d.ts +6 -0
  139. package/lib/typescript/commonjs/components/Banner/Banner.d.ts +3 -0
  140. package/lib/typescript/commonjs/components/BottomNavigation/BottomNavigation.d.ts +1 -1
  141. package/lib/typescript/commonjs/components/Box/Box.d.ts +60 -0
  142. package/lib/typescript/commonjs/components/Box/index.d.ts +3 -0
  143. package/lib/typescript/commonjs/components/Button/Button.d.ts +1 -1
  144. package/lib/typescript/commonjs/components/Card/Card.d.ts +3 -0
  145. package/lib/typescript/commonjs/components/Checkbox/Checkbox.d.ts +4 -2
  146. package/lib/typescript/commonjs/components/Chip/Chip.d.ts +3 -0
  147. package/lib/typescript/commonjs/components/DatePicker/DatePicker.d.ts +6 -3
  148. package/lib/typescript/commonjs/components/DateRangePicker/DateRangePicker.d.ts +6 -0
  149. package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +5 -2
  150. package/lib/typescript/commonjs/components/Drawer/Drawer.d.ts +3 -0
  151. package/lib/typescript/commonjs/components/FloatingActionButton/FloatingActionButton.d.ts +5 -0
  152. package/lib/typescript/commonjs/components/FormField/FormField.d.ts +13 -2
  153. package/lib/typescript/commonjs/components/ImageGallery/ImageGallery.d.ts +6 -0
  154. package/lib/typescript/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +20 -0
  155. package/lib/typescript/commonjs/components/KeyboardAwareScrollView/index.d.ts +3 -0
  156. package/lib/typescript/commonjs/components/KeyboardToolbar/KeyboardToolbar.d.ts +29 -0
  157. package/lib/typescript/commonjs/components/KeyboardToolbar/index.d.ts +3 -0
  158. package/lib/typescript/commonjs/components/ListItem/ListItem.d.ts +3 -0
  159. package/lib/typescript/commonjs/components/Modal/Modal.d.ts +6 -0
  160. package/lib/typescript/commonjs/components/NumberInput/NumberInput.d.ts +6 -2
  161. package/lib/typescript/commonjs/components/OTPInput/OTPInput.d.ts +9 -2
  162. package/lib/typescript/commonjs/components/Radio/Radio.d.ts +2 -2
  163. package/lib/typescript/commonjs/components/Radio/RadioGroup.d.ts +3 -2
  164. package/lib/typescript/commonjs/components/Rating/Rating.d.ts +6 -0
  165. package/lib/typescript/commonjs/components/SearchBar/SearchBar.d.ts +3 -0
  166. package/lib/typescript/commonjs/components/SegmentedControl/SegmentedControl.d.ts +6 -2
  167. package/lib/typescript/commonjs/components/Select/Select.d.ts +6 -0
  168. package/lib/typescript/commonjs/components/Slider/Slider.d.ts +9 -4
  169. package/lib/typescript/commonjs/components/Spinner/Spinner.d.ts +1 -1
  170. package/lib/typescript/commonjs/components/Stepper/Stepper.d.ts +6 -0
  171. package/lib/typescript/commonjs/components/Swipeable/Swipeable.d.ts +3 -0
  172. package/lib/typescript/commonjs/components/Switch/Switch.d.ts +3 -2
  173. package/lib/typescript/commonjs/components/Tabs/Tabs.d.ts +3 -0
  174. package/lib/typescript/commonjs/components/Text/Text.d.ts +25 -0
  175. package/lib/typescript/commonjs/components/Text/index.d.ts +3 -0
  176. package/lib/typescript/commonjs/components/TimePicker/TimePicker.d.ts +6 -3
  177. package/lib/typescript/commonjs/components/index.d.ts +10 -0
  178. package/lib/typescript/commonjs/form/FormContext.d.ts +17 -0
  179. package/lib/typescript/commonjs/form/index.d.ts +9 -0
  180. package/lib/typescript/commonjs/form/path.d.ts +10 -0
  181. package/lib/typescript/commonjs/form/rules.d.ts +31 -0
  182. package/lib/typescript/commonjs/form/types.d.ts +94 -0
  183. package/lib/typescript/commonjs/form/useField.d.ts +27 -0
  184. package/lib/typescript/commonjs/form/useForm.d.ts +10 -0
  185. package/lib/typescript/commonjs/hooks/index.d.ts +3 -0
  186. package/lib/typescript/commonjs/hooks/useControllableState.d.ts +17 -0
  187. package/lib/typescript/commonjs/hooks/useReducedMotion.d.ts +8 -0
  188. package/lib/typescript/commonjs/index.d.ts +4 -2
  189. package/lib/typescript/commonjs/theme/types.d.ts +17 -67
  190. package/lib/typescript/commonjs/utils/hapticUtils.d.ts +8 -0
  191. package/lib/typescript/commonjs/utils/index.d.ts +1 -1
  192. package/lib/typescript/module/components/Accordion/Accordion.d.ts +3 -0
  193. package/lib/typescript/module/components/AnimatePresence/AnimatePresence.d.ts +30 -0
  194. package/lib/typescript/module/components/AnimatePresence/index.d.ts +3 -0
  195. package/lib/typescript/module/components/AppBar/AppBar.d.ts +6 -0
  196. package/lib/typescript/module/components/Banner/Banner.d.ts +3 -0
  197. package/lib/typescript/module/components/BottomNavigation/BottomNavigation.d.ts +1 -1
  198. package/lib/typescript/module/components/Box/Box.d.ts +60 -0
  199. package/lib/typescript/module/components/Box/index.d.ts +3 -0
  200. package/lib/typescript/module/components/Button/Button.d.ts +1 -1
  201. package/lib/typescript/module/components/Card/Card.d.ts +3 -0
  202. package/lib/typescript/module/components/Checkbox/Checkbox.d.ts +4 -2
  203. package/lib/typescript/module/components/Chip/Chip.d.ts +3 -0
  204. package/lib/typescript/module/components/DatePicker/DatePicker.d.ts +6 -3
  205. package/lib/typescript/module/components/DateRangePicker/DateRangePicker.d.ts +6 -0
  206. package/lib/typescript/module/components/Dialog/Dialog.d.ts +5 -2
  207. package/lib/typescript/module/components/Drawer/Drawer.d.ts +3 -0
  208. package/lib/typescript/module/components/FloatingActionButton/FloatingActionButton.d.ts +5 -0
  209. package/lib/typescript/module/components/FormField/FormField.d.ts +13 -2
  210. package/lib/typescript/module/components/ImageGallery/ImageGallery.d.ts +6 -0
  211. package/lib/typescript/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +20 -0
  212. package/lib/typescript/module/components/KeyboardAwareScrollView/index.d.ts +3 -0
  213. package/lib/typescript/module/components/KeyboardToolbar/KeyboardToolbar.d.ts +29 -0
  214. package/lib/typescript/module/components/KeyboardToolbar/index.d.ts +3 -0
  215. package/lib/typescript/module/components/ListItem/ListItem.d.ts +3 -0
  216. package/lib/typescript/module/components/Modal/Modal.d.ts +6 -0
  217. package/lib/typescript/module/components/NumberInput/NumberInput.d.ts +6 -2
  218. package/lib/typescript/module/components/OTPInput/OTPInput.d.ts +9 -2
  219. package/lib/typescript/module/components/Radio/Radio.d.ts +2 -2
  220. package/lib/typescript/module/components/Radio/RadioGroup.d.ts +3 -2
  221. package/lib/typescript/module/components/Rating/Rating.d.ts +6 -0
  222. package/lib/typescript/module/components/SearchBar/SearchBar.d.ts +3 -0
  223. package/lib/typescript/module/components/SegmentedControl/SegmentedControl.d.ts +6 -2
  224. package/lib/typescript/module/components/Select/Select.d.ts +6 -0
  225. package/lib/typescript/module/components/Slider/Slider.d.ts +9 -4
  226. package/lib/typescript/module/components/Spinner/Spinner.d.ts +1 -1
  227. package/lib/typescript/module/components/Stepper/Stepper.d.ts +6 -0
  228. package/lib/typescript/module/components/Swipeable/Swipeable.d.ts +3 -0
  229. package/lib/typescript/module/components/Switch/Switch.d.ts +3 -2
  230. package/lib/typescript/module/components/Tabs/Tabs.d.ts +3 -0
  231. package/lib/typescript/module/components/Text/Text.d.ts +25 -0
  232. package/lib/typescript/module/components/Text/index.d.ts +3 -0
  233. package/lib/typescript/module/components/TimePicker/TimePicker.d.ts +6 -3
  234. package/lib/typescript/module/components/index.d.ts +10 -0
  235. package/lib/typescript/module/form/FormContext.d.ts +17 -0
  236. package/lib/typescript/module/form/index.d.ts +9 -0
  237. package/lib/typescript/module/form/path.d.ts +10 -0
  238. package/lib/typescript/module/form/rules.d.ts +31 -0
  239. package/lib/typescript/module/form/types.d.ts +94 -0
  240. package/lib/typescript/module/form/useField.d.ts +27 -0
  241. package/lib/typescript/module/form/useForm.d.ts +10 -0
  242. package/lib/typescript/module/hooks/index.d.ts +3 -0
  243. package/lib/typescript/module/hooks/useControllableState.d.ts +17 -0
  244. package/lib/typescript/module/hooks/useReducedMotion.d.ts +8 -0
  245. package/lib/typescript/module/index.d.ts +4 -2
  246. package/lib/typescript/module/theme/types.d.ts +17 -67
  247. package/lib/typescript/module/utils/hapticUtils.d.ts +8 -0
  248. package/lib/typescript/module/utils/index.d.ts +1 -1
  249. package/package.json +1 -1
@@ -3,7 +3,8 @@
3
3
  import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
4
4
  import { Animated, Easing, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
5
5
  import { fontFor, useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
- import { triggerHaptic } from "../../utils/index.js";
6
+ import { useReducedMotion, useControllableState } from "../../hooks/index.js";
7
+ import { triggerHaptic, resolveHaptic } from "../../utils/index.js";
7
8
  import { FieldBase } from "../FieldBase/FieldBase.js";
8
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
10
  const sizeMap = {
@@ -36,6 +37,7 @@ const sanitizeChar = (input, kind) => {
36
37
  const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
37
38
  const {
38
39
  value,
40
+ defaultValue,
39
41
  onChange,
40
42
  onComplete,
41
43
  length = 6,
@@ -45,12 +47,18 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
45
47
  error,
46
48
  size = 'md',
47
49
  secure = false,
50
+ haptic,
48
51
  accessibilityLabel,
49
52
  style,
50
53
  cellStyle,
51
54
  textStyle,
52
55
  testID
53
56
  } = props;
57
+ const [current, setCurrent] = useControllableState({
58
+ value,
59
+ defaultValue: defaultValue ?? '',
60
+ onChange
61
+ });
54
62
  const theme = useTheme();
55
63
  const styles = useMemo(() => buildStyles(theme), [theme]);
56
64
  const sizeStyles = {
@@ -81,8 +89,7 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
81
89
  }, [length]);
82
90
  const previousFocusedIndexRef = useRef(-1);
83
91
  const shakeOnError = theme.components.otpInput?.shakeOnError ?? false;
84
- const errorHaptic = theme.components.otpInput?.errorHaptic ?? false;
85
- const selectionHaptic = theme.components.otpInput?.selectionHaptic ?? false;
92
+ const reduceMotion = useReducedMotion();
86
93
  const hasError = Boolean(error);
87
94
  const errorMessage = typeof error === 'string' ? error : undefined;
88
95
 
@@ -90,11 +97,12 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
90
97
  // error is initially true — there's no transition to animate.
91
98
  useEffect(() => {
92
99
  const isFirstRun = previousErrorRef.current === null;
100
+ let anim;
93
101
  if (!isFirstRun && hasError && !previousErrorRef.current) {
94
- if (errorHaptic) triggerHaptic('notificationError');
95
- if (shakeOnError) {
102
+ if (haptic !== false) triggerHaptic('notificationError');
103
+ if (shakeOnError && !reduceMotion) {
96
104
  setNativeValue(shake, 0);
97
- Animated.sequence([Animated.timing(shake, {
105
+ anim = Animated.sequence([Animated.timing(shake, {
98
106
  toValue: 1,
99
107
  duration: 75,
100
108
  easing: Easing.linear,
@@ -114,11 +122,13 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
114
122
  duration: 75,
115
123
  easing: Easing.linear,
116
124
  useNativeDriver: true
117
- })]).start();
125
+ })]);
126
+ anim.start();
118
127
  }
119
128
  }
120
129
  previousErrorRef.current = hasError;
121
- }, [hasError, shake, shakeOnError, errorHaptic]);
130
+ return () => anim?.stop();
131
+ }, [hasError, shake, shakeOnError, haptic]);
122
132
 
123
133
  // Animate underline opacity for the focused cell. Skip on first mount
124
134
  // (no prior focus state) — values are already at 0 and there's nothing to animate.
@@ -127,17 +137,21 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
127
137
  previousFocusedIndexRef.current = focusedIndex;
128
138
  if (previous === focusedIndex) return;
129
139
  if (previous === -1 && focusedIndex === -1) return;
140
+ const anims = [];
130
141
  underlines.forEach((anim, idx) => {
131
142
  const target = idx === focusedIndex ? 1 : 0;
132
143
  // Only animate the cells whose target changed.
133
144
  if (idx !== focusedIndex && idx !== previous) return;
134
- Animated.timing(anim, {
145
+ const animation = Animated.timing(anim, {
135
146
  toValue: target,
136
147
  duration: theme.motion.duration.fast,
137
148
  easing: Easing.bezier(...theme.motion.easing.standard),
138
149
  useNativeDriver: true
139
- }).start();
150
+ });
151
+ animation.start();
152
+ anims.push(animation);
140
153
  });
154
+ return () => anims.forEach(a => a.stop());
141
155
  }, [focusedIndex, underlines, theme]);
142
156
  const focusCell = useCallback(index => {
143
157
  const target = inputsRef.current[index];
@@ -152,10 +166,10 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
152
166
  current?.blur();
153
167
  },
154
168
  clear: () => {
155
- onChange('');
169
+ setCurrent('');
156
170
  focusCell(0);
157
171
  }
158
- }), [focusCell, focusedIndex, onChange]);
172
+ }), [focusCell, focusedIndex, setCurrent]);
159
173
  useEffect(() => {
160
174
  if (autoFocus && !disabled) {
161
175
  // Defer one frame so the inputs are mounted.
@@ -166,19 +180,19 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
166
180
  }, [autoFocus, disabled, focusCell]);
167
181
  const cells = useMemo(() => {
168
182
  const arr = new Array(length).fill('');
169
- for (let i = 0; i < Math.min(value.length, length); i += 1) {
170
- arr[i] = value[i] ?? '';
183
+ for (let i = 0; i < Math.min(current.length, length); i += 1) {
184
+ arr[i] = current[i] ?? '';
171
185
  }
172
186
  return arr;
173
- }, [value, length]);
187
+ }, [current, length]);
174
188
  const updateValue = useCallback(next => {
175
189
  const trimmed = next.slice(0, length);
176
- if (trimmed === value) return;
177
- onChange(trimmed);
190
+ if (trimmed === current) return;
191
+ setCurrent(trimmed);
178
192
  if (trimmed.length === length && onComplete) {
179
193
  onComplete(trimmed);
180
194
  }
181
- }, [length, onChange, onComplete, value]);
195
+ }, [length, setCurrent, onComplete, current]);
182
196
  const handleChangeText = useCallback((index, raw) => {
183
197
  // Strip the ZWSP placeholder (used so iOS fires onKeyPress/Backspace on otherwise-empty cells).
184
198
  const stripped = raw.replace(/\u200B/g, '');
@@ -208,10 +222,11 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
208
222
  writeIndex += 1;
209
223
  }
210
224
  const next = chars.join('').slice(0, length);
211
- const previousLength = value.length;
225
+ const previousLength = current.length;
212
226
  updateValue(next);
213
- if (next.length !== previousLength && selectionHaptic) {
214
- triggerHaptic('selection');
227
+ if (next.length !== previousLength) {
228
+ const h = resolveHaptic(haptic, 'selection');
229
+ if (h) triggerHaptic(h);
215
230
  }
216
231
 
217
232
  // Move focus to the next empty cell or last cell.
@@ -221,7 +236,7 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
221
236
  } else {
222
237
  focusCell(nextFocus);
223
238
  }
224
- }, [cells, focusCell, keyboardType, length, selectionHaptic, updateValue, value]);
239
+ }, [cells, focusCell, keyboardType, length, haptic, updateValue, current]);
225
240
  const handleKeyPress = useCallback((index, e) => {
226
241
  const key = e.nativeEvent.key;
227
242
  if (key !== 'Backspace') return;
@@ -3,7 +3,7 @@
3
3
  import React, { createContext, forwardRef, useContext, useEffect, useMemo, useRef } from 'react';
4
4
  import { Animated, 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 { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  export const RadioGroupContext = /*#__PURE__*/createContext(null);
9
9
  export const useRadioGroup = () => useContext(RadioGroupContext);
@@ -38,7 +38,7 @@ const Radio = /*#__PURE__*/forwardRef((props, ref) => {
38
38
  const {
39
39
  value,
40
40
  selected: selectedProp,
41
- onSelect,
41
+ onChange,
42
42
  disabled: disabledProp = false,
43
43
  label,
44
44
  size = 'md',
@@ -64,17 +64,17 @@ const Radio = /*#__PURE__*/forwardRef((props, ref) => {
64
64
  const inner = sizeOverrides?.inner ?? sizeMap[size].inner;
65
65
  const radioBorderWidth = theme.colors.border.width;
66
66
  const radioLabelGap = theme.components.radio?.labelGap ?? 10;
67
- const pressHapticEnabled = theme.components.radio?.pressHaptic ?? false;
68
- const resolvedHaptic = haptic === undefined ? pressHapticEnabled ? 'selection' : false : haptic;
69
67
  const progress = useRef(createAnimatedValue(selected ? 1 : 0)).current;
70
68
  useEffect(() => {
71
- Animated.spring(progress, {
69
+ const anim = Animated.spring(progress, {
72
70
  toValue: selected ? 1 : 0,
73
71
  damping: theme.motion.spring.snappy.damping,
74
72
  stiffness: theme.motion.spring.snappy.stiffness,
75
73
  mass: theme.motion.spring.snappy.mass,
76
74
  useNativeDriver: true
77
- }).start();
75
+ });
76
+ anim.start();
77
+ return () => anim.stop();
78
78
  }, [selected, progress, theme.motion.spring.snappy]);
79
79
  const scale = progress.interpolate({
80
80
  inputRange: [0, 1],
@@ -82,11 +82,12 @@ const Radio = /*#__PURE__*/forwardRef((props, ref) => {
82
82
  });
83
83
  const handlePress = event => {
84
84
  if (disabled) return;
85
- if (resolvedHaptic !== false) triggerHaptic(resolvedHaptic);
85
+ const h = resolveHaptic(haptic, 'selection');
86
+ if (h) triggerHaptic(h);
86
87
  if (ctx) {
87
- ctx.onSelect(value);
88
+ ctx.onChange(value);
88
89
  } else {
89
- onSelect?.(value);
90
+ onChange?.(value);
90
91
  }
91
92
  rest.onPressOut?.(event);
92
93
  };
@@ -3,9 +3,11 @@
3
3
  import React, { useMemo } from 'react';
4
4
  import { View } from 'react-native';
5
5
  import { RadioGroupContext } from "./Radio.js";
6
+ import { useControllableState } from "../../hooks/index.js";
6
7
  import { jsx as _jsx } from "react/jsx-runtime";
7
8
  const RadioGroup = ({
8
9
  value,
10
+ defaultValue,
9
11
  onChange,
10
12
  children,
11
13
  disabled = false,
@@ -13,11 +15,16 @@ const RadioGroup = ({
13
15
  style,
14
16
  testID
15
17
  }) => {
18
+ const [current, setCurrent] = useControllableState({
19
+ value,
20
+ defaultValue: defaultValue ?? undefined,
21
+ onChange: onChange
22
+ });
16
23
  const ctx = useMemo(() => ({
17
- selectedValue: value,
18
- onSelect: onChange,
24
+ selectedValue: current,
25
+ onChange: setCurrent,
19
26
  disabled
20
- }), [value, onChange, disabled]);
27
+ }), [current, setCurrent, disabled]);
21
28
  return /*#__PURE__*/_jsx(RadioGroupContext.Provider, {
22
29
  value: ctx,
23
30
  children: /*#__PURE__*/_jsx(View, {
@@ -3,7 +3,7 @@
3
3
  import React, { forwardRef, useCallback, useMemo, useRef } from 'react';
4
4
  import { Animated, Easing, Pressable, StyleSheet, Text, View } from 'react-native';
5
5
  import { useTheme } from "../../theme/index.js";
6
- import { triggerHaptic } from "../../utils/hapticUtils.js";
6
+ import { resolveHaptic, triggerHaptic } 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 sizePxMap = {
@@ -80,6 +80,7 @@ const Rating = /*#__PURE__*/forwardRef((props, ref) => {
80
80
  tone = 'warning',
81
81
  label,
82
82
  loading = false,
83
+ haptic,
83
84
  accessibilityLabel,
84
85
  style,
85
86
  containerStyle,
@@ -116,7 +117,6 @@ const Rating = /*#__PURE__*/forwardRef((props, ref) => {
116
117
  }, [max]);
117
118
  const pulseOnPress = theme.components.rating?.pulseOnPress ?? false;
118
119
  const pulseScale = theme.components.rating?.pulseScale ?? 1.2;
119
- const pressHapticEnabled = theme.components.rating?.pressHaptic ?? false;
120
120
  const pulse = useCallback(idx => {
121
121
  const v = scaleRefs[idx];
122
122
  if (!v) return;
@@ -136,10 +136,11 @@ const Rating = /*#__PURE__*/forwardRef((props, ref) => {
136
136
  const commit = useCallback((next, animatedIndex) => {
137
137
  if (!interactive || !onChange) return;
138
138
  const cleaned = clampToStep(next, max, step);
139
- if (pressHapticEnabled) triggerHaptic('selection');
139
+ const h = resolveHaptic(haptic, 'selection');
140
+ if (h) triggerHaptic(h);
140
141
  if (pulseOnPress && typeof animatedIndex === 'number') pulse(animatedIndex);
141
142
  onChange(cleaned);
142
- }, [interactive, onChange, max, step, pulse, pressHapticEnabled, pulseOnPress]);
143
+ }, [interactive, onChange, max, step, pulse, haptic, pulseOnPress]);
143
144
  const handleStarPress = useCallback(index => e => {
144
145
  if (!interactive) return;
145
146
  const w = starWidthRef.current || starSize;
@@ -3,7 +3,7 @@
3
3
  import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { Animated, Easing, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
5
5
  import { fontFor, useTheme, createAnimatedValue } from "../../theme/index.js";
6
- import { triggerHaptic } from "../../utils/hapticUtils.js";
6
+ import { resolveHaptic, triggerHaptic } from "../../utils/hapticUtils.js";
7
7
  import { useDebounce } from "../../hooks/useDebounce.js";
8
8
  import { FieldBase, resolveFieldSize, resolveFieldTextStyle, resolveVariantColors } from "../FieldBase/FieldBase.js";
9
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -24,6 +24,7 @@ const SearchBar = /*#__PURE__*/forwardRef((props, ref) => {
24
24
  size = 'md',
25
25
  variant: variantProp,
26
26
  cancelLabel = 'Cancel',
27
+ haptic,
27
28
  style,
28
29
  accessibilityLabel,
29
30
  testID,
@@ -75,12 +76,14 @@ const SearchBar = /*#__PURE__*/forwardRef((props, ref) => {
75
76
  }, [debouncedValue, debounceMs, onChangeText]);
76
77
  const showCancelButton = showCancel && isFocused;
77
78
  useEffect(() => {
78
- Animated.timing(cancelAnim, {
79
+ const anim = Animated.timing(cancelAnim, {
79
80
  toValue: showCancelButton ? 1 : 0,
80
81
  duration: 200,
81
82
  easing: Easing.bezier(...theme.motion.easing.standard),
82
83
  useNativeDriver: false
83
- }).start();
84
+ });
85
+ anim.start();
86
+ return () => anim.stop();
84
87
  }, [showCancelButton, cancelAnim, theme.motion.easing.standard]);
85
88
  const handleChangeText = useCallback(text => {
86
89
  if (debounceMs !== undefined) {
@@ -90,16 +93,18 @@ const SearchBar = /*#__PURE__*/forwardRef((props, ref) => {
90
93
  onChangeText(text);
91
94
  }, [debounceMs, onChangeText]);
92
95
  const handleClear = useCallback(() => {
93
- triggerHaptic('selection');
96
+ const h = resolveHaptic(haptic, 'selection');
97
+ if (h) triggerHaptic(h);
94
98
  if (debounceMs !== undefined) {
95
99
  setInternalValue('');
96
100
  lastSentRef.current = '';
97
101
  }
98
102
  onChangeText('');
99
103
  onClear?.();
100
- }, [debounceMs, onChangeText, onClear]);
104
+ }, [haptic, debounceMs, onChangeText, onClear]);
101
105
  const handleCancel = useCallback(() => {
102
- triggerHaptic('selection');
106
+ const h = resolveHaptic(haptic, 'selection');
107
+ if (h) triggerHaptic(h);
103
108
  if (debounceMs !== undefined) {
104
109
  setInternalValue('');
105
110
  lastSentRef.current = '';
@@ -107,7 +112,7 @@ const SearchBar = /*#__PURE__*/forwardRef((props, ref) => {
107
112
  onChangeText('');
108
113
  setIsFocused(false);
109
114
  onCancel?.();
110
- }, [debounceMs, onChangeText, onCancel]);
115
+ }, [haptic, debounceMs, onChangeText, onCancel]);
111
116
  const handleSubmit = useCallback(() => {
112
117
  onSubmit?.(debounceMs !== undefined ? internalValue : value);
113
118
  }, [onSubmit, debounceMs, internalValue, value]);
@@ -11,7 +11,8 @@
11
11
  import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
12
12
  import { Animated, Pressable, StyleSheet, Text, View } from 'react-native';
13
13
  import { useTheme, createAnimatedValue } from "../../theme/index.js";
14
- import { triggerHaptic } from "../../utils/index.js";
14
+ import { triggerHaptic, resolveHaptic } from "../../utils/index.js";
15
+ import { useControllableState } from "../../hooks/index.js";
15
16
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
17
  const sizeMap = {
17
18
  sm: {
@@ -29,12 +30,14 @@ const TRACK_PADDING = 2;
29
30
  const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
30
31
  const {
31
32
  segments,
32
- value,
33
+ value: valueProp,
34
+ defaultValue,
33
35
  onChange,
34
36
  size = 'md',
35
37
  fullWidth = true,
36
38
  disabled = false,
37
39
  tone = 'primary',
40
+ haptic,
38
41
  accessibilityLabel,
39
42
  style,
40
43
  containerStyle,
@@ -44,6 +47,11 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
44
47
  labelStyle,
45
48
  testID
46
49
  } = props;
50
+ const [current, setCurrent] = useControllableState({
51
+ value: valueProp,
52
+ defaultValue: defaultValue ?? segments[0]?.value,
53
+ onChange
54
+ });
47
55
  const theme = useTheme();
48
56
  const segTheme = theme.components.segmentedControl;
49
57
  const styles = useMemo(() => buildStyles(theme), [theme]);
@@ -54,7 +62,6 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
54
62
  paddingHorizontal: segTheme?.[size]?.paddingHorizontal ?? baseSize.paddingHorizontal
55
63
  };
56
64
  const trackPadding = segTheme?.trackPadding ?? TRACK_PADDING;
57
- const changeHapticEnabled = segTheme?.changeHaptic ?? false;
58
65
 
59
66
  // Track width is measured from onLayout. Thumb width is a regular number (not
60
67
  // animated) — `width` cannot be driven by the native animated module, and mixing
@@ -64,23 +71,26 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
64
71
  // only animate translateX via the native driver.
65
72
  const [trackWidth, setTrackWidth] = useState(0);
66
73
  const thumbTranslateX = useRef(createAnimatedValue(0)).current;
67
- const activeIndex = Math.max(0, segments.findIndex(s => s.value === value));
74
+ const activeIndex = Math.max(0, segments.findIndex(s => s.value === current));
68
75
  const segmentWidth = trackWidth > 0 ? (trackWidth - trackPadding * 2) / Math.max(segments.length, 1) : 0;
69
76
  const animateThumb = useCallback((index, segWidth) => {
70
- if (segWidth <= 0) return;
77
+ if (segWidth <= 0) return undefined;
71
78
  const targetX = trackPadding + segWidth * index;
72
79
  const spring = theme.motion.spring.snappy;
73
- Animated.spring(thumbTranslateX, {
80
+ const anim = Animated.spring(thumbTranslateX, {
74
81
  toValue: targetX,
75
82
  damping: spring.damping,
76
83
  stiffness: spring.stiffness,
77
84
  mass: spring.mass,
78
85
  useNativeDriver: true
79
- }).start();
86
+ });
87
+ anim.start();
88
+ return anim;
80
89
  }, [theme.motion.spring.snappy, thumbTranslateX]);
81
90
  useEffect(() => {
82
91
  if (segmentWidth > 0) {
83
- animateThumb(activeIndex, segmentWidth);
92
+ const anim = animateThumb(activeIndex, segmentWidth);
93
+ return () => anim?.stop();
84
94
  }
85
95
  }, [activeIndex, segmentWidth, animateThumb]);
86
96
  const handleTrackLayout = useCallback(e => {
@@ -91,10 +101,11 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
91
101
  }, [trackWidth]);
92
102
  const handlePress = useCallback(segment => {
93
103
  if (disabled) return;
94
- if (segment.value === value) return;
95
- if (changeHapticEnabled) triggerHaptic('selection');
96
- onChange(segment.value);
97
- }, [disabled, onChange, value, changeHapticEnabled]);
104
+ if (segment.value === current) return;
105
+ const h = resolveHaptic(haptic, 'selection');
106
+ if (h) triggerHaptic(h);
107
+ setCurrent(segment.value);
108
+ }, [disabled, setCurrent, current, haptic]);
98
109
  const thumbBg = tone === 'primary' ? theme.colors.background.elevated : theme.colors.background.elevated;
99
110
  const activeTextColor = tone === 'primary' ? theme.colors.text.primary : theme.colors.text.primary;
100
111
  return /*#__PURE__*/_jsxs(View, {
@@ -125,7 +136,7 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
125
136
  ...theme.shadows.sm
126
137
  }, selectedIndicatorStyle]
127
138
  }) : null, segments.map(segment => {
128
- const isActive = segment.value === value;
139
+ const isActive = segment.value === current;
129
140
  return /*#__PURE__*/_jsxs(Pressable, {
130
141
  onPress: () => handlePress(segment),
131
142
  disabled: disabled,
@@ -12,7 +12,7 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState }
12
12
  import { Animated, Dimensions, Easing, FlatList, Modal, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
13
13
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
14
14
  import { useTheme, createAnimatedValue } from "../../theme/index.js";
15
- import { triggerHaptic } from "../../utils/index.js";
15
+ import { resolveHaptic, triggerHaptic } from "../../utils/index.js";
16
16
  import { FieldBase, resolveFieldSize, resolveFieldTextStyle, resolveVariantColors } from "../FieldBase/FieldBase.js";
17
17
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
18
  // `unknown` here keeps the forwardRef signature non-generic — consumers who
@@ -27,6 +27,7 @@ const Select = /*#__PURE__*/forwardRef((props, ref) => {
27
27
  getOptionDescription,
28
28
  placeholder,
29
29
  searchable = false,
30
+ haptic,
30
31
  label,
31
32
  error,
32
33
  disabled = false,
@@ -61,34 +62,32 @@ const Select = /*#__PURE__*/forwardRef((props, ref) => {
61
62
  const backdropAnim = useRef(createAnimatedValue(0)).current;
62
63
  const sheetAnim = useRef(createAnimatedValue(sheetMaxHeight)).current;
63
64
  useEffect(() => {
64
- if (open) {
65
- Animated.parallel([
66
- // Backdrop opacity uses JS driver — see Modal.tsx for the Fabric reason.
67
- Animated.timing(backdropAnim, {
68
- toValue: 1,
69
- duration: theme.motion.duration.fast,
70
- easing: Easing.bezier(...theme.motion.easing.standard),
71
- useNativeDriver: false
72
- }), Animated.spring(sheetAnim, {
73
- toValue: 0,
74
- damping: theme.motion.spring.snappy.damping,
75
- stiffness: theme.motion.spring.snappy.stiffness,
76
- mass: theme.motion.spring.snappy.mass,
77
- useNativeDriver: true
78
- })]).start();
79
- } else {
80
- Animated.parallel([Animated.timing(backdropAnim, {
81
- toValue: 0,
82
- duration: theme.motion.duration.fast,
83
- easing: Easing.bezier(...theme.motion.easing.standard),
84
- useNativeDriver: false
85
- }), Animated.timing(sheetAnim, {
86
- toValue: sheetMaxHeight,
87
- duration: theme.motion.duration.fast,
88
- easing: Easing.bezier(...theme.motion.easing.accelerate),
89
- useNativeDriver: true
90
- })]).start();
91
- }
65
+ const anim = open ? Animated.parallel([
66
+ // Backdrop opacity uses JS driver — see Modal.tsx for the Fabric reason.
67
+ Animated.timing(backdropAnim, {
68
+ toValue: 1,
69
+ duration: theme.motion.duration.fast,
70
+ easing: Easing.bezier(...theme.motion.easing.standard),
71
+ useNativeDriver: false
72
+ }), Animated.spring(sheetAnim, {
73
+ toValue: 0,
74
+ damping: theme.motion.spring.snappy.damping,
75
+ stiffness: theme.motion.spring.snappy.stiffness,
76
+ mass: theme.motion.spring.snappy.mass,
77
+ useNativeDriver: true
78
+ })]) : Animated.parallel([Animated.timing(backdropAnim, {
79
+ toValue: 0,
80
+ duration: theme.motion.duration.fast,
81
+ easing: Easing.bezier(...theme.motion.easing.standard),
82
+ useNativeDriver: false
83
+ }), Animated.timing(sheetAnim, {
84
+ toValue: sheetMaxHeight,
85
+ duration: theme.motion.duration.fast,
86
+ easing: Easing.bezier(...theme.motion.easing.accelerate),
87
+ useNativeDriver: true
88
+ })]);
89
+ anim.start();
90
+ return () => anim.stop();
92
91
  }, [open, backdropAnim, sheetAnim, sheetMaxHeight, theme.motion.duration.fast, theme.motion.easing.standard, theme.motion.easing.accelerate, theme.motion.spring.snappy]);
93
92
 
94
93
  // Selection helpers — typed to keep callbacks fully type-safe.
@@ -116,9 +115,10 @@ const Select = /*#__PURE__*/forwardRef((props, ref) => {
116
115
  }, [searchable, query, options, labelOf, descriptionOf]);
117
116
  const handleOpen = useCallback(() => {
118
117
  if (disabled) return;
119
- triggerHaptic('selection');
118
+ const h = resolveHaptic(haptic, 'selection');
119
+ if (h) triggerHaptic(h);
120
120
  setOpen(true);
121
- }, [disabled]);
121
+ }, [disabled, haptic]);
122
122
  const handleClose = useCallback(() => {
123
123
  setOpen(false);
124
124
  setQuery('');
@@ -126,7 +126,8 @@ const Select = /*#__PURE__*/forwardRef((props, ref) => {
126
126
  const handleSelect = useCallback(option => {
127
127
  if (disabledOf(option)) return;
128
128
  const optValue = valueOf(option);
129
- triggerHaptic('selection');
129
+ const h = resolveHaptic(haptic, 'selection');
130
+ if (h) triggerHaptic(h);
130
131
  if (multi) {
131
132
  const current = props.value ?? [];
132
133
  const next = current.includes(optValue) ? current.filter(v => v !== optValue) : [...current, optValue];
@@ -135,7 +136,7 @@ const Select = /*#__PURE__*/forwardRef((props, ref) => {
135
136
  }
136
137
  props.onChange(optValue);
137
138
  handleClose();
138
- }, [multi, props, handleClose, disabledOf, valueOf]);
139
+ }, [multi, props, handleClose, disabledOf, valueOf, haptic]);
139
140
 
140
141
  // Trigger label / placeholder text.
141
142
  const triggerText = selectedOptions.length === 0 ? placeholder ?? 'Select…' : selectedOptions.map(o => labelOf(o)).join(', ');
@@ -386,14 +387,17 @@ const Chevron = ({
386
387
  size,
387
388
  open
388
389
  }) => {
390
+ const theme = useTheme();
389
391
  const rotate = useRef(createAnimatedValue(open ? 1 : 0)).current;
390
392
  useEffect(() => {
391
- Animated.timing(rotate, {
393
+ const anim = Animated.timing(rotate, {
392
394
  toValue: open ? 1 : 0,
393
- duration: 160,
395
+ duration: theme.motion.duration.fast,
394
396
  useNativeDriver: true
395
- }).start();
396
- }, [open, rotate]);
397
+ });
398
+ anim.start();
399
+ return () => anim.stop();
400
+ }, [open, rotate, theme.motion.duration.fast]);
397
401
  const rotation = rotate.interpolate({
398
402
  inputRange: [0, 1],
399
403
  outputRange: ['0deg', '180deg']
@@ -216,19 +216,22 @@ const SkeletonContent = ({
216
216
  const wasLoadingRef = useRef(loading);
217
217
  const fadeAnim = useRef(createAnimatedValue(loading ? 0 : 1)).current;
218
218
  useEffect(() => {
219
+ let anim;
219
220
  if (wasLoadingRef.current && !loading) {
220
221
  fadeAnim.setValue(0);
221
- Animated.timing(fadeAnim, {
222
+ anim = Animated.timing(fadeAnim, {
222
223
  toValue: 1,
223
224
  duration: resolvedFadeDuration,
224
225
  useNativeDriver: true
225
- }).start();
226
+ });
227
+ anim.start();
226
228
  } else if (!wasLoadingRef.current && loading) {
227
229
  // Going back into loading from loaded — snap opacity so the
228
230
  // placeholder appears instantly when data invalidates.
229
231
  fadeAnim.setValue(1);
230
232
  }
231
233
  wasLoadingRef.current = loading;
234
+ return () => anim?.stop();
232
235
  }, [loading, fadeAnim, resolvedFadeDuration]);
233
236
  if (!loading) {
234
237
  if (resolvedFadeDuration <= 0) {