@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
@@ -39,6 +39,18 @@ const Toast = ({
39
39
  const translateX = useRef(createAnimatedValue(0)).current;
40
40
  const opacity = useRef(createAnimatedValue(0)).current;
41
41
  const dismissedRef = useRef(false);
42
+ const stackTranslate = position === 'top' ? index * stackOffset : -index * stackOffset;
43
+ const stackScale = Math.max(1 - index * stackScaleStep, stackMinScale);
44
+
45
+ // The stack offset rides on its own value so the enter spring (translateY)
46
+ // and the layout offset compose once instead of rebuilding a fresh
47
+ // `new Animated.Value` + `Animated.add` node on every render. setNativeValue
48
+ // keeps it in sync after the node has moved to the native driver.
49
+ const stackTranslateY = useRef(createAnimatedValue(stackTranslate)).current;
50
+ useEffect(() => {
51
+ setNativeValue(stackTranslateY, stackTranslate);
52
+ }, [stackTranslate, stackTranslateY]);
53
+ const composedTranslateY = useMemo(() => Animated.add(translateY, stackTranslateY), [translateY, stackTranslateY]);
42
54
  const dismiss = (animateOut = true) => {
43
55
  if (dismissedRef.current) return;
44
56
  dismissedRef.current = true;
@@ -57,7 +69,7 @@ const Toast = ({
57
69
  }
58
70
  };
59
71
  useEffect(() => {
60
- Animated.parallel([Animated.spring(translateY, {
72
+ const enter = Animated.parallel([Animated.spring(translateY, {
61
73
  toValue: 0,
62
74
  damping: enterSpringDamping,
63
75
  stiffness: enterSpringStiffness,
@@ -67,13 +79,17 @@ const Toast = ({
67
79
  toValue: 1,
68
80
  duration: theme.motion.duration.fast,
69
81
  useNativeDriver: true
70
- })]).start();
82
+ })]);
83
+ enter.start();
71
84
  const duration = toast.duration ?? defaultDurationMs;
72
85
  if (duration > 0) {
73
86
  const timer = setTimeout(() => dismiss(true), duration);
74
- return () => clearTimeout(timer);
87
+ return () => {
88
+ enter.stop();
89
+ clearTimeout(timer);
90
+ };
75
91
  }
76
- return undefined;
92
+ return () => enter.stop();
77
93
  // eslint-disable-next-line react-hooks/exhaustive-deps
78
94
  }, []);
79
95
  const panResponder = useMemo(() => PanResponder.create({
@@ -108,11 +124,7 @@ const Toast = ({
108
124
  })]).start();
109
125
  }
110
126
  }
111
- }),
112
- // eslint-disable-next-line react-hooks/exhaustive-deps
113
- []);
114
- const stackTranslate = position === 'top' ? index * stackOffset : -index * stackOffset;
115
- const stackScale = Math.max(1 - index * stackScaleStep, stackMinScale);
127
+ }), [translateX, opacity, swipeDismissThreshold, swipeVelocityThreshold, theme.motion.duration.fast, onDismiss, toast.id]);
116
128
  const handleActionPress = () => {
117
129
  toast.action?.onPress();
118
130
  dismiss(true);
@@ -152,7 +164,7 @@ const Toast = ({
152
164
  shadowColor: theme.shadows.md.shadowColor,
153
165
  opacity,
154
166
  transform: [{
155
- translateY: Animated.add(translateY, new Animated.Value(stackTranslate))
167
+ translateY: composedTranslateY
156
168
  }, {
157
169
  translateX
158
170
  }, {
@@ -102,6 +102,7 @@ const Tooltip = props => {
102
102
  }
103
103
  };
104
104
  }
105
+ let anim;
105
106
  if (visible) {
106
107
  measureAnchor();
107
108
  const enterAnims = [Animated.timing(opacity, {
@@ -118,7 +119,8 @@ const Tooltip = props => {
118
119
  useNativeDriver: true
119
120
  }));
120
121
  }
121
- Animated.parallel(enterAnims).start();
122
+ anim = Animated.parallel(enterAnims);
123
+ anim.start();
122
124
  if (trigger === 'press') {
123
125
  if (autoHideTimer.current) clearTimeout(autoHideTimer.current);
124
126
  autoHideTimer.current = setTimeout(() => {
@@ -138,13 +140,15 @@ const Tooltip = props => {
138
140
  useNativeDriver: true
139
141
  }));
140
142
  }
141
- Animated.parallel(exitAnims).start();
143
+ anim = Animated.parallel(exitAnims);
144
+ anim.start();
142
145
  if (autoHideTimer.current) {
143
146
  clearTimeout(autoHideTimer.current);
144
147
  autoHideTimer.current = null;
145
148
  }
146
149
  }
147
150
  return () => {
151
+ anim?.stop();
148
152
  if (autoHideTimer.current) {
149
153
  clearTimeout(autoHideTimer.current);
150
154
  autoHideTimer.current = null;
@@ -7,6 +7,10 @@ export { Banner } from "./Banner/index.js";
7
7
  export { BottomNavigation } from "./BottomNavigation/index.js";
8
8
  export { BottomSheet, useBottomSheet } from "./BottomSheet/index.js";
9
9
  export { Button } from "./Button/index.js";
10
+ export { Box, Stack, Row, Spacer } from "./Box/index.js";
11
+ export { Text } from "./Text/index.js";
12
+ export { KeyboardAwareScrollView } from "./KeyboardAwareScrollView/index.js";
13
+ export { KeyboardToolbar } from "./KeyboardToolbar/index.js";
10
14
  export { Card } from "./Card/index.js";
11
15
  export { Carousel } from "./Carousel/index.js";
12
16
  export { Checkbox } from "./Checkbox/index.js";
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ import React, { createContext, useContext } from 'react';
4
+ import { jsx as _jsx } from "react/jsx-runtime";
5
+ const FormContext = /*#__PURE__*/createContext(null);
6
+ /**
7
+ * Provides a {@link useForm} instance to descendants so `<FormField name>`,
8
+ * {@link useField} and {@link useFieldArray} can bind by name.
9
+ */
10
+ export const Form = ({
11
+ form,
12
+ children
13
+ }) => /*#__PURE__*/_jsx(FormContext.Provider, {
14
+ value: form,
15
+ children: children
16
+ });
17
+ Form.displayName = 'Form';
18
+
19
+ /** Read the surrounding form. Throws if there is no `<Form>` ancestor. */
20
+ export function useFormContext() {
21
+ const ctx = useContext(FormContext);
22
+ if (!ctx) {
23
+ throw new Error('useFormContext / useField / <FormField name> must be rendered inside a <Form> from @webority-technologies/mobile.');
24
+ }
25
+ return ctx;
26
+ }
27
+
28
+ /** Read the surrounding form, or null when there is no `<Form>` ancestor. */
29
+ export function useOptionalFormContext() {
30
+ return useContext(FormContext);
31
+ }
32
+ //# sourceMappingURL=FormContext.js.map
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ export { useForm } from "./useForm.js";
4
+ export { useField, useFieldArray } from "./useField.js";
5
+ export { Form, useFormContext, useOptionalFormContext } from "./FormContext.js";
6
+ export { getPath, setPath, deletePath } from "./path.js";
7
+
8
+ // Validation rule presets (the dependency-free Yup replacement):
9
+ // import { rules } from '@webority-technologies/mobile';
10
+ // validate: { email: [rules.required(), rules.email()] }
11
+ export * as rules from "./rules.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Immutable get/set/delete for dot- and bracket-notation field paths
5
+ * ("address.city", "phones[0]", "items[2].qty"). Lets a single flat `name`
6
+ * string address nested values and array items, so the form engine can treat
7
+ * every field uniformly regardless of how deep it lives in the value tree.
8
+ */
9
+
10
+ const tokenize = path => {
11
+ const tokens = [];
12
+ const re = /[^.[\]]+/g;
13
+ let m;
14
+ while ((m = re.exec(path)) !== null) {
15
+ const raw = m[0];
16
+ tokens.push(/^\d+$/.test(raw) ? Number(raw) : raw);
17
+ }
18
+ return tokens;
19
+ };
20
+ export const getPath = (obj, path) => {
21
+ const tokens = tokenize(path);
22
+ let cur = obj;
23
+ for (const t of tokens) {
24
+ if (cur == null || typeof cur !== 'object') return undefined;
25
+ cur = cur[t];
26
+ }
27
+ return cur;
28
+ };
29
+ export const setPath = (obj, path, value) => {
30
+ const tokens = tokenize(path);
31
+ if (tokens.length === 0) return obj;
32
+ const root = Array.isArray(obj) ? [...obj] : {
33
+ ...obj
34
+ };
35
+ let cur = root;
36
+ for (let i = 0; i < tokens.length - 1; i++) {
37
+ const t = tokens[i];
38
+ const next = tokens[i + 1];
39
+ const child = cur[t];
40
+ // Clone the existing branch so we never mutate the previous value tree;
41
+ // create the right container shape when the branch is missing.
42
+ const cloned = child != null && typeof child === 'object' ? Array.isArray(child) ? [...child] : {
43
+ ...child
44
+ } : typeof next === 'number' ? [] : {};
45
+ cur[t] = cloned;
46
+ cur = cloned;
47
+ }
48
+ cur[tokens[tokens.length - 1]] = value;
49
+ return root;
50
+ };
51
+ export const deletePath = (obj, path) => {
52
+ const tokens = tokenize(path);
53
+ if (tokens.length === 0) return obj;
54
+ const root = Array.isArray(obj) ? [...obj] : {
55
+ ...obj
56
+ };
57
+ let cur = root;
58
+ for (let i = 0; i < tokens.length - 1; i++) {
59
+ const t = tokens[i];
60
+ const child = cur[t];
61
+ if (child == null || typeof child !== 'object') return root;
62
+ const cloned = Array.isArray(child) ? [...child] : {
63
+ ...child
64
+ };
65
+ cur[t] = cloned;
66
+ cur = cloned;
67
+ }
68
+ const last = tokens[tokens.length - 1];
69
+ if (Array.isArray(cur) && typeof last === 'number') cur.splice(last, 1);else delete cur[last];
70
+ return root;
71
+ };
72
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Validation rule presets — the dependency-free replacement for Yup chains.
5
+ * Each factory returns a {@link ValidationRule}. Rules built on the project's
6
+ * `validators` module skip empty values (so `required()` owns emptiness and you
7
+ * don't get duplicate errors), and accept an optional custom message.
8
+ */
9
+ import { isEmail, isIndianMobile, isPhone, isUrl, isNumeric, isInteger, isAlphanumeric, isStrongPassword, isIndianPincode, isPan, isGstin, isAadhaar, isIfsc } from "../validators/index.js";
10
+ import { getPath } from "./path.js";
11
+ const isEmptyValue = v => v == null || v === '' || Array.isArray(v) && v.length === 0;
12
+ const fromPredicate = (predicate, defaultMsg) => message => value => isEmptyValue(value) || predicate(value) ? undefined : message ?? defaultMsg;
13
+ export const required = (message = 'This field is required') => value => {
14
+ // A required boolean (e.g. "accept terms" checkbox) must be true.
15
+ if (typeof value === 'boolean') return value ? undefined : message;
16
+ return isEmptyValue(value) ? message : undefined;
17
+ };
18
+ export const email = fromPredicate(isEmail, 'Enter a valid email');
19
+ export const mobile = fromPredicate(isIndianMobile, 'Enter a valid mobile number');
20
+ export const phone = fromPredicate(isPhone, 'Enter a valid phone number');
21
+ export const url = fromPredicate(isUrl, 'Enter a valid URL');
22
+ export const numeric = fromPredicate(isNumeric, 'Enter a valid number');
23
+ export const integer = fromPredicate(isInteger, 'Enter a whole number');
24
+ export const alphanumeric = fromPredicate(isAlphanumeric, 'Use letters and numbers only');
25
+ export const pincode = fromPredicate(isIndianPincode, 'Enter a valid 6-digit PIN code');
26
+ export const pan = fromPredicate(isPan, 'Enter a valid PAN');
27
+ export const gstin = fromPredicate(isGstin, 'Enter a valid GSTIN');
28
+ export const aadhaar = fromPredicate(isAadhaar, 'Enter a valid Aadhaar number');
29
+ export const ifsc = fromPredicate(isIfsc, 'Enter a valid IFSC code');
30
+ export const minLength = (length, message) => value => isEmptyValue(value) || String(value).length >= length ? undefined : message ?? `Must be at least ${length} characters`;
31
+ export const maxLength = (length, message) => value => value == null || String(value).length <= length ? undefined : message ?? `Must be at most ${length} characters`;
32
+ export const min = (n, message) => value => isEmptyValue(value) || Number(value) >= n ? undefined : message ?? `Must be at least ${n}`;
33
+ export const max = (n, message) => value => isEmptyValue(value) || Number(value) <= n ? undefined : message ?? `Must be at most ${n}`;
34
+ export const pattern = (regex, message = 'Invalid format') => value => isEmptyValue(value) || regex.test(String(value)) ? undefined : message;
35
+ export const oneOf = (allowed, message = 'Invalid selection') => value => isEmptyValue(value) || allowed.includes(value) ? undefined : message;
36
+
37
+ /** Cross-field equality (e.g. confirm-password). `otherField` is a field path. */
38
+ export const matches = (otherField, message = 'Values do not match') => (value, allValues) => value === getPath(allValues, otherField) ? undefined : message;
39
+ export const strongPassword = (rules, message = 'Password is too weak') => value => isEmptyValue(value) || isStrongPassword(value, rules) ? undefined : message;
40
+
41
+ /**
42
+ * Wrap any custom check. Return `true` (valid), `false` (invalid → default
43
+ * message), or a string (invalid → that message). Async is supported — return
44
+ * a Promise of the same.
45
+ */
46
+ export const custom = (fn, message = 'Invalid value') => async (value, allValues) => {
47
+ const result = await fn(value, allValues);
48
+ if (result === true) return undefined;
49
+ if (result === false) return message;
50
+ return result;
51
+ };
52
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ import { getPath } from "./path.js";
4
+ import { useFormContext } from "./FormContext.js";
5
+ /**
6
+ * Bind a single field by name. Returns everything an input needs; pick the
7
+ * handler your control uses:
8
+ * const email = useField('email');
9
+ * <Input value={email.value} onChangeText={email.onChangeText} onBlur={email.onBlur} error={email.error} />
10
+ * <Select value={country.value} onChange={country.onChange} error={country.error} />
11
+ * <Switch value={!!agree.value} onChange={agree.onChange} />
12
+ */
13
+ export function useField(name) {
14
+ const form = useFormContext();
15
+ return form.getFieldProps(name);
16
+ }
17
+ /**
18
+ * Manage a repeating list field (phone numbers, line items, etc.) by name.
19
+ * Each helper writes the whole array back through the form so validation and
20
+ * dirty-tracking stay correct.
21
+ */
22
+ export function useFieldArray(name) {
23
+ const form = useFormContext();
24
+ const fields = getPath(form.values, name) ?? [];
25
+ const set = next => form.setFieldValue(name, next);
26
+ return {
27
+ fields,
28
+ length: fields.length,
29
+ push: item => set([...fields, item]),
30
+ remove: index => set(fields.filter((_, i) => i !== index)),
31
+ insert: (index, item) => {
32
+ const copy = [...fields];
33
+ copy.splice(index, 0, item);
34
+ set(copy);
35
+ },
36
+ move: (from, to) => {
37
+ const copy = [...fields];
38
+ const [moved] = copy.splice(from, 1);
39
+ copy.splice(to, 0, moved);
40
+ set(copy);
41
+ },
42
+ replace: (index, item) => {
43
+ const copy = [...fields];
44
+ copy[index] = item;
45
+ set(copy);
46
+ }
47
+ };
48
+ }
49
+ //# sourceMappingURL=useField.js.map
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { getPath, setPath } from "./path.js";
5
+ // Loosely typed (Values -> any) so the generic schema from any useForm<Values>
6
+ // instance flows in without variance friction; callers re-apply Values at use.
7
+ const rulesFor = (validate, name) => {
8
+ if (!validate || typeof validate === 'function') return [];
9
+ const r = validate[name];
10
+ if (!r) return [];
11
+ return Array.isArray(r) ? r : [r];
12
+ };
13
+
14
+ /**
15
+ * Dependency-free form engine. Tracks values/errors/touched/dirty, runs
16
+ * synchronous and async validation (per-field rule maps or a form-level
17
+ * function), gates error display behind touched/submit, and on a failed submit
18
+ * focuses the first errored field. The returned object is rebuilt every render
19
+ * so `<Form>` consumers re-render on state changes.
20
+ */
21
+ export function useForm(config) {
22
+ const configRef = useRef(config);
23
+ configRef.current = config;
24
+ const initialRef = useRef(config.initialValues);
25
+ const [values, setValuesState] = useState(config.initialValues);
26
+ const [errors, setErrorsState] = useState({});
27
+ const [touched, setTouchedState] = useState({});
28
+ const [isSubmitting, setSubmitting] = useState(false);
29
+ const [isValidating, setValidating] = useState(false);
30
+ const [submitCount, setSubmitCount] = useState(0);
31
+
32
+ // Mirror of the latest state so handlers (incl. async) never read stale
33
+ // closures and sequential setFieldValue calls in one tick compound correctly.
34
+ const ref = useRef({
35
+ values,
36
+ errors,
37
+ touched,
38
+ submitCount
39
+ });
40
+ ref.current = {
41
+ values,
42
+ errors,
43
+ touched,
44
+ submitCount
45
+ };
46
+ const fieldNodes = useRef(new Map());
47
+ const fieldOrder = useRef([]);
48
+ const focusedFieldRef = useRef(null);
49
+ useEffect(() => {
50
+ if (!configRef.current.enableReinitialize) return;
51
+ const next = configRef.current.initialValues;
52
+ initialRef.current = next;
53
+ ref.current.values = next;
54
+ ref.current.errors = {};
55
+ ref.current.touched = {};
56
+ setValuesState(next);
57
+ setErrorsState({});
58
+ setTouchedState({});
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
+ }, [JSON.stringify(config.initialValues)]);
61
+ const validateField = useCallback(async name => {
62
+ const validate = configRef.current.validate;
63
+ const allValues = ref.current.values;
64
+ if (typeof validate === 'function') {
65
+ const map = (await validate(allValues)) ?? {};
66
+ return map[name] || undefined;
67
+ }
68
+ const value = getPath(allValues, name);
69
+ for (const rule of rulesFor(validate, name)) {
70
+ const res = await rule(value, allValues);
71
+ if (res) return res;
72
+ }
73
+ return undefined;
74
+ }, []);
75
+ const runAll = useCallback(async vals => {
76
+ const validate = configRef.current.validate;
77
+ if (!validate) return {};
78
+ if (typeof validate === 'function') return (await validate(vals)) ?? {};
79
+ const out = {};
80
+ await Promise.all(Object.keys(validate).map(async name => {
81
+ const value = getPath(vals, name);
82
+ for (const rule of rulesFor(validate, name)) {
83
+ const res = await rule(value, vals);
84
+ if (res) {
85
+ out[name] = res;
86
+ break;
87
+ }
88
+ }
89
+ }));
90
+ return out;
91
+ }, []);
92
+ const revalidateField = useCallback(async name => {
93
+ const err = await validateField(name);
94
+ setErrorsState(prev => {
95
+ if (prev[name] === err) return prev;
96
+ const next = {
97
+ ...prev
98
+ };
99
+ if (err) next[name] = err;else delete next[name];
100
+ ref.current.errors = next;
101
+ return next;
102
+ });
103
+ }, [validateField]);
104
+ const validate = useCallback(async () => {
105
+ setValidating(true);
106
+ const errs = await runAll(ref.current.values);
107
+ setValidating(false);
108
+ ref.current.errors = errs;
109
+ setErrorsState(errs);
110
+ return errs;
111
+ }, [runAll]);
112
+ const setFieldValue = useCallback((name, value, shouldValidate) => {
113
+ const next = setPath(ref.current.values, name, value);
114
+ ref.current.values = next;
115
+ setValuesState(next);
116
+ const cfg = configRef.current;
117
+ const willValidate = shouldValidate ?? (ref.current.touched[name] || ref.current.submitCount > 0 || cfg.validateOn === 'change');
118
+ if (willValidate) void revalidateField(name);
119
+ }, [revalidateField]);
120
+ const handleBlur = useCallback(name => {
121
+ setTouchedState(prev => {
122
+ if (prev[name]) return prev;
123
+ const next = {
124
+ ...prev,
125
+ [name]: true
126
+ };
127
+ ref.current.touched = next;
128
+ return next;
129
+ });
130
+ // Blur is the natural moment to surface a field's error.
131
+ void revalidateField(name);
132
+ }, [revalidateField]);
133
+ const setFieldTouched = useCallback((name, isTouched = true, shouldValidate) => {
134
+ const next = {
135
+ ...ref.current.touched,
136
+ [name]: isTouched
137
+ };
138
+ ref.current.touched = next;
139
+ setTouchedState(next);
140
+ if (shouldValidate ?? isTouched) void revalidateField(name);
141
+ }, [revalidateField]);
142
+ const setFieldError = useCallback((name, error) => {
143
+ setErrorsState(prev => {
144
+ const next = {
145
+ ...prev
146
+ };
147
+ if (error) next[name] = error;else delete next[name];
148
+ ref.current.errors = next;
149
+ return next;
150
+ });
151
+ }, []);
152
+ const setErrors = useCallback(errs => {
153
+ ref.current.errors = errs;
154
+ setErrorsState(errs);
155
+ }, []);
156
+ const setValues = useCallback((next, shouldValidate) => {
157
+ const resolved = typeof next === 'function' ? next(ref.current.values) : {
158
+ ...ref.current.values,
159
+ ...next
160
+ };
161
+ ref.current.values = resolved;
162
+ setValuesState(resolved);
163
+ if (shouldValidate) void validate();
164
+ }, [validate]);
165
+ const reset = useCallback(nextValues => {
166
+ const base = nextValues ? {
167
+ ...initialRef.current,
168
+ ...nextValues
169
+ } : initialRef.current;
170
+ initialRef.current = base;
171
+ ref.current.values = base;
172
+ ref.current.errors = {};
173
+ ref.current.touched = {};
174
+ ref.current.submitCount = 0;
175
+ setValuesState(base);
176
+ setErrorsState({});
177
+ setTouchedState({});
178
+ setSubmitCount(0);
179
+ }, []);
180
+ const registerField = useCallback((name, node) => {
181
+ if (node) {
182
+ fieldNodes.current.set(name, node);
183
+ if (!fieldOrder.current.includes(name)) fieldOrder.current.push(name);
184
+ } else {
185
+ fieldNodes.current.delete(name);
186
+ fieldOrder.current = fieldOrder.current.filter(n => n !== name);
187
+ }
188
+ }, []);
189
+ const handleFocus = useCallback(name => {
190
+ focusedFieldRef.current = name;
191
+ }, []);
192
+ const focusAdjacent = useCallback(direction => {
193
+ const order = fieldOrder.current;
194
+ const current = focusedFieldRef.current;
195
+ const index = current ? order.indexOf(current) : -1;
196
+ const target = order[index + direction];
197
+ if (!target) return;
198
+ focusedFieldRef.current = target;
199
+ fieldNodes.current.get(target)?.focus?.();
200
+ }, []);
201
+ const focusNext = useCallback(() => focusAdjacent(1), [focusAdjacent]);
202
+ const focusPrev = useCallback(() => focusAdjacent(-1), [focusAdjacent]);
203
+ const helpers = useMemo(() => ({
204
+ setErrors,
205
+ setFieldError,
206
+ reset,
207
+ setSubmitting
208
+ }), [setErrors, setFieldError, reset]);
209
+ const submit = useCallback(() => {
210
+ void (async () => {
211
+ const cfg = configRef.current;
212
+ const currentValues = ref.current.values;
213
+ ref.current.submitCount += 1;
214
+ setSubmitCount(c => c + 1);
215
+ setValidating(true);
216
+ const errs = await runAll(currentValues);
217
+ setValidating(false);
218
+ const keys = new Set([...fieldOrder.current, ...Object.keys(errs)]);
219
+ const nextTouched = {
220
+ ...ref.current.touched
221
+ };
222
+ keys.forEach(k => {
223
+ nextTouched[k] = true;
224
+ });
225
+ ref.current.touched = nextTouched;
226
+ ref.current.errors = errs;
227
+ setTouchedState(nextTouched);
228
+ setErrorsState(errs);
229
+ const firstError = fieldOrder.current.find(n => errs[n]) ?? Object.keys(errs).find(n => errs[n]);
230
+ if (firstError) {
231
+ fieldNodes.current.get(firstError)?.focus?.();
232
+ return;
233
+ }
234
+ setSubmitting(true);
235
+ try {
236
+ await cfg.onSubmit(currentValues, helpers);
237
+ } finally {
238
+ setSubmitting(false);
239
+ }
240
+ })();
241
+ }, [runAll, helpers]);
242
+ const uiErrors = useMemo(() => {
243
+ const gated = {};
244
+ for (const k of Object.keys(errors)) {
245
+ if (errors[k] && (touched[k] || submitCount > 0)) gated[k] = errors[k];
246
+ }
247
+ return gated;
248
+ }, [errors, touched, submitCount]);
249
+ const isValid = useMemo(() => {
250
+ const validateCfg = configRef.current.validate;
251
+ if (!validateCfg) return true;
252
+ // Form-level async fn can't run synchronously — fall back to known errors.
253
+ if (typeof validateCfg === 'function') return Object.values(errors).every(e => !e);
254
+ for (const name of Object.keys(validateCfg)) {
255
+ const value = getPath(values, name);
256
+ for (const rule of rulesFor(validateCfg, name)) {
257
+ const res = rule(value, values);
258
+ if (typeof res === 'string' && res) return false; // async rules (Promise) are skipped here
259
+ }
260
+ }
261
+ return true;
262
+ }, [values, errors]);
263
+ const dirty = useMemo(() => JSON.stringify(values) !== JSON.stringify(initialRef.current), [values]);
264
+ return {
265
+ values,
266
+ errors: uiErrors,
267
+ rawErrors: errors,
268
+ touched,
269
+ dirty,
270
+ isValid,
271
+ isSubmitting,
272
+ isValidating,
273
+ submitCount,
274
+ setFieldValue,
275
+ setFieldTouched,
276
+ setFieldError,
277
+ setValues,
278
+ setErrors,
279
+ reset,
280
+ validate,
281
+ validateField,
282
+ submit,
283
+ handleBlur,
284
+ focusNext,
285
+ focusPrev,
286
+ registerField,
287
+ getFieldState: name => {
288
+ const value = getPath(values, name);
289
+ const rawError = errors[name];
290
+ const isTouched = !!touched[name];
291
+ return {
292
+ value,
293
+ error: isTouched || submitCount > 0 ? rawError : undefined,
294
+ rawError,
295
+ touched: isTouched,
296
+ dirty: JSON.stringify(value) !== JSON.stringify(getPath(initialRef.current, name))
297
+ };
298
+ },
299
+ getFieldProps: name => ({
300
+ name,
301
+ value: getPath(values, name),
302
+ error: touched[name] || submitCount > 0 ? errors[name] : undefined,
303
+ touched: !!touched[name],
304
+ onChange: val => setFieldValue(name, val),
305
+ onChangeText: text => setFieldValue(name, text),
306
+ onBlur: () => handleBlur(name),
307
+ onFocus: () => handleFocus(name),
308
+ setValue: (val, sv) => setFieldValue(name, val, sv)
309
+ })
310
+ };
311
+ }
312
+ //# sourceMappingURL=useForm.js.map
@@ -3,4 +3,6 @@
3
3
  export { useToggle } from "./useToggle.js";
4
4
  export { useDebounce } from "./useDebounce.js";
5
5
  export { usePressAnimation } from "./usePressAnimation.js";
6
+ export { useReducedMotion } from "./useReducedMotion.js";
7
+ export { useControllableState } from "./useControllableState.js";
6
8
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useRef, useState } from 'react';
4
+ /**
5
+ * Bridges controlled and uncontrolled usage for a value+onChange pair. When
6
+ * `value` is provided the component is controlled (the hook just forwards it
7
+ * and calls `onChange`); otherwise the hook owns the state, seeded from
8
+ * `defaultValue`. The controlled/uncontrolled decision is locked on first
9
+ * render so a component never flips modes mid-life.
10
+ */
11
+ export function useControllableState({
12
+ value,
13
+ defaultValue,
14
+ onChange
15
+ }) {
16
+ const isControlledRef = useRef(value !== undefined);
17
+ const isControlled = isControlledRef.current;
18
+ const [internal, setInternal] = useState(defaultValue);
19
+ const current = isControlled ? value : internal;
20
+ const setValue = useCallback(next => {
21
+ if (!isControlled) setInternal(next);
22
+ onChange?.(next);
23
+ }, [isControlled, onChange]);
24
+ return [current, setValue];
25
+ }
26
+ //# sourceMappingURL=useControllableState.js.map