react-native-enriched-markdown-scaffold 0.7.4-scaffold.2 → 0.7.4-scaffold.4

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.
@@ -57,6 +57,13 @@ class EnrichedMarkdownTextInputManager :
57
57
  stateWrapper: StateWrapper?,
58
58
  ): Any? {
59
59
  view.stateWrapper = stateWrapper
60
+ // Fabric applies props before it hands over the state wrapper, so every layout
61
+ // invalidation raised during the mount transaction was dropped. Replay it now that we can
62
+ // actually push state — otherwise the shadow node keeps the initialMeasure() estimate,
63
+ // which ignores math chips, until the first keystroke.
64
+ if (stateWrapper != null) {
65
+ view.layoutManager.flushPendingInvalidation()
66
+ }
60
67
  return super.updateState(view, props, stateWrapper)
61
68
  }
62
69
 
@@ -69,6 +76,11 @@ class EnrichedMarkdownTextInputManager :
69
76
  view.dismissActiveMention()
70
77
  super.onDropViewInstance(view)
71
78
  view.layoutManager.release()
79
+ // Unreachable while view recycling is off — it needs a `setupViewRecycling()` call from this
80
+ // constructor *and* ReactNativeFeatureFlags.enableViewRecycling(). Kept because the flush
81
+ // above turns a recycled view into a view that pushes state at mount: without this, it would
82
+ // push into the shadow node it was dropped from.
83
+ view.stateWrapper = null
72
84
  }
73
85
 
74
86
  override fun measure(
@@ -708,8 +708,16 @@ class EnrichedMarkdownTextInputView(
708
708
  }
709
709
  invalidate()
710
710
  requestLayout()
711
+ // Chips only reach the text here when `defaultValue` landed before `markdownStyle`
712
+ // (@ReactProp order is undefined). requestLayout() cannot change a height Fabric owns —
713
+ // the shadow tree has to re-measure text that now carries the spans.
714
+ layoutManager.invalidateLayout()
711
715
  } else if (mathStyleChanged) {
712
716
  refreshAllMathChips()
717
+ // A math font size / padding change resizes every chip, so the measured height moves.
718
+ // Colour-only changes end up here too; store() then measures the same size and skips
719
+ // the state update.
720
+ layoutManager.invalidateLayout()
713
721
  }
714
722
  return changed
715
723
  }
@@ -8,21 +8,49 @@ class InputLayoutManager(
8
8
  ) {
9
9
  private var forceHeightRecalculationCounter = 0
10
10
 
11
+ /**
12
+ * An invalidation that arrived before `stateWrapper` existed. Fabric applies every prop
13
+ * (and runs `onAfterUpdateTransaction`) inside `ViewManager.updateProperties`, which runs
14
+ * *before* `updateState` assigns the wrapper — so the whole mount transaction invalidates
15
+ * into the void. Without this flag the measurement store stays empty and Yoga keeps the
16
+ * `initialMeasure()` approximation until the next text/prop change.
17
+ */
18
+ private var pending = false
19
+
11
20
  fun invalidateLayout() {
12
- if (view.stateWrapper == null) return
21
+ val stateWrapper = view.stateWrapper
22
+ if (stateWrapper == null) {
23
+ pending = true
24
+ return
25
+ }
26
+ pending = false
13
27
 
14
28
  val text = view.text
15
29
  val paint = view.paint
16
30
 
31
+ // First call for this view measures against cachedWidth = 0 and stores a throwaway size;
32
+ // Yoga immediately re-measures through getMeasureById() with the real width and overwrites
33
+ // it. The state update below is what makes that second pass happen.
17
34
  val needUpdate = InputMeasurementStore.store(view.id, text, paint)
18
35
  if (!needUpdate) return
19
36
 
20
37
  val state = Arguments.createMap()
21
38
  state.putInt("forceHeightRecalculationCounter", forceHeightRecalculationCounter++)
22
- view.stateWrapper?.updateState(state)
39
+ stateWrapper.updateState(state)
40
+ }
41
+
42
+ /**
43
+ * Replays an invalidation that was dropped while `stateWrapper` was null. Called once the
44
+ * wrapper is assigned, i.e. after every prop of the mount transaction has been applied — so
45
+ * the text carries its math chips and the paint carries the resolved typeface/size.
46
+ */
47
+ fun flushPendingInvalidation() {
48
+ if (!pending) return
49
+ invalidateLayout()
23
50
  }
24
51
 
25
52
  fun release() {
53
+ pending = false
26
54
  InputMeasurementStore.release(view.id)
27
55
  }
28
56
  }
@@ -123,6 +123,45 @@ static char kENRMSegmentFadeAnimatorKey;
123
123
  return concreteComponentDescriptorProvider<EnrichedMarkdownComponentDescriptor>();
124
124
  }
125
125
 
126
+ /// Clears every piece of state that `updateProps` derives from props, back to the
127
+ /// values the default-constructed `EnrichedMarkdownProps` describes.
128
+ ///
129
+ /// `updateProps` applies most props as a per-field diff against `_props`, so this
130
+ /// state is only correct while it stays in phase with the `_props` baseline it was
131
+ /// diffed from. `prepareForRecycle` resets `_props`, so it must reset this too —
132
+ /// otherwise every field the next consumer leaves at its default silently inherits
133
+ /// the previous consumer's value. Called from `initWithFrame:` as well, so the two
134
+ /// baselines cannot drift apart.
135
+ ///
136
+ /// Requires `_fontScaleObserver` to already exist.
137
+ - (void)resetPropDerivedState
138
+ {
139
+ // Discarded rather than cleared field-by-field: the style diff can only clear a
140
+ // field when new and old differ, which never holds against a reset baseline.
141
+ _config = nil;
142
+
143
+ _md4cFlags = [ENRMMd4cFlags propsDefaultFlags];
144
+ _maxFontSizeMultiplier = 0;
145
+ _allowTrailingMargin = NO;
146
+ _fontScaleObserver.allowFontScaling = YES;
147
+ _streamingAnimation = NO;
148
+ _tableStreamingMode = ENRMTableStreamingModeProgressive;
149
+ _spoilerOverlay = ENRMSpoilerOverlayParticles;
150
+ _lineBreakStrategy = NSLineBreakStrategyNone;
151
+ _writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
152
+ _contextMenuItemTexts = nil;
153
+ _contextMenuItemIcons = nil;
154
+ _accessibilityLabels = nil;
155
+
156
+ // Written only once a render completes, so stale until the next one lands.
157
+ _renderedStyleFingerprint = 0;
158
+ _pendingStyleFingerprint = 0;
159
+
160
+ _dirtyFlags = ENRMDirtyNone;
161
+ _heightUpdateCounter = 0;
162
+ _state = nullptr;
163
+ }
164
+
126
165
  - (instancetype)initWithFrame:(CGRect)frame
127
166
  {
128
167
  if (self = [super initWithFrame:frame]) {
@@ -131,28 +170,22 @@ static char kENRMSegmentFadeAnimatorKey;
131
170
 
132
171
  self.backgroundColor = [RCTUIColor clearColor];
133
172
  _parser = [[ENRMMarkdownParser alloc] init];
134
- _md4cFlags = [ENRMMd4cFlags defaultFlags];
135
173
  _segmentViews = [NSMutableArray array];
136
174
  _segmentSignatures = [NSMutableArray array];
137
- _dirtyFlags = ENRMDirtyNone;
138
175
  [self configureSegmentViewRegistry];
139
176
 
140
177
  _renderCoordinator =
141
178
  [[ENRMAsyncRenderCoordinator alloc] initWithQueueLabel:"com.swmansion.enriched.markdown.container.render"];
142
179
 
143
- _maxFontSizeMultiplier = 0;
144
- _allowTrailingMargin = NO;
145
180
  _selectable = YES;
146
181
  _enableLinkPreview = YES;
147
- _streamingAnimation = NO;
148
- _tableStreamingMode = ENRMTableStreamingModeProgressive;
149
182
  _selectionMenuConfig = (ENRMSelectionMenuConfig){.copyAsMarkdown = YES, .copyImageURL = YES};
150
- _lineBreakStrategy = NSLineBreakStrategyNone;
151
- _writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
152
183
  _resolvedLayoutDirection =
153
184
  [[RCTI18nUtil sharedInstance] isRTL] ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
154
185
 
155
186
  _fontScaleObserver = [[FontScaleObserver alloc] init];
187
+ [self resetPropDerivedState];
188
+
156
189
  __weak EnrichedMarkdown *weakSelf = self;
157
190
  _fontScaleObserver.onChange = ^{
158
191
  EnrichedMarkdown *strongSelf = weakSelf;
@@ -624,7 +657,7 @@ static char kENRMSegmentFadeAnimatorKey;
624
657
  selectionEnd:selectionEnd];
625
658
  });
626
659
  return buildEditMenuForSelection(textView.textStorage, textView.selectedRange, segmentMarkdown, strongSelf->_config,
627
- @[ baseMenu ], customItems, strongSelf -> _selectionMenuConfig);
660
+ @[ baseMenu ], customItems, strongSelf->_selectionMenuConfig);
628
661
  }];
629
662
  #endif
630
663
 
@@ -949,9 +982,8 @@ static char kENRMSegmentFadeAnimatorKey;
949
982
 
950
983
  _cachedMarkdown = nil;
951
984
  _renderedMarkdown = nil;
952
- _streamingAnimation = NO;
953
- _tableStreamingMode = ENRMTableStreamingModeProgressive;
954
- _dirtyFlags = ENRMDirtyNone;
985
+
986
+ [self resetPropDerivedState];
955
987
 
956
988
  [super prepareForRecycle];
957
989
  }
@@ -46,6 +46,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
46
46
  };
47
47
 
48
48
  @interface EnrichedMarkdownText () <RCTEnrichedMarkdownTextViewProtocol, UITextViewDelegate>
49
+ - (void)resetPropDerivedState;
49
50
  - (void)setupTextView;
50
51
  - (void)renderMarkdownContent:(NSString *)markdownString;
51
52
  - (void)applyRenderedText:(NSMutableAttributedString *)attributedText;
@@ -177,6 +178,46 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
177
178
  ENRMRequestHeightUpdate<EnrichedMarkdownTextState>(_state, _heightUpdateCounter, self);
178
179
  }
179
180
 
181
+ /// Clears every piece of state that `updateProps` derives from props, back to the
182
+ /// values the default-constructed `EnrichedMarkdownTextProps` describes.
183
+ ///
184
+ /// `updateProps` applies most props as a per-field diff against `_props`, so this
185
+ /// state is only correct while it stays in phase with the `_props` baseline it was
186
+ /// diffed from. `prepareForRecycle` resets `_props`, so it must reset this too —
187
+ /// otherwise every field the next consumer leaves at its default silently inherits
188
+ /// the previous consumer's value. Called from `initWithFrame:` as well, so the two
189
+ /// baselines cannot drift apart.
190
+ ///
191
+ /// Requires `_fontScaleObserver` to already exist.
192
+ - (void)resetPropDerivedState
193
+ {
194
+ // Discarded rather than cleared field-by-field: the style diff can only clear a
195
+ // field when new and old differ, which never holds against a reset baseline.
196
+ _config = nil;
197
+ _spoilerManager = nil;
198
+
199
+ _md4cFlags = [ENRMMd4cFlags propsDefaultFlags];
200
+ _maxFontSizeMultiplier = 0;
201
+ _allowTrailingMargin = NO;
202
+ _fontScaleObserver.allowFontScaling = YES;
203
+ _streamingAnimation = NO;
204
+ _lineBreakStrategy = NSLineBreakStrategyNone;
205
+ _writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
206
+ _contextMenuItemTexts = nil;
207
+ _contextMenuItemIcons = nil;
208
+ _accessibilityLabels = nil;
209
+
210
+ // Written only once a render completes, so stale until the next one lands.
211
+ _renderedStyleFingerprint = 0;
212
+ _pendingStyleFingerprint = 0;
213
+ _lastElementMarginBottom = 0;
214
+
215
+ _dirtyFlags = ENRMDirtyNone;
216
+ _forceHeightUpdateOnNextRender = NO;
217
+ _heightUpdateCounter = 0;
218
+ _state = nullptr;
219
+ }
220
+
180
221
  - (instancetype)initWithFrame:(CGRect)frame
181
222
  {
182
223
  if (self = [super initWithFrame:frame]) {
@@ -185,23 +226,18 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
185
226
 
186
227
  self.backgroundColor = [RCTUIColor clearColor];
187
228
  _parser = [[ENRMMarkdownParser alloc] init];
188
- _md4cFlags = [ENRMMd4cFlags defaultFlags];
189
229
 
190
230
  _renderCoordinator =
191
231
  [[ENRMAsyncRenderCoordinator alloc] initWithQueueLabel:"com.swmansion.enriched.markdown.render"];
192
232
 
193
- _maxFontSizeMultiplier = 0;
194
- _allowTrailingMargin = NO;
195
233
  _enableLinkPreview = YES;
196
- _forceHeightUpdateOnNextRender = NO;
197
234
  _selectionMenuConfig = (ENRMSelectionMenuConfig){.copyAsMarkdown = YES, .copyImageURL = YES};
198
- _lineBreakStrategy = NSLineBreakStrategyNone;
199
- _writingDirectionMode = ENRMWritingDirectionModeFirstStrong;
200
235
  _resolvedLayoutDirection =
201
236
  [[RCTI18nUtil sharedInstance] isRTL] ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
202
- _dirtyFlags = ENRMDirtyNone;
203
237
 
204
238
  _fontScaleObserver = [[FontScaleObserver alloc] init];
239
+ [self resetPropDerivedState];
240
+
205
241
  __weak EnrichedMarkdownText *weakSelf = self;
206
242
  _fontScaleObserver.onChange = ^{
207
243
  EnrichedMarkdownText *strongSelf = weakSelf;
@@ -253,8 +289,7 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
253
289
  selectionEnd:selectionEnd];
254
290
  });
255
291
  return buildEditMenuForSelection(textView.textStorage, textView.selectedRange, strongSelf->_cachedMarkdown,
256
- strongSelf->_config, @[ baseMenu ], customItems,
257
- strongSelf -> _selectionMenuConfig);
292
+ strongSelf->_config, @[ baseMenu ], customItems, strongSelf->_selectionMenuConfig);
258
293
  };
259
294
  #endif
260
295
 
@@ -636,14 +671,15 @@ typedef NS_OPTIONS(NSUInteger, ENRMDirtyFlags) {
636
671
  [_fadeAnimator cancel];
637
672
  _fadeAnimator = nil;
638
673
  _previousTextLength = 0;
639
- _streamingAnimation = NO;
640
- _forceHeightUpdateOnNextRender = NO;
641
674
  _cachedMarkdown = nil;
642
675
  _renderedMarkdown = nil;
643
676
  _accessibilityElements = nil;
644
677
  _accessibilityInfo = nil;
645
678
  _accessibilityNeedsRebuild = NO;
646
679
  [_spoilerManager removeAllOverlays];
680
+
681
+ [self resetPropDerivedState];
682
+
647
683
  if (_textView != nil) {
648
684
  ENRMSetAttributedText(_textView, [[NSAttributedString alloc] initWithString:@""]);
649
685
  _textView.hidden = YES;
@@ -11,6 +11,14 @@
11
11
 
12
12
  + (instancetype)defaultFlags;
13
13
 
14
+ /// Flags matching the codegen `md4cFlags` Props defaults (every flag off).
15
+ ///
16
+ /// Components mirror `md4cFlags` into an ivar with a per-field diff against the
17
+ /// props baseline, so the ivar has to start from the same baseline the props do.
18
+ /// `defaultFlags` enables `latexMath`, which is the right default for the
19
+ /// standalone parser API but would make a `latexMath: false` prop a no-op.
20
+ + (instancetype)propsDefaultFlags;
21
+
14
22
  @end
15
23
 
16
24
  @interface ENRMMarkdownParser : NSObject
@@ -22,6 +22,17 @@ extern MarkdownASTNode *parseMarkdownWithCppParser(NSString *markdown, ENRMMd4cF
22
22
  return [[ENRMMd4cFlags alloc] init];
23
23
  }
24
24
 
25
+ + (instancetype)propsDefaultFlags
26
+ {
27
+ ENRMMd4cFlags *flags = [[ENRMMd4cFlags alloc] init];
28
+ flags.underline = NO;
29
+ flags.latexMath = NO;
30
+ flags.superscript = NO;
31
+ flags.subscript = NO;
32
+ flags.highlight = NO;
33
+ return flags;
34
+ }
35
+
25
36
  - (id)copyWithZone:(NSZone *)zone
26
37
  {
27
38
  ENRMMd4cFlags *copy = [[ENRMMd4cFlags allocWithZone:zone] init];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-enriched-markdown-scaffold",
3
- "version": "0.7.4-scaffold.2",
3
+ "version": "0.7.4-scaffold.4",
4
4
  "description": "Markdown Text component for React Native",
5
5
  "main": "./lib/module/index",
6
6
  "module": "./lib/module/index",