@expensify/react-native-live-markdown 0.1.297 → 0.1.298
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.
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#import <RNLiveMarkdown/MarkdownTextInputDecoratorViewComponentDescriptor.h>
|
|
15
15
|
#import <RNLiveMarkdown/MarkdownTextStorageDelegate.h>
|
|
16
16
|
#import <RNLiveMarkdown/RCTMarkdownStyle.h>
|
|
17
|
+
#import <RNLiveMarkdown/RCTTextInput+AdaptiveImageGlyph.h>
|
|
17
18
|
|
|
18
19
|
#import <objc/runtime.h>
|
|
19
20
|
|
|
@@ -85,6 +86,9 @@ using namespace facebook::react;
|
|
|
85
86
|
// make sure `adjustsFontSizeToFitWidth` is disabled, otherwise formatting will be overwritten
|
|
86
87
|
react_native_assert(_textField.adjustsFontSizeToFitWidth == NO);
|
|
87
88
|
|
|
89
|
+
// Enable TextField AdaptiveImageGlyph support for iOS 18.0+
|
|
90
|
+
[self enableAdaptiveImageGlyphSupport:_textField];
|
|
91
|
+
|
|
88
92
|
_markdownTextFieldObserver = [[MarkdownTextFieldObserver alloc] initWithTextField:_textField markdownUtils:_markdownUtils];
|
|
89
93
|
|
|
90
94
|
// register observers for future edits
|
|
@@ -101,6 +105,9 @@ using namespace facebook::react;
|
|
|
101
105
|
} else if ([backedTextInputView isKindOfClass:[RCTUITextView class]]) {
|
|
102
106
|
_textView = (RCTUITextView *)backedTextInputView;
|
|
103
107
|
|
|
108
|
+
// Enable TextView AdaptiveImageGlyph support for iOS 18.0+
|
|
109
|
+
[self enableAdaptiveImageGlyphSupport:_textView];
|
|
110
|
+
|
|
104
111
|
// register delegate for future edits
|
|
105
112
|
react_native_assert(_textView.textStorage.delegate == nil);
|
|
106
113
|
_markdownTextStorageDelegate = [[MarkdownTextStorageDelegate alloc] initWithTextView:_textView markdownUtils:_markdownUtils];
|
|
@@ -140,6 +147,22 @@ using namespace facebook::react;
|
|
|
140
147
|
}
|
|
141
148
|
}
|
|
142
149
|
|
|
150
|
+
- (void)enableAdaptiveImageGlyphSupport:(UIView *)textInputView {
|
|
151
|
+
if (@available(iOS 18.0, *)) {
|
|
152
|
+
if ([textInputView respondsToSelector:@selector(setSupportsAdaptiveImageGlyph:)]) {
|
|
153
|
+
[textInputView setValue:@YES forKey:@"supportsAdaptiveImageGlyph"];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
- (void)disableAdaptiveImageGlyphSupport:(UIView *)textInputView {
|
|
159
|
+
if (@available(iOS 18.0, *)) {
|
|
160
|
+
if ([textInputView respondsToSelector:@selector(setSupportsAdaptiveImageGlyph:)]) {
|
|
161
|
+
[textInputView setValue:@NO forKey:@"supportsAdaptiveImageGlyph"];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
143
166
|
- (void)removeTextInputObservers
|
|
144
167
|
{
|
|
145
168
|
react_native_assert(_observersAdded && "MarkdownTextInputDecoratorComponentView tried to remove TextInput observers while they were detached");
|
|
@@ -154,6 +177,7 @@ using namespace facebook::react;
|
|
|
154
177
|
}
|
|
155
178
|
_markdownBackedTextInputDelegate = nil;
|
|
156
179
|
[_textView removeObserver:_markdownTextViewObserver forKeyPath:@"defaultTextAttributes" context:NULL];
|
|
180
|
+
[self disableAdaptiveImageGlyphSupport:_textView];
|
|
157
181
|
_markdownTextViewObserver = nil;
|
|
158
182
|
_markdownTextStorageDelegate = nil;
|
|
159
183
|
_textView.textStorage.delegate = nil;
|
|
@@ -165,6 +189,7 @@ using namespace facebook::react;
|
|
|
165
189
|
[_textField removeTarget:_markdownTextFieldObserver action:@selector(textFieldDidEndEditing:) forControlEvents:UIControlEventEditingDidEnd];
|
|
166
190
|
[_textField removeObserver:_markdownTextFieldObserver forKeyPath:@"text" context:NULL];
|
|
167
191
|
[_textField removeObserver:_markdownTextFieldObserver forKeyPath:@"attributedText" context:NULL];
|
|
192
|
+
[self disableAdaptiveImageGlyphSupport:_textField];
|
|
168
193
|
_markdownTextFieldObserver = nil;
|
|
169
194
|
_textField = nil;
|
|
170
195
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import <React/RCTUITextField.h>
|
|
2
|
+
#import <React/RCTUITextView.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
@interface RCTUITextField (AdaptiveImageGlyph)
|
|
7
|
+
- (void)liveMarkdown_insertAdaptiveImageGlyph:(id)glyph replacementRange:(NSRange)replacementRange;
|
|
8
|
+
@end
|
|
9
|
+
|
|
10
|
+
@interface RCTUITextView (AdaptiveImageGlyph)
|
|
11
|
+
- (void)liveMarkdown_insertAdaptiveImageGlyph:(id)glyph replacementRange:(NSRange)replacementRange;
|
|
12
|
+
@end
|
|
13
|
+
|
|
14
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#import <RNLiveMarkdown/RCTTextInput+AdaptiveImageGlyph.h>
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
#import <objc/runtime.h>
|
|
4
|
+
|
|
5
|
+
// Helper method to setup method swizzling for adaptive image glyph
|
|
6
|
+
static void setupAdaptiveImageGlyphSwizzling(Class targetClass, SEL swizzledSelector) {
|
|
7
|
+
if (@available(iOS 18.0, *)) {
|
|
8
|
+
SEL originalSelector = @selector(insertAdaptiveImageGlyph:replacementRange:);
|
|
9
|
+
|
|
10
|
+
if ([targetClass instancesRespondToSelector:originalSelector]) {
|
|
11
|
+
Method originalMethod = class_getInstanceMethod(targetClass, originalSelector);
|
|
12
|
+
Method swizzledMethod = class_getInstanceMethod(targetClass, swizzledSelector);
|
|
13
|
+
|
|
14
|
+
if (originalMethod && swizzledMethod) {
|
|
15
|
+
BOOL didAddMethod = class_addMethod(targetClass,
|
|
16
|
+
originalSelector,
|
|
17
|
+
method_getImplementation(swizzledMethod),
|
|
18
|
+
method_getTypeEncoding(swizzledMethod));
|
|
19
|
+
|
|
20
|
+
if (didAddMethod) {
|
|
21
|
+
class_replaceMethod(targetClass,
|
|
22
|
+
swizzledSelector,
|
|
23
|
+
method_getImplementation(originalMethod),
|
|
24
|
+
method_getTypeEncoding(originalMethod));
|
|
25
|
+
} else {
|
|
26
|
+
method_exchangeImplementations(originalMethod, swizzledMethod);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper method to process genmoji and convert to pasteable image
|
|
34
|
+
static UIImage *processGenmojiImage(id glyph) {
|
|
35
|
+
if (!glyph) {
|
|
36
|
+
return nil;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
NSData *imageData = nil;
|
|
40
|
+
if ([glyph respondsToSelector:@selector(imageContent)]) {
|
|
41
|
+
imageData = [glyph performSelector:@selector(imageContent)];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!imageData) {
|
|
45
|
+
return nil;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
UIImage *originalImage = [UIImage imageWithData:imageData];
|
|
49
|
+
if (!originalImage) {
|
|
50
|
+
return nil;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
CGSize targetSize = CGSizeMake(128, 128);
|
|
54
|
+
UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0);
|
|
55
|
+
[originalImage drawInRect:CGRectMake(0, 0, targetSize.width, targetSize.height)];
|
|
56
|
+
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
|
|
57
|
+
UIGraphicsEndImageContext();
|
|
58
|
+
|
|
59
|
+
if (!resizedImage) {
|
|
60
|
+
return nil;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
NSData *pngData = UIImagePNGRepresentation(resizedImage);
|
|
64
|
+
if (!pngData) {
|
|
65
|
+
return nil;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return [UIImage imageWithData:pngData];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Shared helper method to handle adaptive image glyph paste event
|
|
72
|
+
static void handleAdaptiveImageGlyphPasteEvent(UIView *textInputView, id glyph, NSRange replacementRange) {
|
|
73
|
+
UIImage *processedImage = processGenmojiImage(glyph);
|
|
74
|
+
if (!processedImage) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Save current clipboard contents
|
|
79
|
+
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
|
80
|
+
NSArray *savedItems = pasteboard.items;
|
|
81
|
+
|
|
82
|
+
// Set processed image in clipboard and trigger paste
|
|
83
|
+
pasteboard.image = processedImage;
|
|
84
|
+
|
|
85
|
+
if ([textInputView canPerformAction:@selector(paste:) withSender:nil]) {
|
|
86
|
+
[textInputView performSelector:@selector(paste:) withObject:nil];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Restore previous clipboard contents
|
|
90
|
+
pasteboard.items = savedItems;
|
|
91
|
+
|
|
92
|
+
// Return without calling original method to prevent input genmoji insertion
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@implementation RCTUITextField (AdaptiveImageGlyph)
|
|
97
|
+
|
|
98
|
+
+ (void)load {
|
|
99
|
+
static dispatch_once_t onceToken;
|
|
100
|
+
dispatch_once(&onceToken, ^{
|
|
101
|
+
setupAdaptiveImageGlyphSwizzling([self class], @selector(liveMarkdown_insertAdaptiveImageGlyph:replacementRange:));
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
- (void)liveMarkdown_insertAdaptiveImageGlyph:(id)glyph replacementRange:(NSRange)replacementRange {
|
|
106
|
+
if (@available(iOS 18.0, *)) {
|
|
107
|
+
handleAdaptiveImageGlyphPasteEvent(self, glyph, replacementRange);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@end
|
|
112
|
+
|
|
113
|
+
@implementation RCTUITextView (AdaptiveImageGlyph)
|
|
114
|
+
|
|
115
|
+
+ (void)load {
|
|
116
|
+
static dispatch_once_t onceToken;
|
|
117
|
+
dispatch_once(&onceToken, ^{
|
|
118
|
+
setupAdaptiveImageGlyphSwizzling([self class], @selector(liveMarkdown_insertAdaptiveImageGlyph:replacementRange:));
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
- (void)liveMarkdown_insertAdaptiveImageGlyph:(id)glyph replacementRange:(NSRange)replacementRange {
|
|
123
|
+
if (@available(iOS 18.0, *)) {
|
|
124
|
+
handleAdaptiveImageGlyphPasteEvent(self, glyph, replacementRange);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@end
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expensify/react-native-live-markdown",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.298",
|
|
4
4
|
"description": "Drop-in replacement for React Native's TextInput component with Markdown formatting.",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|