@webority-technologies/mobile 0.0.11 → 0.0.13

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 (47) hide show
  1. package/lib/commonjs/components/Badge/Badge.js +1 -1
  2. package/lib/commonjs/components/BottomNavigation/BottomNavigation.js +11 -3
  3. package/lib/commonjs/components/DatePicker/DatePicker.js +18 -12
  4. package/lib/commonjs/components/DateRangePicker/DateRangePicker.js +14 -9
  5. package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +1 -1
  6. package/lib/commonjs/components/Input/Input.js +1 -1
  7. package/lib/commonjs/components/Modal/Modal.js +4 -4
  8. package/lib/commonjs/components/OTPInput/OTPInput.js +29 -9
  9. package/lib/commonjs/components/ProgressBar/ProgressBar.js +1 -1
  10. package/lib/commonjs/components/SearchBar/SearchBar.js +6 -1
  11. package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +23 -28
  12. package/lib/commonjs/components/Skeleton/Skeleton.js +1 -1
  13. package/lib/commonjs/components/Slider/Slider.js +11 -11
  14. package/lib/commonjs/components/Stepper/Stepper.js +10 -4
  15. package/lib/commonjs/components/Tabs/Tabs.js +7 -5
  16. package/lib/commonjs/components/TimePicker/TimePicker.js +3 -3
  17. package/lib/commonjs/components/Toast/Toast.js +2 -2
  18. package/lib/commonjs/theme/animatedValue.js +20 -1
  19. package/lib/commonjs/theme/index.js +8 -1
  20. package/lib/module/components/Badge/Badge.js +2 -2
  21. package/lib/module/components/BottomNavigation/BottomNavigation.js +11 -3
  22. package/lib/module/components/DatePicker/DatePicker.js +19 -13
  23. package/lib/module/components/DateRangePicker/DateRangePicker.js +15 -10
  24. package/lib/module/components/FloatingActionButton/FloatingActionButton.js +2 -2
  25. package/lib/module/components/Input/Input.js +2 -2
  26. package/lib/module/components/Modal/Modal.js +5 -5
  27. package/lib/module/components/OTPInput/OTPInput.js +30 -10
  28. package/lib/module/components/ProgressBar/ProgressBar.js +2 -2
  29. package/lib/module/components/SearchBar/SearchBar.js +6 -1
  30. package/lib/module/components/SegmentedControl/SegmentedControl.js +24 -29
  31. package/lib/module/components/Skeleton/Skeleton.js +2 -2
  32. package/lib/module/components/Slider/Slider.js +12 -12
  33. package/lib/module/components/Stepper/Stepper.js +10 -4
  34. package/lib/module/components/Tabs/Tabs.js +7 -5
  35. package/lib/module/components/TimePicker/TimePicker.js +4 -4
  36. package/lib/module/components/Toast/Toast.js +3 -3
  37. package/lib/module/theme/animatedValue.js +18 -0
  38. package/lib/module/theme/index.js +1 -1
  39. package/lib/typescript/commonjs/components/BottomNavigation/BottomNavigation.d.ts +7 -0
  40. package/lib/typescript/commonjs/components/SearchBar/SearchBar.d.ts +2 -1
  41. package/lib/typescript/commonjs/theme/animatedValue.d.ts +11 -0
  42. package/lib/typescript/commonjs/theme/index.d.ts +1 -1
  43. package/lib/typescript/module/components/BottomNavigation/BottomNavigation.d.ts +7 -0
  44. package/lib/typescript/module/components/SearchBar/SearchBar.d.ts +2 -1
  45. package/lib/typescript/module/theme/animatedValue.d.ts +11 -0
  46. package/lib/typescript/module/theme/index.d.ts +1 -1
  47. package/package.json +1 -1
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.createAnimatedValue = void 0;
6
+ exports.setNativeValue = exports.createAnimatedValue = void 0;
7
7
  var _reactNative = require("react-native");
8
8
  /**
9
9
  * Create an `Animated.Value` that survives RN 0.85's dev-mode prop deepFreeze.
@@ -24,5 +24,24 @@ var _reactNative = require("react-native");
24
24
  * Use everywhere the library would otherwise call `new Animated.Value(...)`.
25
25
  */
26
26
  const createAnimatedValue = (initial, config) => Object.seal(new _reactNative.Animated.Value(initial, config));
27
+
28
+ /**
29
+ * Set an `Animated.Value` to a target without going through JS.
30
+ *
31
+ * Once a value has been driven by `useNativeDriver: true`, the underlying
32
+ * native node owns it — calling `value.setValue(x)` from JS throws
33
+ * "Attempting to run JS driven animation on animated node that has been moved to native".
34
+ * Use this helper for any value that is *also* used in a native-driven
35
+ * `Animated.timing/spring`. A zero-duration native timing routes the update
36
+ * through the same driver and stays valid across re-mounts and re-runs.
37
+ */
27
38
  exports.createAnimatedValue = createAnimatedValue;
39
+ const setNativeValue = (value, to) => {
40
+ _reactNative.Animated.timing(value, {
41
+ toValue: to,
42
+ duration: 0,
43
+ useNativeDriver: true
44
+ }).start();
45
+ };
46
+ exports.setNativeValue = setNativeValue;
28
47
  //# sourceMappingURL=animatedValue.js.map
@@ -52,7 +52,14 @@ Object.defineProperty(exports, "mergeTheme", {
52
52
  return _merge.mergeTheme;
53
53
  }
54
54
  });
55
- exports.subscribeTheme = exports.setTheme = exports.setColorMode = exports.resetTheme = void 0;
55
+ exports.setColorMode = exports.resetTheme = void 0;
56
+ Object.defineProperty(exports, "setNativeValue", {
57
+ enumerable: true,
58
+ get: function () {
59
+ return _animatedValue.setNativeValue;
60
+ }
61
+ });
62
+ exports.subscribeTheme = exports.setTheme = void 0;
56
63
  Object.defineProperty(exports, "useTheme", {
57
64
  enumerable: true,
58
65
  get: function () {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { forwardRef, useEffect, useMemo, useRef } from 'react';
4
4
  import { Animated, Easing, StyleSheet, Text, View } from 'react-native';
5
- import { fontFor, useTheme, createAnimatedValue } from "../../theme/index.js";
5
+ import { fontFor, useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
6
  import { SkeletonContent } from "../Skeleton/index.js";
7
7
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  const toneFor = (theme, tone) => {
@@ -126,7 +126,7 @@ const Badge = /*#__PURE__*/forwardRef((props, ref) => {
126
126
  const pulseScale = useRef(createAnimatedValue(1)).current;
127
127
  useEffect(() => {
128
128
  if (!pulse || !shouldRender) {
129
- pulseScale.setValue(1);
129
+ setNativeValue(pulseScale, 1);
130
130
  return;
131
131
  }
132
132
  const loop = Animated.loop(Animated.sequence([Animated.timing(pulseScale, {
@@ -48,6 +48,7 @@ const BottomNavigation = /*#__PURE__*/forwardRef((props, ref) => {
48
48
  haptic = 'selection',
49
49
  showLabels = true,
50
50
  variant = 'pill',
51
+ indicatorPosition = 'bottom',
51
52
  style,
52
53
  indicatorStyle,
53
54
  labelStyle,
@@ -193,7 +194,7 @@ const BottomNavigation = /*#__PURE__*/forwardRef((props, ref) => {
193
194
  }, tab.key);
194
195
  }), variant === 'underline' && tabWidth > 0 ? /*#__PURE__*/_jsx(Animated.View, {
195
196
  pointerEvents: "none",
196
- style: [styles.underline, {
197
+ style: [styles.underline, indicatorPosition === 'top' ? styles.underlineTop : styles.underlineBottom, {
197
198
  width: tabWidth,
198
199
  backgroundColor: theme.colors.primary,
199
200
  transform: [{
@@ -243,12 +244,19 @@ const buildStyles = theme => StyleSheet.create({
243
244
  },
244
245
  underline: {
245
246
  position: 'absolute',
246
- bottom: -6,
247
247
  left: 0,
248
- height: UNDERLINE_HEIGHT,
248
+ height: UNDERLINE_HEIGHT
249
+ },
250
+ underlineBottom: {
251
+ bottom: -6,
249
252
  borderTopLeftRadius: 2,
250
253
  borderTopRightRadius: 2
251
254
  },
255
+ underlineTop: {
256
+ top: -6,
257
+ borderBottomLeftRadius: 2,
258
+ borderBottomRightRadius: 2
259
+ },
252
260
  badgePill: {
253
261
  position: 'absolute',
254
262
  top: -4,
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { AccessibilityInfo, Animated, Easing, Modal, Pressable, StyleSheet, Text, View } from 'react-native';
5
- import { useTheme, createAnimatedValue } from "../../theme/index.js";
5
+ import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
6
  import { triggerHaptic } from "../../utils/index.js";
7
7
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
8
8
  const DAY_MS = 24 * 60 * 60 * 1000;
@@ -130,8 +130,8 @@ const DatePicker = props => {
130
130
  useEffect(() => {
131
131
  if (mode !== 'modal') return;
132
132
  if (visible) {
133
- backdrop.setValue(0);
134
- sheet.setValue(0);
133
+ setNativeValue(backdrop, 0);
134
+ setNativeValue(sheet, 0);
135
135
  Animated.parallel([Animated.timing(backdrop, {
136
136
  toValue: 1,
137
137
  duration: theme.motion.duration.normal,
@@ -243,8 +243,8 @@ const DatePicker = props => {
243
243
 
244
244
  // View-mode transition: fade + scale (200ms, native driver).
245
245
  const animateViewTransition = useCallback(() => {
246
- viewFade.setValue(0);
247
- viewScale.setValue(0.9);
246
+ setNativeValue(viewFade, 0);
247
+ setNativeValue(viewScale, 0.9);
248
248
  Animated.parallel([Animated.timing(viewFade, {
249
249
  toValue: 1,
250
250
  duration: 200,
@@ -295,7 +295,7 @@ const DatePicker = props => {
295
295
  if (!cell.inMonth) {
296
296
  setAnchor(new Date(cell.date.getFullYear(), cell.date.getMonth(), 1));
297
297
  }
298
- selectScale.setValue(0.7);
298
+ setNativeValue(selectScale, 0.7);
299
299
  Animated.spring(selectScale, {
300
300
  toValue: 1,
301
301
  damping: theme.motion.spring.bouncy.damping,
@@ -377,11 +377,16 @@ const DatePicker = props => {
377
377
  opacity: viewMode === 'days' ? monthFade : 1
378
378
  }],
379
379
  children: [/*#__PURE__*/_jsx(Animated.Text, {
380
- style: [styles.headerLabel, viewMode === 'days' ? {
380
+ style: [styles.headerLabel,
381
+ // Always include the transform — passing null/undefined as a
382
+ // transform value confuses Animated's processTransform on Fabric.
383
+ // When the month slide isn't active we still drive translateX
384
+ // through the same value, just with target 0.
385
+ {
381
386
  transform: [{
382
- translateX: monthSlide
387
+ translateX: viewMode === 'days' ? monthSlide : 0
383
388
  }]
384
- } : null],
389
+ }],
385
390
  accessibilityLiveRegion: "polite",
386
391
  children: headerLabel
387
392
  }), /*#__PURE__*/_jsx(Text, {
@@ -447,11 +452,12 @@ const DatePicker = props => {
447
452
  backgroundColor: cellBg,
448
453
  borderColor,
449
454
  borderWidth: todayCell && !selected ? 1.5 : 0,
450
- opacity,
451
- transform: selected ? [{
455
+ opacity
456
+ }, selected ? {
457
+ transform: [{
452
458
  scale: selectScale
453
- }] : undefined
454
- }],
459
+ }]
460
+ } : null],
455
461
  children: /*#__PURE__*/_jsx(Text, {
456
462
  style: [styles.dayText, {
457
463
  color: textColor
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { AccessibilityInfo, Animated, Easing, Modal, Pressable, StyleSheet, Text, View } from 'react-native';
5
- import { useTheme, createAnimatedValue } from "../../theme/index.js";
5
+ import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
6
  import { triggerHaptic } from "../../utils/index.js";
7
7
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  const DAY_MS = 24 * 60 * 60 * 1000;
@@ -115,8 +115,8 @@ const DateRangePicker = /*#__PURE__*/forwardRef((props, ref) => {
115
115
  // Modal open / close animation.
116
116
  useEffect(() => {
117
117
  if (visible) {
118
- backdrop.setValue(0);
119
- sheet.setValue(0);
118
+ setNativeValue(backdrop, 0);
119
+ setNativeValue(sheet, 0);
120
120
  Animated.parallel([Animated.timing(backdrop, {
121
121
  toValue: 1,
122
122
  duration: theme.motion.duration.normal,
@@ -556,26 +556,31 @@ const buildStyles = theme => {
556
556
  aspectRatio: 1,
557
557
  alignItems: 'center',
558
558
  justifyContent: 'center',
559
- padding: 2,
559
+ // No horizontal padding so the connector bars on adjacent cells meet
560
+ // without a gap. Vertical breathing room comes from dayInner's height.
561
+ padding: 0,
560
562
  position: 'relative'
561
563
  },
564
+ // Bars sit BEHIND dayInner and bridge the start/end circles to mid-range
565
+ // cells. Their height mirrors dayInner (~90% of cell height) so the visual
566
+ // pill is one continuous shape across multiple cells.
562
567
  barLeft: {
563
568
  position: 'absolute',
564
569
  left: 0,
565
570
  right: '50%',
566
- top: '15%',
567
- bottom: '15%'
571
+ top: '5%',
572
+ bottom: '5%'
568
573
  },
569
574
  barRight: {
570
575
  position: 'absolute',
571
576
  left: '50%',
572
577
  right: 0,
573
- top: '15%',
574
- bottom: '15%'
578
+ top: '5%',
579
+ bottom: '5%'
575
580
  },
576
581
  dayInner: {
577
- width: '100%',
578
- height: '100%',
582
+ width: '90%',
583
+ height: '90%',
579
584
  alignItems: 'center',
580
585
  justifyContent: 'center'
581
586
  },
@@ -3,7 +3,7 @@
3
3
  import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { Animated, Easing, Pressable, StyleSheet, Text, View } from 'react-native';
5
5
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
6
- import { useTheme, createAnimatedValue } from "../../theme/index.js";
6
+ import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
7
7
  import { usePressAnimation } from "../../hooks/usePressAnimation.js";
8
8
  import { triggerHaptic } from "../../utils/hapticUtils.js";
9
9
  import { AppIcon } from "../AppIcon/index.js";
@@ -81,7 +81,7 @@ const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
81
81
  const hideAnim = useRef(createAnimatedValue(0)).current;
82
82
  useEffect(() => {
83
83
  if (!hideOnScroll) {
84
- hideAnim.setValue(0);
84
+ setNativeValue(hideAnim, 0);
85
85
  return;
86
86
  }
87
87
  Animated.timing(hideAnim, {
@@ -5,7 +5,7 @@ import { Animated, Easing, Pressable, StyleSheet, Text, TextInput, View } from '
5
5
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
6
6
  // @ts-ignore - react-native-vector-icons ships no bundled types in this version
7
7
  import Feather from 'react-native-vector-icons/Feather';
8
- import { createAnimatedValue, fontFor, useTheme } from "../../theme/index.js";
8
+ import { createAnimatedValue, fontFor, setNativeValue, useTheme } from "../../theme/index.js";
9
9
  import { triggerHaptic } from "../../utils/index.js";
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  const sizeMap = {
@@ -140,7 +140,7 @@ const Input = /*#__PURE__*/forwardRef((props, ref) => {
140
140
  const prevErrorRef = useRef(hasError);
141
141
  useEffect(() => {
142
142
  if (hasError && !prevErrorRef.current) {
143
- shakeAnim.setValue(0);
143
+ setNativeValue(shakeAnim, 0);
144
144
  Animated.sequence([Animated.timing(shakeAnim, {
145
145
  toValue: 1,
146
146
  duration: 50,
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react';
4
4
  import { AccessibilityInfo, Animated, Dimensions, findNodeHandle, Modal as RNModal, Pressable, StyleSheet, View } from 'react-native';
5
- import { useTheme, createAnimatedValue } from "../../theme/index.js";
5
+ import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
6
  import { triggerHaptic } from "../../utils/hapticUtils.js";
7
7
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  const Modal = /*#__PURE__*/forwardRef((props, ref) => {
@@ -60,10 +60,10 @@ const Modal = /*#__PURE__*/forwardRef((props, ref) => {
60
60
  })]).start();
61
61
  }
62
62
  } else {
63
- backdropAnim.setValue(0);
64
- scaleAnim.setValue(0.9);
65
- opacityAnim.setValue(0);
66
- translateYAnim.setValue(screenHeight);
63
+ setNativeValue(backdropAnim, 0);
64
+ setNativeValue(scaleAnim, 0.9);
65
+ setNativeValue(opacityAnim, 0);
66
+ setNativeValue(translateYAnim, screenHeight);
67
67
  }
68
68
  }, [visible, presentation, duration, backdropAnim, scaleAnim, opacityAnim, translateYAnim, screenHeight, theme.motion.spring.gentle.damping, theme.motion.spring.gentle.stiffness, theme.motion.spring.gentle.mass]);
69
69
 
@@ -2,7 +2,7 @@
2
2
 
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
- import { fontFor, useTheme, createAnimatedValue } from "../../theme/index.js";
5
+ import { fontFor, useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
6
  import { triggerHaptic } from "../../utils/index.js";
7
7
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  const sizeMap = {
@@ -84,7 +84,7 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
84
84
  const isFirstRun = previousErrorRef.current === null;
85
85
  if (!isFirstRun && hasError && !previousErrorRef.current) {
86
86
  triggerHaptic('notificationError');
87
- shake.setValue(0);
87
+ setNativeValue(shake, 0);
88
88
  Animated.sequence([Animated.timing(shake, {
89
89
  toValue: 1,
90
90
  duration: 75,
@@ -170,16 +170,27 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
170
170
  }
171
171
  }, [length, onChange, onComplete, value]);
172
172
  const handleChangeText = useCallback((index, raw) => {
173
- const sanitized = sanitizeChar(raw, keyboardType);
173
+ // Strip the ZWSP placeholder (used so iOS fires onKeyPress/Backspace on otherwise-empty cells).
174
+ const stripped = raw.replace(/\u200B/g, '');
175
+ const sanitized = sanitizeChar(stripped, keyboardType);
174
176
  if (!sanitized) {
175
- // User cleared this cell explicitly via change (rare since we manage backspace via key-press).
177
+ // Cell was cleared (delete / cut). Clear this cell and step focus back.
176
178
  const chars = cells.slice();
179
+ const wasFilled = (chars[index] ?? '').length > 0;
177
180
  chars[index] = '';
178
181
  updateValue(chars.join('').slice(0, length));
182
+ if (!wasFilled && index > 0) {
183
+ // Empty cell + backspace → also clear and focus the previous cell.
184
+ const prev = cells.slice();
185
+ prev[index - 1] = '';
186
+ updateValue(prev.join(''));
187
+ focusCell(index - 1);
188
+ }
179
189
  return;
180
190
  }
181
191
 
182
192
  // Fill cells from current index onward (handles paste of multi-char text).
193
+ // For single-character typing, this overwrites the current cell and advances.
183
194
  const chars = cells.slice();
184
195
  let writeIndex = index;
185
196
  for (let i = 0; i < sanitized.length && writeIndex < length; i += 1) {
@@ -196,7 +207,6 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
196
207
  // Move focus to the next empty cell or last cell.
197
208
  const nextFocus = Math.min(writeIndex, length - 1);
198
209
  if (writeIndex >= length) {
199
- // All filled — blur last cell.
200
210
  inputsRef.current[length - 1]?.blur();
201
211
  } else {
202
212
  focusCell(nextFocus);
@@ -205,9 +215,12 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
205
215
  const handleKeyPress = useCallback((index, e) => {
206
216
  const key = e.nativeEvent.key;
207
217
  if (key !== 'Backspace') return;
218
+ // Backspace on a non-empty cell — clear it. Backspace on an empty cell —
219
+ // step back and clear the previous cell. handleChangeText also covers the
220
+ // empty-cell case via the ZWSP placeholder, so this branch only matters
221
+ // when the platform fires onKeyPress without firing onChangeText.
208
222
  const current = cells[index] ?? '';
209
223
  if (current.length === 0) {
210
- // Move back, clear previous cell.
211
224
  if (index > 0) {
212
225
  const chars = cells.slice();
213
226
  chars[index - 1] = '';
@@ -283,8 +296,11 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
283
296
  children: [/*#__PURE__*/_jsx(TextInput, {
284
297
  ref: node => {
285
298
  inputsRef.current[index] = node;
286
- },
287
- value: secure && isFilled ? '' : char,
299
+ }
300
+ // Always render at least the ZWSP placeholder so iOS keeps
301
+ // firing onChangeText/onKeyPress for Backspace on empty cells.
302
+ ,
303
+ value: (secure && isFilled ? '' : char) || '\u200B',
288
304
  onChangeText: t => handleChangeText(index, t),
289
305
  onKeyPress: e => handleKeyPress(index, e),
290
306
  onFocus: () => handleFocus(index),
@@ -292,8 +308,12 @@ const OTPInput = /*#__PURE__*/forwardRef((props, ref) => {
292
308
  keyboardType: keyboardType,
293
309
  editable: !disabled,
294
310
  selectTextOnFocus: true,
295
- caretHidden: isFilled,
296
- maxLength: length,
311
+ caretHidden: isFilled
312
+ // Only the first cell accepts paste-like multi-char input
313
+ // (e.g., when SMS autofill or clipboard delivers the full code);
314
+ // every other cell is single-char so typing always overwrites.
315
+ ,
316
+ maxLength: index === 0 ? length : 2,
297
317
  textContentType: index === 0 ? 'oneTimeCode' : 'none',
298
318
  autoComplete: index === 0 ? 'sms-otp' : 'off',
299
319
  importantForAutofill: index === 0 ? 'yes' : 'no',
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { forwardRef, useEffect, useMemo, useRef } from 'react';
4
4
  import { Animated, Easing, StyleSheet, View } from 'react-native';
5
- import { useTheme, createAnimatedValue } from "../../theme/index.js";
5
+ import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
6
  import { jsx as _jsx } from "react/jsx-runtime";
7
7
  const toneColor = (theme, tone) => {
8
8
  switch (tone) {
@@ -62,7 +62,7 @@ const ProgressBar = /*#__PURE__*/forwardRef((props, ref) => {
62
62
  }, [animated, clamped, fillAnim, isIndeterminate, theme]);
63
63
  useEffect(() => {
64
64
  if (!isIndeterminate) return;
65
- loopAnim.setValue(0);
65
+ setNativeValue(loopAnim, 0);
66
66
  const animation = Animated.loop(Animated.timing(loopAnim, {
67
67
  toValue: 1,
68
68
  duration: 1500,
@@ -33,7 +33,7 @@ const sizeMap = {
33
33
  const CANCEL_WIDTH = 72;
34
34
  const SearchBar = /*#__PURE__*/forwardRef((props, ref) => {
35
35
  const {
36
- value,
36
+ value: rawValue,
37
37
  onChangeText,
38
38
  onSubmit,
39
39
  placeholder = 'Search…',
@@ -59,6 +59,11 @@ const SearchBar = /*#__PURE__*/forwardRef((props, ref) => {
59
59
  };
60
60
  const cancelWidth = theme.components.searchBar?.cancelButtonWidth ?? CANCEL_WIDTH;
61
61
  const styles = useMemo(() => buildStyles(theme), [theme]);
62
+
63
+ // Coerce nullable inputs to '' once at the top so every downstream read is
64
+ // a guaranteed string. Required when consumers pass `value={state}` where
65
+ // `state` may be `undefined` / `null` before being initialised.
66
+ const value = typeof rawValue === 'string' ? rawValue : '';
62
67
  const [isFocused, setIsFocused] = useState(false);
63
68
  const [internalValue, setInternalValue] = useState(value);
64
69
  const debouncedValue = useDebounce(internalValue, debounceMs ?? 0);
@@ -8,7 +8,7 @@
8
8
  * fully accessible (radiogroup / radio roles + selected state).
9
9
  */
10
10
 
11
- import React, { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react';
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
14
  import { triggerHaptic } from "../../utils/index.js";
@@ -45,44 +45,39 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
45
45
  const styles = useMemo(() => buildStyles(theme), [theme]);
46
46
  const sizeStyles = sizeMap[size];
47
47
 
48
- // Total track width measured from onLayout; thumb width = trackWidth / segments.length.
49
- const trackWidthRef = useRef(0);
48
+ // Track width is measured from onLayout. Thumb width is a regular number (not
49
+ // animated) `width` cannot be driven by the native animated module, and mixing
50
+ // a JS-driven width with a native-driven translateX inside `Animated.parallel`
51
+ // sends the parallel composite into the native driver and JS setValue/timing
52
+ // calls on the same value then throw "moved to native". Keep width as state and
53
+ // only animate translateX via the native driver.
54
+ const [trackWidth, setTrackWidth] = useState(0);
50
55
  const thumbTranslateX = useRef(createAnimatedValue(0)).current;
51
- const thumbWidth = useRef(createAnimatedValue(0)).current;
52
56
  const activeIndex = Math.max(0, segments.findIndex(s => s.value === value));
53
- const animateThumb = useCallback((index, totalWidth) => {
54
- if (totalWidth <= 0 || segments.length === 0) return;
55
- const segWidth = (totalWidth - TRACK_PADDING * 2) / segments.length;
57
+ const segmentWidth = trackWidth > 0 ? (trackWidth - TRACK_PADDING * 2) / Math.max(segments.length, 1) : 0;
58
+ const animateThumb = useCallback((index, segWidth) => {
59
+ if (segWidth <= 0) return;
56
60
  const targetX = TRACK_PADDING + segWidth * index;
57
61
  const spring = theme.motion.spring.snappy;
58
- Animated.parallel([Animated.spring(thumbTranslateX, {
62
+ Animated.spring(thumbTranslateX, {
59
63
  toValue: targetX,
60
64
  damping: spring.damping,
61
65
  stiffness: spring.stiffness,
62
66
  mass: spring.mass,
63
67
  useNativeDriver: true
64
- }), Animated.spring(thumbWidth, {
65
- toValue: segWidth,
66
- damping: spring.damping,
67
- stiffness: spring.stiffness,
68
- mass: spring.mass,
69
- useNativeDriver: false
70
- })]).start();
71
- }, [segments.length, theme.motion.spring.snappy, thumbTranslateX, thumbWidth]);
68
+ }).start();
69
+ }, [theme.motion.spring.snappy, thumbTranslateX]);
72
70
  useEffect(() => {
73
- if (trackWidthRef.current > 0) {
74
- animateThumb(activeIndex, trackWidthRef.current);
71
+ if (segmentWidth > 0) {
72
+ animateThumb(activeIndex, segmentWidth);
75
73
  }
76
- }, [activeIndex, animateThumb]);
74
+ }, [activeIndex, segmentWidth, animateThumb]);
77
75
  const handleTrackLayout = useCallback(e => {
78
76
  const width = e.nativeEvent.layout.width;
79
- if (width === trackWidthRef.current) return;
80
- trackWidthRef.current = width;
81
- // Initial-position the thumb without animation jitter — set then animate to active.
82
- const segWidth = (width - TRACK_PADDING * 2) / Math.max(segments.length, 1);
83
- thumbWidth.setValue(segWidth);
84
- thumbTranslateX.setValue(TRACK_PADDING + segWidth * activeIndex);
85
- }, [activeIndex, segments.length, thumbTranslateX, thumbWidth]);
77
+ if (width !== trackWidth) {
78
+ setTrackWidth(width);
79
+ }
80
+ }, [trackWidth]);
86
81
  const handlePress = useCallback(segment => {
87
82
  if (disabled) return;
88
83
  if (segment.value === value) return;
@@ -105,10 +100,10 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
105
100
  alignSelf: fullWidth ? 'stretch' : 'flex-start'
106
101
  }, style],
107
102
  onLayout: handleTrackLayout,
108
- children: [/*#__PURE__*/_jsx(Animated.View, {
103
+ children: [segmentWidth > 0 ? /*#__PURE__*/_jsx(Animated.View, {
109
104
  pointerEvents: "none",
110
105
  style: [styles.thumb, {
111
- width: thumbWidth,
106
+ width: segmentWidth,
112
107
  height: sizeStyles.height - TRACK_PADDING * 2,
113
108
  borderRadius: theme.radius.sm,
114
109
  backgroundColor: thumbBg,
@@ -117,7 +112,7 @@ const SegmentedControl = /*#__PURE__*/forwardRef((props, ref) => {
117
112
  }],
118
113
  ...theme.shadows.sm
119
114
  }]
120
- }), segments.map(segment => {
115
+ }) : null, segments.map(segment => {
121
116
  const isActive = segment.value === value;
122
117
  return /*#__PURE__*/_jsxs(Pressable, {
123
118
  onPress: () => handlePress(segment),
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { Animated, Easing, StyleSheet, View } from 'react-native';
5
- import { useTheme, createAnimatedValue } from "../../theme/index.js";
5
+ import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
6
  import { Responsive } from "../../utils/index.js";
7
7
  import { useSkeletonDefaults } from "./SkeletonProvider.js";
8
8
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -50,7 +50,7 @@ const Skeleton = ({
50
50
  const progress = useRef(createAnimatedValue(0)).current;
51
51
  const [containerWidth, setContainerWidth] = useState(0);
52
52
  useEffect(() => {
53
- progress.setValue(0);
53
+ setNativeValue(progress, 0);
54
54
  const duration = resolvedVariant === 'pulse' ? PULSE_DURATION : SPEED_DURATION[resolvedSpeed];
55
55
  const animation = resolvedVariant === 'pulse' ? Animated.loop(Animated.sequence([Animated.timing(progress, {
56
56
  toValue: 1,
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { Animated, Easing, PanResponder, Platform, Pressable, StyleSheet, Text, View } from 'react-native';
5
- import { useTheme, createAnimatedValue } from "../../theme/index.js";
5
+ import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
6
6
  import { triggerHaptic } from "../../utils/index.js";
7
7
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  const SIZE_MAP = {
@@ -105,14 +105,14 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
105
105
  highRef.current = hi;
106
106
  lastReportedLow.current = lo;
107
107
  lastReportedHigh.current = hi;
108
- lowX.setValue(valueToPx(lo, trackWidth));
109
- highX.setValue(valueToPx(hi, trackWidth));
108
+ setNativeValue(lowX, valueToPx(lo, trackWidth));
109
+ setNativeValue(highX, valueToPx(hi, trackWidth));
110
110
  } else {
111
111
  const v = props.value;
112
112
  lowRef.current = v;
113
113
  highRef.current = v;
114
114
  lastReportedLow.current = v;
115
- lowX.setValue(valueToPx(v, trackWidth));
115
+ setNativeValue(lowX, valueToPx(v, trackWidth));
116
116
  }
117
117
  // We intentionally listen to props.value across both shapes via JSON; ESLint is fine.
118
118
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -125,10 +125,10 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
125
125
  const [v0, v1] = props.value;
126
126
  const lo = Math.min(v0, v1);
127
127
  const hi = Math.max(v0, v1);
128
- lowX.setValue(valueToPx(lo, w));
129
- highX.setValue(valueToPx(hi, w));
128
+ setNativeValue(lowX, valueToPx(lo, w));
129
+ setNativeValue(highX, valueToPx(hi, w));
130
130
  } else {
131
- lowX.setValue(valueToPx(props.value, w));
131
+ setNativeValue(lowX, valueToPx(props.value, w));
132
132
  }
133
133
  },
134
134
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -179,7 +179,7 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
179
179
  lastReportedLow.current = nextValue;
180
180
  }
181
181
  lowRef.current = nextValue;
182
- lowX.setValue(valueToPx(nextValue, trackWidth));
182
+ setNativeValue(lowX, valueToPx(nextValue, trackWidth));
183
183
  fireChange(nextValue, highRef.current);
184
184
  } else {
185
185
  if (isRange && nextValue < lowRef.current) nextValue = lowRef.current;
@@ -188,7 +188,7 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
188
188
  lastReportedHigh.current = nextValue;
189
189
  }
190
190
  highRef.current = nextValue;
191
- highX.setValue(valueToPx(nextValue, trackWidth));
191
+ setNativeValue(highX, valueToPx(nextValue, trackWidth));
192
192
  fireChange(lowRef.current, nextValue);
193
193
  }
194
194
  },
@@ -218,7 +218,7 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
218
218
  toValue: valueToPx(next, trackWidth),
219
219
  duration: theme.motion.duration.fast,
220
220
  easing: Easing.out(Easing.cubic),
221
- useNativeDriver: false
221
+ useNativeDriver: true
222
222
  }).start();
223
223
  fireChange(next, next);
224
224
  }
@@ -233,7 +233,7 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
233
233
  if (nextLow !== lowRef.current) {
234
234
  lowRef.current = nextLow;
235
235
  lastReportedLow.current = nextLow;
236
- lowX.setValue(valueToPx(nextLow, trackWidth));
236
+ setNativeValue(lowX, valueToPx(nextLow, trackWidth));
237
237
  triggerHaptic('selection');
238
238
  fireChange(nextLow, highRef.current);
239
239
  }
@@ -242,7 +242,7 @@ const Slider = /*#__PURE__*/forwardRef((props, ref) => {
242
242
  if (next !== lowRef.current) {
243
243
  lowRef.current = next;
244
244
  lastReportedLow.current = next;
245
- lowX.setValue(valueToPx(next, trackWidth));
245
+ setNativeValue(lowX, valueToPx(next, trackWidth));
246
246
  triggerHaptic('selection');
247
247
  fireChange(next, next);
248
248
  }