react-native-enriched 0.5.0 → 0.5.2

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.
@@ -65,6 +65,7 @@ import com.swmansion.enriched.textinput.utils.EnrichedSpanState
65
65
  import com.swmansion.enriched.textinput.utils.RichContentReceiver
66
66
  import com.swmansion.enriched.textinput.utils.mergeSpannables
67
67
  import com.swmansion.enriched.textinput.utils.setCheckboxClickListener
68
+ import com.swmansion.enriched.textinput.utils.zwsCountBefore
68
69
  import com.swmansion.enriched.textinput.watchers.EnrichedSpanWatcher
69
70
  import com.swmansion.enriched.textinput.watchers.EnrichedTextWatcher
70
71
  import java.util.regex.Pattern
@@ -623,13 +624,26 @@ class EnrichedTextInputView : AppCompatEditText {
623
624
  val start = selection?.start ?: return
624
625
  val end = selection.end
625
626
  val styleState = spanState?.getStyleStatePayload() ?: return
626
- val selectedText = text?.subSequence(start, end)?.toString() ?: ""
627
+ val currentText = text ?: return
628
+ val selectedText = currentText.subSequence(start, end).toString().replace(EnrichedConstants.ZWS_STRING, "")
629
+
630
+ val visibleStart = start - currentText.zwsCountBefore(start)
631
+ val visibleEnd = end - currentText.zwsCountBefore(end)
627
632
 
628
633
  val reactContext = context as ReactContext
629
634
  val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
630
635
  val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
631
636
  dispatcher?.dispatchEvent(
632
- OnContextMenuItemPressEvent(surfaceId, id, itemText, selectedText, start, end, styleState, experimentalSynchronousEvents),
637
+ OnContextMenuItemPressEvent(
638
+ surfaceId,
639
+ id,
640
+ itemText,
641
+ selectedText,
642
+ visibleStart,
643
+ visibleEnd,
644
+ styleState,
645
+ experimentalSynchronousEvents,
646
+ ),
633
647
  )
634
648
  }
635
649
 
@@ -819,14 +833,14 @@ class EnrichedTextInputView : AppCompatEditText {
819
833
  val isValid = verifyStyle(EnrichedSpans.LINK)
820
834
  if (!isValid) return
821
835
 
822
- parametrizedStyles?.setLinkSpan(start, end, text, url)
836
+ parametrizedStyles?.setLinkSpan(getActualIndex(start), getActualIndex(end), text, url)
823
837
  }
824
838
 
825
839
  fun removeLink(
826
840
  start: Int,
827
841
  end: Int,
828
842
  ) {
829
- parametrizedStyles?.removeLinkSpans(start, end)
843
+ parametrizedStyles?.removeLinkSpans(getActualIndex(start), getActualIndex(end))
830
844
  }
831
845
 
832
846
  fun addImage(
@@ -980,7 +994,8 @@ class EnrichedTextInputView : AppCompatEditText {
980
994
  super.onAttachedToWindow()
981
995
 
982
996
  // https://github.com/facebook/react-native/blob/36df97f500aa0aa8031098caf7526db358b6ddc1/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt#L946
983
- super.setTextIsSelectable(true)
997
+ // setTextIsSelectable internally calls setText(), which fires afterTextChanged that should be marked as a transaction to avoid unwanted side effects
998
+ runAsATransaction { super.setTextIsSelectable(true) }
984
999
 
985
1000
  if (autoFocus && !didAttachToWindow) {
986
1001
  requestFocusProgrammatically()
@@ -233,14 +233,16 @@ class EnrichedSelection(
233
233
  val surfaceId = UIManagerHelper.getSurfaceId(context)
234
234
  val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
235
235
 
236
- val text = editable.substring(start, end)
236
+ val visibleStart = start - editable.zwsCountBefore(start)
237
+ val visibleEnd = end - editable.zwsCountBefore(end)
238
+ val text = editable.substring(start, end).replace(EnrichedConstants.ZWS_STRING, "")
237
239
  dispatcher?.dispatchEvent(
238
240
  OnChangeSelectionEvent(
239
241
  surfaceId,
240
242
  view.id,
241
243
  text,
242
- start,
243
- end,
244
+ visibleStart,
245
+ visibleEnd,
244
246
  view.experimentalSynchronousEvents,
245
247
  ),
246
248
  )
@@ -252,7 +254,7 @@ class EnrichedSelection(
252
254
  start: Int,
253
255
  end: Int,
254
256
  ) {
255
- val text = spannable.substring(start, end)
257
+ val text = spannable.substring(start, end).replace(EnrichedConstants.ZWS_STRING, "")
256
258
  val url = span?.getUrl() ?: ""
257
259
 
258
260
  // Prevents emitting unnecessary events
@@ -261,6 +263,9 @@ class EnrichedSelection(
261
263
  previousLinkDetectedEvent.put("text", text)
262
264
  previousLinkDetectedEvent.put("url", url)
263
265
 
266
+ val visibleStart = start - spannable.zwsCountBefore(start)
267
+ val visibleEnd = end - spannable.zwsCountBefore(end)
268
+
264
269
  val context = view.context as ReactContext
265
270
  val surfaceId = UIManagerHelper.getSurfaceId(context)
266
271
  val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
@@ -270,8 +275,8 @@ class EnrichedSelection(
270
275
  view.id,
271
276
  text,
272
277
  url,
273
- start,
274
- end,
278
+ visibleStart,
279
+ visibleEnd,
275
280
  view.experimentalSynchronousEvents,
276
281
  ),
277
282
  )
@@ -3,6 +3,14 @@ package com.swmansion.enriched.textinput.utils
3
3
  import android.text.SpannableStringBuilder
4
4
  import com.swmansion.enriched.common.EnrichedConstants
5
5
 
6
+ fun CharSequence.zwsCountBefore(index: Int): Int {
7
+ var count = 0
8
+ for (i in 0 until index) {
9
+ if (this[i] == EnrichedConstants.ZWS) count++
10
+ }
11
+ return count
12
+ }
13
+
6
14
  // Removes zero-width spaces from the given range in the SpannableStringBuilder without affecting spans
7
15
  fun SpannableStringBuilder.removeZWS(
8
16
  start: Int,
@@ -40,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN
40
40
  - (void)emitOnMentionEvent:(NSString *)indicator text:(nullable NSString *)text;
41
41
  - (void)emitOnPasteImagesEvent:(NSArray<NSDictionary *> *)images;
42
42
  - (void)anyTextMayHaveBeenModified;
43
+ - (void)scheduleRelayoutIfNeeded;
43
44
  - (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range;
44
45
  - (NSArray<NSNumber *> *)getPresentStyleTypesFrom:(NSArray<NSNumber *> *)types
45
46
  range:(NSRange)range;
@@ -717,18 +717,6 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
717
717
  // now set the new config
718
718
  config = newConfig;
719
719
 
720
- // no emitting during styles reload
721
- blockEmitting = YES;
722
-
723
- // make sure everything is sound in the html
724
- NSString *initiallyProcessedHtml =
725
- [parser initiallyProcessHtml:currentHtml];
726
- if (initiallyProcessedHtml != nullptr) {
727
- [parser replaceWholeFromHtml:initiallyProcessedHtml];
728
- }
729
-
730
- blockEmitting = NO;
731
-
732
720
  // fill the typing attributes with style props
733
721
  defaultTypingAttributes[NSForegroundColorAttributeName] =
734
722
  [config primaryColor];
@@ -742,6 +730,18 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
742
730
  defaultPStyle.minimumLineHeight = [config scaledPrimaryLineHeight];
743
731
  defaultTypingAttributes[NSParagraphStyleAttributeName] = defaultPStyle;
744
732
 
733
+ // no emitting during styles reload
734
+ blockEmitting = YES;
735
+
736
+ // make sure everything is sound in the html
737
+ NSString *initiallyProcessedHtml =
738
+ [parser initiallyProcessHtml:currentHtml];
739
+ if (initiallyProcessedHtml != nullptr) {
740
+ [parser replaceWholeFromHtml:initiallyProcessedHtml];
741
+ }
742
+
743
+ blockEmitting = NO;
744
+
745
745
  textView.typingAttributes = defaultTypingAttributes;
746
746
  textView.selectedRange = prevSelectedRange;
747
747
 
@@ -6,6 +6,17 @@
6
6
 
7
7
  @implementation InputTextView
8
8
 
9
+ - (void)layoutSubviews {
10
+ [super layoutSubviews];
11
+ // UITextView resets contentSize during its own layout pass (triggered when
12
+ // the frame is set on first mount). Re-schedule a relayout so our explicit
13
+ // contentSize is applied after UITextView finishes its internal layout.
14
+ EnrichedTextInputView *input = (EnrichedTextInputView *)_input;
15
+ if (input != nil) {
16
+ [input scheduleRelayoutIfNeeded];
17
+ }
18
+ }
19
+
9
20
  - (void)copy:(id)sender {
10
21
  EnrichedTextInputView *typedInput = (EnrichedTextInputView *)_input;
11
22
  if (typedInput == nullptr) {
@@ -74,12 +85,12 @@
74
85
  NSString *type = item.allKeys[j];
75
86
  if ([type isEqual:UTTypeJPEG.identifier] ||
76
87
  [type isEqual:UTTypePNG.identifier] ||
77
- [type isEqual:UTTypeWebP.identifier] ||
78
88
  [type isEqual:UTTypeHEIC.identifier] ||
79
89
  [type isEqual:UTTypeTIFF.identifier]) {
80
90
  imageData = [self getDataForImageItem:item[type] type:type];
81
- } else if ([type isEqual:UTTypeGIF.identifier]) {
82
- // gifs
91
+ } else if ([type isEqual:UTTypeWebP.identifier] ||
92
+ [type isEqual:UTTypeGIF.identifier]) {
93
+ // webp and gifs: read raw bytes directly — no re-encoding needed
83
94
  imageData = [pasteboard dataForPasteboardType:type];
84
95
  }
85
96
  if (!imageData) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-enriched",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Rich Text Editor component for React Native",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",
@@ -49,7 +49,10 @@
49
49
  "prepare": "bob build",
50
50
  "release": "release-it",
51
51
  "android-studio": "open -a 'Android Studio' apps/example/android",
52
- "xcode": "open -a 'Xcode' apps/example/ios/EnrichedTextInputExample.xcworkspace"
52
+ "xcode": "open -a 'Xcode' apps/example/ios/EnrichedTextInputExample.xcworkspace",
53
+ "test:e2e": ".maestro/scripts/run-tests-all.sh",
54
+ "test:e2e:android": ".maestro/scripts/run-tests.sh --platform android",
55
+ "test:e2e:ios": ".maestro/scripts/run-tests.sh --platform ios"
53
56
  },
54
57
  "keywords": [
55
58
  "react-native",