react-native-controlled-input 0.2.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.
@@ -0,0 +1,20 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "ControlledInput"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/veliseev93/react-native-controlled-input.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17
+ s.private_header_files = "ios/**/*.h"
18
+
19
+ install_modules_dependencies(s)
20
+ end
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 veliseev
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # react-native-controlled-input
2
+
3
+ React Native Controlled Inputative Controlled Input
4
+
5
+ ## Installation
6
+
7
+
8
+ ```sh
9
+ npm install react-native-controlled-input
10
+ ```
11
+
12
+
13
+ ## Usage
14
+
15
+
16
+ ```js
17
+ import { ControlledInputView } from "react-native-controlled-input";
18
+
19
+ // ...
20
+
21
+ <ControlledInputView color="tomato" />
22
+ ```
23
+
24
+
25
+ ## Contributing
26
+
27
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
28
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
29
+ - [Code of conduct](CODE_OF_CONDUCT.md)
30
+
31
+ ## License
32
+
33
+ MIT
34
+
35
+ ---
36
+
37
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,92 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = {name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ControlledInput__' + name]
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath "com.android.tools.build:gradle:8.7.2"
13
+ // noinspection DifferentKotlinGradleVersion
14
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
15
+ classpath "org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:${getExtOrDefault('kotlinVersion')}"
16
+ }
17
+ }
18
+
19
+
20
+ apply plugin: "com.android.library"
21
+ apply plugin: "kotlin-android"
22
+ apply plugin: "org.jetbrains.kotlin.plugin.compose"
23
+
24
+ apply plugin: "com.facebook.react"
25
+
26
+ def getExtOrIntegerDefault(name) {
27
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ControlledInput__" + name]).toInteger()
28
+ }
29
+
30
+ android {
31
+ namespace "com.controlledinput"
32
+
33
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
34
+
35
+ defaultConfig {
36
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
37
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
38
+ }
39
+
40
+ buildFeatures {
41
+ buildConfig true
42
+ compose true
43
+ }
44
+
45
+ buildTypes {
46
+ release {
47
+ minifyEnabled false
48
+ }
49
+ }
50
+
51
+ lintOptions {
52
+ disable "GradleCompatible"
53
+ }
54
+
55
+ compileOptions {
56
+ sourceCompatibility JavaVersion.VERSION_1_8
57
+ targetCompatibility JavaVersion.VERSION_1_8
58
+ }
59
+
60
+ sourceSets {
61
+ main {
62
+ java.srcDirs += [
63
+ "generated/java",
64
+ "generated/jni"
65
+ ]
66
+ }
67
+ }
68
+ }
69
+
70
+ repositories {
71
+ mavenCentral()
72
+ google()
73
+ }
74
+
75
+ def kotlin_version = getExtOrDefault("kotlinVersion")
76
+
77
+ dependencies {
78
+ implementation "com.facebook.react:react-android"
79
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
80
+
81
+ // Material Components (for BottomSheetDialog)
82
+ implementation 'com.google.android.material:material:1.13.0'
83
+
84
+ // Jetpack Compose dependencies
85
+ def composeBom = platform('androidx.compose:compose-bom:2025.12.01')
86
+ implementation composeBom
87
+ implementation 'androidx.compose.ui:ui'
88
+ implementation 'androidx.compose.ui:ui-tooling-preview'
89
+ implementation 'androidx.compose.material3:material3'
90
+ implementation 'androidx.activity:activity-compose:1.12.2'
91
+ debugImplementation 'androidx.compose.ui:ui-tooling'
92
+ }
@@ -0,0 +1,5 @@
1
+ ControlledInput_kotlinVersion=2.0.21
2
+ ControlledInput_minSdkVersion=24
3
+ ControlledInput_targetSdkVersion=34
4
+ ControlledInput_compileSdkVersion=35
5
+ ControlledInput_ndkVersion=27.1.12297006
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,19 @@
1
+ package com.controlledinput;
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+ import java.util.ArrayList
8
+
9
+ class ControlledInputViewPackage : ReactPackage {
10
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
11
+ val viewManagers: MutableList<ViewManager<*, *>> = ArrayList()
12
+ viewManagers.add(ControlledInputViewManager())
13
+ return viewManagers
14
+ }
15
+
16
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
17
+ return emptyList()
18
+ }
19
+ }
@@ -0,0 +1,141 @@
1
+ package com.controlledinput
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.util.AttributeSet
6
+ import android.view.inputmethod.InputMethodManager
7
+ import android.widget.LinearLayout
8
+ import androidx.compose.material3.Text
9
+ import androidx.compose.runtime.LaunchedEffect
10
+ import androidx.compose.runtime.collectAsState
11
+ import androidx.compose.runtime.getValue
12
+ import androidx.compose.runtime.remember
13
+ import androidx.compose.ui.focus.FocusRequester
14
+ import androidx.compose.ui.platform.ComposeView
15
+ import androidx.compose.ui.platform.LocalFocusManager
16
+ import com.facebook.react.bridge.ReactContext
17
+ import com.facebook.react.uimanager.UIManagerHelper
18
+ import kotlinx.coroutines.flow.MutableStateFlow
19
+
20
+ class ControlledInputView : LinearLayout {
21
+ constructor(context: Context) : super(context) {
22
+ configureComponent(context)
23
+ }
24
+
25
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
26
+ configureComponent(context)
27
+ }
28
+
29
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
30
+ context,
31
+ attrs,
32
+ defStyleAttr
33
+ ) {
34
+ configureComponent(context)
35
+ }
36
+
37
+ internal lateinit var viewModel: JetpackComposeViewModel
38
+ private val blurSignal = MutableStateFlow(0)
39
+ private val focusSignal = MutableStateFlow(0)
40
+
41
+ fun blur() {
42
+ // триггерим compose снять фокус
43
+ blurSignal.value = blurSignal.value + 1
44
+
45
+ // на всякий случай прячем клавиатуру на уровне View
46
+ val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
47
+ imm.hideSoftInputFromWindow(windowToken, 0)
48
+
49
+ // и снимаем фокус у самого android view (не всегда достаточно, но не мешает)
50
+ clearFocus()
51
+ }
52
+
53
+ fun focus() {
54
+ // триггерим compose запросить фокус
55
+ focusSignal.value = focusSignal.value + 1
56
+ }
57
+
58
+ private fun configureComponent(context: Context) {
59
+
60
+ layoutParams = LayoutParams(
61
+ LayoutParams.WRAP_CONTENT,
62
+ LayoutParams.WRAP_CONTENT
63
+ )
64
+
65
+ ComposeView(context).also {
66
+ it.layoutParams = LayoutParams(
67
+ LayoutParams.WRAP_CONTENT,
68
+ LayoutParams.WRAP_CONTENT
69
+ )
70
+
71
+ viewModel = JetpackComposeViewModel()
72
+
73
+ it.setContent {
74
+ val value = viewModel.value.collectAsState().value
75
+ val blurTick by blurSignal.collectAsState()
76
+ val focusTick by focusSignal.collectAsState()
77
+ val focusManager = LocalFocusManager.current
78
+ val focusRequester = remember { FocusRequester() }
79
+
80
+ // при каждом blurTick снимаем фокус в compose
81
+ LaunchedEffect(blurTick) {
82
+ if (blurTick > 0) {
83
+ focusManager.clearFocus(force = true)
84
+ }
85
+ }
86
+
87
+ // при каждом focusTick запрашиваем фокус в compose
88
+ LaunchedEffect(focusTick) {
89
+ if (focusTick > 0) {
90
+ focusRequester.requestFocus()
91
+ }
92
+ }
93
+
94
+ JetpackComposeView(
95
+ value = value,
96
+ inputStyle = viewModel.inputStyle,
97
+ onTextChange = { value ->
98
+ val surfaceId = UIManagerHelper.getSurfaceId(context)
99
+ val viewId = this.id
100
+ UIManagerHelper
101
+ .getEventDispatcherForReactTag(context as ReactContext, viewId)
102
+ ?.dispatchEvent(
103
+ TextChangeEvent(
104
+ surfaceId,
105
+ viewId,
106
+ value
107
+ )
108
+ )
109
+ },
110
+ onFocus = {
111
+ val surfaceId = UIManagerHelper.getSurfaceId(context)
112
+ val viewId = this.id
113
+ UIManagerHelper
114
+ .getEventDispatcherForReactTag(context as ReactContext, viewId)
115
+ ?.dispatchEvent(
116
+ FocusEvent(
117
+ surfaceId,
118
+ viewId
119
+ )
120
+ )
121
+ },
122
+ onBlur = {
123
+ val surfaceId = UIManagerHelper.getSurfaceId(context)
124
+ val viewId = this.id
125
+ UIManagerHelper
126
+ .getEventDispatcherForReactTag(context as ReactContext, viewId)
127
+ ?.dispatchEvent(
128
+ BlurEvent(
129
+ surfaceId,
130
+ viewId
131
+ )
132
+ )
133
+ },
134
+ focusRequester = focusRequester
135
+ )
136
+ }
137
+ addView(it)
138
+
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,79 @@
1
+ package com.controlledinput
2
+
3
+ import com.facebook.react.bridge.ReadableMap
4
+ import com.facebook.react.module.annotations.ReactModule
5
+ import com.facebook.react.uimanager.SimpleViewManager
6
+ import com.facebook.react.uimanager.ThemedReactContext
7
+ import com.facebook.react.uimanager.ViewManagerDelegate
8
+ import com.facebook.react.uimanager.annotations.ReactProp
9
+ import com.facebook.react.viewmanagers.ControlledInputViewManagerInterface
10
+ import com.facebook.react.viewmanagers.ControlledInputViewManagerDelegate
11
+ import com.facebook.react.common.MapBuilder
12
+
13
+ @ReactModule(name = ControlledInputViewManager.NAME)
14
+ class ControlledInputViewManager : SimpleViewManager<ControlledInputView>(),
15
+ ControlledInputViewManagerInterface<ControlledInputView> {
16
+ private val mDelegate: ViewManagerDelegate<ControlledInputView>
17
+
18
+ init {
19
+ mDelegate = ControlledInputViewManagerDelegate(this)
20
+ }
21
+
22
+ override fun getDelegate(): ViewManagerDelegate<ControlledInputView>? {
23
+ return mDelegate
24
+ }
25
+
26
+ override fun getName(): String {
27
+ return NAME
28
+ }
29
+
30
+ public override fun createViewInstance(context: ThemedReactContext): ControlledInputView {
31
+ return ControlledInputView(context)
32
+ }
33
+
34
+ @ReactProp(name = "value")
35
+ override fun setValue(view: ControlledInputView, value: String?) {
36
+ value?.let {
37
+ view.viewModel.setValue(value)
38
+ }
39
+ }
40
+
41
+ @ReactProp(name = "inputStyle")
42
+ override fun setInputStyle(view: ControlledInputView, inputStyle: ReadableMap?) {
43
+ val style = if (inputStyle == null) {
44
+ null
45
+ } else {
46
+ InputStyle(
47
+ color = if (inputStyle.hasKey("color")) inputStyle.getString("color") else null,
48
+ fontSize = if (inputStyle.hasKey("fontSize")) inputStyle.getDouble("fontSize") else null,
49
+ height = if (inputStyle.hasKey("height")) inputStyle.getDouble("height") else null,
50
+ paddingTop = if (inputStyle.hasKey("paddingTop")) inputStyle.getDouble("paddingTop") else null,
51
+ paddingBottom = if (inputStyle.hasKey("paddingBottom")) inputStyle.getDouble("paddingBottom") else null,
52
+ paddingLeft = if (inputStyle.hasKey("paddingLeft")) inputStyle.getDouble("paddingLeft") else null,
53
+ paddingRight = if (inputStyle.hasKey("paddingRight")) inputStyle.getDouble("paddingRight") else null,
54
+ borderWidth = if (inputStyle.hasKey("borderWidth")) inputStyle.getDouble("borderWidth") else null,
55
+ borderRadius = if (inputStyle.hasKey("borderRadius")) inputStyle.getDouble("borderRadius") else null,
56
+ borderColor = if (inputStyle.hasKey("borderColor")) inputStyle.getString("borderColor") else null,
57
+ )
58
+ }
59
+ view.viewModel.setInputStyle(style)
60
+ }
61
+
62
+ companion object {
63
+ const val NAME = "ControlledInputView"
64
+ }
65
+
66
+ override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> = mutableMapOf(
67
+ TextChangeEvent.EVENT_NAME to MapBuilder.of("registrationName", "onTextChange"),
68
+ FocusEvent.EVENT_NAME to MapBuilder.of("registrationName", "onFocus"),
69
+ BlurEvent.EVENT_NAME to MapBuilder.of("registrationName", "onBlur")
70
+ )
71
+
72
+ override fun focus(view: ControlledInputView?) {
73
+ view?.focus()
74
+ }
75
+
76
+ override fun blur(view: ControlledInputView?) {
77
+ view?.blur()
78
+ }
79
+ }
@@ -0,0 +1,184 @@
1
+ package com.controlledinput
2
+
3
+ import androidx.compose.foundation.border
4
+ import androidx.compose.foundation.shape.RoundedCornerShape
5
+ import androidx.compose.foundation.layout.Box
6
+ import androidx.compose.ui.draw.clip
7
+ import androidx.compose.foundation.layout.PaddingValues
8
+ import androidx.compose.foundation.layout.fillMaxSize
9
+ import androidx.compose.foundation.layout.fillMaxWidth
10
+ import androidx.compose.foundation.layout.height
11
+ import androidx.compose.foundation.layout.padding
12
+ import androidx.compose.foundation.text.BasicTextField
13
+ import androidx.compose.foundation.text.input.InputTransformation
14
+ import androidx.compose.foundation.text.input.TextFieldState
15
+ import androidx.compose.foundation.text.input.byValue
16
+ import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
17
+ import androidx.compose.foundation.interaction.MutableInteractionSource
18
+ import androidx.compose.foundation.interaction.FocusInteraction
19
+ import androidx.compose.runtime.Composable
20
+ import androidx.compose.runtime.LaunchedEffect
21
+ import androidx.compose.runtime.collectAsState
22
+ import androidx.compose.runtime.getValue
23
+ import androidx.compose.runtime.remember
24
+ import androidx.compose.ui.Modifier
25
+ import androidx.compose.ui.focus.FocusRequester
26
+ import androidx.compose.ui.focus.focusRequester
27
+ import androidx.compose.ui.graphics.Color
28
+ import androidx.compose.ui.text.TextStyle
29
+ import androidx.compose.ui.unit.dp
30
+ import androidx.compose.ui.unit.sp
31
+ import androidx.lifecycle.ViewModel
32
+ import com.facebook.react.bridge.Arguments
33
+ import com.facebook.react.bridge.WritableMap
34
+ import com.facebook.react.uimanager.events.Event
35
+ import kotlinx.coroutines.flow.MutableStateFlow
36
+ import kotlinx.coroutines.flow.StateFlow
37
+
38
+ data class InputStyle(
39
+ val color: String? = null,
40
+ val fontSize: Double? = null,
41
+ val height: Double? = null,
42
+ val paddingTop: Double? = null,
43
+ val paddingBottom: Double? = null,
44
+ val paddingLeft: Double? = null,
45
+ val paddingRight: Double? = null,
46
+ val borderWidth: Double? = null,
47
+ val borderRadius: Double? = null,
48
+ val borderColor: String? = null,
49
+ )
50
+
51
+ @Composable
52
+ fun JetpackComposeView(
53
+ value: String,
54
+ inputStyle: StateFlow<InputStyle?>,
55
+ onTextChange: (value: String) -> Unit,
56
+ onFocus: (() -> Unit)? = null,
57
+ onBlur: (() -> Unit)? = null,
58
+ focusRequester: FocusRequester,
59
+ ) {
60
+ val state = remember { TextFieldState(value) }
61
+ val style by inputStyle.collectAsState()
62
+ val interactionSource = remember { MutableInteractionSource() }
63
+
64
+ if (state.text.toString() != value) {
65
+ state.setTextAndPlaceCursorAtEnd(value)
66
+ }
67
+
68
+ LaunchedEffect(interactionSource) {
69
+ interactionSource.interactions.collect { interaction ->
70
+ when (interaction) {
71
+ is FocusInteraction.Focus -> {
72
+ onFocus?.invoke()
73
+ }
74
+ is FocusInteraction.Unfocus -> {
75
+ onBlur?.invoke()
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ val textColor = style?.color?.let { Color(android.graphics.Color.parseColor(it)) } ?: Color.White
82
+ val fontSize = style?.fontSize?.let { it.sp } ?: 24.sp
83
+ val height = style?.height?.let { it.dp }
84
+ val paddingValues = PaddingValues(
85
+ start = style?.paddingLeft?.dp ?: 0.dp,
86
+ top = style?.paddingTop?.dp ?: 0.dp,
87
+ end = style?.paddingRight?.dp ?: 0.dp,
88
+ bottom = style?.paddingBottom?.dp ?: 0.dp,
89
+ )
90
+ val borderWidth = style?.borderWidth?.dp ?: 0.dp
91
+ val borderRadius = style?.borderRadius?.dp ?: 0.dp
92
+ val borderColor = style?.borderColor?.let { Color(android.graphics.Color.parseColor(it)) } ?: Color.Transparent
93
+
94
+ Box(
95
+ modifier = Modifier
96
+ .fillMaxWidth()
97
+ .then(height?.let { Modifier.height(it) } ?: Modifier)
98
+ .clip(RoundedCornerShape(borderRadius))
99
+ .border(borderWidth, borderColor, RoundedCornerShape(borderRadius))
100
+ ) {
101
+ BasicTextField(
102
+ state,
103
+ inputTransformation = InputTransformation.byValue { _, proposed ->
104
+ onTextChange(proposed.toString())
105
+ proposed
106
+ },
107
+ modifier = Modifier
108
+ .fillMaxWidth()
109
+ .padding(paddingValues)
110
+ .focusRequester(focusRequester),
111
+ textStyle = TextStyle(
112
+ color = textColor,
113
+ fontSize = fontSize,
114
+ ),
115
+ interactionSource = interactionSource,
116
+ )
117
+ }
118
+ }
119
+
120
+ class TextChangeEvent(
121
+ surfaceId: Int,
122
+ viewId: Int,
123
+ val value: String,
124
+ ) : Event<TextChangeEvent>(surfaceId, viewId) {
125
+ override fun getEventName() = EVENT_NAME
126
+
127
+ override fun getCoalescingKey(): Short = 0
128
+
129
+ override fun getEventData(): WritableMap? = Arguments.createMap().also {
130
+
131
+ it.putString("value", value)
132
+ }
133
+
134
+ companion object {
135
+ const val EVENT_NAME = "onTextChange"
136
+ }
137
+ }
138
+
139
+ class FocusEvent(
140
+ surfaceId: Int,
141
+ viewId: Int,
142
+ ) : Event<FocusEvent>(surfaceId, viewId) {
143
+ override fun getEventName() = EVENT_NAME
144
+
145
+ override fun getCoalescingKey(): Short = 0
146
+
147
+ override fun getEventData(): WritableMap? = Arguments.createMap()
148
+
149
+ companion object {
150
+ const val EVENT_NAME = "onFocus"
151
+ }
152
+ }
153
+
154
+ class BlurEvent(
155
+ surfaceId: Int,
156
+ viewId: Int,
157
+ ) : Event<BlurEvent>(surfaceId, viewId) {
158
+ override fun getEventName() = EVENT_NAME
159
+
160
+ override fun getCoalescingKey(): Short = 0
161
+
162
+ override fun getEventData(): WritableMap? = Arguments.createMap()
163
+
164
+ companion object {
165
+ const val EVENT_NAME = "onBlur"
166
+ }
167
+ }
168
+
169
+
170
+ class JetpackComposeViewModel : ViewModel() {
171
+ private val _value = MutableStateFlow("")
172
+ private val _inputStyle = MutableStateFlow<InputStyle?>(null)
173
+
174
+ val value: StateFlow<String> get() = _value
175
+ val inputStyle: StateFlow<InputStyle?> get() = _inputStyle
176
+
177
+ fun setValue(newValue: String) {
178
+ _value.value = newValue
179
+ }
180
+
181
+ fun setInputStyle(style: InputStyle?) {
182
+ _inputStyle.value = style
183
+ }
184
+ }
@@ -0,0 +1,14 @@
1
+ #import <React/RCTViewComponentView.h>
2
+ #import <UIKit/UIKit.h>
3
+
4
+ #ifndef ControlledInputViewNativeComponent_h
5
+ #define ControlledInputViewNativeComponent_h
6
+
7
+ NS_ASSUME_NONNULL_BEGIN
8
+
9
+ @interface ControlledInputView : RCTViewComponentView
10
+ @end
11
+
12
+ NS_ASSUME_NONNULL_END
13
+
14
+ #endif /* ControlledInputViewNativeComponent_h */
@@ -0,0 +1,108 @@
1
+ #import "ControlledInputView.h"
2
+
3
+ #if __has_include("ControlledInput-Swift.h")
4
+ #import "ControlledInput-Swift.h"
5
+ #else
6
+ #import <ControlledInput/ControlledInput-Swift.h>
7
+ #endif
8
+
9
+ #import <React/RCTConversions.h>
10
+ #import <React/RCTFabricComponentsPlugins.h>
11
+
12
+ #import <react/renderer/components/ControlledInputViewSpec/ComponentDescriptors.h>
13
+ #import <react/renderer/components/ControlledInputViewSpec/Props.h>
14
+ #import <react/renderer/components/ControlledInputViewSpec/RCTComponentViewHelpers.h>
15
+
16
+ using namespace facebook::react;
17
+
18
+ @interface ControlledInputView () <RCTControlledInputViewViewProtocol>
19
+ @end
20
+
21
+ @implementation ControlledInputView {
22
+ RNControlledInput * _inputView;
23
+ }
24
+
25
+ + (ComponentDescriptorProvider)componentDescriptorProvider
26
+ {
27
+ return concreteComponentDescriptorProvider<ControlledInputViewComponentDescriptor>();
28
+ }
29
+
30
+ - (instancetype)initWithFrame:(CGRect)frame
31
+ {
32
+ if (self = [super initWithFrame:frame]) {
33
+ static const auto defaultProps = std::make_shared<const ControlledInputViewProps>();
34
+ _props = defaultProps;
35
+
36
+ _inputView = [[RNControlledInput alloc] initWithFrame:self.bounds];
37
+
38
+ self.contentView = _inputView;
39
+ }
40
+
41
+ return self;
42
+ }
43
+
44
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
45
+ {
46
+ const auto &oldViewProps = *std::static_pointer_cast<ControlledInputViewProps const>(_props);
47
+ const auto &newViewProps = *std::static_pointer_cast<ControlledInputViewProps const>(props);
48
+
49
+ if (oldViewProps.value != newViewProps.value) {
50
+ _inputView.value = [NSString stringWithUTF8String:newViewProps.value.c_str()];
51
+ }
52
+
53
+ // Update inputStyle props
54
+ const auto &style = newViewProps.inputStyle;
55
+ const auto &oldStyle = oldViewProps.inputStyle;
56
+
57
+ if (oldStyle.color != style.color) {
58
+ _inputView.textColor = RCTUIColorFromSharedColor(style.color);
59
+ }
60
+
61
+ if (oldStyle.fontSize != style.fontSize) {
62
+ _inputView.fontSize = style.fontSize;
63
+ }
64
+
65
+ if (oldStyle.height != style.height) {
66
+ _inputView.inputHeight = style.height;
67
+ }
68
+
69
+ if (oldStyle.paddingTop != style.paddingTop ||
70
+ oldStyle.paddingBottom != style.paddingBottom ||
71
+ oldStyle.paddingLeft != style.paddingLeft ||
72
+ oldStyle.paddingRight != style.paddingRight) {
73
+ _inputView.padding = UIEdgeInsetsMake(style.paddingTop, style.paddingLeft, style.paddingBottom, style.paddingRight);
74
+ }
75
+
76
+ if (oldStyle.borderWidth != style.borderWidth) {
77
+ _inputView.borderWidth = style.borderWidth;
78
+ }
79
+
80
+ if (oldStyle.borderRadius != style.borderRadius) {
81
+ _inputView.borderRadius = style.borderRadius;
82
+ }
83
+
84
+ if (oldStyle.borderColor != style.borderColor) {
85
+ _inputView.borderColor = RCTUIColorFromSharedColor(style.borderColor);
86
+ }
87
+
88
+ [super updateProps:props oldProps:oldProps];
89
+ }
90
+
91
+ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
92
+ {
93
+ if ([commandName isEqualToString:@"focus"]) {
94
+ NSLog(@"[ControlledInputView] handleCommand focus");
95
+ [_inputView focus];
96
+ return;
97
+ }
98
+
99
+ if ([commandName isEqualToString:@"blur"]) {
100
+ NSLog(@"[ControlledInputView] handleCommand blur");
101
+ [_inputView blur];
102
+ return;
103
+ }
104
+
105
+ [super handleCommand:commandName args:args];
106
+ }
107
+
108
+ @end
@@ -0,0 +1,48 @@
1
+ import UIKit
2
+
3
+ @objc(RNControlledInput)
4
+ public class RNControlledInput: UIView {
5
+
6
+ @objc public var value: String? {
7
+ didSet {
8
+ // Update UI
9
+ }
10
+ }
11
+
12
+ @objc public var textColor: UIColor?
13
+ @objc public var fontSize: CGFloat = 16
14
+ @objc public var inputHeight: CGFloat = 0
15
+ @objc public var padding: UIEdgeInsets = .zero
16
+ @objc public var borderWidth: CGFloat = 0
17
+ @objc public var borderRadius: CGFloat = 0
18
+ @objc public var borderColor: UIColor?
19
+
20
+ public override var canBecomeFirstResponder: Bool {
21
+ true
22
+ }
23
+
24
+ @objc public func focus() {
25
+ print("[ControlledInputView] RNControlledInput.focus()")
26
+ _ = becomeFirstResponder()
27
+ }
28
+
29
+ @objc public func blur() {
30
+ print("[ControlledInputView] RNControlledInput.blur()")
31
+ _ = resignFirstResponder()
32
+ }
33
+
34
+ @objc public override init(frame: CGRect) {
35
+ super.init(frame: frame)
36
+ self.backgroundColor = .red
37
+ self.translatesAutoresizingMaskIntoConstraints = false
38
+
39
+ NSLayoutConstraint.activate([
40
+ self.widthAnchor.constraint(equalToConstant: 100),
41
+ self.heightAnchor.constraint(equalToConstant: 100)
42
+ ])
43
+ }
44
+
45
+ required init?(coder: NSCoder) {
46
+ fatalError("init(coder:) has not been implemented")
47
+ }
48
+ }
@@ -0,0 +1,55 @@
1
+ import {
2
+ codegenNativeComponent,
3
+ type ViewProps,
4
+ type ColorValue,
5
+ type HostComponent,
6
+ } from 'react-native';
7
+ import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
8
+ import type {
9
+ BubblingEventHandler,
10
+ Double,
11
+ } from 'react-native/Libraries/Types/CodegenTypes';
12
+
13
+ interface TextChangeEvent {
14
+ value: string;
15
+ }
16
+
17
+ interface FocusEvent {
18
+ // Empty event
19
+ }
20
+
21
+ interface BlurEvent {
22
+ // Empty event
23
+ }
24
+
25
+ interface InputStyle {
26
+ color?: ColorValue;
27
+ fontSize?: Double;
28
+ height?: Double;
29
+ paddingTop?: Double;
30
+ paddingBottom?: Double;
31
+ paddingLeft?: Double;
32
+ paddingRight?: Double;
33
+ borderWidth?: Double;
34
+ borderRadius?: Double;
35
+ borderColor?: ColorValue;
36
+ }
37
+
38
+ export interface NativeProps extends ViewProps {
39
+ value?: string;
40
+ inputStyle?: InputStyle;
41
+ onTextChange?: BubblingEventHandler<Readonly<TextChangeEvent>>;
42
+ onFocus?: BubblingEventHandler<Readonly<FocusEvent>>;
43
+ onBlur?: BubblingEventHandler<Readonly<BlurEvent>>;
44
+ }
45
+
46
+ export interface NativeCommands {
47
+ focus: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
48
+ blur: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
49
+ }
50
+
51
+ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
52
+ supportedCommands: ['focus', 'blur'],
53
+ });
54
+
55
+ export default codegenNativeComponent<NativeProps>('ControlledInputView');
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ import { forwardRef, useImperativeHandle, useRef } from 'react';
4
+ import { Platform } from 'react-native';
5
+ import ControlledInputViewNativeComponent, { Commands } from './ControlledInputViewNativeComponent';
6
+ import { jsx as _jsx } from "react/jsx-runtime";
7
+ export const ControlledInputView = /*#__PURE__*/forwardRef((props, ref) => {
8
+ const nativeRef = useRef(null);
9
+ useImperativeHandle(ref, () => ({
10
+ blur: () => {
11
+ if (!nativeRef.current) {
12
+ return;
13
+ }
14
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
15
+ console.log(`[ControlledInputView] ${Platform.OS} blur command -> native`);
16
+ Commands.blur(nativeRef.current);
17
+ }
18
+ },
19
+ focus: () => {
20
+ if (!nativeRef.current) {
21
+ return;
22
+ }
23
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
24
+ console.log(`[ControlledInputView] ${Platform.OS} focus command -> native`);
25
+ Commands.focus(nativeRef.current);
26
+ }
27
+ }
28
+ }));
29
+ return /*#__PURE__*/_jsx(ControlledInputViewNativeComponent, {
30
+ ...props,
31
+ ref: nativeRef
32
+ });
33
+ });
34
+ export * from './ControlledInputViewNativeComponent';
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["forwardRef","useImperativeHandle","useRef","Platform","ControlledInputViewNativeComponent","Commands","jsx","_jsx","ControlledInputView","props","ref","nativeRef","blur","current","OS","console","log","focus"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SACEA,UAAU,EACVC,mBAAmB,EACnBC,MAAM,QAGD,OAAO;AACd,SAASC,QAAQ,QAAQ,cAAc;AACvC,OAAOC,kCAAkC,IACvCC,QAAQ,QACH,sCAAsC;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAS9C,OAAO,MAAMC,mBAAmB,gBAAGR,UAAU,CAG3C,CAACS,KAAK,EAAEC,GAAG,KAAK;EAChB,MAAMC,SAAS,GACbT,MAAM,CAAwD,IAAI,CAAC;EAErED,mBAAmB,CAACS,GAAG,EAAE,OAAO;IAC9BE,IAAI,EAAEA,CAAA,KAAM;MACV,IAAI,CAACD,SAAS,CAACE,OAAO,EAAE;QACtB;MACF;MAEA,IAAIV,QAAQ,CAACW,EAAE,KAAK,KAAK,IAAIX,QAAQ,CAACW,EAAE,KAAK,SAAS,EAAE;QACtDC,OAAO,CAACC,GAAG,CACT,yBAAyBb,QAAQ,CAACW,EAAE,yBACtC,CAAC;QACDT,QAAQ,CAACO,IAAI,CAACD,SAAS,CAACE,OAAO,CAAC;MAClC;IACF,CAAC;IACDI,KAAK,EAAEA,CAAA,KAAM;MACX,IAAI,CAACN,SAAS,CAACE,OAAO,EAAE;QACtB;MACF;MAEA,IAAIV,QAAQ,CAACW,EAAE,KAAK,KAAK,IAAIX,QAAQ,CAACW,EAAE,KAAK,SAAS,EAAE;QACtDC,OAAO,CAACC,GAAG,CACT,yBAAyBb,QAAQ,CAACW,EAAE,0BACtC,CAAC;QACDT,QAAQ,CAACY,KAAK,CAACN,SAAS,CAACE,OAAO,CAAC;MACnC;IACF;EACF,CAAC,CAAC,CAAC;EAEH,oBACEN,IAAA,CAACH,kCAAkC;IAAA,GAAKK,KAAK;IAAEC,GAAG,EAAEC;EAAiB,CAAE,CAAC;AAE5E,CAAC,CAAC;AAEF,cAAc,sCAAsC","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=react-native-codegen.d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["react-native-codegen.d.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,36 @@
1
+ import { type ViewProps, type ColorValue, type HostComponent } from 'react-native';
2
+ import type { BubblingEventHandler, Double } from 'react-native/Libraries/Types/CodegenTypes';
3
+ interface TextChangeEvent {
4
+ value: string;
5
+ }
6
+ interface FocusEvent {
7
+ }
8
+ interface BlurEvent {
9
+ }
10
+ interface InputStyle {
11
+ color?: ColorValue;
12
+ fontSize?: Double;
13
+ height?: Double;
14
+ paddingTop?: Double;
15
+ paddingBottom?: Double;
16
+ paddingLeft?: Double;
17
+ paddingRight?: Double;
18
+ borderWidth?: Double;
19
+ borderRadius?: Double;
20
+ borderColor?: ColorValue;
21
+ }
22
+ export interface NativeProps extends ViewProps {
23
+ value?: string;
24
+ inputStyle?: InputStyle;
25
+ onTextChange?: BubblingEventHandler<Readonly<TextChangeEvent>>;
26
+ onFocus?: BubblingEventHandler<Readonly<FocusEvent>>;
27
+ onBlur?: BubblingEventHandler<Readonly<BlurEvent>>;
28
+ }
29
+ export interface NativeCommands {
30
+ focus: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
31
+ blur: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
32
+ }
33
+ export declare const Commands: NativeCommands;
34
+ declare const _default: import("react-native/types_generated/Libraries/Utilities/codegenNativeComponent").NativeComponentType<NativeProps>;
35
+ export default _default;
36
+ //# sourceMappingURL=ControlledInputViewNativeComponent.d.ts.map
@@ -0,0 +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,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,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,UAAU,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"}
@@ -0,0 +1,9 @@
1
+ export interface ControlledInputViewRef {
2
+ blur: () => void;
3
+ focus: () => void;
4
+ }
5
+ export declare const ControlledInputView: import("react").ForwardRefExoticComponent<Omit<Omit<import("./ControlledInputViewNativeComponent").NativeProps, "ref"> & {
6
+ ref?: React.Ref<import("react-native").HostInstance>;
7
+ }, "ref"> & import("react").RefAttributes<ControlledInputViewRef>>;
8
+ export * from './ControlledInputViewNativeComponent';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAcA,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,eAAO,MAAM,mBAAmB;;kEAqC9B,CAAC;AAEH,cAAc,sCAAsC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,174 @@
1
+ {
2
+ "name": "react-native-controlled-input",
3
+ "version": "0.2.0",
4
+ "description": "React Native Controlled Inputative Controlled Input",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.tsx",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "lib",
18
+ "android",
19
+ "ios",
20
+ "cpp",
21
+ "*.podspec",
22
+ "react-native.config.js",
23
+ "!ios/build",
24
+ "!android/build",
25
+ "!android/gradle",
26
+ "!android/gradlew",
27
+ "!android/gradlew.bat",
28
+ "!android/local.properties",
29
+ "!**/__tests__",
30
+ "!**/__fixtures__",
31
+ "!**/__mocks__",
32
+ "!**/.*"
33
+ ],
34
+ "scripts": {
35
+ "example": "yarn workspace react-native-controlled-input-example",
36
+ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
37
+ "prepare": "bob build",
38
+ "typecheck": "tsc",
39
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
40
+ "test": "jest",
41
+ "release": "release-it --only-version"
42
+ },
43
+ "keywords": [
44
+ "react-native",
45
+ "ios",
46
+ "android"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/veliseev93/react-native-controlled-input.git"
51
+ },
52
+ "author": "veliseev <veliseev@ronasit.com> (https://github.com/veliseev93)",
53
+ "license": "MIT",
54
+ "bugs": {
55
+ "url": "https://github.com/veliseev93/react-native-controlled-input/issues"
56
+ },
57
+ "homepage": "https://github.com/veliseev93/react-native-controlled-input#readme",
58
+ "publishConfig": {
59
+ "registry": "https://registry.npmjs.org/"
60
+ },
61
+ "devDependencies": {
62
+ "@commitlint/config-conventional": "^19.8.1",
63
+ "@eslint/compat": "^1.3.2",
64
+ "@eslint/eslintrc": "^3.3.1",
65
+ "@eslint/js": "^9.35.0",
66
+ "@react-native/babel-preset": "0.83.0",
67
+ "@react-native/eslint-config": "0.83.0",
68
+ "@release-it/conventional-changelog": "^10.0.1",
69
+ "@types/jest": "^29.5.14",
70
+ "@types/react": "^19.2.0",
71
+ "commitlint": "^19.8.1",
72
+ "del-cli": "^6.0.0",
73
+ "eslint": "^9.35.0",
74
+ "eslint-config-prettier": "^10.1.8",
75
+ "eslint-plugin-prettier": "^5.5.4",
76
+ "jest": "^29.7.0",
77
+ "lefthook": "^2.0.3",
78
+ "prettier": "^2.8.8",
79
+ "react": "19.2.0",
80
+ "react-native": "0.83.0",
81
+ "react-native-builder-bob": "^0.40.17",
82
+ "release-it": "^19.0.4",
83
+ "turbo": "^2.5.6",
84
+ "typescript": "^5.9.2"
85
+ },
86
+ "peerDependencies": {
87
+ "react": "*",
88
+ "react-native": "*"
89
+ },
90
+ "workspaces": [
91
+ "example"
92
+ ],
93
+ "packageManager": "yarn@4.11.0",
94
+ "react-native-builder-bob": {
95
+ "source": "src",
96
+ "output": "lib",
97
+ "targets": [
98
+ [
99
+ "module",
100
+ {
101
+ "esm": true
102
+ }
103
+ ],
104
+ [
105
+ "typescript",
106
+ {
107
+ "project": "tsconfig.build.json"
108
+ }
109
+ ]
110
+ ]
111
+ },
112
+ "codegenConfig": {
113
+ "name": "ControlledInputViewSpec",
114
+ "type": "all",
115
+ "jsSrcsDir": "src",
116
+ "android": {
117
+ "javaPackageName": "com.controlledinput"
118
+ },
119
+ "ios": {
120
+ "componentProvider": {
121
+ "ControlledInputView": "ControlledInputView"
122
+ }
123
+ }
124
+ },
125
+ "prettier": {
126
+ "quoteProps": "consistent",
127
+ "singleQuote": true,
128
+ "tabWidth": 2,
129
+ "trailingComma": "es5",
130
+ "useTabs": false
131
+ },
132
+ "jest": {
133
+ "preset": "react-native",
134
+ "modulePathIgnorePatterns": [
135
+ "<rootDir>/example/node_modules",
136
+ "<rootDir>/lib/"
137
+ ]
138
+ },
139
+ "commitlint": {
140
+ "extends": [
141
+ "@commitlint/config-conventional"
142
+ ]
143
+ },
144
+ "release-it": {
145
+ "git": {
146
+ "commitMessage": "chore: release ${version}",
147
+ "tagName": "v${version}"
148
+ },
149
+ "npm": {
150
+ "publish": true
151
+ },
152
+ "github": {
153
+ "release": true
154
+ },
155
+ "plugins": {
156
+ "@release-it/conventional-changelog": {
157
+ "preset": {
158
+ "name": "angular"
159
+ }
160
+ }
161
+ }
162
+ },
163
+ "create-react-native-library": {
164
+ "type": "fabric-view",
165
+ "languages": "kotlin-objc",
166
+ "tools": [
167
+ "eslint",
168
+ "jest",
169
+ "lefthook",
170
+ "release-it"
171
+ ],
172
+ "version": "0.56.1"
173
+ }
174
+ }
@@ -0,0 +1,55 @@
1
+ import {
2
+ codegenNativeComponent,
3
+ type ViewProps,
4
+ type ColorValue,
5
+ type HostComponent,
6
+ } from 'react-native';
7
+ import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
8
+ import type {
9
+ BubblingEventHandler,
10
+ Double,
11
+ } from 'react-native/Libraries/Types/CodegenTypes';
12
+
13
+ interface TextChangeEvent {
14
+ value: string;
15
+ }
16
+
17
+ interface FocusEvent {
18
+ // Empty event
19
+ }
20
+
21
+ interface BlurEvent {
22
+ // Empty event
23
+ }
24
+
25
+ interface InputStyle {
26
+ color?: ColorValue;
27
+ fontSize?: Double;
28
+ height?: Double;
29
+ paddingTop?: Double;
30
+ paddingBottom?: Double;
31
+ paddingLeft?: Double;
32
+ paddingRight?: Double;
33
+ borderWidth?: Double;
34
+ borderRadius?: Double;
35
+ borderColor?: ColorValue;
36
+ }
37
+
38
+ export interface NativeProps extends ViewProps {
39
+ value?: string;
40
+ inputStyle?: InputStyle;
41
+ onTextChange?: BubblingEventHandler<Readonly<TextChangeEvent>>;
42
+ onFocus?: BubblingEventHandler<Readonly<FocusEvent>>;
43
+ onBlur?: BubblingEventHandler<Readonly<BlurEvent>>;
44
+ }
45
+
46
+ export interface NativeCommands {
47
+ focus: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
48
+ blur: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
49
+ }
50
+
51
+ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
52
+ supportedCommands: ['focus', 'blur'],
53
+ });
54
+
55
+ export default codegenNativeComponent<NativeProps>('ControlledInputView');
package/src/index.tsx ADDED
@@ -0,0 +1,59 @@
1
+ import {
2
+ forwardRef,
3
+ useImperativeHandle,
4
+ useRef,
5
+ type ComponentProps,
6
+ type ElementRef,
7
+ } from 'react';
8
+ import { Platform } from 'react-native';
9
+ import ControlledInputViewNativeComponent, {
10
+ Commands,
11
+ } from './ControlledInputViewNativeComponent';
12
+
13
+ type NativeProps = ComponentProps<typeof ControlledInputViewNativeComponent>;
14
+
15
+ export interface ControlledInputViewRef {
16
+ blur: () => void;
17
+ focus: () => void;
18
+ }
19
+
20
+ export const ControlledInputView = forwardRef<
21
+ ControlledInputViewRef,
22
+ NativeProps
23
+ >((props, ref) => {
24
+ const nativeRef =
25
+ useRef<ElementRef<typeof ControlledInputViewNativeComponent>>(null);
26
+
27
+ useImperativeHandle(ref, () => ({
28
+ blur: () => {
29
+ if (!nativeRef.current) {
30
+ return;
31
+ }
32
+
33
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
34
+ console.log(
35
+ `[ControlledInputView] ${Platform.OS} blur command -> native`
36
+ );
37
+ Commands.blur(nativeRef.current);
38
+ }
39
+ },
40
+ focus: () => {
41
+ if (!nativeRef.current) {
42
+ return;
43
+ }
44
+
45
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
46
+ console.log(
47
+ `[ControlledInputView] ${Platform.OS} focus command -> native`
48
+ );
49
+ Commands.focus(nativeRef.current);
50
+ }
51
+ },
52
+ }));
53
+
54
+ return (
55
+ <ControlledInputViewNativeComponent {...props} ref={nativeRef as any} />
56
+ );
57
+ });
58
+
59
+ export * from './ControlledInputViewNativeComponent';
@@ -0,0 +1,21 @@
1
+ declare module 'react-native/Libraries/Types/CodegenTypes' {
2
+ export type Double = number;
3
+ export type Float = number;
4
+ export type Int32 = number;
5
+ export type UnsafeObject = object;
6
+
7
+ export interface BubblingEventHandler<T> {
8
+ (event: { nativeEvent: T }): void;
9
+ }
10
+
11
+ export interface DirectEventHandler<T> {
12
+ (event: { nativeEvent: T }): void;
13
+ }
14
+ }
15
+
16
+ declare module 'react-native/Libraries/Utilities/codegenNativeComponent' {
17
+ import type { HostComponent } from 'react-native';
18
+ export default function codegenNativeComponent<T>(
19
+ componentName: string
20
+ ): HostComponent<T>;
21
+ }