react-native-platform-components 1.3.0 → 1.4.0

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/README.md +24 -30
  2. package/android/src/main/java/com/platformcomponents/PCButtonGroupView.kt +498 -0
  3. package/android/src/main/java/com/platformcomponents/PCButtonGroupViewManager.kt +191 -0
  4. package/android/src/main/java/com/platformcomponents/PCButtonSupport.kt +127 -0
  5. package/android/src/main/java/com/platformcomponents/PCButtonView.kt +345 -0
  6. package/android/src/main/java/com/platformcomponents/PCButtonViewManager.kt +126 -0
  7. package/android/src/main/java/com/platformcomponents/PCExpressive.kt +107 -0
  8. package/android/src/main/java/com/platformcomponents/PCFloatingToolbarView.kt +115 -0
  9. package/android/src/main/java/com/platformcomponents/PCFloatingToolbarViewManager.kt +65 -0
  10. package/android/src/main/java/com/platformcomponents/PCSegmentedControlView.kt +11 -3
  11. package/android/src/main/java/com/platformcomponents/PCSegmentedControlViewManager.kt +6 -1
  12. package/android/src/main/java/com/platformcomponents/PCTextFieldView.kt +1018 -0
  13. package/android/src/main/java/com/platformcomponents/PCTextFieldViewManager.kt +248 -0
  14. package/android/src/main/java/com/platformcomponents/PlatformComponentsPackage.kt +4 -0
  15. package/android/src/main/res/values/attrs.xml +13 -0
  16. package/android/src/main/res/values/styles.xml +183 -0
  17. package/ios/PCButton.h +10 -0
  18. package/ios/PCButton.mm +196 -0
  19. package/ios/PCButtonGroup.h +10 -0
  20. package/ios/PCButtonGroup.mm +256 -0
  21. package/ios/PCButtonGroupView.swift +254 -0
  22. package/ios/PCButtonSupport.swift +141 -0
  23. package/ios/PCButtonView.swift +155 -0
  24. package/ios/PCFloatingToolbar.h +10 -0
  25. package/ios/PCFloatingToolbar.mm +76 -0
  26. package/ios/PCFloatingToolbarView.swift +95 -0
  27. package/ios/PCTextField.h +10 -0
  28. package/ios/PCTextField.mm +391 -0
  29. package/ios/PCTextFieldView.swift +1012 -0
  30. package/lib/commonjs/Button.js +77 -0
  31. package/lib/commonjs/Button.js.map +1 -0
  32. package/lib/commonjs/ButtonGroup.js +99 -0
  33. package/lib/commonjs/ButtonGroup.js.map +1 -0
  34. package/lib/commonjs/ButtonGroupNativeComponent.ts +121 -0
  35. package/lib/commonjs/ButtonNativeComponent.ts +76 -0
  36. package/lib/commonjs/FloatingToolbar.js +69 -0
  37. package/lib/commonjs/FloatingToolbar.js.map +1 -0
  38. package/lib/commonjs/FloatingToolbarNativeComponent.ts +31 -0
  39. package/lib/commonjs/SegmentedControl.js +11 -85
  40. package/lib/commonjs/SegmentedControl.js.map +1 -1
  41. package/lib/commonjs/SegmentedControlNativeComponent.ts +3 -0
  42. package/lib/commonjs/TextField.js +195 -0
  43. package/lib/commonjs/TextField.js.map +1 -0
  44. package/lib/commonjs/TextFieldNativeComponent.ts +218 -0
  45. package/lib/commonjs/focusRegistry.js +78 -0
  46. package/lib/commonjs/focusRegistry.js.map +1 -0
  47. package/lib/commonjs/icons.js +92 -0
  48. package/lib/commonjs/icons.js.map +1 -0
  49. package/lib/commonjs/index.js +66 -0
  50. package/lib/commonjs/index.js.map +1 -1
  51. package/lib/commonjs/index.web.js +32 -2
  52. package/lib/commonjs/index.web.js.map +1 -1
  53. package/lib/commonjs/labelStyle.js +27 -0
  54. package/lib/commonjs/labelStyle.js.map +1 -0
  55. package/lib/commonjs/reactNativeInternals.d.js +2 -0
  56. package/lib/commonjs/reactNativeInternals.d.js.map +1 -0
  57. package/lib/module/Button.js +70 -0
  58. package/lib/module/Button.js.map +1 -0
  59. package/lib/module/ButtonGroup.js +92 -0
  60. package/lib/module/ButtonGroup.js.map +1 -0
  61. package/lib/module/ButtonGroupNativeComponent.ts +121 -0
  62. package/lib/module/ButtonNativeComponent.ts +76 -0
  63. package/lib/module/FloatingToolbar.js +62 -0
  64. package/lib/module/FloatingToolbar.js.map +1 -0
  65. package/lib/module/FloatingToolbarNativeComponent.ts +31 -0
  66. package/lib/module/SegmentedControl.js +12 -87
  67. package/lib/module/SegmentedControl.js.map +1 -1
  68. package/lib/module/SegmentedControlNativeComponent.ts +3 -0
  69. package/lib/module/TextField.js +189 -0
  70. package/lib/module/TextField.js.map +1 -0
  71. package/lib/module/TextFieldNativeComponent.ts +218 -0
  72. package/lib/module/focusRegistry.js +74 -0
  73. package/lib/module/focusRegistry.js.map +1 -0
  74. package/lib/module/icons.js +87 -0
  75. package/lib/module/icons.js.map +1 -0
  76. package/lib/module/index.js +6 -0
  77. package/lib/module/index.js.map +1 -1
  78. package/lib/module/index.web.js +28 -0
  79. package/lib/module/index.web.js.map +1 -1
  80. package/lib/module/labelStyle.js +23 -0
  81. package/lib/module/labelStyle.js.map +1 -0
  82. package/lib/module/reactNativeInternals.d.js +2 -0
  83. package/lib/module/reactNativeInternals.d.js.map +1 -0
  84. package/lib/typescript/commonjs/docs/sidebars.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/src/Button.d.ts +76 -0
  86. package/lib/typescript/commonjs/src/Button.d.ts.map +1 -0
  87. package/lib/typescript/commonjs/src/ButtonGroup.d.ts +102 -0
  88. package/lib/typescript/commonjs/src/ButtonGroup.d.ts.map +1 -0
  89. package/lib/typescript/commonjs/src/ButtonGroupNativeComponent.d.ts +93 -0
  90. package/lib/typescript/commonjs/src/ButtonGroupNativeComponent.d.ts.map +1 -0
  91. package/lib/typescript/commonjs/src/ButtonNativeComponent.d.ts +57 -0
  92. package/lib/typescript/commonjs/src/ButtonNativeComponent.d.ts.map +1 -0
  93. package/lib/typescript/commonjs/src/FloatingToolbar.d.ts +45 -0
  94. package/lib/typescript/commonjs/src/FloatingToolbar.d.ts.map +1 -0
  95. package/lib/typescript/commonjs/src/FloatingToolbarNativeComponent.d.ts +24 -0
  96. package/lib/typescript/commonjs/src/FloatingToolbarNativeComponent.d.ts.map +1 -0
  97. package/lib/typescript/commonjs/src/SegmentedControl.d.ts +17 -46
  98. package/lib/typescript/commonjs/src/SegmentedControl.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts +2 -0
  100. package/lib/typescript/commonjs/src/SegmentedControlNativeComponent.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/src/TextField.d.ts +174 -0
  102. package/lib/typescript/commonjs/src/TextField.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/src/TextFieldNativeComponent.d.ts +162 -0
  104. package/lib/typescript/commonjs/src/TextFieldNativeComponent.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/src/focusRegistry.d.ts +13 -0
  106. package/lib/typescript/commonjs/src/focusRegistry.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/src/icons.d.ts +52 -0
  108. package/lib/typescript/commonjs/src/icons.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/src/index.d.ts +6 -0
  110. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/src/index.web.d.ts +121 -0
  112. package/lib/typescript/commonjs/src/index.web.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/src/labelStyle.d.ts +20 -0
  114. package/lib/typescript/commonjs/src/labelStyle.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/src/sharedTypes.d.ts +6 -1
  116. package/lib/typescript/commonjs/src/sharedTypes.d.ts.map +1 -1
  117. package/lib/typescript/module/docs/sidebars.d.ts.map +1 -1
  118. package/lib/typescript/module/src/Button.d.ts +76 -0
  119. package/lib/typescript/module/src/Button.d.ts.map +1 -0
  120. package/lib/typescript/module/src/ButtonGroup.d.ts +102 -0
  121. package/lib/typescript/module/src/ButtonGroup.d.ts.map +1 -0
  122. package/lib/typescript/module/src/ButtonGroupNativeComponent.d.ts +93 -0
  123. package/lib/typescript/module/src/ButtonGroupNativeComponent.d.ts.map +1 -0
  124. package/lib/typescript/module/src/ButtonNativeComponent.d.ts +57 -0
  125. package/lib/typescript/module/src/ButtonNativeComponent.d.ts.map +1 -0
  126. package/lib/typescript/module/src/FloatingToolbar.d.ts +45 -0
  127. package/lib/typescript/module/src/FloatingToolbar.d.ts.map +1 -0
  128. package/lib/typescript/module/src/FloatingToolbarNativeComponent.d.ts +24 -0
  129. package/lib/typescript/module/src/FloatingToolbarNativeComponent.d.ts.map +1 -0
  130. package/lib/typescript/module/src/SegmentedControl.d.ts +17 -46
  131. package/lib/typescript/module/src/SegmentedControl.d.ts.map +1 -1
  132. package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts +2 -0
  133. package/lib/typescript/module/src/SegmentedControlNativeComponent.d.ts.map +1 -1
  134. package/lib/typescript/module/src/TextField.d.ts +174 -0
  135. package/lib/typescript/module/src/TextField.d.ts.map +1 -0
  136. package/lib/typescript/module/src/TextFieldNativeComponent.d.ts +162 -0
  137. package/lib/typescript/module/src/TextFieldNativeComponent.d.ts.map +1 -0
  138. package/lib/typescript/module/src/focusRegistry.d.ts +13 -0
  139. package/lib/typescript/module/src/focusRegistry.d.ts.map +1 -0
  140. package/lib/typescript/module/src/icons.d.ts +52 -0
  141. package/lib/typescript/module/src/icons.d.ts.map +1 -0
  142. package/lib/typescript/module/src/index.d.ts +6 -0
  143. package/lib/typescript/module/src/index.d.ts.map +1 -1
  144. package/lib/typescript/module/src/index.web.d.ts +121 -0
  145. package/lib/typescript/module/src/index.web.d.ts.map +1 -1
  146. package/lib/typescript/module/src/labelStyle.d.ts +20 -0
  147. package/lib/typescript/module/src/labelStyle.d.ts.map +1 -0
  148. package/lib/typescript/module/src/sharedTypes.d.ts +6 -1
  149. package/lib/typescript/module/src/sharedTypes.d.ts.map +1 -1
  150. package/package.json +25 -4
  151. package/react-native.config.js +3 -0
  152. package/shared/PCButtonComponentDescriptors-custom.h +22 -0
  153. package/shared/PCButtonGroupComponentDescriptors-custom.h +22 -0
  154. package/shared/PCButtonGroupShadowNode-custom.cpp +48 -0
  155. package/shared/PCButtonGroupShadowNode-custom.h +55 -0
  156. package/shared/PCButtonGroupState-custom.h +61 -0
  157. package/shared/PCButtonShadowNode-custom.cpp +48 -0
  158. package/shared/PCButtonShadowNode-custom.h +55 -0
  159. package/shared/PCButtonState-custom.h +61 -0
  160. package/shared/PCTextFieldComponentDescriptors-custom.h +22 -0
  161. package/shared/PCTextFieldShadowNode-custom.cpp +47 -0
  162. package/shared/PCTextFieldShadowNode-custom.h +55 -0
  163. package/shared/PCTextFieldState-custom.h +61 -0
  164. package/shared/react/renderer/components/PlatformComponentsViewSpec/ComponentDescriptors.h +3 -0
  165. package/src/Button.tsx +144 -0
  166. package/src/ButtonGroup.tsx +233 -0
  167. package/src/ButtonGroupNativeComponent.ts +121 -0
  168. package/src/ButtonNativeComponent.ts +76 -0
  169. package/src/FloatingToolbar.tsx +113 -0
  170. package/src/FloatingToolbarNativeComponent.ts +31 -0
  171. package/src/SegmentedControl.tsx +26 -118
  172. package/src/SegmentedControlNativeComponent.ts +3 -0
  173. package/src/TextField.tsx +494 -0
  174. package/src/TextFieldNativeComponent.ts +218 -0
  175. package/src/focusRegistry.ts +89 -0
  176. package/src/icons.ts +116 -0
  177. package/src/index.tsx +6 -0
  178. package/src/index.web.tsx +194 -0
  179. package/src/labelStyle.ts +36 -0
  180. package/src/reactNativeInternals.d.ts +22 -0
  181. package/src/sharedTypes.ts +7 -1
@@ -0,0 +1,494 @@
1
+ // TextField.tsx
2
+ import React, {
3
+ forwardRef,
4
+ useCallback,
5
+ useImperativeHandle,
6
+ useLayoutEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from 'react';
11
+ import type { NativeSyntheticEvent, ViewProps } from 'react-native';
12
+
13
+ import NativeTextField, {
14
+ Commands,
15
+ type TextFieldChangeEvent as NativeChangeEvent,
16
+ type TextFieldTextEvent as NativeTextEvent,
17
+ } from './TextFieldNativeComponent';
18
+ import { resolveIcon, type PlatformIcon } from './icons';
19
+ import { normalizeLabelStyle, type LabelStyle } from './labelStyle';
20
+ import { focusRegistry } from './focusRegistry';
21
+ import type { AndroidMaterialMode } from './sharedTypes';
22
+
23
+ /** Keyboard to show; the React Native `TextInput` values. */
24
+ export type TextFieldKeyboardType =
25
+ | 'default'
26
+ | 'number-pad'
27
+ | 'decimal-pad'
28
+ | 'numeric'
29
+ | 'email-address'
30
+ | 'phone-pad'
31
+ | 'url'
32
+ | 'ascii-capable'
33
+ | 'numbers-and-punctuation'
34
+ | 'name-phone-pad'
35
+ | 'twitter'
36
+ | 'web-search'
37
+ | 'visible-password';
38
+
39
+ /** Return key label; the React Native `TextInput` values. */
40
+ export type TextFieldReturnKeyType =
41
+ 'default' | 'done' | 'go' | 'next' | 'search' | 'send' | 'none' | 'previous';
42
+
43
+ /** Autofill hint; a cross-platform subset of the React Native `TextInput` values. */
44
+ export type TextFieldAutoComplete =
45
+ | 'off'
46
+ | 'username'
47
+ | 'password'
48
+ | 'new-password'
49
+ | 'one-time-code'
50
+ | 'email'
51
+ | 'name'
52
+ | 'given-name'
53
+ | 'family-name'
54
+ | 'tel'
55
+ | 'street-address'
56
+ | 'postal-code'
57
+ | 'country'
58
+ | 'cc-number'
59
+ | 'cc-exp'
60
+ | 'cc-csc'
61
+ | 'url';
62
+
63
+ export type TextFieldAutoCapitalize =
64
+ 'none' | 'sentences' | 'words' | 'characters';
65
+
66
+ export type TextFieldClearButtonMode =
67
+ 'never' | 'while-editing' | 'unless-editing' | 'always';
68
+
69
+ /** An iOS text trait: the system default, on, or off. */
70
+ export type TextFieldIOSChoice = 'default' | 'yes' | 'no';
71
+
72
+ /** Focus, blur and submit events carry the text at that moment. */
73
+ export type TextFieldEvent = NativeSyntheticEvent<{ text: string }>;
74
+
75
+ /** Change events carry the text and the native edit counter. */
76
+ export type TextFieldChangeEvent = NativeSyntheticEvent<{
77
+ text: string;
78
+ eventCount: number;
79
+ }>;
80
+
81
+ /** Methods available through the `ref`. */
82
+ export interface TextFieldRef {
83
+ /** Focuses the field and shows the keyboard. */
84
+ focus(): void;
85
+ /** Removes focus and hides the keyboard. */
86
+ blur(): void;
87
+ /** Clears the text. Fires `onChangeText('')`. */
88
+ clear(): void;
89
+ /** Whether the field currently has focus. */
90
+ isFocused(): boolean;
91
+ }
92
+
93
+ export interface TextFieldProps extends Omit<
94
+ ViewProps,
95
+ 'onFocus' | 'onBlur' | 'children'
96
+ > {
97
+ /**
98
+ * Controlled text. Native keeps what the user typed until the next
99
+ * `onChangeText` round-trip, so updating state from `onChangeText` never
100
+ * drops characters.
101
+ */
102
+ value?: string;
103
+
104
+ /** Initial text of an uncontrolled field. */
105
+ defaultValue?: string;
106
+
107
+ /** Called with the new text on every edit. */
108
+ onChangeText?: (text: string) => void;
109
+
110
+ /** Called on every edit with the native event. */
111
+ onChange?: (event: TextFieldChangeEvent) => void;
112
+
113
+ /** Called when the field gains focus. */
114
+ onFocus?: (event: TextFieldEvent) => void;
115
+
116
+ /** Called when the field loses focus. */
117
+ onBlur?: (event: TextFieldEvent) => void;
118
+
119
+ /** Called when the return key is pressed. Single-line fields only. */
120
+ onSubmitEditing?: (event: TextFieldEvent) => void;
121
+
122
+ /**
123
+ * Field label. Android: the Material floating label. iOS: a caption above
124
+ * the field, which takes the tint color while focused.
125
+ */
126
+ label?: string;
127
+
128
+ /** Placeholder shown while the field is empty. */
129
+ placeholder?: string;
130
+
131
+ /** Helper text below the field. */
132
+ supportingText?: string;
133
+
134
+ /**
135
+ * Error state. A string is shown below the field in place of the
136
+ * supporting text; `true` marks the field as invalid and keeps the
137
+ * supporting text.
138
+ */
139
+ error?: boolean | string;
140
+
141
+ /** Text shown before the input, inside the field (a currency sign). */
142
+ prefix?: string;
143
+
144
+ /** Text shown after the input, inside the field (a unit). */
145
+ suffix?: string;
146
+
147
+ /** Icon at the start of the field. */
148
+ leadingIcon?: PlatformIcon;
149
+
150
+ /** Icon at the end of the field. Pressing it calls `onTrailingIconPress`. */
151
+ trailingIcon?: PlatformIcon;
152
+
153
+ /** Called when the trailing icon is pressed. */
154
+ onTrailingIconPress?: () => void;
155
+
156
+ /**
157
+ * When to show a clear button inside the field. Default: `'never'`.
158
+ * Android shows the Material clear icon while the field has text.
159
+ */
160
+ clearButtonMode?: TextFieldClearButtonMode;
161
+
162
+ /**
163
+ * Shows a button that toggles `secureTextEntry`, for password fields.
164
+ * Android: the Material password toggle. iOS: an eye button.
165
+ */
166
+ passwordToggle?: boolean;
167
+
168
+ /** Shows the character count below the field (`12 / 100` with `maxLength`). */
169
+ showCharacterCount?: boolean;
170
+
171
+ /** Maximum number of characters. */
172
+ maxLength?: number;
173
+
174
+ /** Keyboard to show. Default: `'default'`. */
175
+ keyboardType?: TextFieldKeyboardType;
176
+
177
+ /** Return key label. Default: `'default'`. */
178
+ returnKeyType?: TextFieldReturnKeyType;
179
+
180
+ /** Automatic capitalization. Default: `'sentences'`. */
181
+ autoCapitalize?: TextFieldAutoCapitalize;
182
+
183
+ /** Auto-correction and suggestions. Default: `true`. */
184
+ autoCorrect?: boolean;
185
+
186
+ /** Obscures the text, for passwords. */
187
+ secureTextEntry?: boolean;
188
+
189
+ /** Multi-line field that grows with its content. Return inserts a newline. */
190
+ multiline?: boolean;
191
+
192
+ /** Whether the text can be edited. Default: `true`. */
193
+ editable?: boolean;
194
+
195
+ /** Focuses the field when it mounts. */
196
+ autoFocus?: boolean;
197
+
198
+ /** Selects all text when the field gains focus. */
199
+ selectTextOnFocus?: boolean;
200
+
201
+ /**
202
+ * Autofill hint. Android: autofill hints. iOS: `textContentType`, which
203
+ * also enables the password and one-time-code suggestions.
204
+ */
205
+ autoComplete?: TextFieldAutoComplete;
206
+
207
+ /** iOS: keyboard appearance. Default: `'default'`. */
208
+ keyboardAppearance?: 'default' | 'light' | 'dark';
209
+
210
+ /** Font of the input text. */
211
+ textStyle?: LabelStyle;
212
+
213
+ /** Screen-reader label. Defaults to `label`. */
214
+ accessibilityLabel?: string;
215
+
216
+ /**
217
+ * iOS-specific configuration. Each trait defaults to the system default.
218
+ */
219
+ ios?: {
220
+ /**
221
+ * Writing Tools (iOS 18): `'complete'` allows rewrites in place,
222
+ * `'limited'` only the overlay panel, `'none'` disables them.
223
+ */
224
+ writingTools?: 'default' | 'complete' | 'limited' | 'none';
225
+
226
+ /** Inline predictive text (iOS 17). */
227
+ inlinePrediction?: TextFieldIOSChoice;
228
+
229
+ /** Smart quotes. */
230
+ smartQuotes?: TextFieldIOSChoice;
231
+
232
+ /** Smart dashes. */
233
+ smartDashes?: TextFieldIOSChoice;
234
+
235
+ /** Smart insert and delete (spaces around pasted words). */
236
+ smartInsertDelete?: TextFieldIOSChoice;
237
+
238
+ /** Math expression completion, `1+1=` (iOS 18). */
239
+ mathExpressionCompletion?: TextFieldIOSChoice;
240
+
241
+ /** `UITextField.borderStyle`. Default: `'roundedRect'`. */
242
+ borderStyle?: 'roundedRect' | 'none' | 'line' | 'bezel';
243
+
244
+ /**
245
+ * Where the label sits: above the field (default), or in a leading
246
+ * column with the field beside it, the layout of the grouped forms in
247
+ * Contacts and Settings. Pair `'leading'` with `borderStyle: 'none'`
248
+ * inside your own grouped row.
249
+ */
250
+ labelPlacement?: 'above' | 'leading';
251
+
252
+ /** Width of the leading label column, in points. Default: 100. */
253
+ labelWidth?: number;
254
+ };
255
+
256
+ /**
257
+ * Android-specific configuration
258
+ */
259
+ android?: {
260
+ /**
261
+ * Material preference: the Material 3 text field (default), or
262
+ * `'system'` for the platform `EditText` (the hint as placeholder,
263
+ * supporting text below, icons as compound drawables; no box, floating
264
+ * label, clear button, password toggle, counter, prefix or suffix).
265
+ */
266
+ material?: AndroidMaterialMode;
267
+
268
+ /** Material 3 text field style. Default: `'outlined'`. */
269
+ variant?: 'outlined' | 'filled';
270
+
271
+ /** The dense variant, a shorter field. */
272
+ dense?: boolean;
273
+ };
274
+
275
+ /** Test identifier */
276
+ testID?: string;
277
+ }
278
+
279
+ type NativeTextFieldInstance = React.ComponentRef<typeof NativeTextField>;
280
+
281
+ export const TextField = forwardRef<TextFieldRef, TextFieldProps>(
282
+ function TextFieldComponent(props, ref): React.ReactElement {
283
+ const {
284
+ value,
285
+ defaultValue,
286
+ onChangeText,
287
+ onChange,
288
+ onFocus,
289
+ onBlur,
290
+ onSubmitEditing,
291
+ label,
292
+ placeholder,
293
+ supportingText,
294
+ error,
295
+ prefix,
296
+ suffix,
297
+ leadingIcon,
298
+ trailingIcon,
299
+ onTrailingIconPress,
300
+ clearButtonMode,
301
+ passwordToggle,
302
+ showCharacterCount,
303
+ maxLength,
304
+ keyboardType,
305
+ returnKeyType,
306
+ autoCapitalize,
307
+ autoCorrect,
308
+ secureTextEntry,
309
+ multiline,
310
+ editable,
311
+ autoFocus,
312
+ selectTextOnFocus,
313
+ autoComplete,
314
+ keyboardAppearance,
315
+ textStyle,
316
+ accessibilityLabel,
317
+ ios,
318
+ android,
319
+ ...viewProps
320
+ } = props;
321
+
322
+ const nativeRef = useRef<NativeTextFieldInstance>(null);
323
+ const focused = useRef(false);
324
+
325
+ // The same synchronization as React Native's TextInput: every native edit
326
+ // reports an event count, and a controlled value is pushed with the last
327
+ // count JS has seen. Native drops the update when the user has typed
328
+ // since, and the edit that follows brings JS back in line.
329
+ const [mostRecentEventCount, setMostRecentEventCount] = useState(0);
330
+ const [lastNativeText, setLastNativeText] = useState<string | undefined>(
331
+ value ?? defaultValue
332
+ );
333
+
334
+ // Registered like a TextInput, so Keyboard.dismiss() and the ScrollView
335
+ // keyboard props treat the field as one (see focusRegistry.ts)
336
+ useLayoutEffect(() => {
337
+ const node = nativeRef.current;
338
+ if (node == null) return undefined;
339
+ focusRegistry.register(node);
340
+ return () => focusRegistry.unregister(node);
341
+ }, []);
342
+
343
+ useLayoutEffect(() => {
344
+ if (typeof value !== 'string' || value === lastNativeText) return;
345
+ setLastNativeText(value);
346
+ const node = nativeRef.current;
347
+ if (node != null) {
348
+ Commands.setText(node, mostRecentEventCount, value);
349
+ }
350
+ }, [value, lastNativeText, mostRecentEventCount]);
351
+
352
+ useImperativeHandle(
353
+ ref,
354
+ () => ({
355
+ focus: () => {
356
+ const node = nativeRef.current;
357
+ if (node != null) Commands.focus(node);
358
+ },
359
+ blur: () => {
360
+ const node = nativeRef.current;
361
+ if (node != null) Commands.blur(node);
362
+ },
363
+ clear: () => {
364
+ const node = nativeRef.current;
365
+ if (node != null) Commands.clear(node);
366
+ },
367
+ isFocused: () => focused.current,
368
+ }),
369
+ []
370
+ );
371
+
372
+ const handleChange = useCallback(
373
+ (event: NativeSyntheticEvent<NativeChangeEvent>) => {
374
+ const { text, eventCount } = event.nativeEvent;
375
+ setMostRecentEventCount(eventCount);
376
+ setLastNativeText(text);
377
+ onChange?.(event);
378
+ onChangeText?.(text);
379
+ },
380
+ [onChange, onChangeText]
381
+ );
382
+
383
+ const handleFocus = useCallback(
384
+ (event: NativeSyntheticEvent<NativeTextEvent>) => {
385
+ focused.current = true;
386
+ focusRegistry.focused(nativeRef.current);
387
+ onFocus?.(event);
388
+ },
389
+ [onFocus]
390
+ );
391
+
392
+ const handleBlur = useCallback(
393
+ (event: NativeSyntheticEvent<NativeTextEvent>) => {
394
+ focused.current = false;
395
+ focusRegistry.blurred(nativeRef.current);
396
+ onBlur?.(event);
397
+ },
398
+ [onBlur]
399
+ );
400
+
401
+ const handleSubmit = useCallback(
402
+ (event: NativeSyntheticEvent<NativeTextEvent>) => {
403
+ onSubmitEditing?.(event);
404
+ },
405
+ [onSubmitEditing]
406
+ );
407
+
408
+ const handleTrailingIconPress = useCallback(() => {
409
+ onTrailingIconPress?.();
410
+ }, [onTrailingIconPress]);
411
+
412
+ const nativeLeadingIcon = useMemo(
413
+ () => resolveIcon(leadingIcon),
414
+ [leadingIcon]
415
+ );
416
+ const nativeTrailingIcon = useMemo(
417
+ () => resolveIcon(trailingIcon),
418
+ [trailingIcon]
419
+ );
420
+ const nativeTextStyle = useMemo(
421
+ () => normalizeLabelStyle(textStyle),
422
+ [textStyle]
423
+ );
424
+
425
+ const nativeIOS = useMemo(
426
+ () => ({
427
+ writingTools: ios?.writingTools ?? '',
428
+ inlinePrediction: ios?.inlinePrediction ?? '',
429
+ smartQuotes: ios?.smartQuotes ?? '',
430
+ smartDashes: ios?.smartDashes ?? '',
431
+ smartInsertDelete: ios?.smartInsertDelete ?? '',
432
+ mathExpressionCompletion: ios?.mathExpressionCompletion ?? '',
433
+ borderStyle: ios?.borderStyle ?? '',
434
+ labelPlacement: ios?.labelPlacement ?? '',
435
+ labelWidth: ios?.labelWidth ?? 0,
436
+ }),
437
+ [ios]
438
+ );
439
+
440
+ const nativeAndroid = useMemo(
441
+ () => ({
442
+ material: android?.material ?? 'm3',
443
+ variant: android?.variant ?? 'outlined',
444
+ density: android?.dense ? 'dense' : 'standard',
445
+ }),
446
+ [android]
447
+ );
448
+
449
+ return (
450
+ <NativeTextField
451
+ ref={nativeRef}
452
+ initialText={value ?? defaultValue ?? ''}
453
+ label={label ?? ''}
454
+ placeholder={placeholder ?? ''}
455
+ supportingText={supportingText ?? ''}
456
+ errorState={error ? 'error' : 'none'}
457
+ errorText={typeof error === 'string' ? error : ''}
458
+ prefix={prefix ?? ''}
459
+ suffix={suffix ?? ''}
460
+ leadingIcon={nativeLeadingIcon}
461
+ trailingIcon={nativeTrailingIcon}
462
+ clearButtonMode={clearButtonMode ?? 'never'}
463
+ passwordToggle={passwordToggle ? 'shown' : 'hidden'}
464
+ characterCount={showCharacterCount ? 'shown' : 'hidden'}
465
+ maxLength={maxLength ?? 0}
466
+ keyboardType={keyboardType ?? 'default'}
467
+ returnKeyType={returnKeyType ?? 'default'}
468
+ autoCapitalize={autoCapitalize ?? 'sentences'}
469
+ autoCorrect={autoCorrect === false ? 'disabled' : 'enabled'}
470
+ secureTextEntry={secureTextEntry ? 'secure' : 'plain'}
471
+ lines={multiline ? 'multiline' : 'single'}
472
+ interactivity={editable === false ? 'disabled' : 'enabled'}
473
+ autoFocus={autoFocus ? 'focus' : 'none'}
474
+ selectTextOnFocus={selectTextOnFocus ? 'select' : 'keep'}
475
+ autoComplete={autoComplete ?? ''}
476
+ keyboardAppearance={keyboardAppearance ?? 'default'}
477
+ textStyle={nativeTextStyle}
478
+ spokenLabel={accessibilityLabel ?? ''}
479
+ ios={nativeIOS}
480
+ android={nativeAndroid}
481
+ onFieldChange={handleChange}
482
+ onFieldFocus={handleFocus}
483
+ onFieldBlur={handleBlur}
484
+ onFieldSubmit={onSubmitEditing ? handleSubmit : undefined}
485
+ onTrailingIconPress={
486
+ onTrailingIconPress ? handleTrailingIconPress : undefined
487
+ }
488
+ {...viewProps}
489
+ />
490
+ );
491
+ }
492
+ );
493
+
494
+ TextField.displayName = 'TextField';
@@ -0,0 +1,218 @@
1
+ // TextFieldNativeComponent.ts
2
+ import type * as React from 'react';
3
+ import type { HostComponent, ViewProps } from 'react-native';
4
+ import { codegenNativeCommands, codegenNativeComponent } from 'react-native';
5
+ import type { BubblingEventHandler, Double, Int32 } from './codegenTypes';
6
+
7
+ /**
8
+ * Icon fields, pre-resolved on the JS side (see icons.ts).
9
+ */
10
+ export type TextFieldIconProps = Readonly<{
11
+ iconType: string; // '' | 'sfSymbol' | 'drawable' | 'image'
12
+ iconName: string; // SF Symbol (iOS) or drawable resource name (Android)
13
+ iconUri: string; // Resolved image URI when iconType === 'image'
14
+ iconScale: Double; // Resolved image scale when iconType === 'image'
15
+ iconTinted: string; // 'true' | 'false' — draw the image as a tinted template
16
+ }>;
17
+
18
+ /**
19
+ * Text font. Empty strings / 0 mean "platform default".
20
+ */
21
+ export type TextFieldTextStyleProps = Readonly<{
22
+ fontFamily?: string;
23
+ fontSize?: Double;
24
+ fontWeight?: string; // 'normal' | 'bold' | '100'..'900'
25
+ fontStyle?: string; // 'normal' | 'italic'
26
+ }>;
27
+
28
+ /**
29
+ * Fired on every edit. `eventCount` is the native edit counter that JS hands
30
+ * back with `setText`, so a stale controlled value can't overwrite what the
31
+ * user typed since.
32
+ */
33
+ export type TextFieldChangeEvent = Readonly<{
34
+ text: string;
35
+ eventCount: Int32;
36
+ }>;
37
+
38
+ /** Focus, blur and submit carry the text at that moment. */
39
+ export type TextFieldTextEvent = Readonly<{
40
+ text: string;
41
+ }>;
42
+
43
+ /**
44
+ * iOS-specific configuration. Every field is '' for the platform default.
45
+ */
46
+ export type TextFieldIOSProps = Readonly<{
47
+ /** 'default' | 'complete' | 'limited' | 'none' (iOS 18) */
48
+ writingTools?: string;
49
+ /** 'default' | 'yes' | 'no' (iOS 17) */
50
+ inlinePrediction?: string;
51
+ /** 'default' | 'yes' | 'no' */
52
+ smartQuotes?: string;
53
+ /** 'default' | 'yes' | 'no' */
54
+ smartDashes?: string;
55
+ /** 'default' | 'yes' | 'no' */
56
+ smartInsertDelete?: string;
57
+ /** 'default' | 'yes' | 'no' (iOS 18) */
58
+ mathExpressionCompletion?: string;
59
+ /** 'roundedRect' | 'none' | 'line' | 'bezel' */
60
+ borderStyle?: string;
61
+ /** 'above' | 'leading': where the label sits */
62
+ labelPlacement?: string;
63
+ /** Width of a leading label column in points; 0 = default */
64
+ labelWidth?: Double;
65
+ }>;
66
+
67
+ /**
68
+ * Android-specific configuration.
69
+ */
70
+ export type TextFieldAndroidProps = Readonly<{
71
+ /** 'm3' | 'system' (AndroidMaterialMode) */
72
+ material?: string;
73
+ /** 'outlined' | 'filled' */
74
+ variant?: string;
75
+ /** 'standard' | 'dense' */
76
+ density?: string;
77
+ }>;
78
+
79
+ export interface TextFieldNativeProps extends ViewProps {
80
+ /**
81
+ * Text applied when the native view is created. Later updates go through
82
+ * the `setText` command so they can be checked against `eventCount`.
83
+ */
84
+ initialText?: string;
85
+
86
+ /** Floating label (Android) / caption above the field (iOS). */
87
+ label?: string;
88
+
89
+ /** Placeholder shown while the field is empty. */
90
+ placeholder?: string;
91
+
92
+ /** Helper text below the field. */
93
+ supportingText?: string;
94
+
95
+ /** 'none' | 'error' */
96
+ errorState?: string;
97
+
98
+ /** Error message shown instead of the supporting text. */
99
+ errorText?: string;
100
+
101
+ /** Text shown before the input, inside the field. */
102
+ prefix?: string;
103
+
104
+ /** Text shown after the input, inside the field. */
105
+ suffix?: string;
106
+
107
+ /** Icon at the start of the field. */
108
+ leadingIcon?: TextFieldIconProps;
109
+
110
+ /** Icon at the end of the field; pressing it fires onTrailingIconPress. */
111
+ trailingIcon?: TextFieldIconProps;
112
+
113
+ /** 'never' | 'while-editing' | 'unless-editing' | 'always' */
114
+ clearButtonMode?: string;
115
+
116
+ /** 'shown' | 'hidden': a button that toggles secure text entry. */
117
+ passwordToggle?: string;
118
+
119
+ /** 'shown' | 'hidden': the character counter. */
120
+ characterCount?: string;
121
+
122
+ /** Maximum number of UTF-16 characters; 0 = unlimited. */
123
+ maxLength?: Int32;
124
+
125
+ /** React Native keyboardType value. */
126
+ keyboardType?: string;
127
+
128
+ /** React Native returnKeyType value. */
129
+ returnKeyType?: string;
130
+
131
+ /** 'none' | 'sentences' | 'words' | 'characters' */
132
+ autoCapitalize?: string;
133
+
134
+ /** 'enabled' | 'disabled' */
135
+ autoCorrect?: string;
136
+
137
+ /** 'secure' | 'plain' */
138
+ secureTextEntry?: string;
139
+
140
+ /** 'single' | 'multiline' */
141
+ lines?: string;
142
+
143
+ /** 'enabled' | 'disabled' */
144
+ interactivity?: string;
145
+
146
+ /** 'focus' | 'none': focus the field when it mounts. */
147
+ autoFocus?: string;
148
+
149
+ /** 'select' | 'keep': select all text on focus. */
150
+ selectTextOnFocus?: string;
151
+
152
+ /** React Native autoComplete value; '' = platform default. */
153
+ autoComplete?: string;
154
+
155
+ /** 'default' | 'light' | 'dark' (iOS keyboard appearance) */
156
+ keyboardAppearance?: string;
157
+
158
+ /** Font of the input text. */
159
+ textStyle?: TextFieldTextStyleProps;
160
+
161
+ /**
162
+ * Screen-reader label, applied to the native field rather than the host
163
+ * view. Empty means "use the label".
164
+ */
165
+ spokenLabel?: string;
166
+
167
+ ios?: TextFieldIOSProps;
168
+ android?: TextFieldAndroidProps;
169
+
170
+ /** Fired on every user edit. */
171
+ onFieldChange?: BubblingEventHandler<TextFieldChangeEvent>;
172
+
173
+ /** Fired when the field gains focus. */
174
+ onFieldFocus?: BubblingEventHandler<TextFieldTextEvent>;
175
+
176
+ /** Fired when the field loses focus. */
177
+ onFieldBlur?: BubblingEventHandler<TextFieldTextEvent>;
178
+
179
+ /** Fired when the keyboard's return key is pressed (single-line). */
180
+ onFieldSubmit?: BubblingEventHandler<TextFieldTextEvent>;
181
+
182
+ /** Fired when the trailing icon is pressed. */
183
+ onTrailingIconPress?: BubblingEventHandler<Readonly<{}>>;
184
+ }
185
+
186
+ type NativeTextFieldComponent = HostComponent<TextFieldNativeProps>;
187
+
188
+ // `React.ElementRef` rather than `React.ComponentRef`: the codegen shipped with
189
+ // React Native 0.81 (and the 0.76 Android floor) only accepts the former.
190
+ interface NativeCommands {
191
+ /** Focuses the field and shows the keyboard. */
192
+ focus: (viewRef: React.ElementRef<NativeTextFieldComponent>) => void;
193
+
194
+ /** Removes focus and hides the keyboard. */
195
+ blur: (viewRef: React.ElementRef<NativeTextFieldComponent>) => void;
196
+
197
+ /** Clears the text; fires onFieldChange like a user edit. */
198
+ clear: (viewRef: React.ElementRef<NativeTextFieldComponent>) => void;
199
+
200
+ /**
201
+ * Replaces the text, unless the user has edited since `eventCount` was
202
+ * reported, in which case the update is dropped and the next edit event
203
+ * brings JS back in sync.
204
+ */
205
+ setText: (
206
+ viewRef: React.ElementRef<NativeTextFieldComponent>,
207
+ eventCount: Int32,
208
+ text: string
209
+ ) => void;
210
+ }
211
+
212
+ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
213
+ supportedCommands: ['focus', 'blur', 'clear', 'setText'],
214
+ });
215
+
216
+ export default codegenNativeComponent<TextFieldNativeProps>(
217
+ 'PCTextField'
218
+ ) as NativeTextFieldComponent;