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.
Files changed (78) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +141 -0
  3. package/RNTransformerTextInput.podspec +35 -0
  4. package/android/build.gradle +96 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/spotless.gradle +19 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/java/com/appandflow/transformertextinput/TextState.kt +15 -0
  9. package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputDecoratorView.kt +160 -0
  10. package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputDecoratorViewManager.kt +44 -0
  11. package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputJni.kt +25 -0
  12. package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputModule.kt +22 -0
  13. package/android/src/main/java/com/appandflow/transformertextinput/TransformerTextInputPackage.kt +53 -0
  14. package/android/src/main/jni/CMakeLists.txt +62 -0
  15. package/android/src/main/jni/TransformerTextInputJni.cpp +94 -0
  16. package/android/src/main/jni/rntti.h +17 -0
  17. package/cpp/TransformerTextInputDecoratorViewComponentDescriptor.h +16 -0
  18. package/cpp/TransformerTextInputDecoratorViewShadowNode.cpp +21 -0
  19. package/cpp/TransformerTextInputDecoratorViewShadowNode.h +40 -0
  20. package/cpp/TransformerTextInputRuntime.cpp +86 -0
  21. package/cpp/TransformerTextInputRuntime.h +31 -0
  22. package/ios/TransformerTextInputDecoratorView.h +9 -0
  23. package/ios/TransformerTextInputDecoratorView.mm +256 -0
  24. package/ios/TransformerTextInputModule.h +8 -0
  25. package/ios/TransformerTextInputModule.mm +28 -0
  26. package/lib/module/NativeTransformerTextInputModule.js +5 -0
  27. package/lib/module/NativeTransformerTextInputModule.js.map +1 -0
  28. package/lib/module/Transformer.js +15 -0
  29. package/lib/module/Transformer.js.map +1 -0
  30. package/lib/module/TransformerTextInput.js +86 -0
  31. package/lib/module/TransformerTextInput.js.map +1 -0
  32. package/lib/module/TransformerTextInputDecoratorViewNativeComponent.ts +31 -0
  33. package/lib/module/formatters/phone-number.js +315 -0
  34. package/lib/module/formatters/phone-number.js.map +1 -0
  35. package/lib/module/index.js +5 -0
  36. package/lib/module/index.js.map +1 -0
  37. package/lib/module/package.json +1 -0
  38. package/lib/module/registry.js +83 -0
  39. package/lib/module/registry.js.map +1 -0
  40. package/lib/module/selection.js +48 -0
  41. package/lib/module/selection.js.map +1 -0
  42. package/lib/module/utils/useMergeRefs.js +49 -0
  43. package/lib/module/utils/useMergeRefs.js.map +1 -0
  44. package/lib/module/utils/useRefEffect.js +37 -0
  45. package/lib/module/utils/useRefEffect.js.map +1 -0
  46. package/lib/typescript/package.json +1 -0
  47. package/lib/typescript/src/NativeTransformerTextInputModule.d.ts +7 -0
  48. package/lib/typescript/src/NativeTransformerTextInputModule.d.ts.map +1 -0
  49. package/lib/typescript/src/Transformer.d.ts +19 -0
  50. package/lib/typescript/src/Transformer.d.ts.map +1 -0
  51. package/lib/typescript/src/TransformerTextInput.d.ts +247 -0
  52. package/lib/typescript/src/TransformerTextInput.d.ts.map +1 -0
  53. package/lib/typescript/src/TransformerTextInputDecoratorViewNativeComponent.d.ts +12 -0
  54. package/lib/typescript/src/TransformerTextInputDecoratorViewNativeComponent.d.ts.map +1 -0
  55. package/lib/typescript/src/formatters/phone-number.d.ts +18 -0
  56. package/lib/typescript/src/formatters/phone-number.d.ts.map +1 -0
  57. package/lib/typescript/src/index.d.ts +3 -0
  58. package/lib/typescript/src/index.d.ts.map +1 -0
  59. package/lib/typescript/src/registry.d.ts +17 -0
  60. package/lib/typescript/src/registry.d.ts.map +1 -0
  61. package/lib/typescript/src/selection.d.ts +4 -0
  62. package/lib/typescript/src/selection.d.ts.map +1 -0
  63. package/lib/typescript/src/utils/useMergeRefs.d.ts +20 -0
  64. package/lib/typescript/src/utils/useMergeRefs.d.ts.map +1 -0
  65. package/lib/typescript/src/utils/useRefEffect.d.ts +24 -0
  66. package/lib/typescript/src/utils/useRefEffect.d.ts.map +1 -0
  67. package/package.json +199 -0
  68. package/react-native.config.js +13 -0
  69. package/src/NativeTransformerTextInputModule.ts +10 -0
  70. package/src/Transformer.ts +32 -0
  71. package/src/TransformerTextInput.tsx +147 -0
  72. package/src/TransformerTextInputDecoratorViewNativeComponent.ts +31 -0
  73. package/src/formatters/phone-number.ts +327 -0
  74. package/src/index.tsx +10 -0
  75. package/src/registry.ts +120 -0
  76. package/src/selection.ts +62 -0
  77. package/src/utils/useMergeRefs.ts +59 -0
  78. package/src/utils/useRefEffect.ts +42 -0
package/package.json ADDED
@@ -0,0 +1,199 @@
1
+ {
2
+ "name": "react-native-transformer-text-input",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "TextInput component that allows transforming text synchronously with a worklet",
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
+ "./formatters/phone-number": {
14
+ "source": "./src/formatters/phone-number.ts",
15
+ "types": "./lib/typescript/src/formatters/phone-number.d.ts",
16
+ "default": "./lib/module/formatters/phone-number.js"
17
+ },
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "src",
22
+ "lib",
23
+ "android",
24
+ "ios",
25
+ "cpp",
26
+ "*.podspec",
27
+ "react-native.config.js",
28
+ "!ios/build",
29
+ "!android/build",
30
+ "!android/gradle",
31
+ "!android/gradlew",
32
+ "!android/gradlew.bat",
33
+ "!android/local.properties",
34
+ "!**/__tests__",
35
+ "!**/__fixtures__",
36
+ "!**/__mocks__",
37
+ "!**/.*"
38
+ ],
39
+ "scripts": {
40
+ "example": "yarn workspace react-native-transformer-text-input-example",
41
+ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
42
+ "prepare": "bob build",
43
+ "test": "jest",
44
+ "lint": "yarn lint:eslint && yarn lint:ts && yarn lint:android",
45
+ "lint:ts": "tsc",
46
+ "lint:eslint": "eslint \"**/*.{js,ts,tsx}\"",
47
+ "lint:android": "cd example/android && RNTTI_WARNINGS_AS_ERRORS=true ./gradlew :react-native-transformer-text-input:lint --rerun-tasks",
48
+ "release": "release-it --only-version",
49
+ "format:prettier:check": "prettier \"src/**/*.{js,ts,tsx}\" \"example/**/*.{js,ts,tsx}\" --check",
50
+ "format:prettier:write": "yarn format:prettier:check --write",
51
+ "format:android:check": "cd android && ./gradlew spotlessCheck",
52
+ "format:android:write": "cd android && ./gradlew spotlessApply",
53
+ "format:clang:check": "clang-format --dry-run --Werror --glob='{ios,android/src,cpp}/**/*.{h,cpp,m,mm}'",
54
+ "format:clang:write": "clang-format -i --glob='{ios,android/src,cpp}/**/*.{h,hpp,cpp,m,mm}'",
55
+ "format:spotless:check": "cd android && ./gradlew spotlessCheck",
56
+ "format:spotless:write": "cd android && ./gradlew spotlessApply",
57
+ "format:check": "yarn format:prettier:check && yarn format:clang:check && yarn format:spotless:check",
58
+ "format:write": "yarn format:prettier:write && yarn format:clang:write && yarn format:spotless:write"
59
+ },
60
+ "keywords": [
61
+ "react-native",
62
+ "ios",
63
+ "android"
64
+ ],
65
+ "repository": {
66
+ "type": "git",
67
+ "url": "git+https://github.com/AppAndFlow/react-native-transformer-text-input.git"
68
+ },
69
+ "author": "Janic Duplessis <janic@appandflow.com> (https://appandflow.com)",
70
+ "license": "MIT",
71
+ "bugs": {
72
+ "url": "https://github.com/AppAndFlow/react-native-transformer-text-input/issues"
73
+ },
74
+ "homepage": "https://github.com/AppAndFlow/react-native-transformer-text-input#readme",
75
+ "publishConfig": {
76
+ "registry": "https://registry.npmjs.org/"
77
+ },
78
+ "devDependencies": {
79
+ "@commitlint/config-conventional": "^19.8.1",
80
+ "@eslint/compat": "^1.3.2",
81
+ "@eslint/eslintrc": "^3.3.1",
82
+ "@eslint/js": "^9.35.0",
83
+ "@react-native/babel-preset": "0.83.0",
84
+ "@react-native/eslint-config": "0.83.0",
85
+ "@release-it/conventional-changelog": "^10.0.1",
86
+ "@testing-library/react-native": "^13.3.3",
87
+ "@types/jest": "^29.5.14",
88
+ "@types/react": "^19.2.0",
89
+ "clang-format": "^1.8.0",
90
+ "commitlint": "^19.8.1",
91
+ "del-cli": "^6.0.0",
92
+ "eslint": "^9.35.0",
93
+ "eslint-config-prettier": "^10.1.8",
94
+ "eslint-plugin-prettier": "^5.5.4",
95
+ "jest": "^30.2.0",
96
+ "lefthook": "^2.0.3",
97
+ "prettier": "^2.8.8",
98
+ "react": "19.2.0",
99
+ "react-native": "0.83.0",
100
+ "react-native-builder-bob": "^0.40.17",
101
+ "react-native-worklets": "^0.7.1",
102
+ "react-test-renderer": "19.2.0",
103
+ "release-it": "^19.0.4",
104
+ "turbo": "^2.5.6",
105
+ "typescript": "^5.9.2"
106
+ },
107
+ "peerDependencies": {
108
+ "react": "*",
109
+ "react-native": "*",
110
+ "react-native-worklets": "*"
111
+ },
112
+ "workspaces": [
113
+ "example"
114
+ ],
115
+ "packageManager": "yarn@4.11.0",
116
+ "react-native-builder-bob": {
117
+ "source": "src",
118
+ "output": "lib",
119
+ "targets": [
120
+ [
121
+ "module",
122
+ {
123
+ "esm": true
124
+ }
125
+ ],
126
+ [
127
+ "typescript",
128
+ {
129
+ "project": "tsconfig.build.json"
130
+ }
131
+ ]
132
+ ]
133
+ },
134
+ "codegenConfig": {
135
+ "name": "rntti",
136
+ "type": "all",
137
+ "jsSrcsDir": "src",
138
+ "android": {
139
+ "javaPackageName": "com.appandflow.transformertextinput"
140
+ },
141
+ "ios": {
142
+ "modulesProvider": {
143
+ "TransformerTextInputModule": "TransformerTextInputModule"
144
+ },
145
+ "componentProvider": {
146
+ "TransformerTextInputDecoratorView": "TransformerTextInputDecoratorView"
147
+ }
148
+ }
149
+ },
150
+ "prettier": {
151
+ "quoteProps": "consistent",
152
+ "singleQuote": true,
153
+ "tabWidth": 2,
154
+ "trailingComma": "all",
155
+ "useTabs": false
156
+ },
157
+ "jest": {
158
+ "preset": "react-native",
159
+ "modulePathIgnorePatterns": [
160
+ "<rootDir>/example/node_modules",
161
+ "<rootDir>/lib/"
162
+ ]
163
+ },
164
+ "commitlint": {
165
+ "extends": [
166
+ "@commitlint/config-conventional"
167
+ ]
168
+ },
169
+ "release-it": {
170
+ "git": {
171
+ "commitMessage": "chore: release ${version}",
172
+ "tagName": "v${version}"
173
+ },
174
+ "npm": {
175
+ "publish": true
176
+ },
177
+ "github": {
178
+ "release": true
179
+ },
180
+ "plugins": {
181
+ "@release-it/conventional-changelog": {
182
+ "preset": {
183
+ "name": "angular"
184
+ }
185
+ }
186
+ }
187
+ },
188
+ "create-react-native-library": {
189
+ "type": "fabric-view",
190
+ "languages": "kotlin-objc",
191
+ "tools": [
192
+ "eslint",
193
+ "jest",
194
+ "lefthook",
195
+ "release-it"
196
+ ],
197
+ "version": "0.56.0"
198
+ }
199
+ }
@@ -0,0 +1,13 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ android: {
5
+ libraryName: 'rntti',
6
+ componentDescriptors: [
7
+ 'TransformerTextInputDecoratorViewComponentDescriptor',
8
+ ],
9
+ cmakeListsPath: 'src/main/jni/CMakeLists.txt',
10
+ },
11
+ },
12
+ },
13
+ };
@@ -0,0 +1,10 @@
1
+ import type { TurboModule } from 'react-native';
2
+ import { TurboModuleRegistry } from 'react-native';
3
+
4
+ export interface Spec extends TurboModule {
5
+ install(): boolean;
6
+ }
7
+
8
+ export default TurboModuleRegistry.getEnforcing<Spec>(
9
+ 'TransformerTextInputModule',
10
+ );
@@ -0,0 +1,32 @@
1
+ export type Selection = { start: number; end: number };
2
+
3
+ export type TransformerWorklet = (input: {
4
+ value: string;
5
+ previousValue: string;
6
+ selection: Selection;
7
+ previousSelection: Selection;
8
+ }) =>
9
+ | {
10
+ value?: string | null;
11
+ selection?: Selection | null;
12
+ }
13
+ | null
14
+ | undefined;
15
+
16
+ export class Transformer {
17
+ private readonly _worklet: TransformerWorklet;
18
+
19
+ constructor(worklet: TransformerWorklet) {
20
+ const workletHash = (worklet as { __workletHash?: number }).__workletHash;
21
+ if (workletHash == null) {
22
+ throw new Error(
23
+ '[rntti] Transformer must be a worklet. Did you forget to add the "worklet" directive?',
24
+ );
25
+ }
26
+ this._worklet = worklet;
27
+ }
28
+
29
+ get worklet() {
30
+ return this._worklet;
31
+ }
32
+ }
@@ -0,0 +1,147 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ type ElementRef,
8
+ type Ref,
9
+ } from 'react';
10
+ import {
11
+ StyleSheet,
12
+ TextInput,
13
+ type HostInstance,
14
+ type TextInputProps,
15
+ } from 'react-native';
16
+ import { type Selection, type Transformer } from './Transformer';
17
+ import TransformerTextInputDecoratorViewNativeComponent, {
18
+ Commands,
19
+ } from './TransformerTextInputDecoratorViewNativeComponent';
20
+ import { registerTransformer, unregisterTransformer } from './registry';
21
+ import useMergeRefs from './utils/useMergeRefs';
22
+ import { validateSelection } from './selection';
23
+
24
+ type TransformerTextInputInstanceMethods = {
25
+ /**
26
+ * Get the current text value.
27
+ */
28
+ getValue: () => string;
29
+ /**
30
+ * Update the value and/or selection, optionally running the transformer.
31
+ */
32
+ update: (options: {
33
+ /**
34
+ * New value to apply.
35
+ */
36
+ value?: string | null;
37
+ /**
38
+ * Optional selection to apply alongside the value.
39
+ */
40
+ selection?: { start: number; end: number };
41
+ /**
42
+ * Whether to run the transformer on update. Defaults to true.
43
+ */
44
+ transform?: boolean;
45
+ }) => void;
46
+ /**
47
+ * Clear the input value without running the transformer.
48
+ */
49
+ clear: () => void;
50
+ };
51
+
52
+ export type TransformerTextInputInstance = HostInstance &
53
+ TransformerTextInputInstanceMethods;
54
+
55
+ export type TransformerTextInputProps = Omit<TextInputProps, 'value'> & {
56
+ /**
57
+ * Transformer instance used to sync text changes on the UI thread.
58
+ */
59
+ transformer: Transformer;
60
+ };
61
+
62
+ export const TransformerTextInput = forwardRef(
63
+ (
64
+ { transformer, onChangeText, ...others }: TransformerTextInputProps,
65
+ forwardedRef: Ref<TransformerTextInputInstance>,
66
+ ) => {
67
+ const transformerId = useMemo(() => {
68
+ return registerTransformer(transformer);
69
+ }, [transformer]);
70
+
71
+ useEffect(() => {
72
+ return () => {
73
+ unregisterTransformer(transformerId);
74
+ };
75
+ }, [transformerId]);
76
+
77
+ const decoratorRef =
78
+ useRef<
79
+ ElementRef<typeof TransformerTextInputDecoratorViewNativeComponent>
80
+ >(null);
81
+ const textRef = useRef('');
82
+
83
+ const setInputRef = useCallback((instance: HostInstance | null) => {
84
+ if (instance != null) {
85
+ Object.assign(instance, {
86
+ getValue() {
87
+ return textRef.current;
88
+ },
89
+ update({ value, selection, transform }) {
90
+ const nativeRef = decoratorRef.current;
91
+ if (!nativeRef) {
92
+ return;
93
+ }
94
+ let newSelection: Selection;
95
+ const newValue = value ?? textRef.current;
96
+ if (selection != null) {
97
+ validateSelection(selection, newValue.length);
98
+ newSelection = selection;
99
+ } else {
100
+ // When changing value without selection, move cursor to end.
101
+ newSelection = { start: newValue.length, end: newValue.length };
102
+ }
103
+ Commands.update(
104
+ nativeRef,
105
+ transform ?? true,
106
+ value ?? null,
107
+ newSelection.start,
108
+ newSelection.end,
109
+ );
110
+ },
111
+ clear() {
112
+ this.update({ value: '', transform: false });
113
+ },
114
+ } satisfies TransformerTextInputInstanceMethods);
115
+ }
116
+ }, []);
117
+
118
+ const inputRef = useMergeRefs(setInputRef, forwardedRef);
119
+
120
+ const handleChangeText = useCallback(
121
+ (text: string) => {
122
+ textRef.current = text;
123
+ onChangeText?.(text);
124
+ },
125
+ [onChangeText],
126
+ );
127
+
128
+ return (
129
+ <TransformerTextInputDecoratorViewNativeComponent
130
+ ref={decoratorRef}
131
+ style={styles.decorator}
132
+ transformerId={transformerId}
133
+ >
134
+ <TextInput
135
+ // @ts-expect-error
136
+ ref={inputRef}
137
+ onChangeText={handleChangeText}
138
+ {...others}
139
+ />
140
+ </TransformerTextInputDecoratorViewNativeComponent>
141
+ );
142
+ },
143
+ );
144
+
145
+ const styles = StyleSheet.create({
146
+ decorator: { display: 'contents' },
147
+ });
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import {
3
+ codegenNativeCommands,
4
+ codegenNativeComponent,
5
+ type HostComponent,
6
+ type ViewProps,
7
+ type CodegenTypes,
8
+ } from 'react-native';
9
+
10
+ interface NativeProps extends ViewProps {
11
+ transformerId: CodegenTypes.Int32;
12
+ }
13
+
14
+ interface NativeCommands {
15
+ update: (
16
+ viewRef: React.ElementRef<HostComponent<NativeProps>>,
17
+ transform: boolean,
18
+ value: string | null,
19
+ selectionStart: CodegenTypes.Int32,
20
+ selectionEnd: CodegenTypes.Int32,
21
+ ) => void;
22
+ }
23
+
24
+ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
25
+ supportedCommands: ['update'],
26
+ });
27
+
28
+ export default codegenNativeComponent<NativeProps>(
29
+ 'TransformerTextInputDecoratorView',
30
+ { interfaceOnly: true },
31
+ );