react-native-controlled-input 0.6.0 → 0.9.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 +75 -12
- package/android/src/main/java/com/controlledinput/ControlledInputView.kt +6 -4
- package/android/src/main/java/com/controlledinput/ControlledInputViewManager.kt +1 -1
- package/android/src/main/java/com/controlledinput/JetpackComposeView.kt +37 -21
- package/ios/ControlledInputView.mm +39 -27
- package/ios/RNControlledInput.swift +72 -26
- package/lib/module/ControlledInputViewNativeComponent.ts +2 -2
- package/lib/module/index.js +54 -11
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/ControlledInputViewNativeComponent.d.ts +2 -2
- package/lib/typescript/src/ControlledInputViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +6 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ControlledInputViewNativeComponent.ts +2 -2
- package/src/index.tsx +97 -18
package/README.md
CHANGED
|
@@ -1,32 +1,95 @@
|
|
|
1
1
|
# react-native-controlled-input
|
|
2
2
|
|
|
3
|
-
React Native
|
|
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.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Problem
|
|
6
6
|
|
|
7
|
+
With a regular controlled `TextInput`, native input is applied first, then JS receives the change, filters it, and sends the next `value` back.
|
|
8
|
+
|
|
9
|
+
That means invalid characters can still flash in the field for a moment.
|
|
10
|
+
|
|
11
|
+
`react-native-controlled-input` is built for this exact case: you decide what text is valid, and the displayed value stays driven by `value`.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
7
14
|
|
|
8
15
|
```sh
|
|
9
16
|
npm install react-native-controlled-input
|
|
10
17
|
```
|
|
11
18
|
|
|
19
|
+
Requires React Native New Architecture / Fabric.
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useRef, useState } from 'react';
|
|
25
|
+
import { StyleSheet } from 'react-native';
|
|
26
|
+
import {
|
|
27
|
+
ControlledInputView,
|
|
28
|
+
type ControlledInputViewRef,
|
|
29
|
+
} from 'react-native-controlled-input';
|
|
30
|
+
|
|
31
|
+
export function Example() {
|
|
32
|
+
const [value, setValue] = useState('');
|
|
33
|
+
const inputRef = useRef<ControlledInputViewRef>(null);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<ControlledInputView
|
|
37
|
+
ref={inputRef}
|
|
38
|
+
value={value}
|
|
39
|
+
onTextChange={(text) => setValue(text.replace(/\d/g, ''))}
|
|
40
|
+
style={styles.input}
|
|
41
|
+
onFocus={() => {}}
|
|
42
|
+
onBlur={() => {}}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const styles = StyleSheet.create({
|
|
48
|
+
input: {
|
|
49
|
+
height: 48,
|
|
50
|
+
borderWidth: 1,
|
|
51
|
+
borderColor: '#ccc',
|
|
52
|
+
borderRadius: 8,
|
|
53
|
+
paddingHorizontal: 12,
|
|
54
|
+
fontSize: 16,
|
|
55
|
+
color: '#111',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
inputRef.current?.focus();
|
|
62
|
+
inputRef.current?.blur();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Props
|
|
12
66
|
|
|
13
|
-
|
|
67
|
+
| Prop | Type | Description |
|
|
68
|
+
|------|------|-------------|
|
|
69
|
+
| `value` | `string` | Current input value. |
|
|
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. |
|
|
14
74
|
|
|
75
|
+
## Style support
|
|
15
76
|
|
|
16
|
-
|
|
17
|
-
import { ControlledInputView } from "react-native-controlled-input";
|
|
77
|
+
The same `style` API is supported on both iOS and Android.
|
|
18
78
|
|
|
19
|
-
|
|
79
|
+
Commonly used supported styles:
|
|
20
80
|
|
|
21
|
-
|
|
22
|
-
|
|
81
|
+
- `color`, `fontSize`, `fontFamily`
|
|
82
|
+
- `padding`, `paddingVertical`, `paddingHorizontal`
|
|
83
|
+
- `paddingTop`, `paddingBottom`, `paddingLeft`, `paddingRight`, `paddingStart`, `paddingEnd`
|
|
84
|
+
- `borderWidth`, `borderRadius`, `borderColor`, `backgroundColor`
|
|
85
|
+
- layout styles like `width`, `height`, `margin`, `flex`
|
|
23
86
|
|
|
87
|
+
Implementation differs internally between platforms, but usage is the same for library consumers.
|
|
24
88
|
|
|
25
|
-
##
|
|
89
|
+
## Ref
|
|
26
90
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
91
|
+
- `focus()`
|
|
92
|
+
- `blur()`
|
|
30
93
|
|
|
31
94
|
## License
|
|
32
95
|
|
|
@@ -56,17 +56,19 @@ class ControlledInputView : LinearLayout {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
private fun configureComponent(context: Context) {
|
|
59
|
+
setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
59
60
|
|
|
60
61
|
layoutParams = LayoutParams(
|
|
61
|
-
LayoutParams.
|
|
62
|
-
LayoutParams.
|
|
62
|
+
LayoutParams.MATCH_PARENT,
|
|
63
|
+
LayoutParams.MATCH_PARENT
|
|
63
64
|
)
|
|
64
65
|
|
|
65
66
|
ComposeView(context).also {
|
|
66
67
|
it.layoutParams = LayoutParams(
|
|
67
|
-
LayoutParams.
|
|
68
|
-
LayoutParams.
|
|
68
|
+
LayoutParams.MATCH_PARENT,
|
|
69
|
+
LayoutParams.MATCH_PARENT
|
|
69
70
|
)
|
|
71
|
+
it.setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
70
72
|
|
|
71
73
|
viewModel = JetpackComposeViewModel()
|
|
72
74
|
|
|
@@ -47,7 +47,6 @@ class ControlledInputViewManager : SimpleViewManager<ControlledInputView>(),
|
|
|
47
47
|
color = if (inputStyle.hasKey("color")) inputStyle.getString("color") else null,
|
|
48
48
|
fontSize = if (inputStyle.hasKey("fontSize")) inputStyle.getDouble("fontSize") else null,
|
|
49
49
|
fontFamily = if (inputStyle.hasKey("fontFamily")) inputStyle.getString("fontFamily") else null,
|
|
50
|
-
height = if (inputStyle.hasKey("height")) inputStyle.getDouble("height") else null,
|
|
51
50
|
paddingTop = if (inputStyle.hasKey("paddingTop")) inputStyle.getDouble("paddingTop") else null,
|
|
52
51
|
paddingBottom = if (inputStyle.hasKey("paddingBottom")) inputStyle.getDouble("paddingBottom") else null,
|
|
53
52
|
paddingLeft = if (inputStyle.hasKey("paddingLeft")) inputStyle.getDouble("paddingLeft") else null,
|
|
@@ -55,6 +54,7 @@ class ControlledInputViewManager : SimpleViewManager<ControlledInputView>(),
|
|
|
55
54
|
borderWidth = if (inputStyle.hasKey("borderWidth")) inputStyle.getDouble("borderWidth") else null,
|
|
56
55
|
borderRadius = if (inputStyle.hasKey("borderRadius")) inputStyle.getDouble("borderRadius") else null,
|
|
57
56
|
borderColor = if (inputStyle.hasKey("borderColor")) inputStyle.getString("borderColor") else null,
|
|
57
|
+
backgroundColor = if (inputStyle.hasKey("backgroundColor")) inputStyle.getString("backgroundColor") else null,
|
|
58
58
|
)
|
|
59
59
|
}
|
|
60
60
|
view.viewModel.setInputStyle(style)
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
package com.controlledinput
|
|
2
2
|
|
|
3
|
+
import androidx.compose.foundation.background
|
|
3
4
|
import androidx.compose.foundation.border
|
|
4
|
-
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
5
5
|
import androidx.compose.foundation.layout.Box
|
|
6
|
-
import androidx.compose.ui.draw.clip
|
|
7
|
-
import androidx.compose.foundation.layout.PaddingValues
|
|
8
6
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
9
|
-
import androidx.compose.foundation.layout.fillMaxWidth
|
|
10
|
-
import androidx.compose.foundation.layout.height
|
|
11
7
|
import androidx.compose.foundation.layout.padding
|
|
8
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
9
|
+
import androidx.compose.ui.Alignment
|
|
10
|
+
import androidx.compose.ui.draw.clip
|
|
12
11
|
import androidx.compose.foundation.text.BasicTextField
|
|
12
|
+
import androidx.compose.ui.unit.dp
|
|
13
13
|
import androidx.compose.foundation.text.input.InputTransformation
|
|
14
14
|
import androidx.compose.foundation.text.input.TextFieldState
|
|
15
15
|
import androidx.compose.foundation.text.input.byValue
|
|
@@ -29,7 +29,6 @@ import androidx.compose.ui.graphics.Color
|
|
|
29
29
|
import androidx.compose.ui.platform.LocalContext
|
|
30
30
|
import androidx.compose.ui.text.TextStyle
|
|
31
31
|
import androidx.compose.ui.text.font.FontFamily
|
|
32
|
-
import androidx.compose.ui.unit.dp
|
|
33
32
|
import androidx.compose.ui.unit.sp
|
|
34
33
|
import androidx.lifecycle.ViewModel
|
|
35
34
|
import com.facebook.react.bridge.Arguments
|
|
@@ -42,7 +41,6 @@ data class InputStyle(
|
|
|
42
41
|
val color: String? = null,
|
|
43
42
|
val fontSize: Double? = null,
|
|
44
43
|
val fontFamily: String? = null,
|
|
45
|
-
val height: Double? = null,
|
|
46
44
|
val paddingTop: Double? = null,
|
|
47
45
|
val paddingBottom: Double? = null,
|
|
48
46
|
val paddingLeft: Double? = null,
|
|
@@ -50,6 +48,7 @@ data class InputStyle(
|
|
|
50
48
|
val borderWidth: Double? = null,
|
|
51
49
|
val borderRadius: Double? = null,
|
|
52
50
|
val borderColor: String? = null,
|
|
51
|
+
val backgroundColor: String? = null,
|
|
53
52
|
)
|
|
54
53
|
|
|
55
54
|
@Composable
|
|
@@ -96,23 +95,27 @@ fun JetpackComposeView(
|
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
|
-
|
|
100
|
-
val
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
bottom = style?.paddingBottom?.dp ?: 0.dp,
|
|
105
|
-
)
|
|
98
|
+
|
|
99
|
+
val paddingTop = style?.paddingTop?.dp ?: 0.dp
|
|
100
|
+
val paddingBottom = style?.paddingBottom?.dp ?: 0.dp
|
|
101
|
+
val paddingLeft = style?.paddingLeft?.dp ?: 0.dp
|
|
102
|
+
val paddingRight = style?.paddingRight?.dp ?: 0.dp
|
|
106
103
|
val borderWidth = style?.borderWidth?.dp ?: 0.dp
|
|
107
104
|
val borderRadius = style?.borderRadius?.dp ?: 0.dp
|
|
108
|
-
val borderColor = style?.borderColor
|
|
105
|
+
val borderColor = style?.borderColor
|
|
106
|
+
?.let { Color(android.graphics.Color.parseColor(it)) }
|
|
107
|
+
?: Color.Transparent
|
|
108
|
+
val backgroundColor = style?.backgroundColor
|
|
109
|
+
?.let { Color(android.graphics.Color.parseColor(it)) }
|
|
110
|
+
?: Color.Transparent
|
|
111
|
+
val shape = RoundedCornerShape(borderRadius)
|
|
109
112
|
|
|
110
113
|
Box(
|
|
111
114
|
modifier = Modifier
|
|
112
|
-
.
|
|
113
|
-
.
|
|
114
|
-
.
|
|
115
|
-
.border(borderWidth, borderColor,
|
|
115
|
+
.fillMaxSize()
|
|
116
|
+
.clip(shape)
|
|
117
|
+
.background(backgroundColor)
|
|
118
|
+
.border(borderWidth, borderColor, shape),
|
|
116
119
|
) {
|
|
117
120
|
BasicTextField(
|
|
118
121
|
state,
|
|
@@ -121,8 +124,13 @@ fun JetpackComposeView(
|
|
|
121
124
|
proposed
|
|
122
125
|
},
|
|
123
126
|
modifier = Modifier
|
|
124
|
-
.
|
|
125
|
-
.padding(
|
|
127
|
+
.fillMaxSize()
|
|
128
|
+
.padding(
|
|
129
|
+
start = paddingLeft,
|
|
130
|
+
top = paddingTop,
|
|
131
|
+
end = paddingRight,
|
|
132
|
+
bottom = paddingBottom,
|
|
133
|
+
)
|
|
126
134
|
.focusRequester(focusRequester),
|
|
127
135
|
textStyle = TextStyle(
|
|
128
136
|
color = textColor,
|
|
@@ -130,6 +138,14 @@ fun JetpackComposeView(
|
|
|
130
138
|
fontFamily = fontFamily,
|
|
131
139
|
),
|
|
132
140
|
interactionSource = interactionSource,
|
|
141
|
+
decorator = { innerTextField ->
|
|
142
|
+
Box(
|
|
143
|
+
modifier = Modifier.fillMaxSize(),
|
|
144
|
+
contentAlignment = Alignment.CenterStart,
|
|
145
|
+
) {
|
|
146
|
+
innerTextField()
|
|
147
|
+
}
|
|
148
|
+
},
|
|
133
149
|
)
|
|
134
150
|
}
|
|
135
151
|
}
|
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
#import <React/RCTFabricComponentsPlugins.h>
|
|
11
11
|
|
|
12
12
|
#import <react/renderer/components/ControlledInputViewSpec/ComponentDescriptors.h>
|
|
13
|
+
#import <react/renderer/components/ControlledInputViewSpec/EventEmitters.h>
|
|
13
14
|
#import <react/renderer/components/ControlledInputViewSpec/Props.h>
|
|
14
15
|
#import <react/renderer/components/ControlledInputViewSpec/RCTComponentViewHelpers.h>
|
|
15
16
|
|
|
16
17
|
using namespace facebook::react;
|
|
17
18
|
|
|
18
|
-
@interface ControlledInputView () <RCTControlledInputViewViewProtocol>
|
|
19
|
+
@interface ControlledInputView () <RCTControlledInputViewViewProtocol, RNControlledInputDelegate>
|
|
19
20
|
@end
|
|
20
21
|
|
|
21
22
|
@implementation ControlledInputView {
|
|
@@ -34,6 +35,7 @@ using namespace facebook::react;
|
|
|
34
35
|
_props = defaultProps;
|
|
35
36
|
|
|
36
37
|
_inputView = [[RNControlledInput alloc] initWithFrame:self.bounds];
|
|
38
|
+
_inputView.delegate = self;
|
|
37
39
|
|
|
38
40
|
self.contentView = _inputView;
|
|
39
41
|
}
|
|
@@ -50,44 +52,20 @@ using namespace facebook::react;
|
|
|
50
52
|
_inputView.value = [NSString stringWithUTF8String:newViewProps.value.c_str()];
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
// Update inputStyle props
|
|
54
55
|
const auto &style = newViewProps.inputStyle;
|
|
55
56
|
const auto &oldStyle = oldViewProps.inputStyle;
|
|
56
57
|
|
|
57
58
|
if (oldStyle.color != style.color) {
|
|
58
59
|
_inputView.textColor = RCTUIColorFromSharedColor(style.color);
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
if (oldStyle.fontSize != style.fontSize) {
|
|
62
63
|
_inputView.fontSize = style.fontSize;
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
+
|
|
65
66
|
if (oldStyle.fontFamily != style.fontFamily) {
|
|
66
67
|
_inputView.fontFamily = style.fontFamily.empty() ? nil : [NSString stringWithUTF8String:style.fontFamily.c_str()];
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
if (oldStyle.height != style.height) {
|
|
70
|
-
_inputView.inputHeight = style.height;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (oldStyle.paddingTop != style.paddingTop ||
|
|
74
|
-
oldStyle.paddingBottom != style.paddingBottom ||
|
|
75
|
-
oldStyle.paddingLeft != style.paddingLeft ||
|
|
76
|
-
oldStyle.paddingRight != style.paddingRight) {
|
|
77
|
-
_inputView.padding = UIEdgeInsetsMake(style.paddingTop, style.paddingLeft, style.paddingBottom, style.paddingRight);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (oldStyle.borderWidth != style.borderWidth) {
|
|
81
|
-
_inputView.borderWidth = style.borderWidth;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (oldStyle.borderRadius != style.borderRadius) {
|
|
85
|
-
_inputView.borderRadius = style.borderRadius;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (oldStyle.borderColor != style.borderColor) {
|
|
89
|
-
_inputView.borderColor = RCTUIColorFromSharedColor(style.borderColor);
|
|
90
|
-
}
|
|
91
69
|
|
|
92
70
|
[super updateProps:props oldProps:oldProps];
|
|
93
71
|
}
|
|
@@ -109,4 +87,38 @@ using namespace facebook::react;
|
|
|
109
87
|
[super handleCommand:commandName args:args];
|
|
110
88
|
}
|
|
111
89
|
|
|
90
|
+
- (void)controlledInputDidChangeText:(RNControlledInput *)input value:(NSString *)value
|
|
91
|
+
{
|
|
92
|
+
if (_eventEmitter == nullptr) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const auto eventEmitter = std::static_pointer_cast<const ControlledInputViewEventEmitter>(_eventEmitter);
|
|
97
|
+
const char *utf8Value = value.UTF8String ?: "";
|
|
98
|
+
|
|
99
|
+
eventEmitter->onTextChange(ControlledInputViewEventEmitter::OnTextChange {
|
|
100
|
+
.value = std::string(utf8Value),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
- (void)controlledInputDidFocus:(RNControlledInput *)input
|
|
105
|
+
{
|
|
106
|
+
if (_eventEmitter == nullptr) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const auto eventEmitter = std::static_pointer_cast<const ControlledInputViewEventEmitter>(_eventEmitter);
|
|
111
|
+
eventEmitter->onFocus(ControlledInputViewEventEmitter::OnFocus {});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
- (void)controlledInputDidBlur:(RNControlledInput *)input
|
|
115
|
+
{
|
|
116
|
+
if (_eventEmitter == nullptr) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const auto eventEmitter = std::static_pointer_cast<const ControlledInputViewEventEmitter>(_eventEmitter);
|
|
121
|
+
eventEmitter->onBlur(ControlledInputViewEventEmitter::OnBlur {});
|
|
122
|
+
}
|
|
123
|
+
|
|
112
124
|
@end
|
|
@@ -1,49 +1,95 @@
|
|
|
1
1
|
import UIKit
|
|
2
2
|
|
|
3
|
+
@objc public protocol RNControlledInputDelegate: AnyObject {
|
|
4
|
+
func controlledInputDidChangeText(_ input: RNControlledInput, value: String)
|
|
5
|
+
func controlledInputDidFocus(_ input: RNControlledInput)
|
|
6
|
+
func controlledInputDidBlur(_ input: RNControlledInput)
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
@objc(RNControlledInput)
|
|
4
|
-
public class RNControlledInput: UIView {
|
|
5
|
-
|
|
10
|
+
public class RNControlledInput: UIView, UITextFieldDelegate {
|
|
11
|
+
|
|
12
|
+
private let textField = UITextField()
|
|
13
|
+
@objc public weak var delegate: RNControlledInputDelegate?
|
|
14
|
+
|
|
6
15
|
@objc public var value: String? {
|
|
7
16
|
didSet {
|
|
8
|
-
|
|
17
|
+
if textField.text != value {
|
|
18
|
+
textField.text = value
|
|
19
|
+
}
|
|
9
20
|
}
|
|
10
21
|
}
|
|
11
|
-
|
|
12
|
-
@objc public var textColor: UIColor?
|
|
13
|
-
@objc public var fontSize: CGFloat = 16
|
|
14
|
-
@objc public var fontFamily: String?
|
|
15
|
-
@objc public var inputHeight: CGFloat = 0
|
|
16
|
-
@objc public var padding: UIEdgeInsets = .zero
|
|
17
|
-
@objc public var borderWidth: CGFloat = 0
|
|
18
|
-
@objc public var borderRadius: CGFloat = 0
|
|
19
|
-
@objc public var borderColor: UIColor?
|
|
20
22
|
|
|
21
|
-
public
|
|
22
|
-
|
|
23
|
+
@objc public var textColor: UIColor? {
|
|
24
|
+
didSet { textField.textColor = textColor }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@objc public var fontSize: CGFloat = 16 {
|
|
28
|
+
didSet { applyFont() }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@objc public var fontFamily: String? {
|
|
32
|
+
didSet { applyFont() }
|
|
23
33
|
}
|
|
24
34
|
|
|
35
|
+
public override var canBecomeFirstResponder: Bool { true }
|
|
36
|
+
|
|
25
37
|
@objc public func focus() {
|
|
26
|
-
|
|
27
|
-
_ = becomeFirstResponder()
|
|
38
|
+
textField.becomeFirstResponder()
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
@objc public func blur() {
|
|
31
|
-
|
|
32
|
-
_ = resignFirstResponder()
|
|
42
|
+
textField.resignFirstResponder()
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
@objc public override init(frame: CGRect) {
|
|
36
46
|
super.init(frame: frame)
|
|
37
|
-
|
|
38
|
-
self.translatesAutoresizingMaskIntoConstraints = false
|
|
39
|
-
|
|
40
|
-
NSLayoutConstraint.activate([
|
|
41
|
-
self.widthAnchor.constraint(equalToConstant: 100),
|
|
42
|
-
self.heightAnchor.constraint(equalToConstant: 100)
|
|
43
|
-
])
|
|
47
|
+
setupTextField()
|
|
44
48
|
}
|
|
45
|
-
|
|
49
|
+
|
|
46
50
|
required init?(coder: NSCoder) {
|
|
47
51
|
fatalError("init(coder:) has not been implemented")
|
|
48
52
|
}
|
|
53
|
+
|
|
54
|
+
private func setupTextField() {
|
|
55
|
+
textField.translatesAutoresizingMaskIntoConstraints = false
|
|
56
|
+
textField.borderStyle = .none
|
|
57
|
+
textField.delegate = self
|
|
58
|
+
addSubview(textField)
|
|
59
|
+
|
|
60
|
+
NSLayoutConstraint.activate([
|
|
61
|
+
textField.topAnchor.constraint(equalTo: topAnchor),
|
|
62
|
+
textField.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
63
|
+
textField.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
64
|
+
textField.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
applyFont()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public func textFieldDidBeginEditing(_ textField: UITextField) {
|
|
71
|
+
delegate?.controlledInputDidFocus(self)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public func textFieldDidEndEditing(_ textField: UITextField) {
|
|
75
|
+
delegate?.controlledInputDidBlur(self)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
79
|
+
let currentText = textField.text ?? ""
|
|
80
|
+
guard let stringRange = Range(range, in: currentText) else { return true }
|
|
81
|
+
let newText = currentText.replacingCharacters(in: stringRange, with: string)
|
|
82
|
+
|
|
83
|
+
delegate?.controlledInputDidChangeText(self, value: newText)
|
|
84
|
+
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private func applyFont() {
|
|
89
|
+
if let family = fontFamily, let font = UIFont(name: family, size: fontSize) {
|
|
90
|
+
textField.font = font
|
|
91
|
+
} else {
|
|
92
|
+
textField.font = UIFont.systemFont(ofSize: fontSize)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
49
95
|
}
|
|
@@ -26,14 +26,14 @@ interface InputStyle {
|
|
|
26
26
|
color?: ColorValue;
|
|
27
27
|
fontSize?: Double;
|
|
28
28
|
fontFamily?: string;
|
|
29
|
-
height?: Double;
|
|
30
29
|
paddingTop?: Double;
|
|
31
30
|
paddingBottom?: Double;
|
|
32
31
|
paddingLeft?: Double;
|
|
33
32
|
paddingRight?: Double;
|
|
34
33
|
borderWidth?: Double;
|
|
35
34
|
borderRadius?: Double;
|
|
36
|
-
borderColor?:
|
|
35
|
+
borderColor?: string;
|
|
36
|
+
backgroundColor?: string;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export interface NativeProps extends ViewProps {
|
package/lib/module/index.js
CHANGED
|
@@ -1,33 +1,76 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
|
4
|
-
import { Platform } from 'react-native';
|
|
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;
|
|
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
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export const ControlledInputView = /*#__PURE__*/forwardRef(({
|
|
19
|
+
style,
|
|
20
|
+
onTextChange,
|
|
21
|
+
...rest
|
|
22
|
+
}, ref) => {
|
|
8
23
|
const nativeRef = useRef(null);
|
|
24
|
+
const flat = StyleSheet.flatten(style) ?? {};
|
|
25
|
+
let viewStyle;
|
|
26
|
+
let inputStyle;
|
|
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);
|
|
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
|
|
39
|
+
};
|
|
40
|
+
} else {
|
|
41
|
+
const {
|
|
42
|
+
color,
|
|
43
|
+
fontSize,
|
|
44
|
+
fontFamily,
|
|
45
|
+
...iosRest
|
|
46
|
+
} = flat;
|
|
47
|
+
viewStyle = iosRest;
|
|
48
|
+
const hasTextStyle = color != null || fontSize != null || fontFamily != null;
|
|
49
|
+
inputStyle = hasTextStyle ? {
|
|
50
|
+
color: color != null ? processColor(color) : undefined,
|
|
51
|
+
fontSize,
|
|
52
|
+
fontFamily
|
|
53
|
+
} : undefined;
|
|
54
|
+
}
|
|
9
55
|
useImperativeHandle(ref, () => ({
|
|
10
56
|
blur: () => {
|
|
11
|
-
if (!nativeRef.current)
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
57
|
+
if (!nativeRef.current) return;
|
|
14
58
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
15
|
-
console.log(`[ControlledInputView] ${Platform.OS} blur command -> native`);
|
|
16
59
|
Commands.blur(nativeRef.current);
|
|
17
60
|
}
|
|
18
61
|
},
|
|
19
62
|
focus: () => {
|
|
20
|
-
if (!nativeRef.current)
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
63
|
+
if (!nativeRef.current) return;
|
|
23
64
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
24
|
-
console.log(`[ControlledInputView] ${Platform.OS} focus command -> native`);
|
|
25
65
|
Commands.focus(nativeRef.current);
|
|
26
66
|
}
|
|
27
67
|
}
|
|
28
68
|
}));
|
|
29
69
|
return /*#__PURE__*/_jsx(ControlledInputViewNativeComponent, {
|
|
30
|
-
...
|
|
70
|
+
...rest,
|
|
71
|
+
style: viewStyle,
|
|
72
|
+
inputStyle: inputStyle,
|
|
73
|
+
onTextChange: onTextChange ? e => onTextChange(e.nativeEvent.value) : undefined,
|
|
31
74
|
ref: nativeRef
|
|
32
75
|
});
|
|
33
76
|
});
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["forwardRef","useImperativeHandle","useRef","Platform","ControlledInputViewNativeComponent","Commands","jsx","_jsx","ControlledInputView","
|
|
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":[]}
|
|
@@ -11,14 +11,14 @@ interface InputStyle {
|
|
|
11
11
|
color?: ColorValue;
|
|
12
12
|
fontSize?: Double;
|
|
13
13
|
fontFamily?: string;
|
|
14
|
-
height?: Double;
|
|
15
14
|
paddingTop?: Double;
|
|
16
15
|
paddingBottom?: Double;
|
|
17
16
|
paddingLeft?: Double;
|
|
18
17
|
paddingRight?: Double;
|
|
19
18
|
borderWidth?: Double;
|
|
20
19
|
borderRadius?: Double;
|
|
21
|
-
borderColor?:
|
|
20
|
+
borderColor?: string;
|
|
21
|
+
backgroundColor?: string;
|
|
22
22
|
}
|
|
23
23
|
export interface NativeProps extends ViewProps {
|
|
24
24
|
value?: string;
|
|
@@ -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,
|
|
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"}
|
|
@@ -3,6 +3,11 @@ export interface ControlledInputViewRef {
|
|
|
3
3
|
blur: () => void;
|
|
4
4
|
focus: () => void;
|
|
5
5
|
}
|
|
6
|
-
export
|
|
6
|
+
export type ControlledInputViewProps = Omit<NativeProps, 'inputStyle' | 'onTextChange'> & {
|
|
7
|
+
onTextChange?: (value: string) => void;
|
|
8
|
+
};
|
|
9
|
+
export declare const ControlledInputView: import("react").ForwardRefExoticComponent<Omit<NativeProps, "inputStyle" | "onTextChange"> & {
|
|
10
|
+
onTextChange?: (value: string) => void;
|
|
11
|
+
} & import("react").RefAttributes<ControlledInputViewRef>>;
|
|
7
12
|
export * from './ControlledInputViewNativeComponent';
|
|
8
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"
|
|
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"}
|
package/package.json
CHANGED
|
@@ -26,14 +26,14 @@ interface InputStyle {
|
|
|
26
26
|
color?: ColorValue;
|
|
27
27
|
fontSize?: Double;
|
|
28
28
|
fontFamily?: string;
|
|
29
|
-
height?: Double;
|
|
30
29
|
paddingTop?: Double;
|
|
31
30
|
paddingBottom?: Double;
|
|
32
31
|
paddingLeft?: Double;
|
|
33
32
|
paddingRight?: Double;
|
|
34
33
|
borderWidth?: Double;
|
|
35
34
|
borderRadius?: Double;
|
|
36
|
-
borderColor?:
|
|
35
|
+
borderColor?: string;
|
|
36
|
+
backgroundColor?: string;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export interface NativeProps extends ViewProps {
|
package/src/index.tsx
CHANGED
|
@@ -4,7 +4,12 @@ import {
|
|
|
4
4
|
useRef,
|
|
5
5
|
type ElementRef,
|
|
6
6
|
} from 'react';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
Platform,
|
|
9
|
+
processColor,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
type ViewStyle,
|
|
12
|
+
} from 'react-native';
|
|
8
13
|
import ControlledInputViewNativeComponent, {
|
|
9
14
|
Commands,
|
|
10
15
|
type NativeProps,
|
|
@@ -15,42 +20,116 @@ export interface ControlledInputViewRef {
|
|
|
15
20
|
focus: () => void;
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
export type ControlledInputViewProps = Omit<
|
|
24
|
+
NativeProps,
|
|
25
|
+
'inputStyle' | 'onTextChange'
|
|
26
|
+
> & {
|
|
27
|
+
onTextChange?: (value: string) => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// All style props that Android handles via Compose instead of the native View layer
|
|
31
|
+
const ANDROID_HANDLED_KEYS = [
|
|
32
|
+
'color',
|
|
33
|
+
'fontSize',
|
|
34
|
+
'fontFamily',
|
|
35
|
+
'padding',
|
|
36
|
+
'paddingVertical',
|
|
37
|
+
'paddingHorizontal',
|
|
38
|
+
'paddingTop',
|
|
39
|
+
'paddingBottom',
|
|
40
|
+
'paddingLeft',
|
|
41
|
+
'paddingRight',
|
|
42
|
+
'paddingStart',
|
|
43
|
+
'paddingEnd',
|
|
44
|
+
'borderWidth',
|
|
45
|
+
'borderRadius',
|
|
46
|
+
'borderColor',
|
|
47
|
+
'backgroundColor',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
function resolveAndroidPadding(flat: Record<string, any>) {
|
|
51
|
+
const base = flat.padding ?? 0;
|
|
52
|
+
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,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
18
62
|
export const ControlledInputView = forwardRef<
|
|
19
63
|
ControlledInputViewRef,
|
|
20
|
-
|
|
21
|
-
>((
|
|
64
|
+
ControlledInputViewProps
|
|
65
|
+
>(({ style, onTextChange, ...rest }, ref) => {
|
|
22
66
|
const nativeRef =
|
|
23
67
|
useRef<ElementRef<typeof ControlledInputViewNativeComponent>>(null);
|
|
24
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
|
+
|
|
25
108
|
useImperativeHandle(ref, () => ({
|
|
26
109
|
blur: () => {
|
|
27
|
-
if (!nativeRef.current)
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
110
|
+
if (!nativeRef.current) return;
|
|
31
111
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
32
|
-
console.log(
|
|
33
|
-
`[ControlledInputView] ${Platform.OS} blur command -> native`
|
|
34
|
-
);
|
|
35
112
|
Commands.blur(nativeRef.current);
|
|
36
113
|
}
|
|
37
114
|
},
|
|
38
115
|
focus: () => {
|
|
39
|
-
if (!nativeRef.current)
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
116
|
+
if (!nativeRef.current) return;
|
|
43
117
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
44
|
-
console.log(
|
|
45
|
-
`[ControlledInputView] ${Platform.OS} focus command -> native`
|
|
46
|
-
);
|
|
47
118
|
Commands.focus(nativeRef.current);
|
|
48
119
|
}
|
|
49
120
|
},
|
|
50
121
|
}));
|
|
51
122
|
|
|
52
123
|
return (
|
|
53
|
-
<ControlledInputViewNativeComponent
|
|
124
|
+
<ControlledInputViewNativeComponent
|
|
125
|
+
{...rest}
|
|
126
|
+
style={viewStyle}
|
|
127
|
+
inputStyle={inputStyle as NativeProps['inputStyle']}
|
|
128
|
+
onTextChange={
|
|
129
|
+
onTextChange ? (e) => onTextChange(e.nativeEvent.value) : undefined
|
|
130
|
+
}
|
|
131
|
+
ref={nativeRef as any}
|
|
132
|
+
/>
|
|
54
133
|
);
|
|
55
134
|
});
|
|
56
135
|
|