react-native-enriched-html 1.0.0 → 1.0.1

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.
Files changed (272) hide show
  1. package/android/src/main/java/com/swmansion/enriched/common/EnrichedSpanFlags.kt +39 -0
  2. package/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedParser.java +43 -20
  3. package/android/src/main/java/com/swmansion/enriched/common/parser/EnrichedSpanFactory.kt +1 -0
  4. package/android/src/main/java/com/swmansion/enriched/text/EnrichedTextSpanFactory.kt +1 -0
  5. package/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt +2 -1
  6. package/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputSpannableFactory.kt +2 -1
  7. package/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt +8 -5
  8. package/android/src/main/java/com/swmansion/enriched/textinput/events/MentionHandler.kt +12 -2
  9. package/android/src/main/java/com/swmansion/enriched/textinput/events/OnMentionEvent.kt +6 -0
  10. package/android/src/main/java/com/swmansion/enriched/textinput/styles/AlignmentStyles.kt +6 -7
  11. package/android/src/main/java/com/swmansion/enriched/textinput/styles/InlineStyles.kt +13 -1
  12. package/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt +4 -3
  13. package/android/src/main/java/com/swmansion/enriched/textinput/styles/ParagraphStyles.kt +4 -3
  14. package/android/src/main/java/com/swmansion/enriched/textinput/styles/ParametrizedStyles.kt +38 -11
  15. package/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSelection.kt +2 -0
  16. package/android/src/main/java/com/swmansion/enriched/textinput/utils/EnrichedSpannable.kt +2 -1
  17. package/android/src/main/java/com/swmansion/enriched/textinput/utils/Utils.kt +2 -1
  18. package/cpp/parser/GumboNormalizer.c +61 -3
  19. package/cpp/tests/GumboParserTest.cpp +54 -0
  20. package/ios/EnrichedTextInputView.mm +37 -25
  21. package/ios/extensions/LayoutManagerExtension.mm +73 -62
  22. package/ios/extensions/StringExtension.mm +75 -0
  23. package/ios/htmlParser/HtmlParser.h +4 -1
  24. package/ios/htmlParser/HtmlParser.mm +23 -5
  25. package/ios/inputHtmlParser/InputHtmlParser.mm +6 -3
  26. package/ios/interfaces/StyleHeaders.h +4 -0
  27. package/ios/styles/LinkStyle.mm +20 -34
  28. package/ios/styles/MentionStyle.mm +55 -4
  29. package/ios/textHtmlParser/TextHtmlParser.mm +2 -1
  30. package/ios/utils/ParagraphAttributesUtils.mm +7 -0
  31. package/lib/module/index.js +1 -0
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/utils/expandMentionStylesForIndicators.js +2 -3
  34. package/lib/module/utils/expandMentionStylesForIndicators.js.map +1 -1
  35. package/lib/module/utils/normalizeHtmlStyle.js +1 -1
  36. package/lib/module/utils/normalizeHtmlStyle.js.map +1 -1
  37. package/lib/module/web/EnrichedText.css +343 -0
  38. package/lib/module/web/EnrichedText.js +55 -0
  39. package/lib/module/web/EnrichedText.js.map +1 -0
  40. package/lib/module/web/EnrichedTextInput.js +54 -52
  41. package/lib/module/web/EnrichedTextInput.js.map +1 -1
  42. package/lib/module/web/constants/brokenImageGlyph.js +9 -0
  43. package/lib/module/web/constants/brokenImageGlyph.js.map +1 -0
  44. package/lib/module/web/constants/classNames.js +5 -0
  45. package/lib/module/web/constants/classNames.js.map +1 -0
  46. package/lib/module/web/emitLinkDetected.js +12 -0
  47. package/lib/module/web/emitLinkDetected.js.map +1 -0
  48. package/lib/module/web/formats/EnrichedBlockquote.js +1 -1
  49. package/lib/module/web/formats/EnrichedBlockquote.js.map +1 -1
  50. package/lib/module/web/formats/EnrichedCheckboxList.js +2 -2
  51. package/lib/module/web/formats/EnrichedCheckboxList.js.map +1 -1
  52. package/lib/module/web/formats/EnrichedCodeBlock.js +1 -1
  53. package/lib/module/web/formats/EnrichedCodeBlock.js.map +1 -1
  54. package/lib/module/web/formats/EnrichedHeading.js +1 -1
  55. package/lib/module/web/formats/EnrichedHeading.js.map +1 -1
  56. package/lib/module/web/formats/EnrichedImageNodeView.js +1 -1
  57. package/lib/module/web/formats/EnrichedImageNodeView.js.map +1 -1
  58. package/lib/module/web/formats/EnrichedLink.js +43 -3
  59. package/lib/module/web/formats/EnrichedLink.js.map +1 -1
  60. package/lib/module/web/formats/EnrichedOrderedList.js +2 -2
  61. package/lib/module/web/formats/EnrichedOrderedList.js.map +1 -1
  62. package/lib/module/web/formats/EnrichedTextAlign.js +82 -0
  63. package/lib/module/web/formats/EnrichedTextAlign.js.map +1 -0
  64. package/lib/module/web/formats/EnrichedUnorderedList.js +2 -2
  65. package/lib/module/web/formats/EnrichedUnorderedList.js.map +1 -1
  66. package/lib/module/web/formats/applyWrappingListToSelection.js +3 -2
  67. package/lib/module/web/formats/applyWrappingListToSelection.js.map +1 -1
  68. package/lib/module/web/formats/formatRules.js +39 -3
  69. package/lib/module/web/formats/formatRules.js.map +1 -1
  70. package/lib/module/web/formats/listKeyboard.js +4 -1
  71. package/lib/module/web/formats/listKeyboard.js.map +1 -1
  72. package/lib/module/web/normalization/checkboxHtmlNormalizer.js.map +1 -0
  73. package/lib/module/web/normalization/htmlNormalizer.js +514 -0
  74. package/lib/module/web/normalization/htmlNormalizer.js.map +1 -0
  75. package/lib/module/web/normalization/prepareHtmlForWeb.js +78 -0
  76. package/lib/module/web/normalization/prepareHtmlForWeb.js.map +1 -0
  77. package/lib/module/web/{tiptapHtmlNormalizer.js → normalization/tiptapHtmlNormalizer.js} +6 -2
  78. package/lib/module/web/normalization/tiptapHtmlNormalizer.js.map +1 -0
  79. package/lib/module/web/pmPlugins/AutolinkPlugin/autolinkRegex.js +27 -0
  80. package/lib/module/web/pmPlugins/AutolinkPlugin/autolinkRegex.js.map +1 -0
  81. package/lib/module/web/pmPlugins/AutolinkPlugin/index.js +178 -0
  82. package/lib/module/web/pmPlugins/AutolinkPlugin/index.js.map +1 -0
  83. package/lib/module/web/pmPlugins/MentionPlugin/index.js +1 -1
  84. package/lib/module/web/pmPlugins/MentionPlugin/index.js.map +1 -1
  85. package/lib/module/web/pmPlugins/MentionPlugin/makeMentionPluginState.js +23 -4
  86. package/lib/module/web/pmPlugins/MentionPlugin/makeMentionPluginState.js.map +1 -1
  87. package/lib/module/web/pmPlugins/MentionPlugin/setMention.js +17 -18
  88. package/lib/module/web/pmPlugins/MentionPlugin/setMention.js.map +1 -1
  89. package/lib/module/web/pmPlugins/MentionPlugin/useMentionEvents.js +105 -0
  90. package/lib/module/web/pmPlugins/MentionPlugin/useMentionEvents.js.map +1 -0
  91. package/lib/module/web/pmPlugins/ShortcutPlugin.js +11 -0
  92. package/lib/module/web/pmPlugins/ShortcutPlugin.js.map +1 -1
  93. package/lib/module/web/pmPlugins/StrictMarksPlugin.js +2 -0
  94. package/lib/module/web/pmPlugins/StrictMarksPlugin.js.map +1 -1
  95. package/lib/module/web/pmPlugins/StripBoldInStyledHeadingsPlugin.js.map +1 -1
  96. package/lib/module/web/sanitization/htmlSanitizer.js +10 -0
  97. package/lib/module/web/sanitization/htmlSanitizer.js.map +1 -0
  98. package/lib/module/web/styleConversion/buildMentionRulesCSS.js +12 -6
  99. package/lib/module/web/styleConversion/buildMentionRulesCSS.js.map +1 -1
  100. package/lib/module/web/styleConversion/enrichedBaseStyleToCSSProperties.js +159 -0
  101. package/lib/module/web/styleConversion/enrichedBaseStyleToCSSProperties.js.map +1 -0
  102. package/lib/module/web/styleConversion/enrichedInputStyleToCSSProperties.js +3 -155
  103. package/lib/module/web/styleConversion/enrichedInputStyleToCSSProperties.js.map +1 -1
  104. package/lib/module/web/styleConversion/enrichedTextStyleToCSSProperties.js +44 -0
  105. package/lib/module/web/styleConversion/enrichedTextStyleToCSSProperties.js.map +1 -0
  106. package/lib/module/web/styleConversion/{enrichedInputThemingToCSSProperties.js → enrichedThemingToCSSProperties.js} +3 -3
  107. package/lib/module/web/styleConversion/enrichedThemingToCSSProperties.js.map +1 -0
  108. package/lib/module/web/styleConversion/htmlStyleToCSSVariables.js +87 -64
  109. package/lib/module/web/styleConversion/htmlStyleToCSSVariables.js.map +1 -1
  110. package/lib/module/web/styleConversion/inlineImageCSSVariables.js +7 -0
  111. package/lib/module/web/styleConversion/inlineImageCSSVariables.js.map +1 -0
  112. package/lib/module/web/useImageErrorFallback.js +33 -0
  113. package/lib/module/web/useImageErrorFallback.js.map +1 -0
  114. package/lib/module/web/useOnChangeHtml.js +1 -1
  115. package/lib/module/web/useOnChangeHtml.js.map +1 -1
  116. package/lib/module/web/useOnChangeState.js +6 -3
  117. package/lib/module/web/useOnChangeState.js.map +1 -1
  118. package/lib/module/web/useOnLinkDetected.js +32 -45
  119. package/lib/module/web/useOnLinkDetected.js.map +1 -1
  120. package/lib/module/web/usePressInteractions.js +19 -0
  121. package/lib/module/web/usePressInteractions.js.map +1 -0
  122. package/lib/module/web/useStableRef.js +13 -0
  123. package/lib/module/web/useStableRef.js.map +1 -0
  124. package/lib/typescript/src/index.d.ts +1 -0
  125. package/lib/typescript/src/index.d.ts.map +1 -1
  126. package/lib/typescript/src/native/EnrichedText.d.ts +1 -1
  127. package/lib/typescript/src/native/EnrichedText.d.ts.map +1 -1
  128. package/lib/typescript/src/native/EnrichedTextInput.d.ts +1 -1
  129. package/lib/typescript/src/native/EnrichedTextInput.d.ts.map +1 -1
  130. package/lib/typescript/src/types.d.ts +1 -1
  131. package/lib/typescript/src/types.d.ts.map +1 -1
  132. package/lib/typescript/src/utils/expandMentionStylesForIndicators.d.ts +1 -1
  133. package/lib/typescript/src/utils/expandMentionStylesForIndicators.d.ts.map +1 -1
  134. package/lib/typescript/src/utils/normalizeHtmlStyle.d.ts.map +1 -1
  135. package/lib/typescript/src/web/EnrichedText.d.ts +4 -0
  136. package/lib/typescript/src/web/EnrichedText.d.ts.map +1 -0
  137. package/lib/typescript/src/web/EnrichedTextInput.d.ts +2 -2
  138. package/lib/typescript/src/web/EnrichedTextInput.d.ts.map +1 -1
  139. package/lib/typescript/src/web/constants/brokenImageGlyph.d.ts +3 -0
  140. package/lib/typescript/src/web/constants/brokenImageGlyph.d.ts.map +1 -0
  141. package/lib/typescript/src/web/constants/classNames.d.ts +3 -0
  142. package/lib/typescript/src/web/constants/classNames.d.ts.map +1 -0
  143. package/lib/typescript/src/web/emitLinkDetected.d.ts +10 -0
  144. package/lib/typescript/src/web/emitLinkDetected.d.ts.map +1 -0
  145. package/lib/typescript/src/web/formats/EnrichedBlockquote.d.ts.map +1 -1
  146. package/lib/typescript/src/web/formats/EnrichedCheckboxList.d.ts.map +1 -1
  147. package/lib/typescript/src/web/formats/EnrichedCodeBlock.d.ts.map +1 -1
  148. package/lib/typescript/src/web/formats/EnrichedHeading.d.ts.map +1 -1
  149. package/lib/typescript/src/web/formats/EnrichedImageNodeView.d.ts +1 -1
  150. package/lib/typescript/src/web/formats/EnrichedImageNodeView.d.ts.map +1 -1
  151. package/lib/typescript/src/web/formats/EnrichedLink.d.ts +4 -1
  152. package/lib/typescript/src/web/formats/EnrichedLink.d.ts.map +1 -1
  153. package/lib/typescript/src/web/formats/EnrichedOrderedList.d.ts.map +1 -1
  154. package/lib/typescript/src/web/formats/EnrichedTextAlign.d.ts +2 -0
  155. package/lib/typescript/src/web/formats/EnrichedTextAlign.d.ts.map +1 -0
  156. package/lib/typescript/src/web/formats/EnrichedUnorderedList.d.ts.map +1 -1
  157. package/lib/typescript/src/web/formats/applyWrappingListToSelection.d.ts.map +1 -1
  158. package/lib/typescript/src/web/formats/formatRules.d.ts +3 -1
  159. package/lib/typescript/src/web/formats/formatRules.d.ts.map +1 -1
  160. package/lib/typescript/src/web/formats/listKeyboard.d.ts.map +1 -1
  161. package/lib/typescript/src/web/normalization/checkboxHtmlNormalizer.d.ts.map +1 -0
  162. package/lib/typescript/src/web/normalization/htmlNormalizer.d.ts +2 -0
  163. package/lib/typescript/src/web/normalization/htmlNormalizer.d.ts.map +1 -0
  164. package/lib/typescript/src/web/normalization/prepareHtmlForWeb.d.ts +2 -0
  165. package/lib/typescript/src/web/normalization/prepareHtmlForWeb.d.ts.map +1 -0
  166. package/lib/typescript/src/web/normalization/tiptapHtmlNormalizer.d.ts +3 -0
  167. package/lib/typescript/src/web/normalization/tiptapHtmlNormalizer.d.ts.map +1 -0
  168. package/lib/typescript/src/web/pmPlugins/AutolinkPlugin/autolinkRegex.d.ts +7 -0
  169. package/lib/typescript/src/web/pmPlugins/AutolinkPlugin/autolinkRegex.d.ts.map +1 -0
  170. package/lib/typescript/src/web/pmPlugins/AutolinkPlugin/index.d.ts +8 -0
  171. package/lib/typescript/src/web/pmPlugins/AutolinkPlugin/index.d.ts.map +1 -0
  172. package/lib/typescript/src/web/pmPlugins/MentionPlugin/index.d.ts +1 -1
  173. package/lib/typescript/src/web/pmPlugins/MentionPlugin/index.d.ts.map +1 -1
  174. package/lib/typescript/src/web/pmPlugins/MentionPlugin/makeMentionPluginState.d.ts.map +1 -1
  175. package/lib/typescript/src/web/pmPlugins/MentionPlugin/setMention.d.ts.map +1 -1
  176. package/lib/typescript/src/web/pmPlugins/MentionPlugin/useMentionEvents.d.ts +4 -0
  177. package/lib/typescript/src/web/pmPlugins/MentionPlugin/useMentionEvents.d.ts.map +1 -0
  178. package/lib/typescript/src/web/pmPlugins/ShortcutPlugin.d.ts.map +1 -1
  179. package/lib/typescript/src/web/pmPlugins/StrictMarksPlugin.d.ts.map +1 -1
  180. package/lib/typescript/src/web/pmPlugins/StripBoldInStyledHeadingsPlugin.d.ts +6 -1
  181. package/lib/typescript/src/web/pmPlugins/StripBoldInStyledHeadingsPlugin.d.ts.map +1 -1
  182. package/lib/typescript/src/web/sanitization/htmlSanitizer.d.ts +2 -0
  183. package/lib/typescript/src/web/sanitization/htmlSanitizer.d.ts.map +1 -0
  184. package/lib/typescript/src/web/styleConversion/buildMentionRulesCSS.d.ts +2 -2
  185. package/lib/typescript/src/web/styleConversion/buildMentionRulesCSS.d.ts.map +1 -1
  186. package/lib/typescript/src/web/styleConversion/enrichedBaseStyleToCSSProperties.d.ts +8 -0
  187. package/lib/typescript/src/web/styleConversion/enrichedBaseStyleToCSSProperties.d.ts.map +1 -0
  188. package/lib/typescript/src/web/styleConversion/enrichedInputStyleToCSSProperties.d.ts +2 -5
  189. package/lib/typescript/src/web/styleConversion/enrichedInputStyleToCSSProperties.d.ts.map +1 -1
  190. package/lib/typescript/src/web/styleConversion/enrichedTextStyleToCSSProperties.d.ts +4 -0
  191. package/lib/typescript/src/web/styleConversion/enrichedTextStyleToCSSProperties.d.ts.map +1 -0
  192. package/lib/typescript/src/web/styleConversion/{enrichedInputThemingToCSSProperties.d.ts → enrichedThemingToCSSProperties.d.ts} +1 -1
  193. package/lib/typescript/src/web/styleConversion/enrichedThemingToCSSProperties.d.ts.map +1 -0
  194. package/lib/typescript/src/web/styleConversion/htmlStyleToCSSVariables.d.ts +5 -4
  195. package/lib/typescript/src/web/styleConversion/htmlStyleToCSSVariables.d.ts.map +1 -1
  196. package/lib/typescript/src/web/styleConversion/inlineImageCSSVariables.d.ts +3 -0
  197. package/lib/typescript/src/web/styleConversion/inlineImageCSSVariables.d.ts.map +1 -0
  198. package/lib/typescript/src/web/useImageErrorFallback.d.ts +3 -0
  199. package/lib/typescript/src/web/useImageErrorFallback.d.ts.map +1 -0
  200. package/lib/typescript/src/web/useOnChangeState.d.ts.map +1 -1
  201. package/lib/typescript/src/web/useOnLinkDetected.d.ts +2 -2
  202. package/lib/typescript/src/web/useOnLinkDetected.d.ts.map +1 -1
  203. package/lib/typescript/src/web/usePressInteractions.d.ts +2 -0
  204. package/lib/typescript/src/web/usePressInteractions.d.ts.map +1 -0
  205. package/lib/typescript/src/web/useStableRef.d.ts +3 -0
  206. package/lib/typescript/src/web/useStableRef.d.ts.map +1 -0
  207. package/package.json +54 -28
  208. package/src/index.tsx +1 -0
  209. package/src/types.ts +5 -3
  210. package/src/utils/expandMentionStylesForIndicators.ts +3 -3
  211. package/src/utils/normalizeHtmlStyle.ts +2 -1
  212. package/src/web/EnrichedText.css +343 -0
  213. package/src/web/EnrichedText.tsx +82 -0
  214. package/src/web/EnrichedTextInput.tsx +69 -55
  215. package/src/web/constants/brokenImageGlyph.ts +7 -0
  216. package/src/web/constants/classNames.ts +2 -0
  217. package/src/web/emitLinkDetected.ts +29 -0
  218. package/src/web/formats/EnrichedBlockquote.ts +2 -1
  219. package/src/web/formats/EnrichedCheckboxList.ts +5 -2
  220. package/src/web/formats/EnrichedCodeBlock.ts +2 -1
  221. package/src/web/formats/EnrichedHeading.ts +2 -1
  222. package/src/web/formats/EnrichedImageNodeView.tsx +1 -3
  223. package/src/web/formats/EnrichedLink.ts +59 -4
  224. package/src/web/formats/EnrichedOrderedList.ts +5 -2
  225. package/src/web/formats/EnrichedTextAlign.ts +88 -0
  226. package/src/web/formats/EnrichedUnorderedList.ts +5 -2
  227. package/src/web/formats/applyWrappingListToSelection.ts +4 -4
  228. package/src/web/formats/formatRules.ts +48 -4
  229. package/src/web/formats/listKeyboard.ts +6 -1
  230. package/src/web/normalization/htmlNormalizer.ts +656 -0
  231. package/src/web/normalization/prepareHtmlForWeb.ts +93 -0
  232. package/src/web/{tiptapHtmlNormalizer.ts → normalization/tiptapHtmlNormalizer.ts} +12 -2
  233. package/src/web/pmPlugins/AutolinkPlugin/autolinkRegex.ts +43 -0
  234. package/src/web/pmPlugins/AutolinkPlugin/index.ts +274 -0
  235. package/src/web/pmPlugins/MentionPlugin/index.ts +1 -1
  236. package/src/web/pmPlugins/MentionPlugin/makeMentionPluginState.ts +32 -5
  237. package/src/web/pmPlugins/MentionPlugin/setMention.ts +19 -29
  238. package/src/web/pmPlugins/MentionPlugin/useMentionEvents.ts +114 -0
  239. package/src/web/pmPlugins/ShortcutPlugin.ts +7 -0
  240. package/src/web/pmPlugins/StrictMarksPlugin.ts +3 -0
  241. package/src/web/pmPlugins/StripBoldInStyledHeadingsPlugin.ts +41 -36
  242. package/src/web/sanitization/htmlSanitizer.ts +8 -0
  243. package/src/web/styleConversion/buildMentionRulesCSS.ts +30 -11
  244. package/src/web/styleConversion/enrichedBaseStyleToCSSProperties.ts +228 -0
  245. package/src/web/styleConversion/enrichedInputStyleToCSSProperties.ts +6 -217
  246. package/src/web/styleConversion/enrichedTextStyleToCSSProperties.ts +53 -0
  247. package/src/web/styleConversion/{enrichedInputThemingToCSSProperties.ts → enrichedThemingToCSSProperties.ts} +4 -4
  248. package/src/web/styleConversion/htmlStyleToCSSVariables.ts +121 -70
  249. package/src/web/styleConversion/inlineImageCSSVariables.ts +6 -0
  250. package/src/web/useImageErrorFallback.ts +37 -0
  251. package/src/web/useOnChangeHtml.ts +1 -1
  252. package/src/web/useOnChangeState.ts +7 -5
  253. package/src/web/useOnLinkDetected.ts +38 -50
  254. package/src/web/usePressInteractions.tsx +22 -0
  255. package/src/web/useStableRef.ts +12 -0
  256. package/lib/module/web/EnrichedTextInput.css +0 -201
  257. package/lib/module/web/checkboxHtmlNormalizer.js.map +0 -1
  258. package/lib/module/web/pmPlugins/MentionPlugin/subscribeMentionEvents.js +0 -100
  259. package/lib/module/web/pmPlugins/MentionPlugin/subscribeMentionEvents.js.map +0 -1
  260. package/lib/module/web/styleConversion/enrichedInputThemingToCSSProperties.js.map +0 -1
  261. package/lib/module/web/tiptapHtmlNormalizer.js.map +0 -1
  262. package/lib/typescript/src/web/checkboxHtmlNormalizer.d.ts.map +0 -1
  263. package/lib/typescript/src/web/pmPlugins/MentionPlugin/subscribeMentionEvents.d.ts +0 -4
  264. package/lib/typescript/src/web/pmPlugins/MentionPlugin/subscribeMentionEvents.d.ts.map +0 -1
  265. package/lib/typescript/src/web/styleConversion/enrichedInputThemingToCSSProperties.d.ts.map +0 -1
  266. package/lib/typescript/src/web/tiptapHtmlNormalizer.d.ts +0 -3
  267. package/lib/typescript/src/web/tiptapHtmlNormalizer.d.ts.map +0 -1
  268. package/src/web/EnrichedTextInput.css +0 -201
  269. package/src/web/pmPlugins/MentionPlugin/subscribeMentionEvents.ts +0 -105
  270. /package/lib/module/web/{checkboxHtmlNormalizer.js → normalization/checkboxHtmlNormalizer.js} +0 -0
  271. /package/lib/typescript/src/web/{checkboxHtmlNormalizer.d.ts → normalization/checkboxHtmlNormalizer.d.ts} +0 -0
  272. /package/src/web/{checkboxHtmlNormalizer.ts → normalization/checkboxHtmlNormalizer.ts} +0 -0
@@ -0,0 +1,39 @@
1
+ package com.swmansion.enriched.common
2
+
3
+ import android.text.Spannable
4
+ import com.swmansion.enriched.common.spans.EnrichedAlignmentSpan
5
+ import com.swmansion.enriched.common.spans.interfaces.EnrichedInlineSpan
6
+
7
+ // Higher priority spans are processed first, so styles with lower priorities are painted on top of previously applied styles.
8
+ // For example, inline styles are applied on top of paragraph styles, allowing them to override paragraph-level styling.
9
+ // Alignment styles are applied last, ensuring they position the final, fully styled text.
10
+ object EnrichedSpanFlags {
11
+ private const val ALIGNMENT_SPAN_PRIORITY = 0
12
+ private const val INLINE_SPAN_PRIORITY = 1
13
+ private const val PARAGRAPH_SPAN_PRIORITY = 2
14
+
15
+ @JvmStatic
16
+ @JvmOverloads
17
+ fun forSpan(
18
+ span: Any?,
19
+ baseFlags: Int = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
20
+ ): Int {
21
+ val priority =
22
+ when (span) {
23
+ is EnrichedAlignmentSpan -> ALIGNMENT_SPAN_PRIORITY
24
+ is EnrichedInlineSpan -> INLINE_SPAN_PRIORITY
25
+ else -> PARAGRAPH_SPAN_PRIORITY
26
+ }
27
+ return applyPriority(baseFlags, priority)
28
+ }
29
+
30
+ private fun applyPriority(
31
+ flags: Int,
32
+ priority: Int,
33
+ ): Int {
34
+ // Cleaning up priority bits
35
+ val cleared = flags and Spannable.SPAN_PRIORITY.inv()
36
+ // Injecting priority bits
37
+ return cleared or ((priority shl Spannable.SPAN_PRIORITY_SHIFT) and Spannable.SPAN_PRIORITY)
38
+ }
39
+ }
@@ -7,6 +7,7 @@ import android.text.Spanned;
7
7
  import android.text.TextUtils;
8
8
  import android.text.style.ParagraphStyle;
9
9
  import com.swmansion.enriched.common.EnrichedConstants;
10
+ import com.swmansion.enriched.common.EnrichedSpanFlags;
10
11
  import com.swmansion.enriched.common.spans.EnrichedAlignmentSpan;
11
12
  import com.swmansion.enriched.common.spans.EnrichedBoldSpan;
12
13
  import com.swmansion.enriched.common.spans.EnrichedCheckboxListSpan;
@@ -66,6 +67,11 @@ public class EnrichedParser {
66
67
  }
67
68
 
68
69
  public static <T> Spanned fromHtml(String source, T style, EnrichedSpanFactory<T> spanFactory) {
70
+ return fromHtml(source, style, spanFactory, null);
71
+ }
72
+
73
+ public static <T> Spanned fromHtml(
74
+ String source, T style, EnrichedSpanFactory<T> spanFactory, Pattern linkRegex) {
69
75
  Parser parser = new Parser();
70
76
  try {
71
77
  parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
@@ -74,7 +80,7 @@ public class EnrichedParser {
74
80
  throw new RuntimeException(e);
75
81
  }
76
82
  HtmlToSpannedConverter converter =
77
- new HtmlToSpannedConverter(source, style, parser, spanFactory);
83
+ new HtmlToSpannedConverter(source, style, parser, spanFactory, linkRegex);
78
84
  return converter.convert();
79
85
  }
80
86
 
@@ -408,6 +414,7 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
408
414
  private final String mSource;
409
415
  private final XMLReader mReader;
410
416
  private final SpannableStringBuilder mSpannableStringBuilder;
417
+ private final Pattern mLinkRegex;
411
418
  private static Integer currentOrderedListItemIndex = 0;
412
419
  private static Boolean isInOrderedList = false;
413
420
  private static Boolean isInCheckboxList = false;
@@ -432,12 +439,17 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
432
439
  }
433
440
 
434
441
  public HtmlToSpannedConverter(
435
- String source, T style, Parser parser, EnrichedSpanFactory<T> spanFactory) {
442
+ String source,
443
+ T style,
444
+ Parser parser,
445
+ EnrichedSpanFactory<T> spanFactory,
446
+ Pattern linkRegex) {
436
447
  mStyle = style;
437
448
  mSource = source;
438
449
  mSpannableStringBuilder = new SpannableStringBuilder();
439
450
  mReader = parser;
440
451
  mSpanFactory = spanFactory;
452
+ mLinkRegex = linkRegex;
441
453
  }
442
454
 
443
455
  public Spanned convert() {
@@ -467,10 +479,7 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
467
479
  if (end == start) {
468
480
  mSpannableStringBuilder.removeSpan(obj[i]);
469
481
  } else {
470
- // TODO: verify if Spannable.SPAN_EXCLUSIVE_EXCLUSIVE does not break anything.
471
- // Previously it was SPAN_PARAGRAPH. I've changed that in order to fix ranges for list
472
- // items.
473
- mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
482
+ mSpannableStringBuilder.setSpan(obj[i], start, end, EnrichedSpanFlags.forSpan(obj[i]));
474
483
  }
475
484
  }
476
485
 
@@ -505,7 +514,7 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
505
514
 
506
515
  mSpannableStringBuilder.removeSpan(zeroWidthSpaceSpan);
507
516
  mSpannableStringBuilder.setSpan(
508
- zeroWidthSpaceSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
517
+ zeroWidthSpaceSpan, start, end, EnrichedSpanFlags.forSpan(zeroWidthSpaceSpan));
509
518
  }
510
519
 
511
520
  return mSpannableStringBuilder;
@@ -602,7 +611,7 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
602
611
  } else if (tag.equalsIgnoreCase("codeblock")) {
603
612
  endCodeBlock(mSpannableStringBuilder, mStyle, mSpanFactory);
604
613
  } else if (tag.equalsIgnoreCase("a")) {
605
- endA(mSpannableStringBuilder, mStyle, mSpanFactory);
614
+ endA(mSpannableStringBuilder, mStyle, mSpanFactory, mLinkRegex);
606
615
  } else if (tag.equalsIgnoreCase("u")) {
607
616
  end(mSpannableStringBuilder, Underline.class, mSpanFactory.createUnderlineSpan(mStyle));
608
617
  } else if (tag.equalsIgnoreCase("s")) {
@@ -802,7 +811,7 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
802
811
  int len = text.length();
803
812
  if (where != len) {
804
813
  for (Object span : spans) {
805
- text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
814
+ text.setSpan(span, where, len, EnrichedSpanFlags.forSpan(span));
806
815
  }
807
816
  }
808
817
  }
@@ -825,7 +834,7 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
825
834
 
826
835
  if (where != len) {
827
836
  for (Object span : spans) {
828
- text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
837
+ text.setSpan(span, where, len, EnrichedSpanFlags.forSpan(span));
829
838
  }
830
839
  }
831
840
  }
@@ -850,11 +859,19 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
850
859
 
851
860
  int len = text.length();
852
861
  text.append("");
853
- text.setSpan(
854
- spanFactory.createImageSpan(src, Integer.parseInt(width), Integer.parseInt(height)),
855
- len,
856
- text.length(),
857
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
862
+ Object imageSpan =
863
+ spanFactory.createImageSpan(src, parseDimension(width), parseDimension(height));
864
+ text.setSpan(imageSpan, len, text.length(), EnrichedSpanFlags.forSpan(imageSpan));
865
+ }
866
+
867
+ private static int parseDimension(String value) {
868
+ if (value == null) return 0;
869
+ try {
870
+ int parsed = (int) Math.floor(Float.parseFloat(value));
871
+ return Math.max(parsed, 0);
872
+ } catch (NumberFormatException e) {
873
+ return 0;
874
+ }
858
875
  }
859
876
 
860
877
  private static void startA(Editable text, Attributes attributes) {
@@ -862,12 +879,18 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
862
879
  start(text, new Href(href));
863
880
  }
864
881
 
865
- private static <T> void endA(Editable text, T style, EnrichedSpanFactory<T> spanFactory) {
882
+ private static boolean urlMatchesLinkRegex(String url, Pattern linkRegex) {
883
+ if (linkRegex == null) return false;
884
+ return linkRegex.matcher(url).matches();
885
+ }
886
+
887
+ private static <T> void endA(
888
+ Editable text, T style, EnrichedSpanFactory<T> spanFactory, Pattern linkRegex) {
866
889
  Href h = getLast(text, Href.class);
867
- if (h != null) {
868
- if (h.mHref != null) {
869
- setSpanFromMark(text, h, spanFactory.createLinkSpan(h.mHref, style));
870
- }
890
+ if (h != null && h.mHref != null) {
891
+ String linkText = text.subSequence(text.getSpanStart(h), text.length()).toString();
892
+ boolean isManual = !linkText.equals(h.mHref) || !urlMatchesLinkRegex(h.mHref, linkRegex);
893
+ setSpanFromMark(text, h, spanFactory.createLinkSpan(h.mHref, style, isManual));
871
894
  }
872
895
  }
873
896
 
@@ -37,6 +37,7 @@ interface EnrichedSpanFactory<T> {
37
37
  fun createLinkSpan(
38
38
  url: String,
39
39
  style: T,
40
+ isManual: Boolean,
40
41
  ): EnrichedLinkSpan
41
42
 
42
43
  fun createMentionSpan(
@@ -38,6 +38,7 @@ class EnrichedTextSpanFactory : EnrichedSpanFactory<EnrichedTextStyle> {
38
38
  override fun createLinkSpan(
39
39
  url: String,
40
40
  style: EnrichedTextStyle,
41
+ isManual: Boolean,
41
42
  ) = EnrichedTextLinkSpan(url, style)
42
43
 
43
44
  override fun createMentionSpan(
@@ -23,6 +23,7 @@ import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles
23
23
  import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle
24
24
  import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
25
25
  import com.swmansion.enriched.common.EnrichedConstants
26
+ import com.swmansion.enriched.common.EnrichedSpanFlags
26
27
  import com.swmansion.enriched.common.GumboNormalizer
27
28
  import com.swmansion.enriched.common.parser.EnrichedParser
28
29
  import com.swmansion.enriched.common.pixelFromSpOrDp
@@ -277,7 +278,7 @@ class EnrichedTextView : AppCompatTextView {
277
278
 
278
279
  spannable.removeSpan(span)
279
280
  val newSpan = span.rebuildWithStyle(enrichedStyle)
280
- spannable.setSpan(newSpan, start, end, flags)
281
+ spannable.setSpan(newSpan, start, end, EnrichedSpanFlags.forSpan(newSpan, flags))
281
282
  modified = true
282
283
  }
283
284
 
@@ -40,7 +40,8 @@ class EnrichedTextInputSpannableFactory : EnrichedSpanFactory<HtmlStyle> {
40
40
  override fun createLinkSpan(
41
41
  url: String,
42
42
  style: HtmlStyle,
43
- ) = EnrichedInputLinkSpan(url, style, true)
43
+ isManual: Boolean,
44
+ ) = EnrichedInputLinkSpan(url, style, isManual)
44
45
 
45
46
  override fun createMentionSpan(
46
47
  text: String,
@@ -9,6 +9,7 @@ import android.graphics.Color
9
9
  import android.graphics.Rect
10
10
  import android.graphics.text.LineBreaker
11
11
  import android.os.Build
12
+ import android.text.Editable
12
13
  import android.text.InputType
13
14
  import android.text.Spannable
14
15
  import android.text.SpannableString
@@ -38,6 +39,7 @@ import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles
38
39
  import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle
39
40
  import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
40
41
  import com.swmansion.enriched.common.EnrichedConstants
42
+ import com.swmansion.enriched.common.EnrichedSpanFlags
41
43
  import com.swmansion.enriched.common.GumboNormalizer
42
44
  import com.swmansion.enriched.common.parser.EnrichedParser
43
45
  import com.swmansion.enriched.common.pixelFromSpOrDp
@@ -398,8 +400,9 @@ class EnrichedTextInputView :
398
400
  val pasteEnd = (start + insertedLength).coerceIn(0, finalText.length)
399
401
  setSelection(pasteEnd)
400
402
 
401
- // Detect links in the newly pasted range
402
- parametrizedStyles?.detectLinksInRange(finalText, start.coerceAtMost(pasteEnd), pasteEnd)
403
+ // Update links and mentions in the newly pasted range
404
+ val editable = text as? Editable ?: return
405
+ parametrizedStyles?.afterTextChanged(editable, start.coerceAtMost(pasteEnd), pasteEnd)
403
406
  }
404
407
 
405
408
  fun requestFocusProgrammatically() {
@@ -413,7 +416,7 @@ class EnrichedTextInputView :
413
416
  val normalized = GumboNormalizer.normalizeHtml(text.toString()) ?: return text
414
417
 
415
418
  return try {
416
- val parsed = EnrichedParser.fromHtml(normalized, htmlStyle, spannableFactory)
419
+ val parsed = EnrichedParser.fromHtml(normalized, htmlStyle, spannableFactory, linkRegex)
417
420
  parsed.trimEnd('\n')
418
421
  } catch (e: Exception) {
419
422
  Log.e(TAG, "Error parsing normalized HTML: ${e.message}")
@@ -426,7 +429,7 @@ class EnrichedTextInputView :
426
429
 
427
430
  if (isInternalHtml) {
428
431
  try {
429
- val parsed = EnrichedParser.fromHtml(text.toString(), htmlStyle, spannableFactory)
432
+ val parsed = EnrichedParser.fromHtml(text.toString(), htmlStyle, spannableFactory, linkRegex)
430
433
  return parsed.trimEnd('\n')
431
434
  } catch (e: Exception) {
432
435
  Log.e(TAG, "Error parsing HTML: ${e.message}")
@@ -1108,7 +1111,7 @@ class EnrichedTextInputView :
1108
1111
 
1109
1112
  spannable.removeSpan(span)
1110
1113
  val newSpan = span.rebuildWithStyle(htmlStyle)
1111
- spannable.setSpan(newSpan, start, end, flags)
1114
+ spannable.setSpan(newSpan, start, end, EnrichedSpanFlags.forSpan(newSpan, flags))
1112
1115
  }
1113
1116
 
1114
1117
  if (shouldEmitStateChange) {
@@ -27,8 +27,17 @@ class MentionHandler(
27
27
  indicator: String,
28
28
  text: String?,
29
29
  ) {
30
+ // switching directly to an active mention
31
+ if (previousIndicator != indicator) {
32
+ endMention()
33
+
34
+ // explicit startMention event before changeMention event
35
+ if (!text.isNullOrEmpty()) {
36
+ emitEvent(indicator, "")
37
+ }
38
+ }
39
+
30
40
  emitEvent(indicator, text)
31
- previousIndicator = indicator
32
41
  }
33
42
 
34
43
  private fun emitEvent(
@@ -36,8 +45,9 @@ class MentionHandler(
36
45
  text: String?,
37
46
  ) {
38
47
  // Do not emit events too often
39
- if (previousText == text) return
48
+ if (previousIndicator == indicator && previousText == text) return
40
49
 
50
+ previousIndicator = indicator
41
51
  previousText = text
42
52
  val context = view.context as ReactContext
43
53
  val surfaceId = UIManagerHelper.getSurfaceId(context)
@@ -13,6 +13,12 @@ class OnMentionEvent(
13
13
  ) : Event<OnMentionEvent>(surfaceId, viewId) {
14
14
  override fun getEventName(): String = EVENT_NAME
15
15
 
16
+ // start/change/end can be emitted as a burst within a single frame
17
+ // (e.g. when switching mentions: end -> start -> change).
18
+ // The default coalescing would merge them in the batch and drop the
19
+ // intermediate ones, so it must be disabled to deliver every event in order.
20
+ override fun canCoalesce(): Boolean = false
21
+
16
22
  override fun getEventData(): WritableMap? {
17
23
  val eventData: WritableMap = Arguments.createMap()
18
24
  eventData.putString("indicator", indicator)
@@ -4,6 +4,7 @@ import android.text.Editable
4
4
  import android.text.Spannable
5
5
  import android.text.SpannableStringBuilder
6
6
  import com.swmansion.enriched.common.EnrichedConstants
7
+ import com.swmansion.enriched.common.EnrichedSpanFlags
7
8
  import com.swmansion.enriched.textinput.EnrichedTextInputView
8
9
  import com.swmansion.enriched.textinput.spans.EnrichedInputAlignmentSpan
9
10
  import com.swmansion.enriched.textinput.spans.EnrichedInputCheckboxListSpan
@@ -24,12 +25,8 @@ class AlignmentStyles(
24
25
  flags: Int = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
25
26
  ) {
26
27
  val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
27
- spannable.setSpan(
28
- EnrichedInputAlignmentSpan(cssValue),
29
- safeStart,
30
- safeEnd,
31
- flags,
32
- )
28
+ val span = EnrichedInputAlignmentSpan(cssValue)
29
+ spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span, flags))
33
30
  }
34
31
 
35
32
  private fun toCssValue(alignment: String): String =
@@ -302,7 +299,9 @@ class AlignmentStyles(
302
299
  // INCLUSIVE_EXCLUSIVE is intentional here: autoStretchAlignmentSpan will convert
303
300
  // it to EXCLUSIVE_EXCLUSIVE once the merge is complete.
304
301
  val (safeStart, safeEnd) = s.getSafeSpanBoundaries(paraStart, paraEnd)
305
- dominantTopSpan?.let { s.setSpan(it, safeStart, safeEnd, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) }
302
+ dominantTopSpan?.let {
303
+ s.setSpan(it, safeStart, safeEnd, EnrichedSpanFlags.forSpan(it, Spannable.SPAN_INCLUSIVE_EXCLUSIVE))
304
+ }
306
305
  return cursorPosition
307
306
  }
308
307
 
@@ -2,6 +2,7 @@ package com.swmansion.enriched.textinput.styles
2
2
 
3
3
  import android.text.Editable
4
4
  import android.text.Spannable
5
+ import com.swmansion.enriched.common.EnrichedSpanFlags
5
6
  import com.swmansion.enriched.textinput.EnrichedTextInputView
6
7
  import com.swmansion.enriched.textinput.spans.EnrichedSpans
7
8
  import com.swmansion.enriched.textinput.utils.getSafeSpanBoundaries
@@ -41,7 +42,7 @@ class InlineStyles(
41
42
 
42
43
  val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle)
43
44
  val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(minimum, maximum)
44
- spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
45
+ spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span))
45
46
  }
46
47
 
47
48
  private fun <T> setAndMergeSpans(
@@ -222,7 +223,18 @@ class InlineStyles(
222
223
  if (spans.isEmpty()) return false
223
224
 
224
225
  for (span in spans) {
226
+ val spanStart = spannable.getSpanStart(span)
227
+ val spanEnd = spannable.getSpanEnd(span)
228
+
225
229
  spannable.removeSpan(span)
230
+
231
+ if (spanStart < start) {
232
+ setSpan(spannable, config.clazz, spanStart, start - 1)
233
+ }
234
+
235
+ if (spanEnd > end) {
236
+ setSpan(spannable, config.clazz, end, spanEnd)
237
+ }
226
238
  }
227
239
 
228
240
  return true
@@ -5,6 +5,7 @@ import android.text.Spannable
5
5
  import android.text.SpannableStringBuilder
6
6
  import android.text.Spanned
7
7
  import com.swmansion.enriched.common.EnrichedConstants
8
+ import com.swmansion.enriched.common.EnrichedSpanFlags
8
9
  import com.swmansion.enriched.textinput.EnrichedTextInputView
9
10
  import com.swmansion.enriched.textinput.spans.EnrichedInputCheckboxListSpan
10
11
  import com.swmansion.enriched.textinput.spans.EnrichedInputOrderedListSpan
@@ -66,18 +67,18 @@ class ListStyles(
66
67
  when (name) {
67
68
  EnrichedSpans.UNORDERED_LIST -> {
68
69
  val span = EnrichedInputUnorderedListSpan(view.htmlStyle)
69
- spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
70
+ spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span))
70
71
  }
71
72
 
72
73
  EnrichedSpans.ORDERED_LIST -> {
73
74
  val index = getOrderedListIndex(spannable, safeStart)
74
75
  val span = EnrichedInputOrderedListSpan(index, view.htmlStyle)
75
- spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
76
+ spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span))
76
77
  }
77
78
 
78
79
  EnrichedSpans.CHECKBOX_LIST -> {
79
80
  val span = EnrichedInputCheckboxListSpan(isChecked ?: false, view.htmlStyle)
80
- spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
81
+ spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span))
81
82
 
82
83
  // Invalidate layout to update checkbox drawing in case checkbox is bigger than line height
83
84
  view.layoutManager.invalidateLayout()
@@ -4,6 +4,7 @@ import android.text.Editable
4
4
  import android.text.Spannable
5
5
  import android.text.SpannableStringBuilder
6
6
  import android.util.Log
7
+ import com.swmansion.enriched.common.EnrichedSpanFlags
7
8
  import com.swmansion.enriched.textinput.EnrichedTextInputView
8
9
  import com.swmansion.enriched.textinput.spans.EnrichedSpans
9
10
  import com.swmansion.enriched.textinput.spans.interfaces.EnrichedInputSpan
@@ -89,7 +90,7 @@ class ParagraphStyles(
89
90
  }
90
91
 
91
92
  val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(newStart, newEnd)
92
- spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
93
+ spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span))
93
94
  }
94
95
 
95
96
  private fun <T> setSpan(
@@ -105,7 +106,7 @@ class ParagraphStyles(
105
106
 
106
107
  val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle)
107
108
  val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
108
- spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
109
+ spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span))
109
110
  }
110
111
 
111
112
  // Removes spans of the given type in the specified range.
@@ -232,7 +233,7 @@ class ParagraphStyles(
232
233
  val (safeStart, safeEnd) = s.getSafeSpanBoundaries(newStart, newEnd)
233
234
  val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle)
234
235
 
235
- s.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
236
+ s.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span))
236
237
  }
237
238
 
238
239
  private fun handleConflictsDuringNewlineDeletion(
@@ -1,10 +1,12 @@
1
1
  package com.swmansion.enriched.textinput.styles
2
2
 
3
3
  import android.text.Editable
4
+ import android.text.Selection
4
5
  import android.text.Spannable
5
6
  import android.text.SpannableStringBuilder
6
7
  import android.text.Spanned
7
8
  import com.swmansion.enriched.common.EnrichedConstants
9
+ import com.swmansion.enriched.common.EnrichedSpanFlags
8
10
  import com.swmansion.enriched.textinput.EnrichedTextInputView
9
11
  import com.swmansion.enriched.textinput.spans.EnrichedInputImageSpan
10
12
  import com.swmansion.enriched.textinput.spans.EnrichedInputLinkSpan
@@ -17,6 +19,7 @@ class ParametrizedStyles(
17
19
  private val view: EnrichedTextInputView,
18
20
  ) {
19
21
  private var mentionStart: Int? = null
22
+ private var mentionEnd: Int? = null
20
23
  private var isSettingLinkSpan = false
21
24
 
22
25
  var mentionIndicators: Array<String> = emptyArray<String>()
@@ -63,7 +66,7 @@ class ParametrizedStyles(
63
66
  val spanEnd = start + text.length
64
67
  val span = EnrichedInputLinkSpan(url, view.htmlStyle, true)
65
68
  val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd)
66
- spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
69
+ spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span))
67
70
 
68
71
  view.selection?.validateStyles()
69
72
  isSettingLinkSpan = false
@@ -91,7 +94,23 @@ class ParametrizedStyles(
91
94
  endCursorPosition: Int,
92
95
  ) {
93
96
  afterTextChangedLinks(startCursorPosition, endCursorPosition)
94
- afterTextChangedMentions(s, startCursorPosition)
97
+ detectActiveMention(s, startCursorPosition)
98
+ }
99
+
100
+ // Re-runs in-progress mention detection on a pure caret move (no text change),
101
+ fun afterSelectionChangedMentions(
102
+ start: Int,
103
+ end: Int,
104
+ ) {
105
+ val s = view.text ?: return
106
+
107
+ // A non-collapsed selection can't be editing a single mention.
108
+ if (start != end) {
109
+ view.mentionHandler?.endMention()
110
+ return
111
+ }
112
+
113
+ detectActiveMention(s, end)
95
114
  }
96
115
 
97
116
  fun onStyleToggled(
@@ -160,7 +179,7 @@ class ParametrizedStyles(
160
179
  span,
161
180
  safeStart,
162
181
  safeEnd,
163
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
182
+ EnrichedSpanFlags.forSpan(span),
164
183
  )
165
184
  }
166
185
  }
@@ -239,7 +258,7 @@ class ParametrizedStyles(
239
258
  detectLinksInRange(spannable, affectedRange.first, affectedRange.last)
240
259
  }
241
260
 
242
- private fun afterTextChangedMentions(
261
+ private fun detectActiveMention(
243
262
  s: CharSequence,
244
263
  endCursorPosition: Int,
245
264
  ) {
@@ -261,12 +280,16 @@ class ParametrizedStyles(
261
280
 
262
281
  // No previous word -> no mention to be detected
263
282
  if (previousWord == null) {
283
+ mentionStart = null
284
+ mentionEnd = null
264
285
  mentionHandler.endMention()
265
286
  return
266
287
  }
267
288
 
268
289
  // Previous word is not a mention -> end mention
269
290
  if (!mentionRegex.matches(previousWord.text)) {
291
+ mentionStart = null
292
+ mentionEnd = null
270
293
  mentionHandler.endMention()
271
294
  return
272
295
  }
@@ -280,6 +303,9 @@ class ParametrizedStyles(
280
303
  indicator = mentionIndicatorRegex.find(currentWord.text)?.value ?: ""
281
304
  }
282
305
 
306
+ mentionStart = finalStart
307
+ mentionEnd = finalEnd
308
+
283
309
  // Mirror iOS conflicting-styles behaviour: check the full candidate range for
284
310
  // a finalized mention span. If the span's stored text still matches what is in
285
311
  // the buffer the mention is intact — block the event (covers HTML-loaded
@@ -292,21 +318,19 @@ class ParametrizedStyles(
292
318
  val spanEnd = spannable.getSpanEnd(span)
293
319
  val currentSpanText = spannable.subSequence(spanStart, spanEnd).toString()
294
320
  if (currentSpanText == span.getText()) {
321
+ mentionStart = null
322
+ mentionEnd = null
295
323
  mentionHandler.endMention()
296
324
  return
297
325
  }
298
326
  spannable.removeSpan(span)
299
327
  mentionStart = spanStart
328
+ mentionEnd = spanEnd
300
329
  }
301
330
 
302
331
  // Extract text without indicator
303
332
  val text = spannable.subSequence(finalStart, finalEnd).toString().replaceFirst(indicator, "")
304
333
 
305
- // Means we are starting mention
306
- if (text.isEmpty()) {
307
- mentionStart = finalStart
308
- }
309
-
310
334
  mentionHandler.onMention(indicator, text)
311
335
  }
312
336
 
@@ -366,24 +390,27 @@ class ParametrizedStyles(
366
390
  }
367
391
 
368
392
  val start = mentionStart ?: selectionStart
393
+ val end = mentionEnd ?: selectionEnd
369
394
 
370
395
  view.runAsATransaction {
371
- spannable.replace(start, selectionEnd, text)
396
+ spannable.replace(start, end, text)
372
397
 
373
398
  val span = EnrichedInputMentionSpan(text, indicator, attributes, view.htmlStyle)
374
399
  val spanEnd = start + text.length
375
400
  val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd)
376
- spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
401
+ spannable.setSpan(span, safeStart, safeEnd, EnrichedSpanFlags.forSpan(span))
377
402
 
378
403
  val hasSpaceAtTheEnd = spannable.length > safeEnd && spannable[safeEnd] == ' '
379
404
  if (!hasSpaceAtTheEnd) {
380
405
  spannable.insert(safeEnd, " ")
381
406
  }
407
+ Selection.setSelection(spannable, (safeEnd + 1).coerceAtMost(spannable.length))
382
408
  }
383
409
 
384
410
  view.mentionHandler?.reset()
385
411
  view.selection.validateStyles()
386
412
  mentionStart = null
413
+ mentionEnd = null
387
414
  }
388
415
 
389
416
  fun getStyleRange(): Pair<Int, Int> = view.selection?.getInlineSelection() ?: Pair(0, 0)
@@ -55,6 +55,8 @@ class EnrichedSelection(
55
55
  start = finalStart
56
56
  end = finalEnd
57
57
  validateStyles()
58
+
59
+ view.parametrizedStyles?.afterSelectionChangedMentions(finalStart, finalEnd)
58
60
  emitSelectionChangeEvent(view.text, finalStart, finalEnd)
59
61
  }
60
62
 
@@ -3,6 +3,7 @@ package com.swmansion.enriched.textinput.utils
3
3
  import android.text.Spannable
4
4
  import android.text.SpannableString
5
5
  import android.text.SpannableStringBuilder
6
+ import com.swmansion.enriched.common.EnrichedSpanFlags
6
7
  import com.swmansion.enriched.common.spans.interfaces.EnrichedBlockSpan
7
8
  import com.swmansion.enriched.common.spans.interfaces.EnrichedParagraphSpan
8
9
  import com.swmansion.enriched.common.spans.interfaces.EnrichedSpan
@@ -144,7 +145,7 @@ fun Spannable.mergeSpannables(
144
145
  val (_, newParagraphEnd) = builder.getParagraphBounds(spanStart, pasteEnd)
145
146
  val flags = builder.getSpanFlags(span)
146
147
  builder.removeSpan(span)
147
- builder.setSpan(span, spanStart, newParagraphEnd, flags)
148
+ builder.setSpan(span, spanStart, newParagraphEnd, EnrichedSpanFlags.forSpan(span, flags))
148
149
  }
149
150
  }
150
151
 
@@ -7,6 +7,7 @@ import android.text.Spanned
7
7
  import android.util.Log
8
8
  import android.view.MotionEvent
9
9
  import android.widget.TextView
10
+ import com.swmansion.enriched.common.EnrichedSpanFlags
10
11
  import com.swmansion.enriched.textinput.spans.EnrichedInputCheckboxListSpan
11
12
  import org.json.JSONObject
12
13
 
@@ -78,7 +79,7 @@ fun TextView.setCheckboxClickListener() {
78
79
 
79
80
  // Reapply span so changes are visible without need to redraw entire TextView
80
81
  spannable.removeSpan(span)
81
- spannable.setSpan(span, start, end, flags)
82
+ spannable.setSpan(span, start, end, EnrichedSpanFlags.forSpan(span, flags))
82
83
 
83
84
  // For focused input, ensure cursor is active for affected paragraph
84
85
  if (tv.isFocused) {