react-native-transformer-text-input 0.1.0-alpha.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/LICENSE +20 -0
- package/README.md +141 -0
- package/RNTransformerTextInput.podspec +35 -0
- package/android/build.gradle +96 -0
- package/android/gradle.properties +5 -0
- package/android/spotless.gradle +19 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/appandflow/transformertextinput/TextState.kt +15 -0
- package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputDecoratorView.kt +160 -0
- package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputDecoratorViewManager.kt +44 -0
- package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputJni.kt +25 -0
- package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputModule.kt +22 -0
- package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputPackage.kt +53 -0
- package/android/src/main/jni/CMakeLists.txt +62 -0
- package/android/src/main/jni/TransformerTextInputJni.cpp +94 -0
- package/android/src/main/jni/rntti.h +17 -0
- package/cpp/TransformerTextInputDecoratorViewComponentDescriptor.h +16 -0
- package/cpp/TransformerTextInputDecoratorViewShadowNode.cpp +21 -0
- package/cpp/TransformerTextInputDecoratorViewShadowNode.h +40 -0
- package/cpp/TransformerTextInputRuntime.cpp +86 -0
- package/cpp/TransformerTextInputRuntime.h +31 -0
- package/ios/TransformerTextInputDecoratorView.h +9 -0
- package/ios/TransformerTextInputDecoratorView.mm +256 -0
- package/ios/TransformerTextInputModule.h +8 -0
- package/ios/TransformerTextInputModule.mm +28 -0
- package/lib/module/NativeTransformerTextInputModule.js +5 -0
- package/lib/module/NativeTransformerTextInputModule.js.map +1 -0
- package/lib/module/Transformer.js +15 -0
- package/lib/module/Transformer.js.map +1 -0
- package/lib/module/TransformerTextInput.js +86 -0
- package/lib/module/TransformerTextInput.js.map +1 -0
- package/lib/module/TransformerTextInputDecoratorViewNativeComponent.ts +31 -0
- package/lib/module/formatters/phone-number.js +315 -0
- package/lib/module/formatters/phone-number.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/registry.js +83 -0
- package/lib/module/registry.js.map +1 -0
- package/lib/module/selection.js +48 -0
- package/lib/module/selection.js.map +1 -0
- package/lib/module/utils/useMergeRefs.js +49 -0
- package/lib/module/utils/useMergeRefs.js.map +1 -0
- package/lib/module/utils/useRefEffect.js +37 -0
- package/lib/module/utils/useRefEffect.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeTransformerTextInputModule.d.ts +7 -0
- package/lib/typescript/src/NativeTransformerTextInputModule.d.ts.map +1 -0
- package/lib/typescript/src/Transformer.d.ts +19 -0
- package/lib/typescript/src/Transformer.d.ts.map +1 -0
- package/lib/typescript/src/TransformerTextInput.d.ts +247 -0
- package/lib/typescript/src/TransformerTextInput.d.ts.map +1 -0
- package/lib/typescript/src/TransformerTextInputDecoratorViewNativeComponent.d.ts +12 -0
- package/lib/typescript/src/TransformerTextInputDecoratorViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/formatters/phone-number.d.ts +18 -0
- package/lib/typescript/src/formatters/phone-number.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/registry.d.ts +17 -0
- package/lib/typescript/src/registry.d.ts.map +1 -0
- package/lib/typescript/src/selection.d.ts +4 -0
- package/lib/typescript/src/selection.d.ts.map +1 -0
- package/lib/typescript/src/utils/useMergeRefs.d.ts +20 -0
- package/lib/typescript/src/utils/useMergeRefs.d.ts.map +1 -0
- package/lib/typescript/src/utils/useRefEffect.d.ts +24 -0
- package/lib/typescript/src/utils/useRefEffect.d.ts.map +1 -0
- package/package.json +199 -0
- package/react-native.config.js +13 -0
- package/src/NativeTransformerTextInputModule.ts +10 -0
- package/src/Transformer.ts +32 -0
- package/src/TransformerTextInput.tsx +147 -0
- package/src/TransformerTextInputDecoratorViewNativeComponent.ts +31 -0
- package/src/formatters/phone-number.ts +327 -0
- package/src/index.tsx +10 -0
- package/src/registry.ts +120 -0
- package/src/selection.ts +62 -0
- package/src/utils/useMergeRefs.ts +59 -0
- package/src/utils/useRefEffect.ts +42 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.13)
|
|
2
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
3
|
+
|
|
4
|
+
set(LIB_LITERAL rntti)
|
|
5
|
+
set(LIB_TARGET_NAME react_codegen_${LIB_LITERAL})
|
|
6
|
+
|
|
7
|
+
set(LIB_ANDROID_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
|
|
8
|
+
set(LIB_COMMON_DIR ${LIB_ANDROID_DIR}/../cpp)
|
|
9
|
+
set(LIB_ANDROID_GENERATED_JNI_DIR ${LIB_ANDROID_DIR}/build/generated/source/codegen/jni)
|
|
10
|
+
set(LIB_ANDROID_GENERATED_COMPONENTS_DIR ${LIB_ANDROID_GENERATED_JNI_DIR}/react/renderer/components/${LIB_LITERAL})
|
|
11
|
+
set(REACT_NATIVE_WORKLETS_DIR ${LIB_ANDROID_DIR}/../node_modules/react-native-worklets)
|
|
12
|
+
|
|
13
|
+
file(GLOB LIB_CUSTOM_SRCS CONFIGURE_DEPENDS *.cpp ${LIB_COMMON_DIR}/*.cpp)
|
|
14
|
+
file(GLOB LIB_CODEGEN_SRCS CONFIGURE_DEPENDS ${LIB_ANDROID_GENERATED_JNI_DIR}/*.cpp ${LIB_ANDROID_GENERATED_COMPONENTS_DIR}/*.cpp)
|
|
15
|
+
|
|
16
|
+
add_library(
|
|
17
|
+
${LIB_TARGET_NAME}
|
|
18
|
+
SHARED
|
|
19
|
+
${LIB_CUSTOM_SRCS}
|
|
20
|
+
${LIB_CODEGEN_SRCS}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
target_include_directories(
|
|
24
|
+
${LIB_TARGET_NAME}
|
|
25
|
+
PUBLIC
|
|
26
|
+
.
|
|
27
|
+
${LIB_COMMON_DIR}
|
|
28
|
+
${LIB_ANDROID_GENERATED_JNI_DIR}
|
|
29
|
+
${LIB_ANDROID_GENERATED_COMPONENTS_DIR}
|
|
30
|
+
"${REACT_COMMON_DIR}"
|
|
31
|
+
"${REACT_COMMON_DIR}/jsiexecutor"
|
|
32
|
+
"${REACT_NATIVE_WORKLETS_DIR}/Common/cpp"
|
|
33
|
+
"${REACT_NATIVE_WORKLETS_DIR}/android/src/main/cpp"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
message(STATUS "ANDROID_NDK_BUILD_TYPE = ${ANDROID_NDK_BUILD_TYPE}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
add_library(worklets SHARED IMPORTED)
|
|
40
|
+
|
|
41
|
+
set_target_properties(
|
|
42
|
+
worklets
|
|
43
|
+
PROPERTIES
|
|
44
|
+
IMPORTED_LOCATION
|
|
45
|
+
"${REACT_NATIVE_WORKLETS_DIR}/android/build/intermediates/cmake/debug/obj/${ANDROID_ABI}/libworklets.so"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
target_link_libraries(
|
|
49
|
+
${LIB_TARGET_NAME}
|
|
50
|
+
fbjni
|
|
51
|
+
jsi
|
|
52
|
+
reactnative
|
|
53
|
+
worklets
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
target_include_directories(
|
|
57
|
+
${CMAKE_PROJECT_NAME}
|
|
58
|
+
PUBLIC
|
|
59
|
+
${CMAKE_CURRENT_SOURCE_DIR}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
target_compile_reactnative_options(${LIB_TARGET_NAME} PUBLIC)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#include <fbjni/fbjni.h>
|
|
2
|
+
#include <jsi/jsi.h>
|
|
3
|
+
#include <worklets/NativeModules/WorkletsModuleProxy.h>
|
|
4
|
+
#include <worklets/android/WorkletsModule.h>
|
|
5
|
+
|
|
6
|
+
#include "TransformerTextInputRuntime.h"
|
|
7
|
+
|
|
8
|
+
#include <memory>
|
|
9
|
+
|
|
10
|
+
using namespace facebook;
|
|
11
|
+
|
|
12
|
+
namespace rntti {
|
|
13
|
+
|
|
14
|
+
struct JTextSelection : public jni::JavaClass<JTextSelection> {
|
|
15
|
+
static auto constexpr kJavaDescriptor =
|
|
16
|
+
"Lcom/appandflow/transformertextinput/TextSelection;";
|
|
17
|
+
|
|
18
|
+
static jni::local_ref<JTextSelection> create(int start, int end) {
|
|
19
|
+
return newInstance(start, end);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
struct JTextState : public jni::JavaClass<JTextState> {
|
|
24
|
+
static auto constexpr kJavaDescriptor =
|
|
25
|
+
"Lcom/appandflow/transformertextinput/TextState;";
|
|
26
|
+
|
|
27
|
+
static jni::local_ref<JTextState> create(
|
|
28
|
+
std::string const &value,
|
|
29
|
+
jni::local_ref<JTextSelection> selection) {
|
|
30
|
+
return newInstance(value, selection);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
class JTransformerTextInputJni
|
|
35
|
+
: public jni::HybridClass<JTransformerTextInputJni> {
|
|
36
|
+
public:
|
|
37
|
+
static auto constexpr kJavaDescriptor =
|
|
38
|
+
"Lcom/appandflow/transformertextinput/TransformerTextInputJni;";
|
|
39
|
+
|
|
40
|
+
static void setWorkletsModule(
|
|
41
|
+
jni::alias_ref<jni::JClass> jClazz,
|
|
42
|
+
jni::alias_ref<worklets::WorkletsModule::javaobject> module) {
|
|
43
|
+
if (!module) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
auto proxy = module->cthis()->getWorkletsModuleProxy();
|
|
47
|
+
if (!proxy) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
auto uiRuntime = proxy->getUIWorkletRuntime();
|
|
51
|
+
if (!uiRuntime) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
rntti::SetUIWorkletRuntime(uiRuntime);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static jni::local_ref<JTextState> transform(
|
|
58
|
+
jni::alias_ref<jni::JClass> jClazz,
|
|
59
|
+
jint transformerId,
|
|
60
|
+
jni::alias_ref<jni::JString> value,
|
|
61
|
+
jint selectionStart,
|
|
62
|
+
jint selectionEnd) {
|
|
63
|
+
auto transformer = rntti::LookupTransformer(transformerId);
|
|
64
|
+
if (!transformer) {
|
|
65
|
+
return nullptr;
|
|
66
|
+
}
|
|
67
|
+
const auto currentValue = value->toStdString();
|
|
68
|
+
rntti::SelectionRange selection{
|
|
69
|
+
static_cast<int>(selectionStart), static_cast<int>(selectionEnd)};
|
|
70
|
+
auto result = rntti::RunTransformer(transformer, currentValue, selection);
|
|
71
|
+
if (!result) {
|
|
72
|
+
return nullptr;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return JTextState::create(
|
|
76
|
+
result->value,
|
|
77
|
+
JTextSelection::create(result->selection.start, result->selection.end));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static void registerNatives() {
|
|
81
|
+
registerHybrid({
|
|
82
|
+
makeNativeMethod(
|
|
83
|
+
"setWorkletsModule", JTransformerTextInputJni::setWorkletsModule),
|
|
84
|
+
makeNativeMethod("transform", JTransformerTextInputJni::transform),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
} // namespace rntti
|
|
90
|
+
|
|
91
|
+
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
|
|
92
|
+
return facebook::jni::initialize(
|
|
93
|
+
vm, [] { rntti::JTransformerTextInputJni::registerNatives(); });
|
|
94
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <ReactCommon/JavaTurboModule.h>
|
|
4
|
+
#include <ReactCommon/TurboModule.h>
|
|
5
|
+
#include <jsi/jsi.h>
|
|
6
|
+
#include "TransformerTextInputDecoratorViewComponentDescriptor.h"
|
|
7
|
+
|
|
8
|
+
namespace facebook {
|
|
9
|
+
namespace react {
|
|
10
|
+
|
|
11
|
+
JSI_EXPORT
|
|
12
|
+
std::shared_ptr<TurboModule> rntti_ModuleProvider(
|
|
13
|
+
const std::string &moduleName,
|
|
14
|
+
const JavaTurboModule::InitParams ¶ms);
|
|
15
|
+
|
|
16
|
+
} // namespace react
|
|
17
|
+
} // namespace facebook
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
|
4
|
+
|
|
5
|
+
#include "TransformerTextInputDecoratorViewShadowNode.h"
|
|
6
|
+
|
|
7
|
+
namespace facebook::react {
|
|
8
|
+
|
|
9
|
+
class TransformerTextInputDecoratorViewComponentDescriptor final
|
|
10
|
+
: public ConcreteComponentDescriptor<
|
|
11
|
+
TransformerTextInputDecoratorViewShadowNode> {
|
|
12
|
+
public:
|
|
13
|
+
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#include "TransformerTextInputDecoratorViewShadowNode.h"
|
|
2
|
+
|
|
3
|
+
namespace facebook::react {
|
|
4
|
+
|
|
5
|
+
const char TransformerTextInputDecoratorViewComponentName[] =
|
|
6
|
+
"TransformerTextInputDecoratorView";
|
|
7
|
+
|
|
8
|
+
void TransformerTextInputDecoratorViewShadowNode::initialize() {
|
|
9
|
+
// Setting display: contents style results in ForceFlattenView trait being set
|
|
10
|
+
// on the shadow node. This trait causes the node not to have a host view. By
|
|
11
|
+
// removing the trait, it's possible to force RN to create a host view, layout
|
|
12
|
+
// of which can then be customized.
|
|
13
|
+
ShadowNode::traits_.unset(ShadowNodeTraits::ForceFlattenView);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
void TransformerTextInputDecoratorViewShadowNode::layout(
|
|
17
|
+
LayoutContext layoutContext) {
|
|
18
|
+
ConcreteViewShadowNode::layout(layoutContext);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <react/renderer/components/rntti/EventEmitters.h>
|
|
4
|
+
#include <react/renderer/components/rntti/Props.h>
|
|
5
|
+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
6
|
+
#include <react/renderer/core/LayoutContext.h>
|
|
7
|
+
#include <react/renderer/core/ShadowNodeFamily.h>
|
|
8
|
+
|
|
9
|
+
namespace facebook::react {
|
|
10
|
+
|
|
11
|
+
extern const char TransformerTextInputDecoratorViewComponentName[];
|
|
12
|
+
|
|
13
|
+
class TransformerTextInputDecoratorViewShadowNode final
|
|
14
|
+
: public ConcreteViewShadowNode<
|
|
15
|
+
TransformerTextInputDecoratorViewComponentName,
|
|
16
|
+
TransformerTextInputDecoratorViewProps,
|
|
17
|
+
TransformerTextInputDecoratorViewEventEmitter> {
|
|
18
|
+
public:
|
|
19
|
+
TransformerTextInputDecoratorViewShadowNode(
|
|
20
|
+
ShadowNodeFragment const &fragment,
|
|
21
|
+
ShadowNodeFamily::Shared const &family,
|
|
22
|
+
ShadowNodeTraits traits)
|
|
23
|
+
: ConcreteViewShadowNode(fragment, family, traits) {
|
|
24
|
+
initialize();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
TransformerTextInputDecoratorViewShadowNode(
|
|
28
|
+
ShadowNode const &sourceShadowNode,
|
|
29
|
+
ShadowNodeFragment const &fragment)
|
|
30
|
+
: ConcreteViewShadowNode(sourceShadowNode, fragment) {
|
|
31
|
+
initialize();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void layout(LayoutContext layoutContext) override;
|
|
35
|
+
|
|
36
|
+
private:
|
|
37
|
+
void initialize();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#include "TransformerTextInputRuntime.h"
|
|
2
|
+
|
|
3
|
+
namespace rntti {
|
|
4
|
+
|
|
5
|
+
namespace {
|
|
6
|
+
std::shared_ptr<worklets::WorkletRuntime> gUiRuntime;
|
|
7
|
+
} // namespace
|
|
8
|
+
|
|
9
|
+
void SetUIWorkletRuntime(std::shared_ptr<worklets::WorkletRuntime> runtime) {
|
|
10
|
+
if (!runtime) {
|
|
11
|
+
gUiRuntime.reset();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
runtime->schedule([runtime]() { gUiRuntime = runtime; });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
std::optional<jsi::WeakObject> LookupTransformer(int transformerId) {
|
|
18
|
+
auto uiRuntime = gUiRuntime;
|
|
19
|
+
if (!uiRuntime) {
|
|
20
|
+
return std::nullopt;
|
|
21
|
+
}
|
|
22
|
+
auto &runtime = uiRuntime->getJSIRuntime();
|
|
23
|
+
auto transformerRegistry = runtime.global().getPropertyAsObject(
|
|
24
|
+
runtime, "__rntti_registerTransformerRegistry");
|
|
25
|
+
auto transformerRegistryGet =
|
|
26
|
+
transformerRegistry.getPropertyAsFunction(runtime, "get");
|
|
27
|
+
auto transformerValue =
|
|
28
|
+
transformerRegistryGet.call(runtime, jsi::Value(transformerId));
|
|
29
|
+
|
|
30
|
+
if (transformerValue.isNull() || transformerValue.isUndefined() ||
|
|
31
|
+
!transformerValue.isObject()) {
|
|
32
|
+
return std::nullopt;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return jsi::WeakObject(runtime, transformerValue.asObject(runtime));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
std::optional<TransformResult> RunTransformer(
|
|
39
|
+
const std::optional<jsi::WeakObject> &transformer,
|
|
40
|
+
const std::string &value,
|
|
41
|
+
SelectionRange selection) {
|
|
42
|
+
if (!transformer) {
|
|
43
|
+
return std::nullopt;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
auto uiRuntime = gUiRuntime;
|
|
47
|
+
if (!uiRuntime) {
|
|
48
|
+
return std::nullopt;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
auto &jsiRuntime = uiRuntime->getJSIRuntime();
|
|
52
|
+
|
|
53
|
+
auto transformerValue = transformer->lock(jsiRuntime);
|
|
54
|
+
if (transformerValue.isUndefined()) {
|
|
55
|
+
return std::nullopt;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
auto transformerFunction =
|
|
59
|
+
transformerValue.asObject(jsiRuntime).asFunction(jsiRuntime);
|
|
60
|
+
auto resultValue = uiRuntime->runSync(
|
|
61
|
+
transformerFunction,
|
|
62
|
+
jsi::String::createFromUtf8(jsiRuntime, value),
|
|
63
|
+
jsi::Value(selection.start),
|
|
64
|
+
jsi::Value(selection.end));
|
|
65
|
+
|
|
66
|
+
TransformResult result;
|
|
67
|
+
auto resultObject = resultValue.asObject(jsiRuntime);
|
|
68
|
+
auto valueProp = resultObject.getProperty(jsiRuntime, "value");
|
|
69
|
+
if (valueProp.isString()) {
|
|
70
|
+
result.value = valueProp.asString(jsiRuntime).utf8(jsiRuntime);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
auto selectionProp = resultObject.getProperty(jsiRuntime, "selection");
|
|
74
|
+
if (selectionProp.isObject()) {
|
|
75
|
+
auto selectionObject = selectionProp.asObject(jsiRuntime);
|
|
76
|
+
auto startProp = selectionObject.getProperty(jsiRuntime, "start");
|
|
77
|
+
auto endProp = selectionObject.getProperty(jsiRuntime, "end");
|
|
78
|
+
result.selection = SelectionRange{
|
|
79
|
+
static_cast<int>(startProp.asNumber()),
|
|
80
|
+
static_cast<int>(endProp.asNumber())};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
} // namespace rntti
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <jsi/jsi.h>
|
|
4
|
+
#include <worklets/WorkletRuntime/WorkletRuntime.h>
|
|
5
|
+
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <optional>
|
|
8
|
+
#include <string>
|
|
9
|
+
|
|
10
|
+
namespace rntti {
|
|
11
|
+
|
|
12
|
+
struct SelectionRange {
|
|
13
|
+
int start{0};
|
|
14
|
+
int end{0};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
struct TransformResult {
|
|
18
|
+
std::string value;
|
|
19
|
+
SelectionRange selection;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
void SetUIWorkletRuntime(std::shared_ptr<worklets::WorkletRuntime> runtime);
|
|
23
|
+
|
|
24
|
+
std::optional<jsi::WeakObject> LookupTransformer(int transformerId);
|
|
25
|
+
|
|
26
|
+
std::optional<TransformResult> RunTransformer(
|
|
27
|
+
const std::optional<jsi::WeakObject> &transformer,
|
|
28
|
+
const std::string &value,
|
|
29
|
+
SelectionRange selection);
|
|
30
|
+
|
|
31
|
+
} // namespace rntti
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#import "TransformerTextInputDecoratorView.h"
|
|
2
|
+
|
|
3
|
+
#import <React/RCTBackedTextInputDelegate.h>
|
|
4
|
+
#import <React/RCTBackedTextInputViewProtocol.h>
|
|
5
|
+
#import <React/RCTConversions.h>
|
|
6
|
+
#import <React/RCTTextInputComponentView.h>
|
|
7
|
+
#import <react/renderer/components/rntti/EventEmitters.h>
|
|
8
|
+
#import <react/renderer/components/rntti/Props.h>
|
|
9
|
+
#import <react/renderer/components/rntti/RCTComponentViewHelpers.h>
|
|
10
|
+
#import "TransformerTextInputDecoratorViewComponentDescriptor.h"
|
|
11
|
+
#import "TransformerTextInputRuntime.h"
|
|
12
|
+
|
|
13
|
+
using namespace facebook::react;
|
|
14
|
+
|
|
15
|
+
struct RNTTITextState {
|
|
16
|
+
NSString *value;
|
|
17
|
+
NSRange selection;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
@interface TransformerTextInputDecoratorView () <
|
|
21
|
+
RCTTransformerTextInputDecoratorViewViewProtocol,
|
|
22
|
+
RCTBackedTextInputDelegate>
|
|
23
|
+
|
|
24
|
+
@end
|
|
25
|
+
|
|
26
|
+
@implementation TransformerTextInputDecoratorView {
|
|
27
|
+
std::optional<jsi::WeakObject> _transformer;
|
|
28
|
+
bool _observersAdded;
|
|
29
|
+
__weak id<RCTBackedTextInputDelegate> _baseDelegate;
|
|
30
|
+
__weak UIView<RCTBackedTextInputViewProtocol> *_backedTextInput;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
34
|
+
{
|
|
35
|
+
return concreteComponentDescriptorProvider<TransformerTextInputDecoratorViewComponentDescriptor>();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
39
|
+
{
|
|
40
|
+
if (self = [super initWithFrame:frame]) {
|
|
41
|
+
_props = TransformerTextInputDecoratorViewShadowNode::defaultSharedProps();
|
|
42
|
+
_observersAdded = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return self;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
|
|
49
|
+
{
|
|
50
|
+
const auto &oldViewProps = *std::static_pointer_cast<TransformerTextInputDecoratorViewProps const>(_props);
|
|
51
|
+
const auto &newViewProps = *std::static_pointer_cast<TransformerTextInputDecoratorViewProps const>(props);
|
|
52
|
+
|
|
53
|
+
if (oldViewProps.transformerId != newViewProps.transformerId) {
|
|
54
|
+
_transformer = rntti::LookupTransformer(newViewProps.transformerId);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[super updateProps:props oldProps:oldProps];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
- (void)applyValue:(NSString *)newValue
|
|
61
|
+
{
|
|
62
|
+
NSMutableAttributedString *newAttributedText =
|
|
63
|
+
[[NSMutableAttributedString alloc] initWithAttributedString:_backedTextInput.attributedText];
|
|
64
|
+
|
|
65
|
+
[newAttributedText replaceCharactersInRange:NSMakeRange(0, newAttributedText.length) withString:newValue];
|
|
66
|
+
_backedTextInput.attributedText = newAttributedText;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
- (NSRange)currentSelection
|
|
70
|
+
{
|
|
71
|
+
UITextRange *selectedTextRange = _backedTextInput.selectedTextRange;
|
|
72
|
+
if (!selectedTextRange) {
|
|
73
|
+
return NSMakeRange(0, 0);
|
|
74
|
+
}
|
|
75
|
+
NSInteger start = [_backedTextInput offsetFromPosition:_backedTextInput.beginningOfDocument
|
|
76
|
+
toPosition:selectedTextRange.start];
|
|
77
|
+
NSInteger end = [_backedTextInput offsetFromPosition:_backedTextInput.beginningOfDocument
|
|
78
|
+
toPosition:selectedTextRange.end];
|
|
79
|
+
return NSMakeRange(start, end - start);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
- (NSString *)currentValue
|
|
83
|
+
{
|
|
84
|
+
return _backedTextInput.attributedText.string ?: @"";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (void)applySelection:(NSRange)selection
|
|
88
|
+
{
|
|
89
|
+
UITextPosition *startPosition = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument
|
|
90
|
+
offset:selection.location];
|
|
91
|
+
UITextPosition *endPosition = [_backedTextInput positionFromPosition:_backedTextInput.beginningOfDocument
|
|
92
|
+
offset:selection.location + selection.length];
|
|
93
|
+
if (!startPosition || !endPosition) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
UITextRange *range = [_backedTextInput textRangeFromPosition:startPosition toPosition:endPosition];
|
|
97
|
+
if (range) {
|
|
98
|
+
[_backedTextInput setSelectedTextRange:range notifyDelegate:NO];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
- (RNTTITextState)transformTextState:(RNTTITextState)state
|
|
103
|
+
{
|
|
104
|
+
if (!_transformer) {
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
rntti::SelectionRange selectionRange{
|
|
109
|
+
static_cast<int>(state.selection.location), static_cast<int>(state.selection.location + state.selection.length)};
|
|
110
|
+
auto transformResult = rntti::RunTransformer(_transformer, RCTStringFromNSString(state.value), selectionRange);
|
|
111
|
+
if (!transformResult) {
|
|
112
|
+
return state;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
NSString *value = RCTNSStringFromString(transformResult->value);
|
|
116
|
+
NSRange selection =
|
|
117
|
+
NSMakeRange(transformResult->selection.start, transformResult->selection.end - transformResult->selection.start);
|
|
118
|
+
|
|
119
|
+
return RNTTITextState{value, selection};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
- (void)didAddSubview:(UIView *)subview
|
|
123
|
+
{
|
|
124
|
+
[super didAddSubview:subview];
|
|
125
|
+
|
|
126
|
+
[self addTextInputObservers];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
- (void)willRemoveSubview:(UIView *)subview
|
|
130
|
+
{
|
|
131
|
+
[super willRemoveSubview:subview];
|
|
132
|
+
|
|
133
|
+
[self removeTextInputObservers];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
- (void)addTextInputObservers
|
|
137
|
+
{
|
|
138
|
+
react_native_assert(
|
|
139
|
+
!_observersAdded &&
|
|
140
|
+
"MarkdownTextInputDecoratorComponentView tried to add TextInput observers while they were attached");
|
|
141
|
+
react_native_assert(
|
|
142
|
+
self.subviews.count > 0 && "MarkdownTextInputDecoratorComponentView is mounted without any children");
|
|
143
|
+
UIView *childView = self.subviews[0];
|
|
144
|
+
react_native_assert(
|
|
145
|
+
[childView isKindOfClass:[RCTTextInputComponentView class]] &&
|
|
146
|
+
"Child component of MarkdownTextInputDecoratorComponentView is not an instance of RCTTextInputComponentView.");
|
|
147
|
+
RCTTextInputComponentView *textInputComponentView = (RCTTextInputComponentView *)childView;
|
|
148
|
+
UIView<RCTBackedTextInputViewProtocol> *backedTextInputView =
|
|
149
|
+
[textInputComponentView valueForKey:@"_backedTextInputView"];
|
|
150
|
+
|
|
151
|
+
_backedTextInput = backedTextInputView;
|
|
152
|
+
_baseDelegate = backedTextInputView.textInputDelegate;
|
|
153
|
+
backedTextInputView.textInputDelegate = self;
|
|
154
|
+
|
|
155
|
+
_observersAdded = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
- (void)removeTextInputObservers
|
|
159
|
+
{
|
|
160
|
+
_backedTextInput = nil;
|
|
161
|
+
_baseDelegate = nil;
|
|
162
|
+
_observersAdded = false;
|
|
163
|
+
_transformer = std::nullopt;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
- (void)textInputDidBeginEditing
|
|
167
|
+
{
|
|
168
|
+
[_baseDelegate textInputDidBeginEditing];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
- (void)textInputDidChange
|
|
172
|
+
{
|
|
173
|
+
// Current values
|
|
174
|
+
NSString *currentValue = [self currentValue];
|
|
175
|
+
NSRange currentSelection = [self currentSelection];
|
|
176
|
+
RNTTITextState current{currentValue, currentSelection};
|
|
177
|
+
RNTTITextState next = [self transformTextState:current];
|
|
178
|
+
bool didTransformValue = ![next.value isEqualToString:current.value];
|
|
179
|
+
if (didTransformValue) {
|
|
180
|
+
[self applyValue:next.value];
|
|
181
|
+
}
|
|
182
|
+
if (didTransformValue || !NSEqualRanges(next.selection, current.selection)) {
|
|
183
|
+
[self applySelection:next.selection];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
[_baseDelegate textInputDidChange];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
- (void)textInputDidChangeSelection
|
|
190
|
+
{
|
|
191
|
+
[_baseDelegate textInputDidChangeSelection];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
- (void)textInputDidEndEditing
|
|
195
|
+
{
|
|
196
|
+
[_baseDelegate textInputDidEndEditing];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
- (void)textInputDidReturn
|
|
200
|
+
{
|
|
201
|
+
[_baseDelegate textInputDidReturn];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
- (BOOL)textInputShouldBeginEditing
|
|
205
|
+
{
|
|
206
|
+
return [_baseDelegate textInputShouldBeginEditing];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
- (nonnull NSString *)textInputShouldChangeText:(nonnull NSString *)text inRange:(NSRange)range
|
|
210
|
+
{
|
|
211
|
+
return [_baseDelegate textInputShouldChangeText:text inRange:range];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
- (BOOL)textInputShouldEndEditing
|
|
215
|
+
{
|
|
216
|
+
return [_baseDelegate textInputShouldEndEditing];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
- (BOOL)textInputShouldReturn
|
|
220
|
+
{
|
|
221
|
+
return [_baseDelegate textInputShouldReturn];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
- (BOOL)textInputShouldSubmitOnReturn
|
|
225
|
+
{
|
|
226
|
+
return [_baseDelegate textInputShouldSubmitOnReturn];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
|
|
230
|
+
{
|
|
231
|
+
RCTTransformerTextInputDecoratorViewHandleCommand(self, commandName, args);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
- (void)update:(BOOL)transform
|
|
235
|
+
value:(NSString *)value
|
|
236
|
+
selectionStart:(NSInteger)selectionStart
|
|
237
|
+
selectionEnd:(NSInteger)selectionEnd
|
|
238
|
+
{
|
|
239
|
+
NSString *currentValue = [self currentValue];
|
|
240
|
+
NSRange currentSelection = [self currentSelection];
|
|
241
|
+
NSString *providedValue = value ?: currentValue;
|
|
242
|
+
NSRange providedSelection = NSMakeRange(selectionStart, selectionEnd - selectionStart);
|
|
243
|
+
RNTTITextState provided{providedValue, providedSelection};
|
|
244
|
+
RNTTITextState next = transform ? [self transformTextState:provided] : provided;
|
|
245
|
+
bool didTransformValue = ![next.value isEqualToString:currentValue];
|
|
246
|
+
if (didTransformValue) {
|
|
247
|
+
[self applyValue:next.value];
|
|
248
|
+
}
|
|
249
|
+
if (didTransformValue || !NSEqualRanges(next.selection, currentSelection)) {
|
|
250
|
+
[self applySelection:next.selection];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
[_baseDelegate textInputDidChange];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#import "TransformerTextInputModule.h"
|
|
2
|
+
|
|
3
|
+
#import <jsi/jsi.h>
|
|
4
|
+
#import <rnworklets/worklets/apple/WorkletsModule.h>
|
|
5
|
+
|
|
6
|
+
#import "TransformerTextInputRuntime.h"
|
|
7
|
+
|
|
8
|
+
@implementation TransformerTextInputModule
|
|
9
|
+
|
|
10
|
+
RCT_EXPORT_MODULE(TransformerTextInputModule)
|
|
11
|
+
|
|
12
|
+
@synthesize moduleRegistry = _moduleRegistry;
|
|
13
|
+
|
|
14
|
+
- (NSNumber *)install
|
|
15
|
+
{
|
|
16
|
+
WorkletsModule *workletsModule = [_moduleRegistry moduleForName:"WorkletsModule"];
|
|
17
|
+
auto uiRuntime = [workletsModule getWorkletsModuleProxy]->getUIWorkletRuntime();
|
|
18
|
+
rntti::SetUIWorkletRuntime(uiRuntime);
|
|
19
|
+
return @YES;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
23
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
24
|
+
{
|
|
25
|
+
return std::make_shared<facebook::react::NativeTransformerTextInputModuleSpecJSI>(params);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeTransformerTextInputModule.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AAMlD,eAAeA,mBAAmB,CAACC,YAAY,CAC7C,4BACF,CAAC","ignoreList":[]}
|