Skip to content

Commit a88dbf0

Browse files
Merge pull request #29 from divyanshu-patil/feat/typerich_ios
feat: support for iOS
2 parents efdc8bc + b1aa17a commit a88dbf0

22 files changed

+1717
-150
lines changed

ReactNativeTypeRich.podspec

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 = "ReactNativeTypeRich"
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/divyanshu-patil/react-native-typerich.git", :tag => "#{s.version}" }
15+
16+
# s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17+
s.source_files = "ios/**/*.{h,m,mm,cpp}"
18+
s.private_header_files = "ios/**/*.h"
19+
20+
s.public_header_files = [
21+
"ios/**/*.h",
22+
"ios/cpp/**/*.h"
23+
]
24+
25+
s.header_mappings_dir = "ios"
26+
27+
s.pod_target_xcconfig = {
28+
"HEADER_SEARCH_PATHS" => '"$(PODS_TARGET_SRCROOT)/ios"',
29+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
30+
}
31+
32+
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
33+
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
34+
if respond_to?(:install_modules_dependencies, true)
35+
install_modules_dependencies(s)
36+
else
37+
s.dependency "React-Core"
38+
end
39+
40+
end
41+

TypeRichTextInput.podspec

Lines changed: 0 additions & 20 deletions
This file was deleted.

example/ios/Podfile.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2449,8 +2449,7 @@ PODS:
24492449
- React-perflogger (= 0.83.0)
24502450
- React-utils (= 0.83.0)
24512451
- SocketRocket
2452-
- SocketRocket (0.7.1)
2453-
- TypeRichTextInput (0.1.10):
2452+
- ReactNativeTypeRich (1.1.1):
24542453
- boost
24552454
- DoubleConversion
24562455
- fast_float
@@ -2478,6 +2477,7 @@ PODS:
24782477
- ReactCommon/turbomodule/core
24792478
- SocketRocket
24802479
- Yoga
2480+
- SocketRocket (0.7.1)
24812481
- Yoga (0.0.0)
24822482

24832483
DEPENDENCIES:
@@ -2558,8 +2558,8 @@ DEPENDENCIES:
25582558
- ReactAppDependencyProvider (from `build/generated/ios/ReactAppDependencyProvider`)
25592559
- ReactCodegen (from `build/generated/ios/ReactCodegen`)
25602560
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
2561+
- ReactNativeTypeRich (from `../..`)
25612562
- SocketRocket (~> 0.7.1)
2562-
- TypeRichTextInput (from `../..`)
25632563
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
25642564

25652565
SPEC REPOS:
@@ -2720,7 +2720,7 @@ EXTERNAL SOURCES:
27202720
:path: build/generated/ios/ReactCodegen
27212721
ReactCommon:
27222722
:path: "../node_modules/react-native/ReactCommon"
2723-
TypeRichTextInput:
2723+
ReactNativeTypeRich:
27242724
:path: "../.."
27252725
Yoga:
27262726
:path: "../node_modules/react-native/ReactCommon/yoga"
@@ -2802,8 +2802,8 @@ SPEC CHECKSUMS:
28022802
ReactAppDependencyProvider: ebcf3a78dc1bcdf054c9e8d309244bade6b31568
28032803
ReactCodegen: 11c08ff43a62009d48c71de000352e4515918801
28042804
ReactCommon: 424cc34cf5055d69a3dcf02f3436481afb8b0f6f
2805+
ReactNativeTypeRich: 11271dfea8f942192be22384369f24f0c4cdf5ba
28052806
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
2806-
TypeRichTextInput: 8d48184140751812859bb561d2bc245754cd3c5d
28072807
Yoga: 6ca93c8c13f56baeec55eb608577619b17a4d64e
28082808

28092809
PODFILE CHECKSUM: 32064aec2e4fd956051ae18c71720479742bc76a

example/src/App.tsx

Lines changed: 91 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Pressable,
99
TextInput,
1010
Image,
11+
Platform,
1112
} from 'react-native';
1213
import {
1314
TypeRichTextInput,
@@ -32,7 +33,7 @@ export default function App() {
3233
end: 0,
3334
});
3435
const [image, setImage] = useState<onPasteImageEventData | null>(null);
35-
const [value, setValue] = useState<string>('');
36+
const [value, setValue] = useState<string>('hhh');
3637

3738
const multilineValue = `hello
3839
div
@@ -43,16 +44,19 @@ export default function App() {
4344
// };
4445

4546
const handleFocus = () => {
47+
console.log('focus commands');
4648
inputRef.current?.focus();
4749
};
4850

4951
const handleBlur = () => {
52+
console.log('blur commands');
5053
inputRef.current?.blur();
5154
};
5255

53-
const handleSetValue = (value = 'default value') => {
54-
textRef.current = value;
55-
inputRef.current?.setText(value);
56+
const handleSetValue = (text = 'default value') => {
57+
textRef.current = text;
58+
inputRef.current?.setText(text);
59+
inputRef.current?.setSelection(text.length, text.length);
5660
};
5761

5862
const handleSetSelection = () => {
@@ -94,14 +98,36 @@ export default function App() {
9498
}
9599

96100
const handleInsertTextAtCursor = () => {
97-
const textToInsert = 'Test';
98-
inputRef.current?.insertTextAt(
99-
selectionRef.current.start,
100-
selectionRef.current.end,
101-
textToInsert
102-
);
101+
const insert = 'Test';
102+
103+
const { start, end } = selectionRef.current;
104+
const currentText = textRef.current ?? '';
105+
106+
const nextText =
107+
currentText.slice(0, start) + insert + currentText.slice(end);
108+
109+
textRef.current = nextText;
110+
111+
inputRef.current?.insertTextAt(start, end, insert);
103112
};
104113

114+
function handleFastTypingProgrammatically() {
115+
const randomWords = ['hii', 'hello', ' ', 'my name is', 'test', 'div'];
116+
let i = 0;
117+
118+
const interval = setInterval(() => {
119+
if (i >= 100) {
120+
clearInterval(interval);
121+
return;
122+
}
123+
124+
const index = Math.floor(Math.random() * randomWords.length);
125+
handleSetValue(textRef.current + randomWords[index]);
126+
127+
i++;
128+
}, 100); // 50ms
129+
}
130+
105131
return (
106132
<>
107133
<ScrollView
@@ -118,12 +144,13 @@ export default function App() {
118144
<View style={styles.editor}>
119145
<TypeRichTextInput
120146
ref={inputRef}
121-
value={value}
147+
// value={value}
148+
defaultValue={textRef.current}
122149
style={styles.editorInput}
123150
placeholder="custom textinput"
124151
placeholderTextColor="rgb(0, 26, 114)"
125-
selectionColor="deepskyblue"
126-
cursorColor="dodgerblue"
152+
selectionColor="green"
153+
cursorColor="red"
127154
autoCapitalize="words"
128155
autoFocus
129156
onChangeText={(text: string) => {
@@ -139,20 +166,20 @@ export default function App() {
139166
// ANDROID_EXPERIMENTAL_SYNCHRONOUS_EVENTS
140167
// }
141168
multiline
142-
numberOfLines={4}
169+
// numberOfLines={5} // prefer maxHeight on iOS
170+
scrollEnabled
143171
onPasteImageData={(e) => {
144172
setImage(e);
145173
console.log(e);
146174
}}
147-
defaultValue={textRef.current}
148175
keyboardAppearance="dark"
149176
editable={true}
150177
lineHeight={22}
151-
fontFamily="serif"
178+
fontFamily={Platform.select({ ios: 'georgia', android: 'serif' })} // fontweight won't work unless this is used
152179
fontStyle="italic"
153-
fontWeight={'700'}
154-
fontSize={12}
155-
color="darkgreen"
180+
fontWeight={'200'}
181+
fontSize={24}
182+
color="indigo"
156183
/>
157184
</View>
158185
<TextInput
@@ -163,8 +190,8 @@ export default function App() {
163190
width: '100%',
164191
marginVertical: 20,
165192
}}
166-
// multiline={false}
167-
// numberOfLines={2}
193+
// multiline
194+
// numberOfLines={4}
168195
/>
169196
<View style={styles.btnContainer}>
170197
<View style={styles.buttonStack}>
@@ -206,11 +233,13 @@ export default function App() {
206233
<Text style={styles.label2}>set controlled Value</Text>
207234
</Pressable>
208235
<Pressable
209-
disabled
210-
onPress={() => {}}
211-
style={[styles.button, { backgroundColor: 'gray' }]}
236+
onPress={() => {
237+
// works only with the programmatic setText use
238+
handleFastTypingProgrammatically();
239+
}}
240+
style={[styles.button]}
212241
>
213-
<Text style={styles.label2}>Todo</Text>
242+
<Text style={styles.label2}>very Fast Typing</Text>
214243
</Pressable>
215244
</View>
216245
<View style={styles.buttonStack}>
@@ -230,6 +259,21 @@ export default function App() {
230259

231260
// this MUST preserve cursor after native fix
232261
inputRef.current?.setText(next);
262+
let { start: selStart, end: selEnd } = selectionRef.current;
263+
264+
if (selStart <= start) {
265+
// before wrap → no change
266+
} else if (selStart < end) {
267+
// inside wrapped range → +1
268+
selStart += 1;
269+
selEnd += 1;
270+
} else {
271+
// at or after end → +2
272+
selStart += 2;
273+
selEnd += 2;
274+
}
275+
276+
inputRef.current?.setSelection(selStart, selEnd);
233277
}}
234278
>
235279
<Text style={styles.label2}>Wrap middle with * *</Text>
@@ -251,7 +295,23 @@ export default function App() {
251295
);
252296
}
253297

254-
const ImageInfo = ({ image }: { image: any }) => {
298+
const ImageInfo = ({ image }: { image: onPasteImageEventData }) => {
299+
function formatFileSize(bytes: number): string {
300+
if (bytes <= 0) return '0 KB';
301+
302+
const KB = 1024;
303+
const MB = KB * KB;
304+
305+
const sizeInMB = bytes / MB;
306+
307+
if (sizeInMB < 0.9) {
308+
const sizeInKB = bytes / KB;
309+
return `${sizeInKB.toFixed(1)} KB`;
310+
}
311+
312+
return `${sizeInMB.toFixed(2)} MB`;
313+
}
314+
255315
return (
256316
<View>
257317
<Text style={{ color: 'red', fontWeight: 'bold' }}>
@@ -275,7 +335,7 @@ const ImageInfo = ({ image }: { image: any }) => {
275335
fontWeight: 'regular',
276336
}}
277337
>
278-
{image.fileSize}
338+
{image.fileSize} ({formatFileSize(image.fileSize)})
279339
</Text>
280340
</Text>
281341
<Text style={{ color: 'red', fontWeight: 'bold' }}>
@@ -323,7 +383,7 @@ const ImageInfo = ({ image }: { image: any }) => {
323383
fontWeight: 'regular',
324384
}}
325385
>
326-
{image.error?.message ?? 'no error'}
386+
{image.error?.message || 'no error'}
327387
</Text>
328388
</Text>
329389
</View>
@@ -377,12 +437,12 @@ const styles = StyleSheet.create({
377437
editorInput: {
378438
marginTop: 24,
379439
width: '100%',
380-
// maxHeight: 180,
440+
maxHeight: 280,
381441
backgroundColor: 'gainsboro',
382442
// fontSize: 34,
383443
fontFamily: 'Nunito-Regular',
384-
paddingVertical: 12,
385-
paddingHorizontal: 14,
444+
// paddingVertical: 12,
445+
// paddingHorizontal: 14,
386446
},
387447
scrollPlaceholder: {
388448
marginTop: 24,

ios/TypeRichTextInputView.h

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
11
#import <React/RCTViewComponentView.h>
2+
#import <react/renderer/core/State.h>
23
#import <UIKit/UIKit.h>
34

4-
#ifndef TypeRichTextInputViewNativeComponent_h
5-
#define TypeRichTextInputViewNativeComponent_h
6-
75
NS_ASSUME_NONNULL_BEGIN
86

9-
@interface TypeRichTextInputView : RCTViewComponentView
10-
@end
7+
@interface TypeRichTextInputView : RCTViewComponentView <UITextViewDelegate>
118

12-
NS_ASSUME_NONNULL_END
9+
@property(nonatomic, assign) BOOL blockEmitting;
10+
11+
- (CGSize)measureSize:(CGFloat)maxWidth;
12+
13+
// events
14+
- (void)emitPasteImageEventWith:(NSString *)uri
15+
type:(NSString *)type
16+
fileName:(NSString *)fileName
17+
fileSize:(NSUInteger)fileSize;
1318

14-
#endif /* TypeRichTextInputViewNativeComponent_h */
19+
// commands
20+
- (void)handleCommand:(NSString *)commandName
21+
args:(NSArray *)args;
22+
23+
// helpers used by commands
24+
- (BOOL)isTouchInProgress;
25+
//- (BOOL)isHandlingUserInput;
26+
- (void)invalidateTextLayoutFromCommand;
27+
- (void)updatePlaceholderVisibilityFromCommand;
28+
- (void)dispatchSelectionChangeIfNeeded;
29+
30+
@end
31+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)