react-native-controlled-input 0.9.1 → 0.11.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.
package/README.md CHANGED
@@ -68,9 +68,15 @@ inputRef.current?.blur();
68
68
  |------|------|-------------|
69
69
  | `value` | `string` | Current input value. |
70
70
  | `onTextChange` | `(value: string) => void` | Called with the next text value. Filter it and update `value`. |
71
- | `onFocus` | `() => void` | Called on focus. |
72
- | `onBlur` | `() => void` | Called on blur. |
73
- | `style` | `StyleProp<ViewStyle>` | Input styles. Same public API on iOS and Android, with platform-specific internal handling. |
71
+ | `onFocus` | `() => void` | Called when the text input is focused. |
72
+ | `onBlur` | `() => void` | Called when the text input is blurred. |
73
+ | `autoComplete` | `string` | Specifies autocomplete hints for the system. Same as React Native [`TextInput`](https://reactnative.dev/docs/textinput#autocomplete). |
74
+ | `autoCapitalize` | `string` | Can be `none`, `sentences`, `words`, `characters`. Same as React Native [`TextInput`](https://reactnative.dev/docs/textinput#autocapitalize). |
75
+ | `keyboardType` | `string` | Determines which keyboard to open, e.g. `numeric`. Same as React Native [`TextInput`](https://reactnative.dev/docs/textinput#keyboardtype). |
76
+ | `returnKeyType` | `string` | Determines how the return key should look. Same as React Native [`TextInput`](https://reactnative.dev/docs/textinput#returnkeytype). |
77
+ | `placeholder` | `string` | The string that will be rendered before text input has been entered. |
78
+ | `placeholderTextColor` | `ColorValue` | The text color of the placeholder string. |
79
+ | `selectionColor` | `ColorValue` | The highlight and cursor color of the text input. |
74
80
 
75
81
  ## Style support
76
82
 
@@ -1,11 +1,9 @@
1
1
  package com.controlledinput
2
2
 
3
- import android.annotation.SuppressLint
4
3
  import android.content.Context
5
4
  import android.util.AttributeSet
6
5
  import android.view.inputmethod.InputMethodManager
7
6
  import android.widget.LinearLayout
8
- import androidx.compose.material3.Text
9
7
  import androidx.compose.runtime.LaunchedEffect
10
8
  import androidx.compose.runtime.collectAsState
11
9
  import androidx.compose.runtime.getValue
@@ -96,6 +94,13 @@ class ControlledInputView : LinearLayout {
96
94
  JetpackComposeView(
97
95
  value = value,
98
96
  inputStyle = viewModel.inputStyle,
97
+ autoComplete = viewModel.autoComplete,
98
+ placeholder = viewModel.placeholder,
99
+ placeholderTextColor = viewModel.placeholderTextColor,
100
+ selectionColor = viewModel.selectionColor,
101
+ autoCapitalize = viewModel.autoCapitalize,
102
+ keyboardType = viewModel.keyboardType,
103
+ returnKeyType = viewModel.returnKeyType,
99
104
  onTextChange = { value ->
100
105
  val surfaceId = UIManagerHelper.getSurfaceId(context)
101
106
  val viewId = this.id
@@ -140,4 +145,4 @@ class ControlledInputView : LinearLayout {
140
145
 
141
146
  }
142
147
  }
143
- }
148
+ }
@@ -38,6 +38,41 @@ class ControlledInputViewManager : SimpleViewManager<ControlledInputView>(),
38
38
  }
39
39
  }
40
40
 
41
+ @ReactProp(name = "placeholder")
42
+ override fun setPlaceholder(view: ControlledInputView, placeholder: String?) {
43
+ view.viewModel.setPlaceholder(placeholder)
44
+ }
45
+
46
+ @ReactProp(name = "placeholderTextColor", customType = "Color")
47
+ override fun setPlaceholderTextColor(view: ControlledInputView, placeholderTextColor: Int?) {
48
+ view.viewModel.setPlaceholderTextColor(placeholderTextColor)
49
+ }
50
+
51
+ @ReactProp(name = "selectionColor", customType = "Color")
52
+ override fun setSelectionColor(view: ControlledInputView, selectionColor: Int?) {
53
+ view.viewModel.setSelectionColor(selectionColor)
54
+ }
55
+
56
+ @ReactProp(name = "autoComplete")
57
+ override fun setAutoComplete(view: ControlledInputView, autoComplete: String?) {
58
+ view.viewModel.setAutoCompleteWithAutofill(view, autoComplete)
59
+ }
60
+
61
+ @ReactProp(name = "autoCapitalize")
62
+ override fun setAutoCapitalize(view: ControlledInputView, autoCapitalize: String?) {
63
+ view.viewModel.setAutoCapitalize(autoCapitalize)
64
+ }
65
+
66
+ @ReactProp(name = "keyboardType")
67
+ override fun setKeyboardType(view: ControlledInputView, keyboardType: String?) {
68
+ view.viewModel.setKeyboardType(keyboardType)
69
+ }
70
+
71
+ @ReactProp(name = "returnKeyType")
72
+ override fun setReturnKeyType(view: ControlledInputView, returnKeyType: String?) {
73
+ view.viewModel.setReturnKeyType(returnKeyType)
74
+ }
75
+
41
76
  @ReactProp(name = "inputStyle")
42
77
  override fun setInputStyle(view: ControlledInputView, inputStyle: ReadableMap?) {
43
78
  val style = if (inputStyle == null) {
@@ -2,6 +2,7 @@ package com.controlledinput
2
2
 
3
3
  import androidx.compose.foundation.background
4
4
  import androidx.compose.foundation.border
5
+ import androidx.compose.foundation.text.KeyboardOptions
5
6
  import androidx.compose.foundation.layout.Box
6
7
  import androidx.compose.foundation.layout.fillMaxSize
7
8
  import androidx.compose.foundation.layout.padding
@@ -25,16 +26,26 @@ import androidx.compose.ui.Modifier
25
26
  import androidx.compose.ui.focus.FocusRequester
26
27
  import androidx.compose.ui.focus.focusRequester
27
28
  import android.graphics.Typeface
29
+ import androidx.compose.foundation.text.selection.LocalTextSelectionColors
30
+ import androidx.compose.foundation.text.selection.TextSelectionColors
31
+ import androidx.compose.runtime.CompositionLocalProvider
28
32
  import androidx.compose.ui.graphics.Color
29
33
  import androidx.compose.ui.platform.LocalContext
30
34
  import androidx.compose.ui.text.TextStyle
31
35
  import androidx.compose.ui.text.font.FontFamily
36
+ import androidx.compose.ui.text.input.ImeAction
37
+ import androidx.compose.ui.text.input.KeyboardCapitalization
38
+ import androidx.compose.ui.text.input.KeyboardType
39
+ import androidx.compose.ui.autofill.AutofillNode
40
+ import androidx.compose.ui.autofill.AutofillType
41
+ import androidx.compose.ui.layout.onGloballyPositioned
42
+ import androidx.compose.ui.layout.boundsInWindow
43
+ import androidx.compose.ui.platform.LocalAutofill
44
+ import androidx.compose.ui.platform.LocalAutofillTree
32
45
  import androidx.compose.ui.unit.sp
33
- import androidx.lifecycle.ViewModel
34
46
  import com.facebook.react.bridge.Arguments
35
47
  import com.facebook.react.bridge.WritableMap
36
48
  import com.facebook.react.uimanager.events.Event
37
- import kotlinx.coroutines.flow.MutableStateFlow
38
49
  import kotlinx.coroutines.flow.StateFlow
39
50
 
40
51
  data class InputStyle(
@@ -55,6 +66,13 @@ data class InputStyle(
55
66
  fun JetpackComposeView(
56
67
  value: String,
57
68
  inputStyle: StateFlow<InputStyle?>,
69
+ autoComplete: StateFlow<String?>,
70
+ placeholder: StateFlow<String?>,
71
+ placeholderTextColor: StateFlow<Int?>,
72
+ selectionColor: StateFlow<Int?>,
73
+ autoCapitalize: StateFlow<String?>,
74
+ keyboardType: StateFlow<String?>,
75
+ returnKeyType: StateFlow<String?>,
58
76
  onTextChange: (value: String) -> Unit,
59
77
  onFocus: (() -> Unit)? = null,
60
78
  onBlur: (() -> Unit)? = null,
@@ -62,8 +80,24 @@ fun JetpackComposeView(
62
80
  ) {
63
81
  val state = remember { TextFieldState(value) }
64
82
  val style by inputStyle.collectAsState()
83
+ val keyboardTypeValue by keyboardType.collectAsState()
84
+ val autoCapitalizeValue by autoCapitalize.collectAsState()
85
+ val returnKeyTypeValue by returnKeyType.collectAsState()
86
+ val autoCompleteValue by autoComplete.collectAsState()
87
+ val placeholderValue by placeholder.collectAsState()
88
+ val placeholderTextColorValue by placeholderTextColor.collectAsState()
89
+ val selectionColorValue by selectionColor.collectAsState()
65
90
  val interactionSource = remember { MutableInteractionSource() }
66
91
 
92
+ val autofill = LocalAutofill.current
93
+ val autofillNode = remember {
94
+ AutofillNode(
95
+ autofillTypes = toAutofillTypes(autoCompleteValue),
96
+ onFill = { onTextChange(it) }
97
+ )
98
+ }
99
+ LocalAutofillTree.current += autofillNode
100
+
67
101
  if (state.text.toString() != value) {
68
102
  state.setTextAndPlaceCursorAtEnd(value)
69
103
  }
@@ -73,9 +107,11 @@ fun JetpackComposeView(
73
107
  when (interaction) {
74
108
  is FocusInteraction.Focus -> {
75
109
  onFocus?.invoke()
110
+ autofill?.requestAutofillForNode(autofillNode)
76
111
  }
77
112
  is FocusInteraction.Unfocus -> {
78
113
  onBlur?.invoke()
114
+ autofill?.cancelAutofillForNode(autofillNode)
79
115
  }
80
116
  }
81
117
  }
@@ -110,46 +146,124 @@ fun JetpackComposeView(
110
146
  ?: Color.Transparent
111
147
  val shape = RoundedCornerShape(borderRadius)
112
148
 
113
- Box(
114
- modifier = Modifier
115
- .fillMaxSize()
116
- .clip(shape)
117
- .background(backgroundColor)
118
- .border(borderWidth, borderColor, shape),
119
- ) {
120
- BasicTextField(
121
- state,
122
- inputTransformation = InputTransformation.byValue { _, proposed ->
123
- onTextChange(proposed.toString())
124
- proposed
125
- },
149
+ val cursorColor = selectionColorValue?.let { Color(it) } ?: textColor
150
+ val textSelectionColors = remember(cursorColor) {
151
+ TextSelectionColors(
152
+ handleColor = cursorColor,
153
+ backgroundColor = cursorColor.copy(alpha = 0.4f)
154
+ )
155
+ }
156
+
157
+ CompositionLocalProvider(LocalTextSelectionColors provides textSelectionColors) {
158
+ Box(
126
159
  modifier = Modifier
127
160
  .fillMaxSize()
128
- .padding(
129
- start = paddingLeft,
130
- top = paddingTop,
131
- end = paddingRight,
132
- bottom = paddingBottom,
133
- )
134
- .focusRequester(focusRequester),
135
- textStyle = TextStyle(
136
- color = textColor,
137
- fontSize = fontSize,
138
- fontFamily = fontFamily,
139
- ),
140
- interactionSource = interactionSource,
141
- decorator = { innerTextField ->
142
- Box(
143
- modifier = Modifier.fillMaxSize(),
144
- contentAlignment = Alignment.CenterStart,
145
- ) {
146
- innerTextField()
147
- }
148
- },
149
- )
161
+ .clip(shape)
162
+ .background(backgroundColor)
163
+ .border(borderWidth, borderColor, shape),
164
+ ) {
165
+ BasicTextField(
166
+ state,
167
+ inputTransformation = InputTransformation.byValue { _, proposed ->
168
+ onTextChange(proposed.toString())
169
+ proposed
170
+ },
171
+ modifier = Modifier
172
+ .fillMaxSize()
173
+ .padding(
174
+ start = paddingLeft,
175
+ top = paddingTop,
176
+ end = paddingRight,
177
+ bottom = paddingBottom,
178
+ )
179
+ .onGloballyPositioned {
180
+ autofillNode.boundingBox = it.boundsInWindow()
181
+ }
182
+ .focusRequester(focusRequester),
183
+ textStyle = TextStyle(
184
+ color = textColor,
185
+ fontSize = fontSize,
186
+ fontFamily = fontFamily,
187
+ ),
188
+ keyboardOptions = KeyboardOptions(
189
+ capitalization = toComposeCapitalization(autoCapitalizeValue),
190
+ keyboardType = toComposeKeyboardType(keyboardTypeValue),
191
+ imeAction = toComposeImeAction(returnKeyTypeValue),
192
+ ),
193
+ interactionSource = interactionSource,
194
+ cursorBrush = androidx.compose.ui.graphics.SolidColor(cursorColor),
195
+ decorator = { innerTextField ->
196
+ Box(
197
+ modifier = Modifier.fillMaxSize(),
198
+ contentAlignment = Alignment.CenterStart,
199
+ ) {
200
+ if (state.text.isEmpty() && !placeholderValue.isNullOrEmpty()) {
201
+ val finalPlaceholderColor = placeholderTextColorValue?.let { Color(it) }
202
+ ?: textColor.copy(alpha = 0.5f)
203
+ androidx.compose.material3.Text(
204
+ text = placeholderValue!!,
205
+ style = TextStyle(
206
+ color = finalPlaceholderColor,
207
+ fontSize = fontSize,
208
+ fontFamily = fontFamily,
209
+ )
210
+ )
211
+ }
212
+ innerTextField()
213
+ }
214
+ },
215
+ )
216
+ }
150
217
  }
151
218
  }
152
219
 
220
+ private fun toComposeKeyboardType(value: String?): KeyboardType = when (value) {
221
+ "ascii-capable" -> KeyboardType.Ascii
222
+ "numbers-and-punctuation" -> KeyboardType.Text
223
+ "url" -> KeyboardType.Uri
224
+ "number-pad", "numeric" -> KeyboardType.Number
225
+ "phone-pad" -> KeyboardType.Phone
226
+ "email-address" -> KeyboardType.Email
227
+ "decimal-pad" -> KeyboardType.Decimal
228
+ "visible-password" -> KeyboardType.Password
229
+ else -> KeyboardType.Text
230
+ }
231
+
232
+ private fun toComposeCapitalization(value: String?): KeyboardCapitalization = when (value) {
233
+ "none" -> KeyboardCapitalization.None
234
+ "characters" -> KeyboardCapitalization.Characters
235
+ "words" -> KeyboardCapitalization.Words
236
+ "sentences" -> KeyboardCapitalization.Sentences
237
+ else -> KeyboardCapitalization.Sentences
238
+ }
239
+
240
+ private fun toComposeImeAction(value: String?): ImeAction = when (value) {
241
+ "go" -> ImeAction.Go
242
+ "next" -> ImeAction.Next
243
+ "search" -> ImeAction.Search
244
+ "send" -> ImeAction.Send
245
+ "done" -> ImeAction.Done
246
+ "none" -> ImeAction.None
247
+ "previous" -> ImeAction.Previous
248
+ else -> ImeAction.Default
249
+ }
250
+
251
+ private fun toAutofillTypes(autoComplete: String?): List<AutofillType> = when (autoComplete) {
252
+ "email" -> listOf(AutofillType.EmailAddress)
253
+ "name", "given-name", "family-name", "additional-name" -> listOf(AutofillType.PersonFullName)
254
+ "username" -> listOf(AutofillType.Username)
255
+ "password", "new-password" -> listOf(AutofillType.Password)
256
+ "tel" -> listOf(AutofillType.PhoneNumber)
257
+ "postal-code" -> listOf(AutofillType.PostalCode)
258
+ "street-address" -> listOf(AutofillType.AddressStreet)
259
+ "cc-number" -> listOf(AutofillType.CreditCardNumber)
260
+ "cc-exp" -> listOf(AutofillType.CreditCardExpirationDate)
261
+ "cc-exp-month" -> listOf(AutofillType.CreditCardExpirationMonth)
262
+ "cc-exp-year" -> listOf(AutofillType.CreditCardExpirationYear)
263
+ "cc-csc" -> listOf(AutofillType.CreditCardSecurityCode)
264
+ else -> emptyList()
265
+ }
266
+
153
267
  class TextChangeEvent(
154
268
  surfaceId: Int,
155
269
  viewId: Int,
@@ -198,20 +312,3 @@ class BlurEvent(
198
312
  const val EVENT_NAME = "onBlur"
199
313
  }
200
314
  }
201
-
202
-
203
- class JetpackComposeViewModel : ViewModel() {
204
- private val _value = MutableStateFlow("")
205
- private val _inputStyle = MutableStateFlow<InputStyle?>(null)
206
-
207
- val value: StateFlow<String> get() = _value
208
- val inputStyle: StateFlow<InputStyle?> get() = _inputStyle
209
-
210
- fun setValue(newValue: String) {
211
- _value.value = newValue
212
- }
213
-
214
- fun setInputStyle(style: InputStyle?) {
215
- _inputStyle.value = style
216
- }
217
- }
@@ -0,0 +1,98 @@
1
+ package com.controlledinput
2
+
3
+ import android.os.Build
4
+ import android.view.View
5
+ import androidx.lifecycle.ViewModel
6
+ import kotlinx.coroutines.flow.MutableStateFlow
7
+ import kotlinx.coroutines.flow.StateFlow
8
+
9
+ class JetpackComposeViewModel : ViewModel() {
10
+ private val _value = MutableStateFlow("")
11
+ private val _inputStyle = MutableStateFlow<InputStyle?>(null)
12
+ private val _autoComplete = MutableStateFlow<String?>(null)
13
+ private val _placeholder = MutableStateFlow<String?>(null)
14
+ private val _placeholderTextColor = MutableStateFlow<Int?>(null)
15
+ private val _selectionColor = MutableStateFlow<Int?>(null)
16
+ private val _autoCapitalize = MutableStateFlow<String?>(null)
17
+ private val _keyboardType = MutableStateFlow<String?>(null)
18
+ private val _returnKeyType = MutableStateFlow<String?>(null)
19
+
20
+ val value: StateFlow<String> get() = _value
21
+ val inputStyle: StateFlow<InputStyle?> get() = _inputStyle
22
+ val autoComplete: StateFlow<String?> get() = _autoComplete
23
+ val placeholder: StateFlow<String?> get() = _placeholder
24
+ val placeholderTextColor: StateFlow<Int?> get() = _placeholderTextColor
25
+ val selectionColor: StateFlow<Int?> get() = _selectionColor
26
+ val autoCapitalize: StateFlow<String?> get() = _autoCapitalize
27
+ val keyboardType: StateFlow<String?> get() = _keyboardType
28
+ val returnKeyType: StateFlow<String?> get() = _returnKeyType
29
+
30
+ fun setValue(newValue: String) {
31
+ _value.value = newValue
32
+ }
33
+
34
+ fun setInputStyle(style: InputStyle?) {
35
+ _inputStyle.value = style
36
+ }
37
+
38
+ fun setAutoComplete(newValue: String?) {
39
+ _autoComplete.value = newValue
40
+ }
41
+
42
+ fun setPlaceholder(newValue: String?) {
43
+ _placeholder.value = newValue
44
+ }
45
+
46
+ fun setPlaceholderTextColor(newValue: Int?) {
47
+ _placeholderTextColor.value = newValue
48
+ }
49
+
50
+ fun setSelectionColor(newValue: Int?) {
51
+ _selectionColor.value = newValue
52
+ }
53
+
54
+ fun setAutoCompleteWithAutofill(hostView: View, newValue: String?) {
55
+ setAutoComplete(newValue)
56
+
57
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
58
+ return
59
+ }
60
+
61
+ val hint = when (newValue) {
62
+ "email" -> View.AUTOFILL_HINT_EMAIL_ADDRESS
63
+ "name", "given-name", "family-name" -> View.AUTOFILL_HINT_NAME
64
+ "username" -> View.AUTOFILL_HINT_USERNAME
65
+ "password", "new-password" -> View.AUTOFILL_HINT_PASSWORD
66
+ "tel" -> View.AUTOFILL_HINT_PHONE
67
+ "postal-code" -> View.AUTOFILL_HINT_POSTAL_CODE
68
+ "street-address" -> View.AUTOFILL_HINT_POSTAL_ADDRESS
69
+ "cc-number" -> View.AUTOFILL_HINT_CREDIT_CARD_NUMBER
70
+ "cc-exp" -> View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE
71
+ "cc-exp-month" -> View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH
72
+ "cc-exp-year" -> View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR
73
+ "cc-csc" -> View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE
74
+ "additional-name" -> View.AUTOFILL_HINT_NAME
75
+ else -> null
76
+ }
77
+
78
+ if (hint == null || newValue == "off" || newValue.isNullOrEmpty()) {
79
+ hostView.setAutofillHints(*emptyArray())
80
+ return
81
+ }
82
+
83
+ hostView.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES
84
+ hostView.setAutofillHints(hint)
85
+ }
86
+
87
+ fun setAutoCapitalize(newValue: String?) {
88
+ _autoCapitalize.value = newValue
89
+ }
90
+
91
+ fun setKeyboardType(newValue: String?) {
92
+ _keyboardType.value = newValue
93
+ }
94
+
95
+ fun setReturnKeyType(newValue: String?) {
96
+ _returnKeyType.value = newValue
97
+ }
98
+ }
@@ -52,6 +52,34 @@ using namespace facebook::react;
52
52
  _inputView.value = [NSString stringWithUTF8String:newViewProps.value.c_str()];
53
53
  }
54
54
 
55
+ if (oldViewProps.autoComplete != newViewProps.autoComplete) {
56
+ _inputView.autoComplete = newViewProps.autoComplete.empty() ? nil : [NSString stringWithUTF8String:newViewProps.autoComplete.c_str()];
57
+ }
58
+
59
+ if (oldViewProps.keyboardType != newViewProps.keyboardType) {
60
+ _inputView.keyboardType = newViewProps.keyboardType.empty() ? nil : [NSString stringWithUTF8String:newViewProps.keyboardType.c_str()];
61
+ }
62
+
63
+ if (oldViewProps.returnKeyType != newViewProps.returnKeyType) {
64
+ _inputView.returnKeyType = newViewProps.returnKeyType.empty() ? nil : [NSString stringWithUTF8String:newViewProps.returnKeyType.c_str()];
65
+ }
66
+
67
+ if (oldViewProps.autoCapitalize != newViewProps.autoCapitalize) {
68
+ _inputView.autoCapitalize = newViewProps.autoCapitalize.empty() ? nil : [NSString stringWithUTF8String:newViewProps.autoCapitalize.c_str()];
69
+ }
70
+
71
+ if (oldViewProps.placeholder != newViewProps.placeholder) {
72
+ _inputView.placeholder = newViewProps.placeholder.empty() ? nil : [NSString stringWithUTF8String:newViewProps.placeholder.c_str()];
73
+ }
74
+
75
+ if (oldViewProps.placeholderTextColor != newViewProps.placeholderTextColor) {
76
+ _inputView.placeholderTextColor = RCTUIColorFromSharedColor(newViewProps.placeholderTextColor);
77
+ }
78
+
79
+ if (oldViewProps.selectionColor != newViewProps.selectionColor) {
80
+ _inputView.selectionColor = RCTUIColorFromSharedColor(newViewProps.selectionColor);
81
+ }
82
+
55
83
  const auto &style = newViewProps.inputStyle;
56
84
  const auto &oldStyle = oldViewProps.inputStyle;
57
85
 
@@ -32,6 +32,34 @@ public class RNControlledInput: UIView, UITextFieldDelegate {
32
32
  didSet { applyFont() }
33
33
  }
34
34
 
35
+ @objc public var autoComplete: String? {
36
+ didSet { applyAutoComplete() }
37
+ }
38
+
39
+ @objc public var autoCapitalize: String? {
40
+ didSet { applyAutoCapitalize() }
41
+ }
42
+
43
+ @objc public var keyboardType: String? {
44
+ didSet { applyKeyboardType() }
45
+ }
46
+
47
+ @objc public var returnKeyType: String? {
48
+ didSet { applyReturnKeyType() }
49
+ }
50
+
51
+ @objc public var placeholder: String? {
52
+ didSet { applyPlaceholder() }
53
+ }
54
+
55
+ @objc public var placeholderTextColor: UIColor? {
56
+ didSet { applyPlaceholder() }
57
+ }
58
+
59
+ @objc public var selectionColor: UIColor? {
60
+ didSet { textField.tintColor = selectionColor }
61
+ }
62
+
35
63
  public override var canBecomeFirstResponder: Bool { true }
36
64
 
37
65
  @objc public func focus() {
@@ -55,6 +83,7 @@ public class RNControlledInput: UIView, UITextFieldDelegate {
55
83
  textField.translatesAutoresizingMaskIntoConstraints = false
56
84
  textField.borderStyle = .none
57
85
  textField.delegate = self
86
+ textField.textColor = textColor
58
87
  addSubview(textField)
59
88
 
60
89
  NSLayoutConstraint.activate([
@@ -65,6 +94,29 @@ public class RNControlledInput: UIView, UITextFieldDelegate {
65
94
  ])
66
95
 
67
96
  applyFont()
97
+ applyAutoComplete()
98
+ applyAutoCapitalize()
99
+ applyKeyboardType()
100
+ applyReturnKeyType()
101
+ applyPlaceholder()
102
+ }
103
+
104
+ private func applyPlaceholder() {
105
+ guard let placeholder = placeholder else {
106
+ textField.attributedPlaceholder = nil
107
+ return
108
+ }
109
+
110
+ var attributes: [NSAttributedString.Key: Any] = [:]
111
+ if let placeholderTextColor = placeholderTextColor {
112
+ attributes[.foregroundColor] = placeholderTextColor
113
+ }
114
+
115
+ textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: attributes)
116
+
117
+ if let color = textColor {
118
+ textField.textColor = color
119
+ }
68
120
  }
69
121
 
70
122
  public func textFieldDidBeginEditing(_ textField: UITextField) {
@@ -82,6 +134,12 @@ public class RNControlledInput: UIView, UITextFieldDelegate {
82
134
 
83
135
  delegate?.controlledInputDidChangeText(self, value: newText)
84
136
 
137
+ // If the replacement string is more than one character, it's likely an autocomplete/paste
138
+ // In this case, we should let the UITextField update itself to handle it correctly
139
+ if string.count > 1 {
140
+ return true
141
+ }
142
+
85
143
  return false
86
144
  }
87
145
 
@@ -92,4 +150,150 @@ public class RNControlledInput: UIView, UITextFieldDelegate {
92
150
  textField.font = UIFont.systemFont(ofSize: fontSize)
93
151
  }
94
152
  }
153
+
154
+ private func applyAutoComplete() {
155
+ guard let autoComplete else {
156
+ textField.textContentType = nil
157
+ return
158
+ }
159
+
160
+ if autoComplete == "off" || autoComplete.isEmpty {
161
+ textField.textContentType = nil
162
+ return
163
+ }
164
+
165
+ let mappedValue: String
166
+ switch autoComplete {
167
+ case "email":
168
+ mappedValue = "emailAddress"
169
+ case "tel":
170
+ mappedValue = "telephoneNumber"
171
+ case "name":
172
+ mappedValue = "name"
173
+ case "given-name":
174
+ mappedValue = "givenName"
175
+ case "middle-name":
176
+ mappedValue = "middleName"
177
+ case "family-name":
178
+ mappedValue = "familyName"
179
+ case "username":
180
+ mappedValue = "username"
181
+ case "password":
182
+ mappedValue = "password"
183
+ case "new-password":
184
+ mappedValue = "newPassword"
185
+ case "one-time-code":
186
+ mappedValue = "oneTimeCode"
187
+ case "postal-code":
188
+ mappedValue = "postalCode"
189
+ case "street-address":
190
+ mappedValue = "fullStreetAddress"
191
+ case "country":
192
+ mappedValue = "countryName"
193
+ case "cc-number":
194
+ mappedValue = "creditCardNumber"
195
+ case "cc-csc":
196
+ mappedValue = "creditCardSecurityCode"
197
+ case "cc-exp":
198
+ mappedValue = "creditCardExpiration"
199
+ case "cc-exp-month":
200
+ mappedValue = "creditCardExpirationMonth"
201
+ case "cc-exp-year":
202
+ mappedValue = "creditCardExpirationYear"
203
+ case "birthdate-day":
204
+ mappedValue = "birthdateDay"
205
+ case "birthdate-month":
206
+ mappedValue = "birthdateMonth"
207
+ case "birthdate-year":
208
+ mappedValue = "birthdateYear"
209
+ case "url":
210
+ mappedValue = "URL"
211
+ default:
212
+ mappedValue = autoComplete
213
+ }
214
+
215
+ textField.textContentType = UITextContentType(rawValue: mappedValue)
216
+ }
217
+
218
+ private func applyAutoCapitalize() {
219
+ guard let autoCapitalize else {
220
+ textField.autocapitalizationType = .sentences
221
+ return
222
+ }
223
+
224
+ switch autoCapitalize {
225
+ case "none":
226
+ textField.autocapitalizationType = .none
227
+ case "words":
228
+ textField.autocapitalizationType = .words
229
+ case "sentences":
230
+ textField.autocapitalizationType = .sentences
231
+ case "characters":
232
+ textField.autocapitalizationType = .allCharacters
233
+ default:
234
+ textField.autocapitalizationType = .sentences
235
+ }
236
+ }
237
+
238
+ private func applyKeyboardType() {
239
+ switch keyboardType {
240
+ case "ascii-capable":
241
+ textField.keyboardType = .asciiCapable
242
+ case "numbers-and-punctuation":
243
+ textField.keyboardType = .numbersAndPunctuation
244
+ case "url":
245
+ textField.keyboardType = .URL
246
+ case "number-pad":
247
+ textField.keyboardType = .numberPad
248
+ case "phone-pad":
249
+ textField.keyboardType = .phonePad
250
+ case "name-phone-pad":
251
+ textField.keyboardType = .namePhonePad
252
+ case "email-address":
253
+ textField.keyboardType = .emailAddress
254
+ case "decimal-pad":
255
+ textField.keyboardType = .decimalPad
256
+ case "twitter":
257
+ textField.keyboardType = .twitter
258
+ case "web-search":
259
+ textField.keyboardType = .webSearch
260
+ case "visible-password":
261
+ textField.keyboardType = .asciiCapable
262
+ case "numeric":
263
+ textField.keyboardType = .numbersAndPunctuation
264
+ default:
265
+ textField.keyboardType = .default
266
+ }
267
+ }
268
+
269
+ private func applyReturnKeyType() {
270
+ switch returnKeyType {
271
+ case "done":
272
+ textField.returnKeyType = .done
273
+ case "go":
274
+ textField.returnKeyType = .go
275
+ case "next":
276
+ textField.returnKeyType = .next
277
+ case "search":
278
+ textField.returnKeyType = .search
279
+ case "send":
280
+ textField.returnKeyType = .send
281
+ case "none":
282
+ textField.returnKeyType = .default
283
+ case "previous":
284
+ textField.returnKeyType = .default
285
+ case "route":
286
+ textField.returnKeyType = .route
287
+ case "yahoo":
288
+ textField.returnKeyType = .yahoo
289
+ case "emergency-call":
290
+ textField.returnKeyType = .emergencyCall
291
+ case "google":
292
+ textField.returnKeyType = .google
293
+ case "join":
294
+ textField.returnKeyType = .join
295
+ default:
296
+ textField.returnKeyType = .default
297
+ }
298
+ }
95
299
  }
@@ -1,28 +1,28 @@
1
1
  import {
2
2
  codegenNativeComponent,
3
- type ViewProps,
4
3
  type ColorValue,
5
4
  type HostComponent,
5
+ type ViewProps,
6
6
  } from 'react-native';
7
- import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
7
+ import { codegenNativeCommands } from 'react-native';
8
8
  import type {
9
9
  BubblingEventHandler,
10
10
  Double,
11
11
  } from 'react-native/Libraries/Types/CodegenTypes';
12
12
 
13
- interface TextChangeEvent {
13
+ export interface TextChangeEvent {
14
14
  value: string;
15
15
  }
16
16
 
17
- interface FocusEvent {
17
+ export interface FocusEvent {
18
18
  // Empty event
19
19
  }
20
20
 
21
- interface BlurEvent {
21
+ export interface BlurEvent {
22
22
  // Empty event
23
23
  }
24
24
 
25
- interface InputStyle {
25
+ export interface InputStyle {
26
26
  color?: ColorValue;
27
27
  fontSize?: Double;
28
28
  fontFamily?: string;
@@ -38,6 +38,13 @@ interface InputStyle {
38
38
 
39
39
  export interface NativeProps extends ViewProps {
40
40
  value?: string;
41
+ placeholder?: string;
42
+ placeholderTextColor?: ColorValue;
43
+ selectionColor?: ColorValue;
44
+ autoComplete?: string;
45
+ autoCapitalize?: string;
46
+ keyboardType?: string;
47
+ returnKeyType?: string;
41
48
  inputStyle?: InputStyle;
42
49
  onTextChange?: BubblingEventHandler<Readonly<TextChangeEvent>>;
43
50
  onFocus?: BubblingEventHandler<Readonly<FocusEvent>>;
@@ -1,50 +1,52 @@
1
1
  "use strict";
2
2
 
3
- import { forwardRef, useImperativeHandle, useRef } from 'react';
3
+ import { forwardRef, memo, useImperativeHandle, useRef } from 'react';
4
4
  import { Platform, processColor, StyleSheet } from 'react-native';
5
5
  import ControlledInputViewNativeComponent, { Commands } from './ControlledInputViewNativeComponent';
6
6
  import { jsx as _jsx } from "react/jsx-runtime";
7
7
  // All style props that Android handles via Compose instead of the native View layer
8
- const ANDROID_HANDLED_KEYS = ['color', 'fontSize', 'fontFamily', 'padding', 'paddingVertical', 'paddingHorizontal', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingStart', 'paddingEnd', 'borderWidth', 'borderRadius', 'borderColor', 'backgroundColor'];
9
- function resolveAndroidPadding(flat) {
10
- const base = flat.padding ?? 0;
8
+ const androidComposeHandledKeys = ['color', 'fontSize', 'fontFamily', 'padding', 'paddingVertical', 'paddingHorizontal', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'borderWidth', 'borderRadius', 'borderColor', 'backgroundColor'];
9
+ function resolveAndroidComposeViewPadding(flat) {
10
+ const padding = flat.padding ?? 0;
11
11
  return {
12
- paddingTop: flat.paddingTop ?? flat.paddingVertical ?? base,
13
- paddingBottom: flat.paddingBottom ?? flat.paddingVertical ?? base,
14
- paddingLeft: flat.paddingLeft ?? flat.paddingStart ?? flat.paddingHorizontal ?? base,
15
- paddingRight: flat.paddingRight ?? flat.paddingEnd ?? flat.paddingHorizontal ?? base
12
+ paddingTop: flat.paddingTop ?? flat.paddingVertical ?? padding,
13
+ paddingBottom: flat.paddingBottom ?? flat.paddingVertical ?? padding,
14
+ paddingLeft: flat.paddingLeft ?? flat.paddingHorizontal ?? padding,
15
+ paddingRight: flat.paddingRight ?? flat.paddingHorizontal ?? padding
16
16
  };
17
17
  }
18
- export const ControlledInputView = /*#__PURE__*/forwardRef(({
18
+ export const ControlledInputView = /*#__PURE__*/memo(/*#__PURE__*/forwardRef(({
19
19
  style,
20
20
  onTextChange,
21
+ selectionColor,
22
+ placeholderTextColor,
21
23
  ...rest
22
24
  }, ref) => {
23
25
  const nativeRef = useRef(null);
24
- const flat = StyleSheet.flatten(style) ?? {};
26
+ const flattenedStyle = StyleSheet.flatten(style) ?? {};
25
27
  let viewStyle;
26
28
  let inputStyle;
27
29
  if (Platform.OS === 'android') {
28
- viewStyle = Object.fromEntries(Object.entries(flat).filter(([k]) => !ANDROID_HANDLED_KEYS.includes(k)));
29
- const hasPadding = ANDROID_HANDLED_KEYS.slice(3, 12).some(k => flat[k] != null);
30
+ viewStyle = Object.fromEntries(Object.entries(flattenedStyle).filter(([k]) => !androidComposeHandledKeys.includes(k)));
31
+ const hasPadding = Object.entries(flattenedStyle).some(([k, v]) => k.includes('padding') && v != null);
30
32
  inputStyle = {
31
- color: flat.color,
32
- fontSize: flat.fontSize,
33
- fontFamily: flat.fontFamily,
34
- ...(hasPadding ? resolveAndroidPadding(flat) : {}),
35
- borderWidth: flat.borderWidth,
36
- borderRadius: flat.borderRadius,
37
- borderColor: flat.borderColor,
38
- backgroundColor: flat.backgroundColor
33
+ color: flattenedStyle.color,
34
+ fontSize: flattenedStyle.fontSize,
35
+ fontFamily: flattenedStyle.fontFamily,
36
+ ...(hasPadding ? resolveAndroidComposeViewPadding(flattenedStyle) : {}),
37
+ borderWidth: flattenedStyle.borderWidth,
38
+ borderRadius: flattenedStyle.borderRadius,
39
+ borderColor: flattenedStyle.borderColor,
40
+ backgroundColor: flattenedStyle.backgroundColor
39
41
  };
40
42
  } else {
41
43
  const {
42
44
  color,
43
45
  fontSize,
44
46
  fontFamily,
45
- ...iosRest
46
- } = flat;
47
- viewStyle = iosRest;
47
+ ...iosViewStyle
48
+ } = flattenedStyle;
49
+ viewStyle = iosViewStyle;
48
50
  const hasTextStyle = color != null || fontSize != null || fontFamily != null;
49
51
  inputStyle = hasTextStyle ? {
50
52
  color: color != null ? processColor(color) : undefined,
@@ -52,6 +54,11 @@ export const ControlledInputView = /*#__PURE__*/forwardRef(({
52
54
  fontFamily
53
55
  } : undefined;
54
56
  }
57
+ const handleTextChange = e => {
58
+ if (onTextChange) {
59
+ onTextChange(e.nativeEvent.value);
60
+ }
61
+ };
55
62
  useImperativeHandle(ref, () => ({
56
63
  blur: () => {
57
64
  if (!nativeRef.current) return;
@@ -68,11 +75,14 @@ export const ControlledInputView = /*#__PURE__*/forwardRef(({
68
75
  }));
69
76
  return /*#__PURE__*/_jsx(ControlledInputViewNativeComponent, {
70
77
  ...rest,
78
+ placeholderTextColor: placeholderTextColor,
79
+ selectionColor: selectionColor,
71
80
  style: viewStyle,
72
81
  inputStyle: inputStyle,
73
- onTextChange: onTextChange ? e => onTextChange(e.nativeEvent.value) : undefined,
82
+ onTextChange: handleTextChange,
74
83
  ref: nativeRef
75
84
  });
76
- });
85
+ }));
86
+ ControlledInputView.displayName = 'ControlledInputView';
77
87
  export * from './ControlledInputViewNativeComponent';
78
88
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["forwardRef","useImperativeHandle","useRef","Platform","processColor","StyleSheet","ControlledInputViewNativeComponent","Commands","jsx","_jsx","ANDROID_HANDLED_KEYS","resolveAndroidPadding","flat","base","padding","paddingTop","paddingVertical","paddingBottom","paddingLeft","paddingStart","paddingHorizontal","paddingRight","paddingEnd","ControlledInputView","style","onTextChange","rest","ref","nativeRef","flatten","viewStyle","inputStyle","OS","Object","fromEntries","entries","filter","k","includes","hasPadding","slice","some","color","fontSize","fontFamily","borderWidth","borderRadius","borderColor","backgroundColor","iosRest","hasTextStyle","undefined","blur","current","focus","e","nativeEvent","value"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["forwardRef","memo","useImperativeHandle","useRef","Platform","processColor","StyleSheet","ControlledInputViewNativeComponent","Commands","jsx","_jsx","androidComposeHandledKeys","resolveAndroidComposeViewPadding","flat","padding","paddingTop","paddingVertical","paddingBottom","paddingLeft","paddingHorizontal","paddingRight","ControlledInputView","style","onTextChange","selectionColor","placeholderTextColor","rest","ref","nativeRef","flattenedStyle","flatten","viewStyle","inputStyle","OS","Object","fromEntries","entries","filter","k","includes","hasPadding","some","v","color","fontSize","fontFamily","borderWidth","borderRadius","borderColor","backgroundColor","iosViewStyle","hasTextStyle","undefined","handleTextChange","e","nativeEvent","value","blur","current","focus","displayName"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
@@ -1,13 +1,13 @@
1
- import { type ViewProps, type ColorValue, type HostComponent } from 'react-native';
1
+ import { type ColorValue, type HostComponent, type ViewProps } from 'react-native';
2
2
  import type { BubblingEventHandler, Double } from 'react-native/Libraries/Types/CodegenTypes';
3
- interface TextChangeEvent {
3
+ export interface TextChangeEvent {
4
4
  value: string;
5
5
  }
6
- interface FocusEvent {
6
+ export interface FocusEvent {
7
7
  }
8
- interface BlurEvent {
8
+ export interface BlurEvent {
9
9
  }
10
- interface InputStyle {
10
+ export interface InputStyle {
11
11
  color?: ColorValue;
12
12
  fontSize?: Double;
13
13
  fontFamily?: string;
@@ -22,6 +22,13 @@ interface InputStyle {
22
22
  }
23
23
  export interface NativeProps extends ViewProps {
24
24
  value?: string;
25
+ placeholder?: string;
26
+ placeholderTextColor?: ColorValue;
27
+ selectionColor?: ColorValue;
28
+ autoComplete?: string;
29
+ autoCapitalize?: string;
30
+ keyboardType?: string;
31
+ returnKeyType?: string;
25
32
  inputStyle?: InputStyle;
26
33
  onTextChange?: BubblingEventHandler<Readonly<TextChangeEvent>>;
27
34
  onFocus?: BubblingEventHandler<Readonly<FocusEvent>>;
@@ -1 +1 @@
1
- {"version":3,"file":"ControlledInputViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/ControlledInputViewNativeComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EACV,oBAAoB,EACpB,MAAM,EACP,MAAM,2CAA2C,CAAC;AAEnD,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,UAAU;CAEnB;AAED,UAAU,SAAS;CAElB;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;IACrD,MAAM,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,KAAK,IAAI,CAAC;IACvE,IAAI,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,KAAK,IAAI,CAAC;CACvE;AAED,eAAO,MAAM,QAAQ,EAAE,cAErB,CAAC;;AAEH,wBAA0E"}
1
+ {"version":3,"file":"ControlledInputViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/ControlledInputViewNativeComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EACV,oBAAoB,EACpB,MAAM,EACP,MAAM,2CAA2C,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;CAE1B;AAED,MAAM,WAAW,SAAS;CAEzB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,UAAU,CAAC;IAClC,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;IACrD,MAAM,CAAC,EAAE,oBAAoB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,KAAK,IAAI,CAAC;IACvE,IAAI,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,KAAK,IAAI,CAAC;CACvE;AAED,eAAO,MAAM,QAAQ,EAAE,cAErB,CAAC;;AAEH,wBAA0E"}
@@ -1,12 +1,14 @@
1
+ import { type TextInputProps } from 'react-native';
1
2
  import { type NativeProps } from './ControlledInputViewNativeComponent';
2
3
  export interface ControlledInputViewRef {
3
4
  blur: () => void;
4
5
  focus: () => void;
5
6
  }
6
- export type ControlledInputViewProps = Omit<NativeProps, 'inputStyle' | 'onTextChange'> & {
7
+ type ForwardedTextInputProps = Pick<TextInputProps, 'autoComplete' | 'autoCapitalize' | 'keyboardType' | 'returnKeyType' | 'placeholder' | 'placeholderTextColor' | 'selectionColor'>;
8
+ export type ControlledInputViewProps = Omit<NativeProps, 'inputStyle' | 'onTextChange' | keyof ForwardedTextInputProps> & ForwardedTextInputProps & {
7
9
  onTextChange?: (value: string) => void;
8
10
  };
9
- export declare const ControlledInputView: import("react").ForwardRefExoticComponent<Omit<NativeProps, "inputStyle" | "onTextChange"> & {
11
+ export declare const ControlledInputView: import("react").NamedExoticComponent<Omit<NativeProps, "placeholder" | "placeholderTextColor" | "selectionColor" | "autoComplete" | "autoCapitalize" | "keyboardType" | "returnKeyType" | "inputStyle" | "onTextChange"> & ForwardedTextInputProps & {
10
12
  onTextChange?: (value: string) => void;
11
13
  } & import("react").RefAttributes<ControlledInputViewRef>>;
12
14
  export * from './ControlledInputViewNativeComponent';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAYA,OAA2C,EAEzC,KAAK,WAAW,EACjB,MAAM,sCAAsC,CAAC;AAE9C,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,WAAW,EACX,YAAY,GAAG,cAAc,CAC9B,GAAG;IACF,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,CAAC;AAkCF,eAAO,MAAM,mBAAmB;mBAnCf,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;0DA2GtC,CAAC;AAEH,cAAc,sCAAsC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,cAAc,EAGpB,MAAM,cAAc,CAAC;AACtB,OAA2C,EAEzC,KAAK,WAAW,EAEjB,MAAM,sCAAsC,CAAC;AAE9C,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,KAAK,uBAAuB,GAAG,IAAI,CACjC,cAAc,EACZ,cAAc,GACd,gBAAgB,GAChB,cAAc,GACd,eAAe,GACf,aAAa,GACb,sBAAsB,GACtB,gBAAgB,CACnB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,WAAW,EACX,YAAY,GAAG,cAAc,GAAG,MAAM,uBAAuB,CAC9D,GACC,uBAAuB,GAAG;IACxB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,CAAC;AA+BJ,eAAO,MAAM,mBAAmB;mBAhCb,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;0DAwHzC,CAAC;AAIF,cAAc,sCAAsC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-controlled-input",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "React Native Controlled Inputative Controlled Input",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -1,28 +1,28 @@
1
1
  import {
2
2
  codegenNativeComponent,
3
- type ViewProps,
4
3
  type ColorValue,
5
4
  type HostComponent,
5
+ type ViewProps,
6
6
  } from 'react-native';
7
- import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
7
+ import { codegenNativeCommands } from 'react-native';
8
8
  import type {
9
9
  BubblingEventHandler,
10
10
  Double,
11
11
  } from 'react-native/Libraries/Types/CodegenTypes';
12
12
 
13
- interface TextChangeEvent {
13
+ export interface TextChangeEvent {
14
14
  value: string;
15
15
  }
16
16
 
17
- interface FocusEvent {
17
+ export interface FocusEvent {
18
18
  // Empty event
19
19
  }
20
20
 
21
- interface BlurEvent {
21
+ export interface BlurEvent {
22
22
  // Empty event
23
23
  }
24
24
 
25
- interface InputStyle {
25
+ export interface InputStyle {
26
26
  color?: ColorValue;
27
27
  fontSize?: Double;
28
28
  fontFamily?: string;
@@ -38,6 +38,13 @@ interface InputStyle {
38
38
 
39
39
  export interface NativeProps extends ViewProps {
40
40
  value?: string;
41
+ placeholder?: string;
42
+ placeholderTextColor?: ColorValue;
43
+ selectionColor?: ColorValue;
44
+ autoComplete?: string;
45
+ autoCapitalize?: string;
46
+ keyboardType?: string;
47
+ returnKeyType?: string;
41
48
  inputStyle?: InputStyle;
42
49
  onTextChange?: BubblingEventHandler<Readonly<TextChangeEvent>>;
43
50
  onFocus?: BubblingEventHandler<Readonly<FocusEvent>>;
package/src/index.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  forwardRef,
3
+ memo,
3
4
  useImperativeHandle,
4
5
  useRef,
5
6
  type ElementRef,
@@ -8,11 +9,14 @@ import {
8
9
  Platform,
9
10
  processColor,
10
11
  StyleSheet,
12
+ type TextInputProps,
11
13
  type ViewStyle,
14
+ type TextStyle,
12
15
  } from 'react-native';
13
16
  import ControlledInputViewNativeComponent, {
14
17
  Commands,
15
18
  type NativeProps,
19
+ type TextChangeEvent,
16
20
  } from './ControlledInputViewNativeComponent';
17
21
 
18
22
  export interface ControlledInputViewRef {
@@ -20,15 +24,27 @@ export interface ControlledInputViewRef {
20
24
  focus: () => void;
21
25
  }
22
26
 
27
+ type ForwardedTextInputProps = Pick<
28
+ TextInputProps,
29
+ | 'autoComplete'
30
+ | 'autoCapitalize'
31
+ | 'keyboardType'
32
+ | 'returnKeyType'
33
+ | 'placeholder'
34
+ | 'placeholderTextColor'
35
+ | 'selectionColor'
36
+ >;
37
+
23
38
  export type ControlledInputViewProps = Omit<
24
39
  NativeProps,
25
- 'inputStyle' | 'onTextChange'
26
- > & {
27
- onTextChange?: (value: string) => void;
28
- };
40
+ 'inputStyle' | 'onTextChange' | keyof ForwardedTextInputProps
41
+ > &
42
+ ForwardedTextInputProps & {
43
+ onTextChange?: (value: string) => void;
44
+ };
29
45
 
30
46
  // All style props that Android handles via Compose instead of the native View layer
31
- const ANDROID_HANDLED_KEYS = [
47
+ const androidComposeHandledKeys = [
32
48
  'color',
33
49
  'fontSize',
34
50
  'fontFamily',
@@ -39,98 +55,113 @@ const ANDROID_HANDLED_KEYS = [
39
55
  'paddingBottom',
40
56
  'paddingLeft',
41
57
  'paddingRight',
42
- 'paddingStart',
43
- 'paddingEnd',
44
58
  'borderWidth',
45
59
  'borderRadius',
46
60
  'borderColor',
47
61
  'backgroundColor',
48
62
  ];
49
63
 
50
- function resolveAndroidPadding(flat: Record<string, any>) {
51
- const base = flat.padding ?? 0;
64
+ function resolveAndroidComposeViewPadding(flat: Record<string, any>) {
65
+ const padding = flat.padding ?? 0;
66
+
52
67
  return {
53
- paddingTop: flat.paddingTop ?? flat.paddingVertical ?? base,
54
- paddingBottom: flat.paddingBottom ?? flat.paddingVertical ?? base,
55
- paddingLeft:
56
- flat.paddingLeft ?? flat.paddingStart ?? flat.paddingHorizontal ?? base,
57
- paddingRight:
58
- flat.paddingRight ?? flat.paddingEnd ?? flat.paddingHorizontal ?? base,
68
+ paddingTop: flat.paddingTop ?? flat.paddingVertical ?? padding,
69
+ paddingBottom: flat.paddingBottom ?? flat.paddingVertical ?? padding,
70
+ paddingLeft: flat.paddingLeft ?? flat.paddingHorizontal ?? padding,
71
+ paddingRight: flat.paddingRight ?? flat.paddingHorizontal ?? padding,
59
72
  };
60
73
  }
61
74
 
62
- export const ControlledInputView = forwardRef<
63
- ControlledInputViewRef,
64
- ControlledInputViewProps
65
- >(({ style, onTextChange, ...rest }, ref) => {
66
- const nativeRef =
67
- useRef<ElementRef<typeof ControlledInputViewNativeComponent>>(null);
68
-
69
- const flat = (StyleSheet.flatten(style) ?? {}) as Record<string, any>;
70
-
71
- let viewStyle: ViewStyle;
72
- let inputStyle: Record<string, any> | undefined;
73
-
74
- if (Platform.OS === 'android') {
75
- viewStyle = Object.fromEntries(
76
- Object.entries(flat).filter(([k]) => !ANDROID_HANDLED_KEYS.includes(k))
77
- ) as ViewStyle;
78
-
79
- const hasPadding = ANDROID_HANDLED_KEYS.slice(3, 12).some(
80
- (k) => flat[k] != null
81
- );
82
-
83
- inputStyle = {
84
- color: flat.color,
85
- fontSize: flat.fontSize,
86
- fontFamily: flat.fontFamily,
87
- ...(hasPadding ? resolveAndroidPadding(flat) : {}),
88
- borderWidth: flat.borderWidth,
89
- borderRadius: flat.borderRadius,
90
- borderColor: flat.borderColor,
91
- backgroundColor: flat.backgroundColor,
92
- };
93
- } else {
94
- const { color, fontSize, fontFamily, ...iosRest } = flat;
95
- viewStyle = iosRest as ViewStyle;
96
-
97
- const hasTextStyle =
98
- color != null || fontSize != null || fontFamily != null;
99
- inputStyle = hasTextStyle
100
- ? {
101
- color: color != null ? processColor(color) : undefined,
102
- fontSize,
103
- fontFamily,
104
- }
105
- : undefined;
106
- }
107
-
108
- useImperativeHandle(ref, () => ({
109
- blur: () => {
110
- if (!nativeRef.current) return;
111
- if (Platform.OS === 'ios' || Platform.OS === 'android') {
112
- Commands.blur(nativeRef.current);
113
- }
114
- },
115
- focus: () => {
116
- if (!nativeRef.current) return;
117
- if (Platform.OS === 'ios' || Platform.OS === 'android') {
118
- Commands.focus(nativeRef.current);
119
- }
120
- },
121
- }));
122
-
123
- return (
124
- <ControlledInputViewNativeComponent
125
- {...rest}
126
- style={viewStyle}
127
- inputStyle={inputStyle as NativeProps['inputStyle']}
128
- onTextChange={
129
- onTextChange ? (e) => onTextChange(e.nativeEvent.value) : undefined
75
+ export const ControlledInputView = memo(
76
+ forwardRef<ControlledInputViewRef, ControlledInputViewProps>(
77
+ (
78
+ { style, onTextChange, selectionColor, placeholderTextColor, ...rest },
79
+ ref
80
+ ) => {
81
+ const nativeRef =
82
+ useRef<ElementRef<typeof ControlledInputViewNativeComponent>>(null);
83
+
84
+ const flattenedStyle = (StyleSheet.flatten(style) ?? {}) as TextStyle;
85
+
86
+ let viewStyle: ViewStyle;
87
+ let inputStyle: Record<string, any> | undefined;
88
+
89
+ if (Platform.OS === 'android') {
90
+ viewStyle = Object.fromEntries(
91
+ Object.entries(flattenedStyle).filter(
92
+ ([k]) => !androidComposeHandledKeys.includes(k)
93
+ )
94
+ );
95
+
96
+ const hasPadding = Object.entries(flattenedStyle).some(
97
+ ([k, v]) => k.includes('padding') && v != null
98
+ );
99
+
100
+ inputStyle = {
101
+ color: flattenedStyle.color,
102
+ fontSize: flattenedStyle.fontSize,
103
+ fontFamily: flattenedStyle.fontFamily,
104
+ ...(hasPadding
105
+ ? resolveAndroidComposeViewPadding(flattenedStyle)
106
+ : {}),
107
+ borderWidth: flattenedStyle.borderWidth,
108
+ borderRadius: flattenedStyle.borderRadius,
109
+ borderColor: flattenedStyle.borderColor,
110
+ backgroundColor: flattenedStyle.backgroundColor,
111
+ };
112
+ } else {
113
+ const { color, fontSize, fontFamily, ...iosViewStyle } = flattenedStyle;
114
+ viewStyle = iosViewStyle;
115
+
116
+ const hasTextStyle =
117
+ color != null || fontSize != null || fontFamily != null;
118
+ inputStyle = hasTextStyle
119
+ ? {
120
+ color: color != null ? processColor(color) : undefined,
121
+ fontSize,
122
+ fontFamily,
123
+ }
124
+ : undefined;
130
125
  }
131
- ref={nativeRef as any}
132
- />
133
- );
134
- });
126
+
127
+ const handleTextChange = (e: {
128
+ nativeEvent: Readonly<TextChangeEvent>;
129
+ }) => {
130
+ if (onTextChange) {
131
+ onTextChange(e.nativeEvent.value);
132
+ }
133
+ };
134
+
135
+ useImperativeHandle(ref, () => ({
136
+ blur: () => {
137
+ if (!nativeRef.current) return;
138
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
139
+ Commands.blur(nativeRef.current);
140
+ }
141
+ },
142
+ focus: () => {
143
+ if (!nativeRef.current) return;
144
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
145
+ Commands.focus(nativeRef.current);
146
+ }
147
+ },
148
+ }));
149
+
150
+ return (
151
+ <ControlledInputViewNativeComponent
152
+ {...rest}
153
+ placeholderTextColor={placeholderTextColor}
154
+ selectionColor={selectionColor}
155
+ style={viewStyle}
156
+ inputStyle={inputStyle}
157
+ onTextChange={handleTextChange}
158
+ ref={nativeRef}
159
+ />
160
+ );
161
+ }
162
+ )
163
+ );
164
+
165
+ ControlledInputView.displayName = 'ControlledInputView';
135
166
 
136
167
  export * from './ControlledInputViewNativeComponent';