@expensify/react-native-live-markdown 0.1.326 → 0.1.328

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/README.md CHANGED
@@ -193,27 +193,27 @@ Currently, `react-native-live-markdown` supports only [ExpensiMark](https://gith
193
193
 
194
194
  ### React Native compatibility
195
195
 
196
- | | 0.73 | 0.74 | 0.75 | 0.76 | 0.77 | 0.78 | 0.79 | 0.80 | 0.81 | 0.82 | 0.83 |
197
- |:-----------------:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
198
- | 0.1.321+ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
199
- | 0.1.304 - 0.1.320 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
200
- | 0.1.297 – 0.1.303 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
201
- | 0.1.260 – 0.1.296 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
202
- | 0.1.256 – 0.1.259 | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
203
- | 0.1.248 – 0.1.255 | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
204
- | 0.1.235 – 0.1.247 | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
205
- | 0.1.141 – 0.1.234 | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
206
- | 0.1.129 – 0.1.140 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
207
- | 0.1.122 – 0.1.128 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
208
- | 0.1.15 – 0.1.121 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
196
+ | | 0.73 | 0.74 | 0.75 | 0.76 | 0.77 | 0.78 | 0.79 | 0.80 | 0.81 | 0.82 | 0.83 | 0.84 | 0.85 |
197
+ |:-----------------:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|
198
+ | 0.1.321+ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
199
+ | 0.1.304 - 0.1.320 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
200
+ | 0.1.297 – 0.1.303 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
201
+ | 0.1.260 – 0.1.296 | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
202
+ | 0.1.256 – 0.1.259 | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
203
+ | 0.1.248 – 0.1.255 | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
204
+ | 0.1.235 – 0.1.247 | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
205
+ | 0.1.141 – 0.1.234 | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
206
+ | 0.1.129 – 0.1.140 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
207
+ | 0.1.122 – 0.1.128 | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
208
+ | 0.1.15 – 0.1.121 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
209
209
 
210
210
 
211
211
  ### `react-native-worklets` compatibility
212
212
 
213
- | | 0.6.x | 0.7.x |
214
- |:-----------------:|:-----:|:-----:|
215
- | 0.1.321+ | ❌ | ✅ |
216
- | 0.1.308 – 0.1.320 | ✅ | ❌ |
213
+ | | 0.6.x | 0.7.x | 0.8.x | 0.9.x |
214
+ |:-----------------:|:-----:|:-----:|:-----:|:-----:|
215
+ | 0.1.321+ | ❌ | ✅ | ✅ | ✅ |
216
+ | 0.1.308 – 0.1.320 | ✅ | ❌ | ❌ | ❌ |
217
217
 
218
218
 
219
219
  ## License
@@ -48,6 +48,12 @@ private:
48
48
  YGMeasureMode heightMode);
49
49
  static YogaLayoutableShadowNode &
50
50
  shadowNodeFromContext(YGNodeConstRef yogaNode);
51
+
52
+ // Persisted RCTMarkdownUtils instance shared across shadow node clones so
53
+ // that MarkdownParser's one-entry memo cache (keyed on text + parserId)
54
+ // survives repeated Yoga measure callbacks instead of being discarded on
55
+ // every call to applyMarkdownFormattingToTextInputState.
56
+ mutable std::shared_ptr<void> markdownUtils_;
51
57
  };
52
58
 
53
59
  } // namespace react
@@ -32,6 +32,13 @@ MarkdownTextInputDecoratorShadowNode::MarkdownTextInputDecoratorShadowNode(
32
32
  ShadowNode const &sourceShadowNode,
33
33
  ShadowNodeFragment const &fragment)
34
34
  : ConcreteViewShadowNode(sourceShadowNode, fragment) {
35
+ // Carry the persisted RCTMarkdownUtils over from the source node so the
36
+ // MarkdownParser memo cache survives the frequent cloning that happens
37
+ // during layout and re-render cycles.
38
+ const auto &source =
39
+ static_cast<const MarkdownTextInputDecoratorShadowNode &>(sourceShadowNode);
40
+ markdownUtils_ = source.markdownUtils_;
41
+
35
42
  initialize();
36
43
  makeChildNodeMutable();
37
44
 
@@ -182,12 +189,20 @@ void MarkdownTextInputDecoratorShadowNode::applyMarkdownFormattingToTextInputSta
182
189
  const auto defaultNSTextAttributes =
183
190
  RCTNSTextAttributesFromTextAttributes(defaultTextAttributes);
184
191
 
185
- // this can possibly be optimized
192
+ // Lazily create and persist the RCTMarkdownUtils instance so the MarkdownParser
193
+ // one-entry memo cache (keyed on text + parserId) survives repeated Yoga measure
194
+ // callbacks. Previously a fresh utils/parser was allocated on every call,
195
+ // discarding the cache and forcing a full JSI re-parse each time.
196
+ if (!markdownUtils_) {
197
+ RCTMarkdownUtils *freshUtils = [[RCTMarkdownUtils alloc] init];
198
+ markdownUtils_ = std::shared_ptr<void>(
199
+ (__bridge_retained void *)freshUtils, [](void *p) { CFRelease(p); });
200
+ }
201
+ RCTMarkdownUtils *utils = (__bridge RCTMarkdownUtils *)markdownUtils_.get();
202
+
186
203
  RCTMarkdownStyle *markdownStyle =
187
204
  [[RCTMarkdownStyle alloc] initWithStruct:decoratorProps.markdownStyle];
188
- RCTMarkdownUtils *utils = [[RCTMarkdownUtils alloc] init];
189
- [utils setMarkdownStyle:markdownStyle];
190
- [utils setParserId:[NSNumber numberWithInt:decoratorProps.parserId]];
205
+ NSNumber *parserId = [NSNumber numberWithInt:decoratorProps.parserId];
191
206
 
192
207
  // convert the attibuted string stored in state to
193
208
  // NSAttributedString
@@ -228,7 +243,10 @@ void MarkdownTextInputDecoratorShadowNode::applyMarkdownFormattingToTextInputSta
228
243
 
229
244
  // apply markdown
230
245
  NSMutableAttributedString *newString = [nsAttributedString mutableCopy];
231
- [utils applyMarkdownFormatting:newString withDefaultTextAttributes:defaultNSTextAttributes];
246
+ [utils applyMarkdownFormatting:newString
247
+ withDefaultTextAttributes:defaultNSTextAttributes
248
+ markdownStyle:markdownStyle
249
+ parserId:parserId];
232
250
 
233
251
  // create a clone of the old TextInputState and update the
234
252
  // attributed string box to point to the string with markdown
@@ -240,7 +258,10 @@ void MarkdownTextInputDecoratorShadowNode::applyMarkdownFormattingToTextInputSta
240
258
 
241
259
  // apply markdown
242
260
  NSMutableAttributedString *newString = [nsAttributedString mutableCopy];
243
- [utils applyMarkdownFormatting:newString withDefaultTextAttributes:defaultNSTextAttributes];
261
+ [utils applyMarkdownFormatting:newString
262
+ withDefaultTextAttributes:defaultNSTextAttributes
263
+ markdownStyle:markdownStyle
264
+ parserId:parserId];
244
265
 
245
266
  // create a clone of the old TextInputState and update the
246
267
  // attributed string box to point to the string with markdown
@@ -11,6 +11,15 @@ NS_ASSUME_NONNULL_BEGIN
11
11
  - (void)applyMarkdownFormatting:(nonnull NSMutableAttributedString *)attributedString
12
12
  withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes;
13
13
 
14
+ // Atomically sets the style/parser and applies formatting under a single lock.
15
+ // Use this from the shadow node measure path, where one RCTMarkdownUtils
16
+ // instance is shared across shadow node clones and may be accessed from
17
+ // concurrent Fabric commits/layout passes.
18
+ - (void)applyMarkdownFormatting:(nonnull NSMutableAttributedString *)attributedString
19
+ withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
20
+ markdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle
21
+ parserId:(nonnull NSNumber *)parserId;
22
+
14
23
  @end
15
24
 
16
25
  NS_ASSUME_NONNULL_END
@@ -34,4 +34,22 @@
34
34
  withMarkdownStyle:_markdownStyle];
35
35
  }
36
36
 
37
+ - (void)applyMarkdownFormatting:(nonnull NSMutableAttributedString *)attributedString
38
+ withDefaultTextAttributes:(nonnull NSDictionary<NSAttributedStringKey, id> *)defaultTextAttributes
39
+ markdownStyle:(nonnull RCTMarkdownStyle *)markdownStyle
40
+ parserId:(nonnull NSNumber *)parserId
41
+ {
42
+ // Keep the style/parserId assignment and the parse+format together under a
43
+ // single lock. The shadow node shares one instance across clones, and Fabric
44
+ // runs commits/layout optimistically on multiple threads, so without this the
45
+ // setters could interleave with another thread's parse/format and apply the
46
+ // wrong parserId/style for a frame. `@synchronized` is recursive, so nesting
47
+ // with `MarkdownParser`'s own `@synchronized(self)` in `parse:` is safe.
48
+ @synchronized (self) {
49
+ _markdownStyle = markdownStyle;
50
+ _parserId = parserId;
51
+ [self applyMarkdownFormatting:attributedString withDefaultTextAttributes:defaultTextAttributes];
52
+ }
53
+ }
54
+
37
55
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expensify/react-native-live-markdown",
3
- "version": "0.1.326",
3
+ "version": "0.1.328",
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",
@@ -64,12 +64,13 @@
64
64
  "@commitlint/config-conventional": "^17.0.2",
65
65
  "@evilmartians/lefthook": "^1.5.0",
66
66
  "@lwc/eslint-plugin-lwc": "^1.7.2",
67
- "@react-native-community/cli": "20.0.0",
68
- "@react-native-community/cli-platform-android": "20.0.0",
69
- "@react-native-community/cli-platform-ios": "20.0.0",
70
- "@react-native/babel-preset": "0.83.1",
71
- "@react-native/eslint-config": "0.83.1",
72
- "@react-native/metro-config": "0.83.1",
67
+ "@react-native-community/cli": "20.1.0",
68
+ "@react-native-community/cli-platform-android": "20.1.0",
69
+ "@react-native-community/cli-platform-ios": "20.1.0",
70
+ "@react-native/babel-preset": "0.85.3",
71
+ "@react-native/eslint-config": "0.85.3",
72
+ "@react-native/eslint-plugin": "^0.85.3",
73
+ "@react-native/metro-config": "0.85.3",
73
74
  "@release-it/conventional-changelog": "^5.0.0",
74
75
  "@types/jest": "^29.5.14",
75
76
  "@types/react": "^19.2.0",
@@ -89,6 +90,7 @@
89
90
  "eslint-plugin-jsx-a11y": "^6.8.0",
90
91
  "eslint-plugin-prettier": "^4.0.0",
91
92
  "eslint-plugin-promise": "^6.1.1",
93
+ "eslint-plugin-react-native": "^5.0.0",
92
94
  "eslint-plugin-tsdoc": "^0.2.17",
93
95
  "expensify-common": "2.0.148",
94
96
  "jest": "^29.6.3",
@@ -96,15 +98,15 @@
96
98
  "nodemon": "^3.1.3",
97
99
  "patch-package": "^8.0.0",
98
100
  "prettier": "2.8.8",
99
- "react": "19.2.0",
100
- "react-dom": "19.2.0",
101
- "react-native": "0.83.1",
101
+ "react": "19.2.3",
102
+ "react-dom": "19.2.3",
103
+ "react-native": "0.85.3",
102
104
  "react-native-builder-bob": "^0.30.2",
103
105
  "react-native-web": "^0.20.0",
106
+ "react-native-worklets": "0.9.1",
104
107
  "release-it": "^15.0.0",
105
108
  "turbo": "^1.10.7",
106
- "typescript": "^5.8.3",
107
- "react-native-worklets": "0.7.2"
109
+ "typescript": "^5.8.3"
108
110
  },
109
111
  "peerDependencies": {
110
112
  "expensify-common": ">=2.0.148",
@@ -112,11 +114,6 @@
112
114
  "react-native": "*",
113
115
  "react-native-worklets": ">=0.7.0"
114
116
  },
115
- "overrides": {
116
- "@expo/webpack-config": {
117
- "expo": "^54.0.32"
118
- }
119
- },
120
117
  "workspaces": [
121
118
  "./example",
122
119
  "./WebExample"