Skip to content

Commit 3ad7f30

Browse files
committed
feat: add iOSExperimentalHTMLSerializer prop
1 parent ba773cf commit 3ad7f30

9 files changed

Lines changed: 48 additions & 13 deletions

android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,13 @@ class EnrichedTextInputViewManager :
239239
view?.experimentalSynchronousEvents = value
240240
}
241241

242+
override fun setIOSExperimentalHTMLSerializer(
243+
view: EnrichedTextInputView?,
244+
value: Boolean,
245+
) {
246+
// iOS only prop
247+
}
248+
242249
override fun focus(view: EnrichedTextInputView?) {
243250
view?.requestFocusProgrammatically()
244251
}

apps/example/src/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ export default function App() {
302302
onChangeText={(e) => handleChangeText(e.nativeEvent)}
303303
onChangeHtml={(e) => handleChangeHtml(e.nativeEvent)}
304304
onChangeState={(e) => handleChangeState(e.nativeEvent)}
305+
iOSExperimentalHTMLSerializer={true}
305306
onLinkDetected={handleLinkDetected}
306307
onMentionDetected={console.log}
307308
onStartMention={handleStartMention}

ios/EnrichedTextInputView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
3030
NSDictionary<NSNumber *, NSArray<NSNumber *> *> *blockingStyles;
3131
@public
3232
BOOL blockEmitting;
33+
BOOL useExperimentalHTMLParser;
3334
}
3435
- (CGSize)measureSize:(CGFloat)maxWidth;
3536
- (void)emitOnLinkDetectedEvent:(NSString *)text

ios/EnrichedTextInputView.mm

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ - (void)setDefaults {
8080
_emitHtml = NO;
8181
blockEmitting = NO;
8282
_emitFocusBlur = YES;
83+
useExperimentalHTMLParser = NO;
8384

8485
defaultTypingAttributes =
8586
[[NSMutableDictionary<NSAttributedStringKey, id> alloc] init];
@@ -229,6 +230,8 @@ - (void)updateProps:(Props::Shared const &)props
229230
BOOL isFirstMount = NO;
230231
BOOL stylePropChanged = NO;
231232

233+
useExperimentalHTMLParser = newViewProps.iOSExperimentalHTMLSerializer;
234+
232235
// initial config
233236
if (config == nullptr) {
234237
isFirstMount = YES;

ios/inputParser/EnrichedAttributedStringHTMLSerializer.mm

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ - (NSString *)buildHtmlFromAttributedString:(NSAttributedString *)text
4545
pretify:(BOOL)pretify {
4646

4747
if (text.length == 0)
48-
return @"<html>\n<p></p>\n</html>";
48+
return DefaultHtmlValue;
4949

5050
HTMLElement *root = [self buildRootNodeFromAttributedString:text];
5151

@@ -59,10 +59,10 @@ - (HTMLElement *)buildRootNodeFromAttributedString:(NSAttributedString *)text {
5959
NSString *plain = text.string;
6060

6161
HTMLElement *root = [HTMLElement new];
62-
root.tag = "html";
62+
root.tag = HtmlTagHTML;
6363

6464
HTMLElement *br = [HTMLElement new];
65-
br.tag = "br";
65+
br.tag = HtmlTagBR;
6666
br.selfClosing = YES;
6767

6868
__block id<BaseStyleProtocol> previousParagraphStyle = nil;
@@ -161,7 +161,7 @@ - (HTMLElement *)currentParagraphType:(NSNumber *)currentParagraphType
161161
rootNode:(HTMLElement *)rootNode {
162162
if (!currentParagraphType) {
163163
HTMLElement *outer = [HTMLElement new];
164-
outer.tag = "p";
164+
outer.tag = HtmlParagraphTag;
165165
[rootNode.children addObject:outer];
166166
return outer;
167167
}
@@ -190,7 +190,7 @@ - (HTMLElement *)currentParagraphType:(NSNumber *)currentParagraphType
190190
rootNode:(HTMLElement *)rootNode {
191191
if (!currentStyle) {
192192
HTMLElement *outer = [HTMLElement new];
193-
outer.tag = "p";
193+
outer.tag = HtmlParagraphTag;
194194
[rootNode.children addObject:outer];
195195
return outer;
196196
}
@@ -273,7 +273,7 @@ - (void)createHtmlFromNode:(HTMLNode *)node
273273
[self createHtmlFromNode:child into:buffer pretify:pretify];
274274

275275
if (addNewLineAfter)
276-
appendC(buffer, "\n");
276+
appendC(buffer, NewLine);
277277

278278
appendCloseTag(buffer, element.tag);
279279
}

ios/inputParser/EnrichedAttributedStringHTMLSerializerTagUtils.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ static const unichar HtmlLessThanChar = '<';
1414
static const unichar HtmlGreaterThanChar = '>';
1515
static const unichar HtmlAmpersandChar = '&';
1616

17+
static const char *NewLine = "\n";
1718
static const char *NewlineOpenTag = "\n<";
1819

1920
static const char *OpenTagStart = "<";
@@ -35,6 +36,12 @@ static const char *HtmlTagBR = "br";
3536
static const char *HtmlTagHTML = "html";
3637
static const char *HtmlTagBlockquote = "blockquote";
3738
static const char *HtmlTagCodeblock = "codeblock";
39+
static const char *HtmlHRTag = "hr";
40+
static const char *HtmlChecklistTag = "checklist";
41+
static const char *HtmlContentTag = "content";
42+
static const char *HtmlParagraphTag = "p";
43+
44+
static NSString *const DefaultHtmlValue = @"<html>\n<p></p>\n</html>";
3845

3946
static inline void appendC(NSMutableData *buf, const char *c) {
4047
if (!c)
@@ -109,7 +116,9 @@ static inline BOOL isBlockTag(const char *t) {
109116
case 'p':
110117
return t[1] == '\0';
111118
case 'h':
112-
return t[2] == '\0' && (t[1] == '1' || t[1] == '2' || t[1] == '3');
119+
return t[2] == '\0' &&
120+
(t[1] == '1' || t[1] == '2' || t[1] == '3' || t[1] == '4' ||
121+
t[1] == '5' || t[1] == '6' || t[1] == 'r');
113122
case 'u':
114123
return strcmp(t, HtmlTagUL) == 0;
115124
case 'o':
@@ -119,7 +128,8 @@ static inline BOOL isBlockTag(const char *t) {
119128
case 'b':
120129
return strcmp(t, HtmlTagBR) == 0 || strcmp(t, HtmlTagBlockquote) == 0;
121130
case 'c':
122-
return strcmp(t, HtmlTagCodeblock) == 0;
131+
return strcmp(t, HtmlTagCodeblock) == 0 || strcmp(t, HtmlContentTag) ||
132+
strcmp(t, HtmlChecklistTag);
123133
default:
124134
return NO;
125135
}

ios/inputParser/InputParser.mm

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,18 @@ - (instancetype)initWithInput:(id)input {
2121
}
2222

2323
- (NSString *)parseToHtmlFromRange:(NSRange)range {
24-
NSAttributedString *sub =
25-
[_input->textView.textStorage attributedSubstringFromRange:range];
24+
25+
if (_input->useExperimentalHTMLParser) {
26+
NSAttributedString *attributedString =
27+
[_input->textView.textStorage attributedSubstringFromRange:range];
28+
return [_attributedStringHTMLSerializer
29+
buildHtmlFromAttributedString:attributedString
30+
pretify:YES];
31+
}
32+
33+
NSInteger offset = range.location;
34+
NSString *text =
35+
[_input->textView.textStorage.string substringWithRange:range];
2636

2737
if (text.length == 0) {
2838
return @"<html>\n<p></p>\n</html>";
@@ -526,9 +536,6 @@ - (NSString *)tagContentForStyle:(NSNumber *)style
526536
return @"p";
527537
}
528538
return @"";
529-
return [_htmlParser buildHtmlFromAttributedString:sub pretify:YES];
530-
return [_attributedStringHTMLSerializer buildHtmlFromAttributedString:sub
531-
pretify:YES];
532539
}
533540

534541
- (void)replaceWholeFromHtml:(NSString *_Nonnull)html {

src/EnrichedTextInput.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ export interface EnrichedTextInputProps extends Omit<ViewProps, 'children'> {
150150
* Disabled by default.
151151
*/
152152
androidExperimentalSynchronousEvents?: boolean;
153+
// new implementation of html serializer for iOS which fixes some edge cases and
154+
// improves performance. Still experimental because we don't want to enable it by default yet.
155+
iOSExperimentalHTMLSerializer?: boolean;
153156
}
154157

155158
const nullthrows = <T,>(value: T | null | undefined): T => {
@@ -199,6 +202,7 @@ export const EnrichedTextInput = ({
199202
onChangeSelection,
200203
androidExperimentalSynchronousEvents = false,
201204
scrollEnabled = true,
205+
iOSExperimentalHTMLSerializer = false,
202206
...rest
203207
}: EnrichedTextInputProps) => {
204208
const nativeRef = useRef<ComponentType | null>(null);
@@ -399,6 +403,7 @@ export const EnrichedTextInput = ({
399403
androidExperimentalSynchronousEvents={
400404
androidExperimentalSynchronousEvents
401405
}
406+
iOSExperimentalHTMLSerializer={iOSExperimentalHTMLSerializer}
402407
scrollEnabled={scrollEnabled}
403408
{...rest}
404409
/>

src/EnrichedTextInputNativeComponent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export interface NativeProps extends ViewProps {
163163

164164
// Experimental
165165
androidExperimentalSynchronousEvents: boolean;
166+
iOSExperimentalHTMLSerializer: boolean;
166167
}
167168

168169
type ComponentType = HostComponent<NativeProps>;

0 commit comments

Comments
 (0)