react-native-controlled-input 0.9.0 → 0.10.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
@@ -1,6 +1,6 @@
1
1
  # react-native-controlled-input
2
2
 
3
- A controlled React Native input for cases where regular `TextInput` can briefly show invalid characters before your filtered `value` is rendered back from JS.
3
+ A controlled React Native input that lets you format and constrain the value exactly how you want in JS, while keeping the displayed text in sync without invalid characters flashing in the field.
4
4
 
5
5
  ## Problem
6
6
 
@@ -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,10 @@ class ControlledInputView : LinearLayout {
96
94
  JetpackComposeView(
97
95
  value = value,
98
96
  inputStyle = viewModel.inputStyle,
97
+ autoComplete = viewModel.autoComplete,
98
+ autoCapitalize = viewModel.autoCapitalize,
99
+ keyboardType = viewModel.keyboardType,
100
+ returnKeyType = viewModel.returnKeyType,
99
101
  onTextChange = { value ->
100
102
  val surfaceId = UIManagerHelper.getSurfaceId(context)
101
103
  val viewId = this.id
@@ -140,4 +142,4 @@ class ControlledInputView : LinearLayout {
140
142
 
141
143
  }
142
144
  }
143
- }
145
+ }
@@ -38,6 +38,26 @@ class ControlledInputViewManager : SimpleViewManager<ControlledInputView>(),
38
38
  }
39
39
  }
40
40
 
41
+ @ReactProp(name = "autoComplete")
42
+ override fun setAutoComplete(view: ControlledInputView, autoComplete: String?) {
43
+ view.viewModel.setAutoCompleteWithAutofill(view, autoComplete)
44
+ }
45
+
46
+ @ReactProp(name = "autoCapitalize")
47
+ override fun setAutoCapitalize(view: ControlledInputView, autoCapitalize: String?) {
48
+ view.viewModel.setAutoCapitalize(autoCapitalize)
49
+ }
50
+
51
+ @ReactProp(name = "keyboardType")
52
+ override fun setKeyboardType(view: ControlledInputView, keyboardType: String?) {
53
+ view.viewModel.setKeyboardType(keyboardType)
54
+ }
55
+
56
+ @ReactProp(name = "returnKeyType")
57
+ override fun setReturnKeyType(view: ControlledInputView, returnKeyType: String?) {
58
+ view.viewModel.setReturnKeyType(returnKeyType)
59
+ }
60
+
41
61
  @ReactProp(name = "inputStyle")
42
62
  override fun setInputStyle(view: ControlledInputView, inputStyle: ReadableMap?) {
43
63
  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
@@ -29,12 +30,19 @@ import androidx.compose.ui.graphics.Color
29
30
  import androidx.compose.ui.platform.LocalContext
30
31
  import androidx.compose.ui.text.TextStyle
31
32
  import androidx.compose.ui.text.font.FontFamily
33
+ import androidx.compose.ui.text.input.ImeAction
34
+ import androidx.compose.ui.text.input.KeyboardCapitalization
35
+ import androidx.compose.ui.text.input.KeyboardType
36
+ import androidx.compose.ui.autofill.AutofillNode
37
+ import androidx.compose.ui.autofill.AutofillType
38
+ import androidx.compose.ui.layout.onGloballyPositioned
39
+ import androidx.compose.ui.layout.boundsInWindow
40
+ import androidx.compose.ui.platform.LocalAutofill
41
+ import androidx.compose.ui.platform.LocalAutofillTree
32
42
  import androidx.compose.ui.unit.sp
33
- import androidx.lifecycle.ViewModel
34
43
  import com.facebook.react.bridge.Arguments
35
44
  import com.facebook.react.bridge.WritableMap
36
45
  import com.facebook.react.uimanager.events.Event
37
- import kotlinx.coroutines.flow.MutableStateFlow
38
46
  import kotlinx.coroutines.flow.StateFlow
39
47
 
40
48
  data class InputStyle(
@@ -55,6 +63,10 @@ data class InputStyle(
55
63
  fun JetpackComposeView(
56
64
  value: String,
57
65
  inputStyle: StateFlow<InputStyle?>,
66
+ autoComplete: StateFlow<String?>,
67
+ autoCapitalize: StateFlow<String?>,
68
+ keyboardType: StateFlow<String?>,
69
+ returnKeyType: StateFlow<String?>,
58
70
  onTextChange: (value: String) -> Unit,
59
71
  onFocus: (() -> Unit)? = null,
60
72
  onBlur: (() -> Unit)? = null,
@@ -62,8 +74,21 @@ fun JetpackComposeView(
62
74
  ) {
63
75
  val state = remember { TextFieldState(value) }
64
76
  val style by inputStyle.collectAsState()
77
+ val keyboardTypeValue by keyboardType.collectAsState()
78
+ val autoCapitalizeValue by autoCapitalize.collectAsState()
79
+ val returnKeyTypeValue by returnKeyType.collectAsState()
80
+ val autoCompleteValue by autoComplete.collectAsState()
65
81
  val interactionSource = remember { MutableInteractionSource() }
66
82
 
83
+ val autofill = LocalAutofill.current
84
+ val autofillNode = remember {
85
+ AutofillNode(
86
+ autofillTypes = toAutofillTypes(autoCompleteValue),
87
+ onFill = { onTextChange(it) }
88
+ )
89
+ }
90
+ LocalAutofillTree.current += autofillNode
91
+
67
92
  if (state.text.toString() != value) {
68
93
  state.setTextAndPlaceCursorAtEnd(value)
69
94
  }
@@ -73,9 +98,11 @@ fun JetpackComposeView(
73
98
  when (interaction) {
74
99
  is FocusInteraction.Focus -> {
75
100
  onFocus?.invoke()
101
+ autofill?.requestAutofillForNode(autofillNode)
76
102
  }
77
103
  is FocusInteraction.Unfocus -> {
78
104
  onBlur?.invoke()
105
+ autofill?.cancelAutofillForNode(autofillNode)
79
106
  }
80
107
  }
81
108
  }
@@ -131,12 +158,20 @@ fun JetpackComposeView(
131
158
  end = paddingRight,
132
159
  bottom = paddingBottom,
133
160
  )
161
+ .onGloballyPositioned {
162
+ autofillNode.boundingBox = it.boundsInWindow()
163
+ }
134
164
  .focusRequester(focusRequester),
135
165
  textStyle = TextStyle(
136
166
  color = textColor,
137
167
  fontSize = fontSize,
138
168
  fontFamily = fontFamily,
139
169
  ),
170
+ keyboardOptions = KeyboardOptions(
171
+ capitalization = toComposeCapitalization(autoCapitalizeValue),
172
+ keyboardType = toComposeKeyboardType(keyboardTypeValue),
173
+ imeAction = toComposeImeAction(returnKeyTypeValue),
174
+ ),
140
175
  interactionSource = interactionSource,
141
176
  decorator = { innerTextField ->
142
177
  Box(
@@ -150,6 +185,53 @@ fun JetpackComposeView(
150
185
  }
151
186
  }
152
187
 
188
+ private fun toComposeKeyboardType(value: String?): KeyboardType = when (value) {
189
+ "ascii-capable" -> KeyboardType.Ascii
190
+ "numbers-and-punctuation" -> KeyboardType.Text
191
+ "url" -> KeyboardType.Uri
192
+ "number-pad", "numeric" -> KeyboardType.Number
193
+ "phone-pad" -> KeyboardType.Phone
194
+ "email-address" -> KeyboardType.Email
195
+ "decimal-pad" -> KeyboardType.Decimal
196
+ "visible-password" -> KeyboardType.Password
197
+ else -> KeyboardType.Text
198
+ }
199
+
200
+ private fun toComposeCapitalization(value: String?): KeyboardCapitalization = when (value) {
201
+ "none" -> KeyboardCapitalization.None
202
+ "characters" -> KeyboardCapitalization.Characters
203
+ "words" -> KeyboardCapitalization.Words
204
+ "sentences" -> KeyboardCapitalization.Sentences
205
+ else -> KeyboardCapitalization.Sentences
206
+ }
207
+
208
+ private fun toComposeImeAction(value: String?): ImeAction = when (value) {
209
+ "go" -> ImeAction.Go
210
+ "next" -> ImeAction.Next
211
+ "search" -> ImeAction.Search
212
+ "send" -> ImeAction.Send
213
+ "done" -> ImeAction.Done
214
+ "none" -> ImeAction.None
215
+ "previous" -> ImeAction.Previous
216
+ else -> ImeAction.Default
217
+ }
218
+
219
+ private fun toAutofillTypes(autoComplete: String?): List<AutofillType> = when (autoComplete) {
220
+ "email" -> listOf(AutofillType.EmailAddress)
221
+ "name", "given-name", "family-name", "additional-name" -> listOf(AutofillType.PersonFullName)
222
+ "username" -> listOf(AutofillType.Username)
223
+ "password", "new-password" -> listOf(AutofillType.Password)
224
+ "tel" -> listOf(AutofillType.PhoneNumber)
225
+ "postal-code" -> listOf(AutofillType.PostalCode)
226
+ "street-address" -> listOf(AutofillType.AddressStreet)
227
+ "cc-number" -> listOf(AutofillType.CreditCardNumber)
228
+ "cc-exp" -> listOf(AutofillType.CreditCardExpirationDate)
229
+ "cc-exp-month" -> listOf(AutofillType.CreditCardExpirationMonth)
230
+ "cc-exp-year" -> listOf(AutofillType.CreditCardExpirationYear)
231
+ "cc-csc" -> listOf(AutofillType.CreditCardSecurityCode)
232
+ else -> emptyList()
233
+ }
234
+
153
235
  class TextChangeEvent(
154
236
  surfaceId: Int,
155
237
  viewId: Int,
@@ -198,20 +280,3 @@ class BlurEvent(
198
280
  const val EVENT_NAME = "onBlur"
199
281
  }
200
282
  }
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,80 @@
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 _autoCapitalize = MutableStateFlow<String?>(null)
14
+ private val _keyboardType = MutableStateFlow<String?>(null)
15
+ private val _returnKeyType = MutableStateFlow<String?>(null)
16
+
17
+ val value: StateFlow<String> get() = _value
18
+ val inputStyle: StateFlow<InputStyle?> get() = _inputStyle
19
+ val autoComplete: StateFlow<String?> get() = _autoComplete
20
+ val autoCapitalize: StateFlow<String?> get() = _autoCapitalize
21
+ val keyboardType: StateFlow<String?> get() = _keyboardType
22
+ val returnKeyType: StateFlow<String?> get() = _returnKeyType
23
+
24
+ fun setValue(newValue: String) {
25
+ _value.value = newValue
26
+ }
27
+
28
+ fun setInputStyle(style: InputStyle?) {
29
+ _inputStyle.value = style
30
+ }
31
+
32
+ fun setAutoComplete(newValue: String?) {
33
+ _autoComplete.value = newValue
34
+ }
35
+
36
+ fun setAutoCompleteWithAutofill(hostView: View, newValue: String?) {
37
+ setAutoComplete(newValue)
38
+
39
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
40
+ return
41
+ }
42
+
43
+ val hint = when (newValue) {
44
+ "email" -> View.AUTOFILL_HINT_EMAIL_ADDRESS
45
+ "name", "given-name", "family-name" -> View.AUTOFILL_HINT_NAME
46
+ "username" -> View.AUTOFILL_HINT_USERNAME
47
+ "password", "new-password" -> View.AUTOFILL_HINT_PASSWORD
48
+ "tel" -> View.AUTOFILL_HINT_PHONE
49
+ "postal-code" -> View.AUTOFILL_HINT_POSTAL_CODE
50
+ "street-address" -> View.AUTOFILL_HINT_POSTAL_ADDRESS
51
+ "cc-number" -> View.AUTOFILL_HINT_CREDIT_CARD_NUMBER
52
+ "cc-exp" -> View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE
53
+ "cc-exp-month" -> View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH
54
+ "cc-exp-year" -> View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR
55
+ "cc-csc" -> View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE
56
+ "additional-name" -> View.AUTOFILL_HINT_NAME
57
+ else -> null
58
+ }
59
+
60
+ if (hint == null || newValue == "off" || newValue.isNullOrEmpty()) {
61
+ hostView.setAutofillHints(*emptyArray())
62
+ return
63
+ }
64
+
65
+ hostView.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES
66
+ hostView.setAutofillHints(hint)
67
+ }
68
+
69
+ fun setAutoCapitalize(newValue: String?) {
70
+ _autoCapitalize.value = newValue
71
+ }
72
+
73
+ fun setKeyboardType(newValue: String?) {
74
+ _keyboardType.value = newValue
75
+ }
76
+
77
+ fun setReturnKeyType(newValue: String?) {
78
+ _returnKeyType.value = newValue
79
+ }
80
+ }
@@ -52,6 +52,22 @@ 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
+
55
71
  const auto &style = newViewProps.inputStyle;
56
72
  const auto &oldStyle = oldViewProps.inputStyle;
57
73
 
@@ -32,6 +32,22 @@ 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
+
35
51
  public override var canBecomeFirstResponder: Bool { true }
36
52
 
37
53
  @objc public func focus() {
@@ -65,6 +81,10 @@ public class RNControlledInput: UIView, UITextFieldDelegate {
65
81
  ])
66
82
 
67
83
  applyFont()
84
+ applyAutoComplete()
85
+ applyAutoCapitalize()
86
+ applyKeyboardType()
87
+ applyReturnKeyType()
68
88
  }
69
89
 
70
90
  public func textFieldDidBeginEditing(_ textField: UITextField) {
@@ -82,6 +102,12 @@ public class RNControlledInput: UIView, UITextFieldDelegate {
82
102
 
83
103
  delegate?.controlledInputDidChangeText(self, value: newText)
84
104
 
105
+ // If the replacement string is more than one character, it's likely an autocomplete/paste
106
+ // In this case, we should let the UITextField update itself to handle it correctly
107
+ if string.count > 1 {
108
+ return true
109
+ }
110
+
85
111
  return false
86
112
  }
87
113
 
@@ -92,4 +118,150 @@ public class RNControlledInput: UIView, UITextFieldDelegate {
92
118
  textField.font = UIFont.systemFont(ofSize: fontSize)
93
119
  }
94
120
  }
121
+
122
+ private func applyAutoComplete() {
123
+ guard let autoComplete else {
124
+ textField.textContentType = nil
125
+ return
126
+ }
127
+
128
+ if autoComplete == "off" || autoComplete.isEmpty {
129
+ textField.textContentType = nil
130
+ return
131
+ }
132
+
133
+ let mappedValue: String
134
+ switch autoComplete {
135
+ case "email":
136
+ mappedValue = "emailAddress"
137
+ case "tel":
138
+ mappedValue = "telephoneNumber"
139
+ case "name":
140
+ mappedValue = "name"
141
+ case "given-name":
142
+ mappedValue = "givenName"
143
+ case "middle-name":
144
+ mappedValue = "middleName"
145
+ case "family-name":
146
+ mappedValue = "familyName"
147
+ case "username":
148
+ mappedValue = "username"
149
+ case "password":
150
+ mappedValue = "password"
151
+ case "new-password":
152
+ mappedValue = "newPassword"
153
+ case "one-time-code":
154
+ mappedValue = "oneTimeCode"
155
+ case "postal-code":
156
+ mappedValue = "postalCode"
157
+ case "street-address":
158
+ mappedValue = "fullStreetAddress"
159
+ case "country":
160
+ mappedValue = "countryName"
161
+ case "cc-number":
162
+ mappedValue = "creditCardNumber"
163
+ case "cc-csc":
164
+ mappedValue = "creditCardSecurityCode"
165
+ case "cc-exp":
166
+ mappedValue = "creditCardExpiration"
167
+ case "cc-exp-month":
168
+ mappedValue = "creditCardExpirationMonth"
169
+ case "cc-exp-year":
170
+ mappedValue = "creditCardExpirationYear"
171
+ case "birthdate-day":
172
+ mappedValue = "birthdateDay"
173
+ case "birthdate-month":
174
+ mappedValue = "birthdateMonth"
175
+ case "birthdate-year":
176
+ mappedValue = "birthdateYear"
177
+ case "url":
178
+ mappedValue = "URL"
179
+ default:
180
+ mappedValue = autoComplete
181
+ }
182
+
183
+ textField.textContentType = UITextContentType(rawValue: mappedValue)
184
+ }
185
+
186
+ private func applyAutoCapitalize() {
187
+ guard let autoCapitalize else {
188
+ textField.autocapitalizationType = .sentences
189
+ return
190
+ }
191
+
192
+ switch autoCapitalize {
193
+ case "none":
194
+ textField.autocapitalizationType = .none
195
+ case "words":
196
+ textField.autocapitalizationType = .words
197
+ case "sentences":
198
+ textField.autocapitalizationType = .sentences
199
+ case "characters":
200
+ textField.autocapitalizationType = .allCharacters
201
+ default:
202
+ textField.autocapitalizationType = .sentences
203
+ }
204
+ }
205
+
206
+ private func applyKeyboardType() {
207
+ switch keyboardType {
208
+ case "ascii-capable":
209
+ textField.keyboardType = .asciiCapable
210
+ case "numbers-and-punctuation":
211
+ textField.keyboardType = .numbersAndPunctuation
212
+ case "url":
213
+ textField.keyboardType = .URL
214
+ case "number-pad":
215
+ textField.keyboardType = .numberPad
216
+ case "phone-pad":
217
+ textField.keyboardType = .phonePad
218
+ case "name-phone-pad":
219
+ textField.keyboardType = .namePhonePad
220
+ case "email-address":
221
+ textField.keyboardType = .emailAddress
222
+ case "decimal-pad":
223
+ textField.keyboardType = .decimalPad
224
+ case "twitter":
225
+ textField.keyboardType = .twitter
226
+ case "web-search":
227
+ textField.keyboardType = .webSearch
228
+ case "visible-password":
229
+ textField.keyboardType = .asciiCapable
230
+ case "numeric":
231
+ textField.keyboardType = .numbersAndPunctuation
232
+ default:
233
+ textField.keyboardType = .default
234
+ }
235
+ }
236
+
237
+ private func applyReturnKeyType() {
238
+ switch returnKeyType {
239
+ case "done":
240
+ textField.returnKeyType = .done
241
+ case "go":
242
+ textField.returnKeyType = .go
243
+ case "next":
244
+ textField.returnKeyType = .next
245
+ case "search":
246
+ textField.returnKeyType = .search
247
+ case "send":
248
+ textField.returnKeyType = .send
249
+ case "none":
250
+ textField.returnKeyType = .default
251
+ case "previous":
252
+ textField.returnKeyType = .default
253
+ case "route":
254
+ textField.returnKeyType = .route
255
+ case "yahoo":
256
+ textField.returnKeyType = .yahoo
257
+ case "emergency-call":
258
+ textField.returnKeyType = .emergencyCall
259
+ case "google":
260
+ textField.returnKeyType = .google
261
+ case "join":
262
+ textField.returnKeyType = .join
263
+ default:
264
+ textField.returnKeyType = .default
265
+ }
266
+ }
95
267
  }
@@ -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,10 @@ interface InputStyle {
38
38
 
39
39
  export interface NativeProps extends ViewProps {
40
40
  value?: string;
41
+ autoComplete?: string;
42
+ autoCapitalize?: string;
43
+ keyboardType?: string;
44
+ returnKeyType?: string;
41
45
  inputStyle?: InputStyle;
42
46
  onTextChange?: BubblingEventHandler<Readonly<TextChangeEvent>>;
43
47
  onFocus?: BubblingEventHandler<Readonly<FocusEvent>>;
@@ -1,50 +1,50 @@
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
21
  ...rest
22
22
  }, ref) => {
23
23
  const nativeRef = useRef(null);
24
- const flat = StyleSheet.flatten(style) ?? {};
24
+ const flattenedStyle = StyleSheet.flatten(style) ?? {};
25
25
  let viewStyle;
26
26
  let inputStyle;
27
27
  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);
28
+ viewStyle = Object.fromEntries(Object.entries(flattenedStyle).filter(([k]) => !androidComposeHandledKeys.includes(k)));
29
+ const hasPadding = Object.entries(flattenedStyle).some(([k, v]) => k.includes('padding') && v != null);
30
30
  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
31
+ color: flattenedStyle.color,
32
+ fontSize: flattenedStyle.fontSize,
33
+ fontFamily: flattenedStyle.fontFamily,
34
+ ...(hasPadding ? resolveAndroidComposeViewPadding(flattenedStyle) : {}),
35
+ borderWidth: flattenedStyle.borderWidth,
36
+ borderRadius: flattenedStyle.borderRadius,
37
+ borderColor: flattenedStyle.borderColor,
38
+ backgroundColor: flattenedStyle.backgroundColor
39
39
  };
40
40
  } else {
41
41
  const {
42
42
  color,
43
43
  fontSize,
44
44
  fontFamily,
45
- ...iosRest
46
- } = flat;
47
- viewStyle = iosRest;
45
+ ...iosViewStyle
46
+ } = flattenedStyle;
47
+ viewStyle = iosViewStyle;
48
48
  const hasTextStyle = color != null || fontSize != null || fontFamily != null;
49
49
  inputStyle = hasTextStyle ? {
50
50
  color: color != null ? processColor(color) : undefined,
@@ -52,6 +52,11 @@ export const ControlledInputView = /*#__PURE__*/forwardRef(({
52
52
  fontFamily
53
53
  } : undefined;
54
54
  }
55
+ const handleTextChange = e => {
56
+ if (onTextChange) {
57
+ onTextChange(e.nativeEvent.value);
58
+ }
59
+ };
55
60
  useImperativeHandle(ref, () => ({
56
61
  blur: () => {
57
62
  if (!nativeRef.current) return;
@@ -70,9 +75,10 @@ export const ControlledInputView = /*#__PURE__*/forwardRef(({
70
75
  ...rest,
71
76
  style: viewStyle,
72
77
  inputStyle: inputStyle,
73
- onTextChange: onTextChange ? e => onTextChange(e.nativeEvent.value) : undefined,
78
+ onTextChange: handleTextChange,
74
79
  ref: nativeRef
75
80
  });
76
- });
81
+ }));
82
+ ControlledInputView.displayName = 'ControlledInputView';
77
83
  export * from './ControlledInputViewNativeComponent';
78
84
  //# 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","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,10 @@ interface InputStyle {
22
22
  }
23
23
  export interface NativeProps extends ViewProps {
24
24
  value?: string;
25
+ autoComplete?: string;
26
+ autoCapitalize?: string;
27
+ keyboardType?: string;
28
+ returnKeyType?: string;
25
29
  inputStyle?: InputStyle;
26
30
  onTextChange?: BubblingEventHandler<Readonly<TextChangeEvent>>;
27
31
  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,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'>;
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, "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,EACd,cAAc,GAAG,gBAAgB,GAAG,cAAc,GAAG,eAAe,CACrE,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;0DAmHzC,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.0",
3
+ "version": "0.10.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,10 @@ interface InputStyle {
38
38
 
39
39
  export interface NativeProps extends ViewProps {
40
40
  value?: string;
41
+ autoComplete?: string;
42
+ autoCapitalize?: string;
43
+ keyboardType?: string;
44
+ returnKeyType?: string;
41
45
  inputStyle?: InputStyle;
42
46
  onTextChange?: BubblingEventHandler<Readonly<TextChangeEvent>>;
43
47
  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,21 @@ export interface ControlledInputViewRef {
20
24
  focus: () => void;
21
25
  }
22
26
 
27
+ type ForwardedTextInputProps = Pick<
28
+ TextInputProps,
29
+ 'autoComplete' | 'autoCapitalize' | 'keyboardType' | 'returnKeyType'
30
+ >;
31
+
23
32
  export type ControlledInputViewProps = Omit<
24
33
  NativeProps,
25
- 'inputStyle' | 'onTextChange'
26
- > & {
27
- onTextChange?: (value: string) => void;
28
- };
34
+ 'inputStyle' | 'onTextChange' | keyof ForwardedTextInputProps
35
+ > &
36
+ ForwardedTextInputProps & {
37
+ onTextChange?: (value: string) => void;
38
+ };
29
39
 
30
40
  // All style props that Android handles via Compose instead of the native View layer
31
- const ANDROID_HANDLED_KEYS = [
41
+ const androidComposeHandledKeys = [
32
42
  'color',
33
43
  'fontSize',
34
44
  'fontFamily',
@@ -39,98 +49,108 @@ const ANDROID_HANDLED_KEYS = [
39
49
  'paddingBottom',
40
50
  'paddingLeft',
41
51
  'paddingRight',
42
- 'paddingStart',
43
- 'paddingEnd',
44
52
  'borderWidth',
45
53
  'borderRadius',
46
54
  'borderColor',
47
55
  'backgroundColor',
48
56
  ];
49
57
 
50
- function resolveAndroidPadding(flat: Record<string, any>) {
51
- const base = flat.padding ?? 0;
58
+ function resolveAndroidComposeViewPadding(flat: Record<string, any>) {
59
+ const padding = flat.padding ?? 0;
60
+
52
61
  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,
62
+ paddingTop: flat.paddingTop ?? flat.paddingVertical ?? padding,
63
+ paddingBottom: flat.paddingBottom ?? flat.paddingVertical ?? padding,
64
+ paddingLeft: flat.paddingLeft ?? flat.paddingHorizontal ?? padding,
65
+ paddingRight: flat.paddingRight ?? flat.paddingHorizontal ?? padding,
59
66
  };
60
67
  }
61
68
 
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
69
+ export const ControlledInputView = memo(
70
+ forwardRef<ControlledInputViewRef, ControlledInputViewProps>(
71
+ ({ style, onTextChange, ...rest }, ref) => {
72
+ const nativeRef =
73
+ useRef<ElementRef<typeof ControlledInputViewNativeComponent>>(null);
74
+
75
+ const flattenedStyle = (StyleSheet.flatten(style) ?? {}) as TextStyle;
76
+
77
+ let viewStyle: ViewStyle;
78
+ let inputStyle: Record<string, any> | undefined;
79
+
80
+ if (Platform.OS === 'android') {
81
+ viewStyle = Object.fromEntries(
82
+ Object.entries(flattenedStyle).filter(
83
+ ([k]) => !androidComposeHandledKeys.includes(k)
84
+ )
85
+ );
86
+
87
+ const hasPadding = Object.entries(flattenedStyle).some(
88
+ ([k, v]) => k.includes('padding') && v != null
89
+ );
90
+
91
+ inputStyle = {
92
+ color: flattenedStyle.color,
93
+ fontSize: flattenedStyle.fontSize,
94
+ fontFamily: flattenedStyle.fontFamily,
95
+ ...(hasPadding
96
+ ? resolveAndroidComposeViewPadding(flattenedStyle)
97
+ : {}),
98
+ borderWidth: flattenedStyle.borderWidth,
99
+ borderRadius: flattenedStyle.borderRadius,
100
+ borderColor: flattenedStyle.borderColor,
101
+ backgroundColor: flattenedStyle.backgroundColor,
102
+ };
103
+ } else {
104
+ const { color, fontSize, fontFamily, ...iosViewStyle } = flattenedStyle;
105
+ viewStyle = iosViewStyle;
106
+
107
+ const hasTextStyle =
108
+ color != null || fontSize != null || fontFamily != null;
109
+ inputStyle = hasTextStyle
110
+ ? {
111
+ color: color != null ? processColor(color) : undefined,
112
+ fontSize,
113
+ fontFamily,
114
+ }
115
+ : undefined;
130
116
  }
131
- ref={nativeRef as any}
132
- />
133
- );
134
- });
117
+
118
+ const handleTextChange = (e: {
119
+ nativeEvent: Readonly<TextChangeEvent>;
120
+ }) => {
121
+ if (onTextChange) {
122
+ onTextChange(e.nativeEvent.value);
123
+ }
124
+ };
125
+
126
+ useImperativeHandle(ref, () => ({
127
+ blur: () => {
128
+ if (!nativeRef.current) return;
129
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
130
+ Commands.blur(nativeRef.current);
131
+ }
132
+ },
133
+ focus: () => {
134
+ if (!nativeRef.current) return;
135
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
136
+ Commands.focus(nativeRef.current);
137
+ }
138
+ },
139
+ }));
140
+
141
+ return (
142
+ <ControlledInputViewNativeComponent
143
+ {...rest}
144
+ style={viewStyle}
145
+ inputStyle={inputStyle}
146
+ onTextChange={handleTextChange}
147
+ ref={nativeRef}
148
+ />
149
+ );
150
+ }
151
+ )
152
+ );
153
+
154
+ ControlledInputView.displayName = 'ControlledInputView';
135
155
 
136
156
  export * from './ControlledInputViewNativeComponent';