@webority-technologies/mobile 0.0.22 → 0.0.23

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 (181) hide show
  1. package/lib/commonjs/components/Accordion/Accordion.js +4 -2
  2. package/lib/commonjs/components/Avatar/Avatar.js +4 -2
  3. package/lib/commonjs/components/Badge/Badge.js +5 -5
  4. package/lib/commonjs/components/Banner/Banner.js +8 -4
  5. package/lib/commonjs/components/BottomNavigation/BottomNavigation.js +6 -4
  6. package/lib/commonjs/components/BottomSheet/BottomSheet.js +8 -9
  7. package/lib/commonjs/components/Box/Box.js +162 -0
  8. package/lib/commonjs/components/Box/index.js +37 -0
  9. package/lib/commonjs/components/Button/Button.js +7 -7
  10. package/lib/commonjs/components/Carousel/Carousel.js +4 -2
  11. package/lib/commonjs/components/Checkbox/Checkbox.js +14 -5
  12. package/lib/commonjs/components/DatePicker/DatePicker.js +9 -7
  13. package/lib/commonjs/components/DateRangePicker/DateRangePicker.js +5 -2
  14. package/lib/commonjs/components/Dialog/Dialog.js +2 -2
  15. package/lib/commonjs/components/FieldBase/FieldBase.js +8 -4
  16. package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +13 -5
  17. package/lib/commonjs/components/FormField/FormField.js +61 -25
  18. package/lib/commonjs/components/Input/Input.js +41 -29
  19. package/lib/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +102 -0
  20. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +13 -0
  21. package/lib/commonjs/components/KeyboardToolbar/KeyboardToolbar.js +130 -0
  22. package/lib/commonjs/components/KeyboardToolbar/index.js +13 -0
  23. package/lib/commonjs/components/Modal/Modal.js +17 -6
  24. package/lib/commonjs/components/NumberInput/NumberInput.js +35 -28
  25. package/lib/commonjs/components/OTPInput/OTPInput.js +33 -18
  26. package/lib/commonjs/components/Radio/Radio.js +7 -5
  27. package/lib/commonjs/components/Radio/RadioGroup.js +10 -3
  28. package/lib/commonjs/components/SearchBar/SearchBar.js +4 -2
  29. package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +20 -10
  30. package/lib/commonjs/components/Select/Select.js +33 -32
  31. package/lib/commonjs/components/Skeleton/SkeletonContent.js +5 -2
  32. package/lib/commonjs/components/Slider/Slider.js +42 -26
  33. package/lib/commonjs/components/Spinner/Spinner.js +5 -5
  34. package/lib/commonjs/components/Switch/Switch.js +29 -16
  35. package/lib/commonjs/components/Tabs/Tabs.js +4 -2
  36. package/lib/commonjs/components/Text/Text.js +142 -0
  37. package/lib/commonjs/components/Text/index.js +13 -0
  38. package/lib/commonjs/components/TimePicker/TimePicker.js +10 -7
  39. package/lib/commonjs/components/Toast/Toast.js +22 -10
  40. package/lib/commonjs/components/Tooltip/Tooltip.js +6 -2
  41. package/lib/commonjs/components/index.js +135 -89
  42. package/lib/commonjs/form/FormContext.js +40 -0
  43. package/lib/commonjs/form/index.js +68 -0
  44. package/lib/commonjs/form/path.js +79 -0
  45. package/lib/commonjs/form/rules.js +67 -0
  46. package/lib/commonjs/form/types.js +2 -0
  47. package/lib/commonjs/form/useField.js +54 -0
  48. package/lib/commonjs/form/useForm.js +316 -0
  49. package/lib/commonjs/hooks/index.js +14 -0
  50. package/lib/commonjs/hooks/useControllableState.js +30 -0
  51. package/lib/commonjs/hooks/useReducedMotion.js +31 -0
  52. package/lib/commonjs/index.js +96 -11
  53. package/lib/commonjs/theme/ThemeContext.js +30 -2
  54. package/lib/commonjs/theme/tokens.js +12 -0
  55. package/lib/module/components/Accordion/Accordion.js +4 -2
  56. package/lib/module/components/Avatar/Avatar.js +4 -2
  57. package/lib/module/components/Badge/Badge.js +5 -5
  58. package/lib/module/components/Banner/Banner.js +8 -4
  59. package/lib/module/components/BottomNavigation/BottomNavigation.js +6 -4
  60. package/lib/module/components/BottomSheet/BottomSheet.js +8 -9
  61. package/lib/module/components/Box/Box.js +156 -0
  62. package/lib/module/components/Box/index.js +4 -0
  63. package/lib/module/components/Button/Button.js +7 -7
  64. package/lib/module/components/Carousel/Carousel.js +4 -2
  65. package/lib/module/components/Checkbox/Checkbox.js +14 -5
  66. package/lib/module/components/DatePicker/DatePicker.js +9 -7
  67. package/lib/module/components/DateRangePicker/DateRangePicker.js +5 -2
  68. package/lib/module/components/Dialog/Dialog.js +2 -2
  69. package/lib/module/components/FieldBase/FieldBase.js +8 -4
  70. package/lib/module/components/FloatingActionButton/FloatingActionButton.js +13 -5
  71. package/lib/module/components/FormField/FormField.js +62 -26
  72. package/lib/module/components/Input/Input.js +41 -29
  73. package/lib/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.js +98 -0
  74. package/lib/module/components/KeyboardAwareScrollView/index.js +4 -0
  75. package/lib/module/components/KeyboardToolbar/KeyboardToolbar.js +125 -0
  76. package/lib/module/components/KeyboardToolbar/index.js +4 -0
  77. package/lib/module/components/Modal/Modal.js +17 -6
  78. package/lib/module/components/NumberInput/NumberInput.js +30 -23
  79. package/lib/module/components/OTPInput/OTPInput.js +30 -15
  80. package/lib/module/components/Radio/Radio.js +7 -5
  81. package/lib/module/components/Radio/RadioGroup.js +10 -3
  82. package/lib/module/components/SearchBar/SearchBar.js +4 -2
  83. package/lib/module/components/SegmentedControl/SegmentedControl.js +20 -10
  84. package/lib/module/components/Select/Select.js +33 -32
  85. package/lib/module/components/Skeleton/SkeletonContent.js +5 -2
  86. package/lib/module/components/Slider/Slider.js +42 -26
  87. package/lib/module/components/Spinner/Spinner.js +5 -5
  88. package/lib/module/components/Switch/Switch.js +29 -16
  89. package/lib/module/components/Tabs/Tabs.js +4 -2
  90. package/lib/module/components/Text/Text.js +138 -0
  91. package/lib/module/components/Text/index.js +4 -0
  92. package/lib/module/components/TimePicker/TimePicker.js +10 -7
  93. package/lib/module/components/Toast/Toast.js +22 -10
  94. package/lib/module/components/Tooltip/Tooltip.js +6 -2
  95. package/lib/module/components/index.js +4 -0
  96. package/lib/module/form/FormContext.js +32 -0
  97. package/lib/module/form/index.js +12 -0
  98. package/lib/module/form/path.js +72 -0
  99. package/lib/module/form/rules.js +52 -0
  100. package/lib/module/form/types.js +2 -0
  101. package/lib/module/form/useField.js +49 -0
  102. package/lib/module/form/useForm.js +312 -0
  103. package/lib/module/hooks/index.js +2 -0
  104. package/lib/module/hooks/useControllableState.js +26 -0
  105. package/lib/module/hooks/useReducedMotion.js +27 -0
  106. package/lib/module/index.js +3 -1
  107. package/lib/module/theme/ThemeContext.js +30 -2
  108. package/lib/module/theme/tokens.js +12 -0
  109. package/lib/typescript/commonjs/components/BottomNavigation/BottomNavigation.d.ts +1 -1
  110. package/lib/typescript/commonjs/components/Box/Box.d.ts +60 -0
  111. package/lib/typescript/commonjs/components/Box/index.d.ts +3 -0
  112. package/lib/typescript/commonjs/components/Button/Button.d.ts +1 -1
  113. package/lib/typescript/commonjs/components/Checkbox/Checkbox.d.ts +3 -2
  114. package/lib/typescript/commonjs/components/DatePicker/DatePicker.d.ts +3 -3
  115. package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +2 -2
  116. package/lib/typescript/commonjs/components/FormField/FormField.d.ts +13 -2
  117. package/lib/typescript/commonjs/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +20 -0
  118. package/lib/typescript/commonjs/components/KeyboardAwareScrollView/index.d.ts +3 -0
  119. package/lib/typescript/commonjs/components/KeyboardToolbar/KeyboardToolbar.d.ts +29 -0
  120. package/lib/typescript/commonjs/components/KeyboardToolbar/index.d.ts +3 -0
  121. package/lib/typescript/commonjs/components/NumberInput/NumberInput.d.ts +3 -2
  122. package/lib/typescript/commonjs/components/OTPInput/OTPInput.d.ts +3 -2
  123. package/lib/typescript/commonjs/components/Radio/Radio.d.ts +2 -2
  124. package/lib/typescript/commonjs/components/Radio/RadioGroup.d.ts +3 -2
  125. package/lib/typescript/commonjs/components/SegmentedControl/SegmentedControl.d.ts +3 -2
  126. package/lib/typescript/commonjs/components/Slider/Slider.d.ts +6 -4
  127. package/lib/typescript/commonjs/components/Spinner/Spinner.d.ts +1 -1
  128. package/lib/typescript/commonjs/components/Switch/Switch.d.ts +3 -2
  129. package/lib/typescript/commonjs/components/Text/Text.d.ts +25 -0
  130. package/lib/typescript/commonjs/components/Text/index.d.ts +3 -0
  131. package/lib/typescript/commonjs/components/TimePicker/TimePicker.d.ts +3 -3
  132. package/lib/typescript/commonjs/components/index.d.ts +8 -0
  133. package/lib/typescript/commonjs/form/FormContext.d.ts +17 -0
  134. package/lib/typescript/commonjs/form/index.d.ts +9 -0
  135. package/lib/typescript/commonjs/form/path.d.ts +10 -0
  136. package/lib/typescript/commonjs/form/rules.d.ts +31 -0
  137. package/lib/typescript/commonjs/form/types.d.ts +94 -0
  138. package/lib/typescript/commonjs/form/useField.d.ts +27 -0
  139. package/lib/typescript/commonjs/form/useForm.d.ts +10 -0
  140. package/lib/typescript/commonjs/hooks/index.d.ts +3 -0
  141. package/lib/typescript/commonjs/hooks/useControllableState.d.ts +17 -0
  142. package/lib/typescript/commonjs/hooks/useReducedMotion.d.ts +8 -0
  143. package/lib/typescript/commonjs/index.d.ts +4 -2
  144. package/lib/typescript/commonjs/theme/types.d.ts +15 -0
  145. package/lib/typescript/module/components/BottomNavigation/BottomNavigation.d.ts +1 -1
  146. package/lib/typescript/module/components/Box/Box.d.ts +60 -0
  147. package/lib/typescript/module/components/Box/index.d.ts +3 -0
  148. package/lib/typescript/module/components/Button/Button.d.ts +1 -1
  149. package/lib/typescript/module/components/Checkbox/Checkbox.d.ts +3 -2
  150. package/lib/typescript/module/components/DatePicker/DatePicker.d.ts +3 -3
  151. package/lib/typescript/module/components/Dialog/Dialog.d.ts +2 -2
  152. package/lib/typescript/module/components/FormField/FormField.d.ts +13 -2
  153. package/lib/typescript/module/components/KeyboardAwareScrollView/KeyboardAwareScrollView.d.ts +20 -0
  154. package/lib/typescript/module/components/KeyboardAwareScrollView/index.d.ts +3 -0
  155. package/lib/typescript/module/components/KeyboardToolbar/KeyboardToolbar.d.ts +29 -0
  156. package/lib/typescript/module/components/KeyboardToolbar/index.d.ts +3 -0
  157. package/lib/typescript/module/components/NumberInput/NumberInput.d.ts +3 -2
  158. package/lib/typescript/module/components/OTPInput/OTPInput.d.ts +3 -2
  159. package/lib/typescript/module/components/Radio/Radio.d.ts +2 -2
  160. package/lib/typescript/module/components/Radio/RadioGroup.d.ts +3 -2
  161. package/lib/typescript/module/components/SegmentedControl/SegmentedControl.d.ts +3 -2
  162. package/lib/typescript/module/components/Slider/Slider.d.ts +6 -4
  163. package/lib/typescript/module/components/Spinner/Spinner.d.ts +1 -1
  164. package/lib/typescript/module/components/Switch/Switch.d.ts +3 -2
  165. package/lib/typescript/module/components/Text/Text.d.ts +25 -0
  166. package/lib/typescript/module/components/Text/index.d.ts +3 -0
  167. package/lib/typescript/module/components/TimePicker/TimePicker.d.ts +3 -3
  168. package/lib/typescript/module/components/index.d.ts +8 -0
  169. package/lib/typescript/module/form/FormContext.d.ts +17 -0
  170. package/lib/typescript/module/form/index.d.ts +9 -0
  171. package/lib/typescript/module/form/path.d.ts +10 -0
  172. package/lib/typescript/module/form/rules.d.ts +31 -0
  173. package/lib/typescript/module/form/types.d.ts +94 -0
  174. package/lib/typescript/module/form/useField.d.ts +27 -0
  175. package/lib/typescript/module/form/useForm.d.ts +10 -0
  176. package/lib/typescript/module/hooks/index.d.ts +3 -0
  177. package/lib/typescript/module/hooks/useControllableState.d.ts +17 -0
  178. package/lib/typescript/module/hooks/useReducedMotion.d.ts +8 -0
  179. package/lib/typescript/module/index.d.ts +4 -2
  180. package/lib/typescript/module/theme/types.d.ts +15 -0
  181. package/package.json +1 -1
@@ -437,12 +437,14 @@ const ThumbnailItem = ({
437
437
  }) => {
438
438
  const scale = useRef(createAnimatedValue(isActive ? 1.1 : 1)).current;
439
439
  useEffect(() => {
440
- Animated.spring(scale, {
440
+ const anim = Animated.spring(scale, {
441
441
  toValue: isActive ? 1.1 : 1,
442
442
  useNativeDriver: true,
443
443
  friction: 6,
444
444
  tension: 80
445
- }).start();
445
+ });
446
+ anim.start();
447
+ return () => anim.stop();
446
448
  }, [isActive, scale]);
447
449
  const content = useMemo(() => {
448
450
  if (renderThumbnail) return renderThumbnail(item, index);
@@ -3,6 +3,7 @@
3
3
  import React, { forwardRef, 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 { useControllableState } from "../../hooks/index.js";
6
7
  import { triggerHaptic } from "../../utils/hapticUtils.js";
7
8
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
9
  const sizeMap = {
@@ -26,6 +27,7 @@ const toneColor = (theme, tone) => {
26
27
  const Checkbox = /*#__PURE__*/forwardRef((props, ref) => {
27
28
  const {
28
29
  checked,
30
+ defaultChecked,
29
31
  onChange,
30
32
  indeterminate = false,
31
33
  disabled = false,
@@ -43,22 +45,29 @@ const Checkbox = /*#__PURE__*/forwardRef((props, ref) => {
43
45
  testID,
44
46
  ...rest
45
47
  } = props;
48
+ const [current, setCurrent] = useControllableState({
49
+ value: checked,
50
+ defaultValue: defaultChecked ?? false,
51
+ onChange
52
+ });
46
53
  const theme = useTheme();
47
54
  const styles = useMemo(() => buildStyles(theme), [theme]);
48
55
  const boxSize = theme.components.checkbox?.[size]?.boxSize ?? sizeMap[size];
49
56
  const checkboxBorderWidth = theme.colors.border.width;
50
57
  const checkboxLabelGap = theme.components.checkbox?.labelGap ?? 10;
51
58
  const fillColor = toneColor(theme, tone);
52
- const isActive = checked || indeterminate;
59
+ const isActive = current || indeterminate;
53
60
  const progress = useRef(createAnimatedValue(isActive ? 1 : 0)).current;
54
61
  useEffect(() => {
55
- Animated.spring(progress, {
62
+ const anim = Animated.spring(progress, {
56
63
  toValue: isActive ? 1 : 0,
57
64
  damping: theme.motion.spring.snappy.damping,
58
65
  stiffness: theme.motion.spring.snappy.stiffness,
59
66
  mass: theme.motion.spring.snappy.mass,
60
67
  useNativeDriver: true
61
- }).start();
68
+ });
69
+ anim.start();
70
+ return () => anim.stop();
62
71
  }, [isActive, progress, theme.motion.spring.snappy]);
63
72
  const scale = progress.interpolate({
64
73
  inputRange: [0, 1],
@@ -67,7 +76,7 @@ const Checkbox = /*#__PURE__*/forwardRef((props, ref) => {
67
76
  const handlePress = event => {
68
77
  if (disabled) return;
69
78
  if (haptic !== false && theme.components.checkbox?.pressHaptic) triggerHaptic(haptic);
70
- onChange(!checked);
79
+ setCurrent(!current);
71
80
  rest.onPressOut?.(event);
72
81
  };
73
82
  const accessibleLabel = accessibilityLabel ?? label;
@@ -84,7 +93,7 @@ const Checkbox = /*#__PURE__*/forwardRef((props, ref) => {
84
93
  accessibilityRole: "checkbox",
85
94
  accessibilityLabel: accessibleLabel,
86
95
  accessibilityState: {
87
- checked: indeterminate ? 'mixed' : checked,
96
+ checked: indeterminate ? 'mixed' : current,
88
97
  disabled
89
98
  },
90
99
  testID: testID,
@@ -10,14 +10,14 @@ import { PickerTrigger } from "../PickerTrigger/index.js";
10
10
  /**
11
11
  * DatePicker operates in two modes:
12
12
  *
13
- * 1. **Controlled-modal mode** — pass `visible` (plus `onSelect` / `onClose`)
13
+ * 1. **Controlled-modal mode** — pass `visible` (plus `onChange` / `onClose`)
14
14
  * and own the open state externally. The component renders only the modal.
15
15
  * 2. **Trigger mode** — omit `visible`. The component renders a built-in
16
16
  * PickerTrigger field (label / value / placeholder / chevron / clear /
17
17
  * helper / error / size / variant) and manages its own open state. The
18
18
  * field opens the modal on press and closes it on confirm/cancel.
19
19
  *
20
- * `onSelect` / `onClose` are typed optional to support trigger-only usage
20
+ * `onChange` / `onClose` are typed optional to support trigger-only usage
21
21
  * where the consumer may not need either callback. In controlled-modal mode
22
22
  * they remain semantically required.
23
23
  */
@@ -99,7 +99,7 @@ const buildDecadeCells = anchorYear => {
99
99
  const DatePicker = props => {
100
100
  const {
101
101
  value,
102
- onSelect,
102
+ onChange,
103
103
  onClose,
104
104
  minDate,
105
105
  maxDate,
@@ -174,7 +174,7 @@ const DatePicker = props => {
174
174
  if (open) {
175
175
  backdrop.setValue(0);
176
176
  setNativeValue(sheet, 0);
177
- Animated.parallel([Animated.timing(backdrop, {
177
+ const anim = Animated.parallel([Animated.timing(backdrop, {
178
178
  toValue: 1,
179
179
  duration: theme.motion.duration.normal,
180
180
  easing: Easing.out(Easing.cubic),
@@ -185,7 +185,9 @@ const DatePicker = props => {
185
185
  stiffness: theme.motion.spring.gentle.stiffness,
186
186
  mass: theme.motion.spring.gentle.mass,
187
187
  useNativeDriver: true
188
- })]).start();
188
+ })]);
189
+ anim.start();
190
+ return () => anim.stop();
189
191
  }
190
192
  }, [open, mode, backdrop, sheet, theme.motion]);
191
193
  const disabledIsoSet = useMemo(() => {
@@ -385,9 +387,9 @@ const DatePicker = props => {
385
387
  const handleConfirm = useCallback(() => {
386
388
  if (!pendingDate) return;
387
389
  if (theme.components.datePicker?.haptic) triggerHaptic('notificationSuccess');
388
- onSelect?.(pendingDate);
390
+ onChange?.(pendingDate);
389
391
  handleClose();
390
- }, [handleClose, onSelect, pendingDate, theme.components.datePicker]);
392
+ }, [handleClose, onChange, pendingDate, theme.components.datePicker]);
391
393
  const sheetTranslate = sheet.interpolate({
392
394
  inputRange: [0, 1],
393
395
  outputRange: [320, 0]
@@ -147,10 +147,11 @@ const DateRangePicker = /*#__PURE__*/forwardRef((props, ref) => {
147
147
  // Modal open / close animation. Backdrop opacity uses JS driver — see
148
148
  // Modal.tsx for the Fabric reason. Sheet transform stays native.
149
149
  useEffect(() => {
150
+ let anim;
150
151
  if (open) {
151
152
  backdrop.setValue(0);
152
153
  setNativeValue(sheet, 0);
153
- Animated.parallel([Animated.timing(backdrop, {
154
+ anim = Animated.parallel([Animated.timing(backdrop, {
154
155
  toValue: 1,
155
156
  duration: theme.motion.duration.normal,
156
157
  easing: Easing.out(Easing.cubic),
@@ -161,8 +162,10 @@ const DateRangePicker = /*#__PURE__*/forwardRef((props, ref) => {
161
162
  stiffness: theme.motion.spring.gentle.stiffness,
162
163
  mass: theme.motion.spring.gentle.mass,
163
164
  useNativeDriver: true
164
- })]).start();
165
+ })]);
166
+ anim.start();
165
167
  }
168
+ return () => anim?.stop();
166
169
  }, [open, backdrop, sheet, theme.motion]);
167
170
  const disabledIsoSet = useMemo(() => {
168
171
  const set = new Set();
@@ -144,7 +144,7 @@ const tintForVariant = (theme, variant) => {
144
144
  base: theme.colors.warning,
145
145
  muted: theme.colors.background.secondary
146
146
  };
147
- case 'danger':
147
+ case 'error':
148
148
  return {
149
149
  base: theme.colors.error,
150
150
  muted: theme.colors.background.secondary
@@ -175,7 +175,7 @@ const actionStyleFor = (theme, tone, variant) => {
175
175
  textColor: theme.colors.text.inverse
176
176
  };
177
177
  }
178
- case 'danger':
178
+ case 'error':
179
179
  return {
180
180
  backgroundColor: theme.colors.error,
181
181
  borderColor: 'transparent',
@@ -286,20 +286,24 @@ export const FieldBase = props => {
286
286
  const focusAnim = useRef(createAnimatedValue(focused ? 1 : 0)).current;
287
287
  const errorAnim = useRef(createAnimatedValue(error ? 1 : 0)).current;
288
288
  useEffect(() => {
289
- Animated.timing(focusAnim, {
289
+ const anim = Animated.timing(focusAnim, {
290
290
  toValue: focused ? 1 : 0,
291
291
  duration: theme.motion.duration.fast,
292
292
  easing: Easing.bezier(...theme.motion.easing.standard),
293
293
  useNativeDriver: false
294
- }).start();
294
+ });
295
+ anim.start();
296
+ return () => anim.stop();
295
297
  }, [focused, focusAnim, theme.motion.duration.fast, theme.motion.easing.standard]);
296
298
  useEffect(() => {
297
- Animated.timing(errorAnim, {
299
+ const anim = Animated.timing(errorAnim, {
298
300
  toValue: error ? 1 : 0,
299
301
  duration: theme.motion.duration.fast,
300
302
  easing: Easing.bezier(...theme.motion.easing.standard),
301
303
  useNativeDriver: false
302
- }).start();
304
+ });
305
+ anim.start();
306
+ return () => anim.stop();
303
307
  }, [error, errorAnim, theme.motion.duration.fast, theme.motion.easing.standard]);
304
308
 
305
309
  // Resting border + fill (pre-animation). Error wins over focus when both
@@ -90,12 +90,14 @@ const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
90
90
  setNativeValue(hideAnim, 0);
91
91
  return;
92
92
  }
93
- Animated.timing(hideAnim, {
93
+ const anim = Animated.timing(hideAnim, {
94
94
  toValue: isScrolling ? 1 : 0,
95
95
  duration: theme.motion.duration.normal,
96
96
  easing: Easing.bezier(...theme.motion.easing.standard),
97
97
  useNativeDriver: true
98
- }).start();
98
+ });
99
+ anim.start();
100
+ return () => anim.stop();
99
101
  }, [hideOnScroll, isScrolling, hideAnim, theme.motion.duration.normal, theme.motion.easing.standard]);
100
102
  const hideTranslateY = hideAnim.interpolate({
101
103
  inputRange: [0, 1],
@@ -271,12 +273,13 @@ const FloatingActionButtonGroup = props => {
271
273
  // eslint-disable-next-line react-hooks/exhaustive-deps
272
274
  }, [actions.length]);
273
275
  useEffect(() => {
274
- Animated.timing(progress, {
276
+ const progressAnim = Animated.timing(progress, {
275
277
  toValue: isOpen ? 1 : 0,
276
278
  duration: theme.motion.duration.normal,
277
279
  easing: Easing.bezier(...theme.motion.easing.standard),
278
280
  useNativeDriver: true
279
- }).start();
281
+ });
282
+ progressAnim.start();
280
283
  const animations = itemAnims.current.map(value => Animated.timing(value, {
281
284
  toValue: isOpen ? 1 : 0,
282
285
  duration: theme.motion.duration.normal,
@@ -285,7 +288,12 @@ const FloatingActionButtonGroup = props => {
285
288
  }));
286
289
  // Reverse stagger order on close so items closest to primary collapse last
287
290
  const ordered = isOpen ? animations : [...animations].reverse();
288
- Animated.stagger(staggerMs, ordered).start();
291
+ const staggerAnim = Animated.stagger(staggerMs, ordered);
292
+ staggerAnim.start();
293
+ return () => {
294
+ progressAnim.stop();
295
+ staggerAnim.stop();
296
+ };
289
297
  }, [isOpen, progress, theme.motion.duration.normal, theme.motion.easing.standard, actions.length, itemAnimsVersion, staggerMs]);
290
298
  const setOpen = useCallback(next => {
291
299
  if (pressHaptic) triggerHaptic('impactLight');
@@ -3,13 +3,15 @@
3
3
  import React, { forwardRef, useEffect, useMemo, useRef } from 'react';
4
4
  import { Animated, StyleSheet, Text, View } from 'react-native';
5
5
  import { fontFor, useTheme, createAnimatedValue } from "../../theme/index.js";
6
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
6
+ import { useOptionalFormContext } from "../../form/FormContext.js";
7
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
8
  const FormField = /*#__PURE__*/forwardRef((props, ref) => {
8
9
  const {
9
10
  label,
10
11
  helperText,
11
12
  error,
12
13
  required = false,
14
+ name,
13
15
  children,
14
16
  layout = 'stacked',
15
17
  labelStyle,
@@ -22,19 +24,32 @@ const FormField = /*#__PURE__*/forwardRef((props, ref) => {
22
24
  } = props;
23
25
  const theme = useTheme();
24
26
  const styles = useMemo(() => buildStyles(theme), [theme]);
25
- const errorOpacity = useRef(createAnimatedValue(error ? 1 : 0)).current;
27
+ const formCtx = useOptionalFormContext();
28
+ const isConnected = name != null && formCtx != null;
29
+ const field = isConnected ? formCtx.getFieldProps(name) : null;
30
+ const isRenderProp = typeof children === 'function';
31
+
32
+ // Error the FormField renders itself. When connected to an element child the
33
+ // child (Input/Select etc.) shows the injected error, so FormField stays
34
+ // quiet to avoid a duplicate message; the render-prop form keeps it here.
35
+ const ownError = isConnected ? isRenderProp ? field?.error : undefined : error;
36
+ // Error used purely for assistive tech — always reflects the real error.
37
+ const a11yError = isConnected ? field?.error : error;
38
+ const errorOpacity = useRef(createAnimatedValue(ownError ? 1 : 0)).current;
26
39
  useEffect(() => {
27
- Animated.timing(errorOpacity, {
28
- toValue: error ? 1 : 0,
29
- duration: 150,
40
+ const anim = Animated.timing(errorOpacity, {
41
+ toValue: ownError ? 1 : 0,
42
+ duration: theme.motion.duration.fast,
30
43
  useNativeDriver: true
31
- }).start();
32
- }, [error, errorOpacity]);
44
+ });
45
+ anim.start();
46
+ return () => anim.stop();
47
+ }, [ownError, errorOpacity, theme.motion.duration.fast]);
33
48
  const computedAccessibilityLabel = useMemo(() => {
34
49
  if (accessibilityLabel) return accessibilityLabel;
35
50
  if (!label) return undefined;
36
- return error ? `${label}, error: ${error}` : label;
37
- }, [accessibilityLabel, label, error]);
51
+ return a11yError ? `${label}, error: ${a11yError}` : label;
52
+ }, [accessibilityLabel, label, a11yError]);
38
53
  const labelNode = label ? /*#__PURE__*/_jsxs(Text, {
39
54
  style: [styles.label, layout === 'inline' ? styles.labelInline : null, labelStyle],
40
55
  children: [label, required ? /*#__PURE__*/_jsx(Text, {
@@ -43,39 +58,60 @@ const FormField = /*#__PURE__*/forwardRef((props, ref) => {
43
58
  }) : null]
44
59
  }) : null;
45
60
  const inputContainerStyle = layout === 'inline' ? styles.inputInline : styles.inputStacked;
46
- const showError = Boolean(error);
61
+ const showError = Boolean(ownError);
47
62
  const showHelper = !showError && Boolean(helperText);
63
+ const renderedChildren = (() => {
64
+ if (isRenderProp) {
65
+ return field ? children(field) : null;
66
+ }
67
+ if (isConnected && field && /*#__PURE__*/React.isValidElement(children)) {
68
+ const child = children;
69
+ const childOnChangeText = child.props.onChangeText;
70
+ const childOnBlur = child.props.onBlur;
71
+ const childOnFocus = child.props.onFocus;
72
+ return /*#__PURE__*/React.cloneElement(child, {
73
+ value: field.value,
74
+ onChangeText: text => {
75
+ field.onChangeText(text);
76
+ childOnChangeText?.(text);
77
+ },
78
+ onBlur: e => {
79
+ field.onBlur();
80
+ childOnBlur?.(e);
81
+ },
82
+ onFocus: e => {
83
+ field.onFocus();
84
+ childOnFocus?.(e);
85
+ },
86
+ error: field.error,
87
+ ref: node => formCtx.registerField(name, node ?? null)
88
+ });
89
+ }
90
+ return children;
91
+ })();
48
92
  return /*#__PURE__*/_jsxs(View, {
49
93
  ref: ref,
50
94
  style: [layout === 'inline' ? styles.rootInline : styles.rootStacked, containerStyle],
51
95
  accessibilityLabel: computedAccessibilityLabel
52
- // Surface the invalid state to assistive tech via the hint. RN's
53
- // AccessibilityState type does not expose an `invalid` field, so the
54
- // error message itself is folded into the label and hint.
96
+ // RN's AccessibilityState type has no `invalid` field, so the error is
97
+ // folded into the label and surfaced again via the hint.
55
98
  ,
56
- accessibilityHint: error ? error : undefined,
99
+ accessibilityHint: a11yError ? a11yError : undefined,
57
100
  testID: testID,
58
- children: [layout === 'inline' ? /*#__PURE__*/_jsxs(_Fragment, {
59
- children: [labelNode, /*#__PURE__*/_jsx(View, {
60
- style: [inputContainerStyle, inputContainerStyleProp],
61
- children: children
62
- })]
63
- }) : /*#__PURE__*/_jsxs(_Fragment, {
64
- children: [labelNode, /*#__PURE__*/_jsx(View, {
65
- style: [inputContainerStyle, inputContainerStyleProp],
66
- children: children
67
- })]
101
+ children: [labelNode, /*#__PURE__*/_jsx(View, {
102
+ style: [inputContainerStyle, inputContainerStyleProp],
103
+ children: renderedChildren
68
104
  }), showHelper ? /*#__PURE__*/_jsx(Text, {
69
105
  style: [styles.helper, helperStyle],
70
106
  numberOfLines: 2,
71
107
  children: helperText
72
- }) : null, error ? /*#__PURE__*/_jsx(Animated.Text, {
108
+ }) : null, ownError ? /*#__PURE__*/_jsx(Animated.Text, {
73
109
  style: [styles.error, {
74
110
  opacity: errorOpacity
75
111
  }, errorStyle],
76
112
  numberOfLines: 2,
77
113
  accessibilityLiveRegion: "polite",
78
- children: error
114
+ children: ownError
79
115
  }) : null]
80
116
  });
81
117
  });
@@ -4,6 +4,7 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState }
4
4
  import { Animated, Easing, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
5
5
  import { createAnimatedValue, fontFor, setNativeValue, useTheme } from "../../theme/index.js";
6
6
  import { triggerHaptic } from "../../utils/index.js";
7
+ import { useReducedMotion } from "../../hooks/index.js";
7
8
  import { FieldBase, resolveFieldSize, resolveFieldTextStyle, resolveVariantColors } from "../FieldBase/FieldBase.js";
8
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
10
  // FieldBase carries every dimension except `multilineMinHeight` — Input owns
@@ -79,26 +80,31 @@ const Input = /*#__PURE__*/forwardRef((props, ref) => {
79
80
 
80
81
  // Floating label animation
81
82
  useEffect(() => {
82
- Animated.timing(labelAnim, {
83
+ const anim = Animated.timing(labelAnim, {
83
84
  toValue: shouldFloat ? 1 : 0,
84
85
  duration: theme.motion.duration.fast,
85
86
  easing: Easing.bezier(...theme.motion.easing.standard),
86
87
  useNativeDriver: false
87
- }).start();
88
+ });
89
+ anim.start();
90
+ return () => anim.stop();
88
91
  }, [shouldFloat, labelAnim, theme.motion.duration.fast, theme.motion.easing.standard]);
89
92
 
90
93
  // Helper / error fade
91
94
  useEffect(() => {
92
- Animated.timing(messageAnim, {
95
+ const anim = Animated.timing(messageAnim, {
93
96
  toValue: hasError || Boolean(helperText) ? 1 : 0,
94
- duration: 150,
97
+ duration: theme.motion.duration.fast,
95
98
  easing: Easing.bezier(...theme.motion.easing.standard),
96
99
  useNativeDriver: true
97
- }).start();
98
- }, [hasError, helperText, messageAnim, theme.motion.easing.standard]);
100
+ });
101
+ anim.start();
102
+ return () => anim.stop();
103
+ }, [hasError, helperText, messageAnim, theme.motion.duration.fast, theme.motion.easing.standard]);
99
104
 
100
105
  // Error shake — off by default; consumers opt in via
101
106
  // theme.components.input.shakeOnError. The haptic ships with the shake.
107
+ const reduceMotion = useReducedMotion();
102
108
  const shakeOnError = theme.components.input?.shakeOnError ?? false;
103
109
  const prevErrorRef = useRef(hasError);
104
110
  useEffect(() => {
@@ -106,33 +112,39 @@ const Input = /*#__PURE__*/forwardRef((props, ref) => {
106
112
  prevErrorRef.current = hasError;
107
113
  return;
108
114
  }
115
+ let anim;
109
116
  if (hasError && !prevErrorRef.current) {
110
- setNativeValue(shakeAnim, 0);
111
- Animated.sequence([Animated.timing(shakeAnim, {
112
- toValue: 1,
113
- duration: 50,
114
- useNativeDriver: true
115
- }), Animated.timing(shakeAnim, {
116
- toValue: -1,
117
- duration: 50,
118
- useNativeDriver: true
119
- }), Animated.timing(shakeAnim, {
120
- toValue: 1,
121
- duration: 50,
122
- useNativeDriver: true
123
- }), Animated.timing(shakeAnim, {
124
- toValue: -1,
125
- duration: 50,
126
- useNativeDriver: true
127
- }), Animated.timing(shakeAnim, {
128
- toValue: 0,
129
- duration: 100,
130
- useNativeDriver: true
131
- })]).start();
117
+ // Reduce Motion: keep the error haptic, skip the shake.
118
+ if (!reduceMotion) {
119
+ setNativeValue(shakeAnim, 0);
120
+ anim = Animated.sequence([Animated.timing(shakeAnim, {
121
+ toValue: 1,
122
+ duration: 50,
123
+ useNativeDriver: true
124
+ }), Animated.timing(shakeAnim, {
125
+ toValue: -1,
126
+ duration: 50,
127
+ useNativeDriver: true
128
+ }), Animated.timing(shakeAnim, {
129
+ toValue: 1,
130
+ duration: 50,
131
+ useNativeDriver: true
132
+ }), Animated.timing(shakeAnim, {
133
+ toValue: -1,
134
+ duration: 50,
135
+ useNativeDriver: true
136
+ }), Animated.timing(shakeAnim, {
137
+ toValue: 0,
138
+ duration: 100,
139
+ useNativeDriver: true
140
+ })]);
141
+ anim.start();
142
+ }
132
143
  triggerHaptic('notificationError');
133
144
  }
134
145
  prevErrorRef.current = hasError;
135
- }, [hasError, shakeAnim, shakeOnError]);
146
+ return () => anim?.stop();
147
+ }, [hasError, shakeAnim, shakeOnError, reduceMotion]);
136
148
  const handleFocus = useCallback(e => {
137
149
  setIsFocused(true);
138
150
  onFocus?.(e);
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+
3
+ import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
4
+ import { findNodeHandle, Keyboard, Platform, ScrollView, TextInput } from 'react-native';
5
+ import { jsx as _jsx } from "react/jsx-runtime";
6
+ /**
7
+ * A ScrollView that keeps the focused input visible above the keyboard — no
8
+ * native dependency. On keyboard show it pads the content by the keyboard
9
+ * height and scrolls the focused TextInput so its bottom sits `extraScrollHeight`
10
+ * above the keyboard, but only when it would otherwise be covered (so already-
11
+ * visible fields don't jump). The classic measure-and-scroll approach, in pure RN.
12
+ */
13
+ const KeyboardAwareScrollView = /*#__PURE__*/forwardRef((props, ref) => {
14
+ const {
15
+ children,
16
+ extraScrollHeight = 24,
17
+ disabled = false,
18
+ contentContainerStyle,
19
+ onScroll,
20
+ onLayout,
21
+ ...rest
22
+ } = props;
23
+ const scrollRef = useRef(null);
24
+ const scrollYRef = useRef(0);
25
+ const viewportHeightRef = useRef(0);
26
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
27
+ const setRefs = useCallback(node => {
28
+ scrollRef.current = node;
29
+ if (typeof ref === 'function') ref(node);else if (ref) ref.current = node;
30
+ }, [ref]);
31
+ const scrollFocusedIntoView = useCallback(kbHeight => {
32
+ const scroll = scrollRef.current;
33
+ const scrollNode = scroll ? findNodeHandle(scroll) : null;
34
+ const stateApi = TextInput?.State;
35
+ const focused = stateApi?.currentlyFocusedInput?.();
36
+ if (!scroll || scrollNode == null || !focused?.measureLayout) return;
37
+ focused.measureLayout(scrollNode, (_left, top, _width, height) => {
38
+ const scrollY = scrollYRef.current;
39
+ const viewportH = viewportHeightRef.current;
40
+ if (viewportH <= 0) return;
41
+ const keyboardTop = viewportH - kbHeight - extraScrollHeight;
42
+ const fieldBottomInViewport = top - scrollY + height;
43
+ if (fieldBottomInViewport > keyboardTop) {
44
+ const delta = fieldBottomInViewport - keyboardTop;
45
+ scroll.scrollTo({
46
+ y: scrollY + delta,
47
+ animated: true
48
+ });
49
+ }
50
+ }, () => undefined);
51
+ }, [extraScrollHeight]);
52
+ useEffect(() => {
53
+ if (disabled) return undefined;
54
+ // iOS exposes the will* events (smoother); Android only fires did*.
55
+ const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
56
+ const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
57
+ let rafId;
58
+ const showSub = Keyboard.addListener(showEvent, e => {
59
+ const height = e.endCoordinates?.height ?? 0;
60
+ setKeyboardHeight(height);
61
+ // Wait a frame so the padding is applied before we scroll. Tracked so
62
+ // it can be cancelled on unmount (never runs against a torn-down tree).
63
+ if (rafId != null) cancelAnimationFrame(rafId);
64
+ rafId = requestAnimationFrame(() => scrollFocusedIntoView(height));
65
+ });
66
+ const hideSub = Keyboard.addListener(hideEvent, () => setKeyboardHeight(0));
67
+ return () => {
68
+ if (rafId != null) cancelAnimationFrame(rafId);
69
+ showSub.remove();
70
+ hideSub.remove();
71
+ };
72
+ }, [disabled, scrollFocusedIntoView]);
73
+ const handleScroll = useCallback(e => {
74
+ scrollYRef.current = e.nativeEvent.contentOffset.y;
75
+ onScroll?.(e);
76
+ }, [onScroll]);
77
+ const handleLayout = useCallback(e => {
78
+ viewportHeightRef.current = e.nativeEvent.layout.height;
79
+ onLayout?.(e);
80
+ }, [onLayout]);
81
+ return /*#__PURE__*/_jsx(ScrollView, {
82
+ ref: setRefs,
83
+ onScroll: handleScroll,
84
+ onLayout: handleLayout,
85
+ scrollEventThrottle: 16,
86
+ keyboardShouldPersistTaps: "handled",
87
+ keyboardDismissMode: Platform.OS === 'ios' ? 'interactive' : 'on-drag',
88
+ contentContainerStyle: [contentContainerStyle, {
89
+ paddingBottom: keyboardHeight
90
+ }],
91
+ ...rest,
92
+ children: children
93
+ });
94
+ });
95
+ KeyboardAwareScrollView.displayName = 'KeyboardAwareScrollView';
96
+ export { KeyboardAwareScrollView };
97
+ export default KeyboardAwareScrollView;
98
+ //# sourceMappingURL=KeyboardAwareScrollView.js.map
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export { KeyboardAwareScrollView } from "./KeyboardAwareScrollView.js";
4
+ //# sourceMappingURL=index.js.map