diff --git a/.maestro/enrichedInput/flows/dynamic_ol_markers.yaml b/.maestro/enrichedInput/flows/dynamic_ol_markers.yaml new file mode 100644 index 000000000..6c7eb5b00 --- /dev/null +++ b/.maestro/enrichedInput/flows/dynamic_ol_markers.yaml @@ -0,0 +1,56 @@ +appId: swmansion.enriched.example +--- +# Validates that ordered list margins dynamically adjust when transitioning +# between single-digit (9) and double-digit (10) indexes. +- launchApp + +- tapOn: + id: 'toggle-screen-button' + +- runFlow: + file: '../subflows/set_editor_value.yaml' + env: + VALUE: > + +
    +
  1. A
  2. +
  3. B
  4. +
  5. C
  6. +
  7. D
  8. +
  9. E
  10. +
  11. F
  12. +
  13. G
  14. +
  15. H
  16. +
  17. I
  18. +
+ + +- tapOn: + id: 'size-max-button' + +- runFlow: + file: '../subflows/capture_or_assert_screenshot.yaml' + env: + SCREENSHOT_NAME: 'dynamic_ol_markers_before' + +- tapOn: + id: "editor-input" + point: "50%, 95%" +- pressKey: Enter +- inputText: "J" + +- runFlow: + file: '../subflows/capture_or_assert_screenshot.yaml' + env: + SCREENSHOT_NAME: 'dynamic_ol_markers_after' + +- tapOn: + id: "editor-input" + point: "50%, 95%" +- pressKey: Backspace +- pressKey: Backspace + +- runFlow: + file: '../subflows/capture_or_assert_screenshot.yaml' + env: + SCREENSHOT_NAME: 'dynamic_ol_markers_revert' diff --git a/.maestro/enrichedInput/screenshots/android/checkbox_toggle.png b/.maestro/enrichedInput/screenshots/android/checkbox_toggle.png index b3b661a85..2ebc3fd4b 100644 Binary files a/.maestro/enrichedInput/screenshots/android/checkbox_toggle.png and b/.maestro/enrichedInput/screenshots/android/checkbox_toggle.png differ diff --git a/.maestro/enrichedInput/screenshots/android/dynamic_ol_markers_after.png b/.maestro/enrichedInput/screenshots/android/dynamic_ol_markers_after.png new file mode 100644 index 000000000..64f598cec Binary files /dev/null and b/.maestro/enrichedInput/screenshots/android/dynamic_ol_markers_after.png differ diff --git a/.maestro/enrichedInput/screenshots/android/dynamic_ol_markers_before.png b/.maestro/enrichedInput/screenshots/android/dynamic_ol_markers_before.png new file mode 100644 index 000000000..f7e4937d1 Binary files /dev/null and b/.maestro/enrichedInput/screenshots/android/dynamic_ol_markers_before.png differ diff --git a/.maestro/enrichedInput/screenshots/android/dynamic_ol_markers_revert.png b/.maestro/enrichedInput/screenshots/android/dynamic_ol_markers_revert.png new file mode 100644 index 000000000..f567d83b6 Binary files /dev/null and b/.maestro/enrichedInput/screenshots/android/dynamic_ol_markers_revert.png differ diff --git a/.maestro/enrichedInput/screenshots/android/empty_element_parsing.png b/.maestro/enrichedInput/screenshots/android/empty_element_parsing.png index 7e28ad4ea..a6fd95fb1 100644 Binary files a/.maestro/enrichedInput/screenshots/android/empty_element_parsing.png and b/.maestro/enrichedInput/screenshots/android/empty_element_parsing.png differ diff --git a/.maestro/enrichedInput/screenshots/android/empty_lists_parsing.png b/.maestro/enrichedInput/screenshots/android/empty_lists_parsing.png index d777b0b24..4af0f5167 100644 Binary files a/.maestro/enrichedInput/screenshots/android/empty_lists_parsing.png and b/.maestro/enrichedInput/screenshots/android/empty_lists_parsing.png differ diff --git a/.maestro/enrichedInput/screenshots/android/extending_paragraph_style_on_paste_after_copy.png b/.maestro/enrichedInput/screenshots/android/extending_paragraph_style_on_paste_after_copy.png index f717ac4fa..1bea7002c 100644 Binary files a/.maestro/enrichedInput/screenshots/android/extending_paragraph_style_on_paste_after_copy.png and b/.maestro/enrichedInput/screenshots/android/extending_paragraph_style_on_paste_after_copy.png differ diff --git a/.maestro/enrichedInput/screenshots/android/extending_paragraph_style_on_paste_after_cut.png b/.maestro/enrichedInput/screenshots/android/extending_paragraph_style_on_paste_after_cut.png index f717ac4fa..1bea7002c 100644 Binary files a/.maestro/enrichedInput/screenshots/android/extending_paragraph_style_on_paste_after_cut.png and b/.maestro/enrichedInput/screenshots/android/extending_paragraph_style_on_paste_after_cut.png differ diff --git a/.maestro/enrichedInput/screenshots/android/font_scaling.png b/.maestro/enrichedInput/screenshots/android/font_scaling.png index 6b955aa4e..22495890f 100644 Binary files a/.maestro/enrichedInput/screenshots/android/font_scaling.png and b/.maestro/enrichedInput/screenshots/android/font_scaling.png differ diff --git a/.maestro/enrichedInput/screenshots/android/html_link_not_extended.png b/.maestro/enrichedInput/screenshots/android/html_link_not_extended.png index d0198a17d..929c98c7a 100644 Binary files a/.maestro/enrichedInput/screenshots/android/html_link_not_extended.png and b/.maestro/enrichedInput/screenshots/android/html_link_not_extended.png differ diff --git a/.maestro/enrichedInput/screenshots/android/image_inside_list_parsing.png b/.maestro/enrichedInput/screenshots/android/image_inside_list_parsing.png index 8c7af1ca3..b6055e22f 100644 Binary files a/.maestro/enrichedInput/screenshots/android/image_inside_list_parsing.png and b/.maestro/enrichedInput/screenshots/android/image_inside_list_parsing.png differ diff --git a/.maestro/enrichedInput/screenshots/android/inline_styles.png b/.maestro/enrichedInput/screenshots/android/inline_styles.png index 818443706..c7255dc7d 100644 Binary files a/.maestro/enrichedInput/screenshots/android/inline_styles.png and b/.maestro/enrichedInput/screenshots/android/inline_styles.png differ diff --git a/.maestro/enrichedInput/screenshots/android/inline_styles_merge.png b/.maestro/enrichedInput/screenshots/android/inline_styles_merge.png index e1b1cece0..8fa74ed41 100644 Binary files a/.maestro/enrichedInput/screenshots/android/inline_styles_merge.png and b/.maestro/enrichedInput/screenshots/android/inline_styles_merge.png differ diff --git a/.maestro/enrichedInput/screenshots/android/inline_styles_removal.png b/.maestro/enrichedInput/screenshots/android/inline_styles_removal.png index 731ac2b2a..c9e51d0c7 100644 Binary files a/.maestro/enrichedInput/screenshots/android/inline_styles_removal.png and b/.maestro/enrichedInput/screenshots/android/inline_styles_removal.png differ diff --git a/.maestro/enrichedInput/screenshots/android/link_not_extended.png b/.maestro/enrichedInput/screenshots/android/link_not_extended.png index 8fba4d554..63ac35167 100644 Binary files a/.maestro/enrichedInput/screenshots/android/link_not_extended.png and b/.maestro/enrichedInput/screenshots/android/link_not_extended.png differ diff --git a/.maestro/enrichedInput/screenshots/android/links_visual_auto.png b/.maestro/enrichedInput/screenshots/android/links_visual_auto.png index 369f3a2ea..5c4778e6c 100644 Binary files a/.maestro/enrichedInput/screenshots/android/links_visual_auto.png and b/.maestro/enrichedInput/screenshots/android/links_visual_auto.png differ diff --git a/.maestro/enrichedInput/screenshots/android/links_visual_manual.png b/.maestro/enrichedInput/screenshots/android/links_visual_manual.png index cc265a9db..65f205f9a 100644 Binary files a/.maestro/enrichedInput/screenshots/android/links_visual_manual.png and b/.maestro/enrichedInput/screenshots/android/links_visual_manual.png differ diff --git a/.maestro/enrichedInput/screenshots/android/list_newline_insertion.png b/.maestro/enrichedInput/screenshots/android/list_newline_insertion.png index 12b3b0384..644dc7cce 100644 Binary files a/.maestro/enrichedInput/screenshots/android/list_newline_insertion.png and b/.maestro/enrichedInput/screenshots/android/list_newline_insertion.png differ diff --git a/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_1.png b/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_1.png index 74947bcfd..8fa6291ad 100644 Binary files a/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_1.png and b/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_1.png differ diff --git a/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_2.png b/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_2.png index 7a053e6c5..c092fb52d 100644 Binary files a/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_2.png and b/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_2.png differ diff --git a/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_3.png b/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_3.png index d80b9a756..cb34c00b1 100644 Binary files a/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_3.png and b/.maestro/enrichedInput/screenshots/android/mention_popup_closing_on_cursor_travel_3.png differ diff --git a/.maestro/enrichedInput/screenshots/android/ordered_list_renumbering_after_deleting_second_line.png b/.maestro/enrichedInput/screenshots/android/ordered_list_renumbering_after_deleting_second_line.png index 5d00fb1ce..a9bd4b766 100644 Binary files a/.maestro/enrichedInput/screenshots/android/ordered_list_renumbering_after_deleting_second_line.png and b/.maestro/enrichedInput/screenshots/android/ordered_list_renumbering_after_deleting_second_line.png differ diff --git a/.maestro/enrichedInput/screenshots/android/ordered_list_renumbering_after_emptying_second_line.png b/.maestro/enrichedInput/screenshots/android/ordered_list_renumbering_after_emptying_second_line.png index 53049a519..1b4605ea4 100644 Binary files a/.maestro/enrichedInput/screenshots/android/ordered_list_renumbering_after_emptying_second_line.png and b/.maestro/enrichedInput/screenshots/android/ordered_list_renumbering_after_emptying_second_line.png differ diff --git a/.maestro/enrichedInput/screenshots/android/paragraph_styles_alignment.png b/.maestro/enrichedInput/screenshots/android/paragraph_styles_alignment.png index 31828ca25..7ef5ac4d0 100644 Binary files a/.maestro/enrichedInput/screenshots/android/paragraph_styles_alignment.png and b/.maestro/enrichedInput/screenshots/android/paragraph_styles_alignment.png differ diff --git a/.maestro/enrichedInput/screenshots/android/paragraph_styles_blocks.png b/.maestro/enrichedInput/screenshots/android/paragraph_styles_blocks.png index 4d075df95..09ed9ee13 100644 Binary files a/.maestro/enrichedInput/screenshots/android/paragraph_styles_blocks.png and b/.maestro/enrichedInput/screenshots/android/paragraph_styles_blocks.png differ diff --git a/.maestro/enrichedInput/screenshots/android/paragraph_styles_headings.png b/.maestro/enrichedInput/screenshots/android/paragraph_styles_headings.png index d94481f0a..cd0e16d10 100644 Binary files a/.maestro/enrichedInput/screenshots/android/paragraph_styles_headings.png and b/.maestro/enrichedInput/screenshots/android/paragraph_styles_headings.png differ diff --git a/.maestro/enrichedInput/screenshots/android/paragraph_styles_lists.png b/.maestro/enrichedInput/screenshots/android/paragraph_styles_lists.png index 7bb6e38db..d6716f616 100644 Binary files a/.maestro/enrichedInput/screenshots/android/paragraph_styles_lists.png and b/.maestro/enrichedInput/screenshots/android/paragraph_styles_lists.png differ diff --git a/.maestro/enrichedInput/screenshots/android/paragraph_styles_no_crash.png b/.maestro/enrichedInput/screenshots/android/paragraph_styles_no_crash.png index 3278d7428..91d8dbbc8 100644 Binary files a/.maestro/enrichedInput/screenshots/android/paragraph_styles_no_crash.png and b/.maestro/enrichedInput/screenshots/android/paragraph_styles_no_crash.png differ diff --git a/.maestro/enrichedInput/screenshots/android/scrolling_paragraph_styles_bottom.png b/.maestro/enrichedInput/screenshots/android/scrolling_paragraph_styles_bottom.png index 9c2dd9795..fe9bf8db8 100644 Binary files a/.maestro/enrichedInput/screenshots/android/scrolling_paragraph_styles_bottom.png and b/.maestro/enrichedInput/screenshots/android/scrolling_paragraph_styles_bottom.png differ diff --git a/.maestro/enrichedInput/screenshots/android/scrolling_paragraph_styles_top.png b/.maestro/enrichedInput/screenshots/android/scrolling_paragraph_styles_top.png index 3b946339f..298171e07 100644 Binary files a/.maestro/enrichedInput/screenshots/android/scrolling_paragraph_styles_top.png and b/.maestro/enrichedInput/screenshots/android/scrolling_paragraph_styles_top.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/dynamic_ol_markers_after.png b/.maestro/enrichedInput/screenshots/ios/dynamic_ol_markers_after.png new file mode 100644 index 000000000..65dcb826d Binary files /dev/null and b/.maestro/enrichedInput/screenshots/ios/dynamic_ol_markers_after.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/dynamic_ol_markers_before.png b/.maestro/enrichedInput/screenshots/ios/dynamic_ol_markers_before.png new file mode 100644 index 000000000..d76caeb35 Binary files /dev/null and b/.maestro/enrichedInput/screenshots/ios/dynamic_ol_markers_before.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/dynamic_ol_markers_revert.png b/.maestro/enrichedInput/screenshots/ios/dynamic_ol_markers_revert.png new file mode 100644 index 000000000..c3461cd4c Binary files /dev/null and b/.maestro/enrichedInput/screenshots/ios/dynamic_ol_markers_revert.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/empty_element_parsing.png b/.maestro/enrichedInput/screenshots/ios/empty_element_parsing.png index ea9dd3310..0a72ed95a 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/empty_element_parsing.png and b/.maestro/enrichedInput/screenshots/ios/empty_element_parsing.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/empty_lists_parsing.png b/.maestro/enrichedInput/screenshots/ios/empty_lists_parsing.png index 015e7d397..0fc4306d6 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/empty_lists_parsing.png and b/.maestro/enrichedInput/screenshots/ios/empty_lists_parsing.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/image_inside_list_parsing.png b/.maestro/enrichedInput/screenshots/ios/image_inside_list_parsing.png index 599bd6522..5fbddd064 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/image_inside_list_parsing.png and b/.maestro/enrichedInput/screenshots/ios/image_inside_list_parsing.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/list_newline_insertion.png b/.maestro/enrichedInput/screenshots/ios/list_newline_insertion.png index ea1bdc616..8d10b6ccf 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/list_newline_insertion.png and b/.maestro/enrichedInput/screenshots/ios/list_newline_insertion.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/ordered_list_renumbering_after_deleting_second_line.png b/.maestro/enrichedInput/screenshots/ios/ordered_list_renumbering_after_deleting_second_line.png index d72a13061..b9e6f15a6 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/ordered_list_renumbering_after_deleting_second_line.png and b/.maestro/enrichedInput/screenshots/ios/ordered_list_renumbering_after_deleting_second_line.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/ordered_list_renumbering_after_emptying_second_line.png b/.maestro/enrichedInput/screenshots/ios/ordered_list_renumbering_after_emptying_second_line.png index 431d95210..8116c7122 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/ordered_list_renumbering_after_emptying_second_line.png and b/.maestro/enrichedInput/screenshots/ios/ordered_list_renumbering_after_emptying_second_line.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/paragraph_styles_alignment.png b/.maestro/enrichedInput/screenshots/ios/paragraph_styles_alignment.png index 28f837a59..63b13dd07 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/paragraph_styles_alignment.png and b/.maestro/enrichedInput/screenshots/ios/paragraph_styles_alignment.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/paragraph_styles_lists.png b/.maestro/enrichedInput/screenshots/ios/paragraph_styles_lists.png index 2bee088d8..d0b9a2ed7 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/paragraph_styles_lists.png and b/.maestro/enrichedInput/screenshots/ios/paragraph_styles_lists.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/paragraph_styles_no_crash.png b/.maestro/enrichedInput/screenshots/ios/paragraph_styles_no_crash.png index c54bd5738..538c2def3 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/paragraph_styles_no_crash.png and b/.maestro/enrichedInput/screenshots/ios/paragraph_styles_no_crash.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/scrolling_paragraph_styles_bottom.png b/.maestro/enrichedInput/screenshots/ios/scrolling_paragraph_styles_bottom.png index 96087f395..7b52e6e43 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/scrolling_paragraph_styles_bottom.png and b/.maestro/enrichedInput/screenshots/ios/scrolling_paragraph_styles_bottom.png differ diff --git a/.maestro/enrichedInput/screenshots/ios/scrolling_paragraph_styles_top.png b/.maestro/enrichedInput/screenshots/ios/scrolling_paragraph_styles_top.png index 241461ace..2fc6a1463 100644 Binary files a/.maestro/enrichedInput/screenshots/ios/scrolling_paragraph_styles_top.png and b/.maestro/enrichedInput/screenshots/ios/scrolling_paragraph_styles_top.png differ diff --git a/.maestro/enrichedText/flows/dynamic_ol_markers.yaml b/.maestro/enrichedText/flows/dynamic_ol_markers.yaml new file mode 100644 index 000000000..b8bd5d6e7 --- /dev/null +++ b/.maestro/enrichedText/flows/dynamic_ol_markers.yaml @@ -0,0 +1,41 @@ +appId: swmansion.enriched.example +--- +# Validates that ordered list margins dynamically adjust when transitioning +# between single-digit (9) and double-digit (10) indexes. +- launchApp + +- tapOn: + id: 'toggle-screen-button' + +- tapOn: + id: 'toggle-enriched-text-screen-button' + +- runFlow: + file: '../subflows/set_enriched_text_value.yaml' + env: + VALUE: > + +
    +
  1. A
  2. +
  3. B
  4. +
  5. C
  6. +
+
+
    +
  1. A
  2. +
  3. B
  4. +
  5. C
  6. +
  7. D
  8. +
  9. E
  10. +
  11. F
  12. +
  13. G
  14. +
  15. H
  16. +
  17. I
  18. +
  19. J
  20. +
+ + +- runFlow: + file: '../subflows/capture_or_assert_screenshot.yaml' + env: + SCREENSHOT_NAME: 'dynamic_ol_markers' diff --git a/.maestro/enrichedText/screenshots/android/alignment_visual.png b/.maestro/enrichedText/screenshots/android/alignment_visual.png index c3e03756a..7c75ce27e 100644 Binary files a/.maestro/enrichedText/screenshots/android/alignment_visual.png and b/.maestro/enrichedText/screenshots/android/alignment_visual.png differ diff --git a/.maestro/enrichedText/screenshots/android/dynamic_ol_markers.png b/.maestro/enrichedText/screenshots/android/dynamic_ol_markers.png new file mode 100644 index 000000000..d572bec99 Binary files /dev/null and b/.maestro/enrichedText/screenshots/android/dynamic_ol_markers.png differ diff --git a/.maestro/enrichedText/screenshots/android/empty_list_elements_display.png b/.maestro/enrichedText/screenshots/android/empty_list_elements_display.png index 62859c08e..cc42cad1d 100644 Binary files a/.maestro/enrichedText/screenshots/android/empty_list_elements_display.png and b/.maestro/enrichedText/screenshots/android/empty_list_elements_display.png differ diff --git a/.maestro/enrichedText/screenshots/android/font_scaling.png b/.maestro/enrichedText/screenshots/android/font_scaling.png index 488791012..4382f8163 100644 Binary files a/.maestro/enrichedText/screenshots/android/font_scaling.png and b/.maestro/enrichedText/screenshots/android/font_scaling.png differ diff --git a/.maestro/enrichedText/screenshots/android/inline_styles_display.png b/.maestro/enrichedText/screenshots/android/inline_styles_display.png index fe658211f..806725a3f 100644 Binary files a/.maestro/enrichedText/screenshots/android/inline_styles_display.png and b/.maestro/enrichedText/screenshots/android/inline_styles_display.png differ diff --git a/.maestro/enrichedText/screenshots/android/paragraph_styles_display.png b/.maestro/enrichedText/screenshots/android/paragraph_styles_display.png index 112148d77..8a9dce562 100644 Binary files a/.maestro/enrichedText/screenshots/android/paragraph_styles_display.png and b/.maestro/enrichedText/screenshots/android/paragraph_styles_display.png differ diff --git a/.maestro/enrichedText/screenshots/ios/alignment_visual.png b/.maestro/enrichedText/screenshots/ios/alignment_visual.png index cffcfac4b..b25f5d91e 100644 Binary files a/.maestro/enrichedText/screenshots/ios/alignment_visual.png and b/.maestro/enrichedText/screenshots/ios/alignment_visual.png differ diff --git a/.maestro/enrichedText/screenshots/ios/dynamic_ol_markers.png b/.maestro/enrichedText/screenshots/ios/dynamic_ol_markers.png new file mode 100644 index 000000000..1e5763cb4 Binary files /dev/null and b/.maestro/enrichedText/screenshots/ios/dynamic_ol_markers.png differ diff --git a/.maestro/enrichedText/screenshots/ios/empty_list_elements_display.png b/.maestro/enrichedText/screenshots/ios/empty_list_elements_display.png index 03dcbd827..b90b8b7ea 100644 Binary files a/.maestro/enrichedText/screenshots/ios/empty_list_elements_display.png and b/.maestro/enrichedText/screenshots/ios/empty_list_elements_display.png differ diff --git a/.maestro/enrichedText/screenshots/ios/font_scaling.png b/.maestro/enrichedText/screenshots/ios/font_scaling.png index 95d5c5808..3c7139eda 100644 Binary files a/.maestro/enrichedText/screenshots/ios/font_scaling.png and b/.maestro/enrichedText/screenshots/ios/font_scaling.png differ diff --git a/.maestro/enrichedText/screenshots/ios/paragraph_styles_display.png b/.maestro/enrichedText/screenshots/ios/paragraph_styles_display.png index 8abd1b2af..a09e6d38c 100644 Binary files a/.maestro/enrichedText/screenshots/ios/paragraph_styles_display.png and b/.maestro/enrichedText/screenshots/ios/paragraph_styles_display.png differ diff --git a/android/src/main/java/com/swmansion/enriched/common/OrderedListMargins.kt b/android/src/main/java/com/swmansion/enriched/common/OrderedListMargins.kt new file mode 100644 index 000000000..fc1cb4148 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/common/OrderedListMargins.kt @@ -0,0 +1,30 @@ +package com.swmansion.enriched.common + +import android.graphics.Paint +import android.text.Spanned +import com.swmansion.enriched.common.spans.EnrichedOrderedListSpan + +// Recomputes the shared marker column width for every ordered-list item. +// Returns true when any item's column width changed, so callers can force a relayout. +fun updateOrderedListColumnMargins( + text: Spanned, + paint: Paint, +): Boolean { + val spans = text.getSpans(0, text.length, EnrichedOrderedListSpan::class.java) + val sortedSpans = spans.sortedBy { text.getSpanStart(it) } + + var changed = false + var previousIndex = 0 + var highestIndex = 0 + for (span in sortedSpans.reversed()) { + val currentIndex = span.index + if (currentIndex > previousIndex) { + highestIndex = currentIndex + } + if (span.updateColumnMargin(paint, highestIndex)) changed = true + + previousIndex = currentIndex + } + + return changed +} diff --git a/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedOrderedListSpan.kt b/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedOrderedListSpan.kt index d57619c91..a4d05be7d 100644 --- a/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedOrderedListSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/common/spans/EnrichedOrderedListSpan.kt @@ -10,6 +10,8 @@ import android.text.style.LeadingMarginSpan import android.text.style.MetricAffectingSpan import com.swmansion.enriched.common.EnrichedStyle import com.swmansion.enriched.common.spans.interfaces.EnrichedParagraphSpan +import kotlin.math.ceil +import kotlin.math.max open class EnrichedOrderedListSpan( var index: Int, @@ -17,6 +19,22 @@ open class EnrichedOrderedListSpan( ) : MetricAffectingSpan(), LeadingMarginSpan, EnrichedParagraphSpan { + var columnMargin = enrichedStyle.olMarginLeft + + // Computes the marker column width from the widest marker in the list ("."). + // Returns true when the column width actually changed, so callers can force a relayout. + fun updateColumnMargin( + paint: Paint, + highestIndex: Int, + ): Boolean { + val highestIndexText = "$highestIndex." + val highestIndexWidth = ceil(paint.measureText(highestIndexText)).toInt() + val newColumnMargin = max(enrichedStyle.olMarginLeft, highestIndexWidth) + if (newColumnMargin == columnMargin) return false + columnMargin = newColumnMargin + return true + } + override fun updateMeasureState(p0: TextPaint) { // Do nothing, but inform layout that this span affects text metrics } @@ -25,7 +43,7 @@ open class EnrichedOrderedListSpan( // Do nothing, but inform layout that this span affects text metrics } - override fun getLeadingMargin(first: Boolean): Int = enrichedStyle.olMarginLeft + enrichedStyle.olGapWidth + override fun getLeadingMargin(first: Boolean): Int = columnMargin + enrichedStyle.olGapWidth override fun drawLeadingMargin( canvas: Canvas, @@ -46,7 +64,7 @@ open class EnrichedOrderedListSpan( val width = paint.measureText(text) val yPosition = baseline.toFloat() - val xPosition = (enrichedStyle.olMarginLeft + x - width / 2) * dir + val xPosition = (columnMargin + x - width) * dir val originalColor = paint.color val originalTypeface = paint.typeface diff --git a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextStyle.kt b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextStyle.kt index d1ee9b4ff..c79890bee 100644 --- a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextStyle.kt +++ b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextStyle.kt @@ -98,7 +98,7 @@ data class EnrichedTextStyle( blockquoteStripeWidth = parseFloat(blockquote, "borderWidth", allowFontScaling).toInt(), blockquoteGapWidth = parseFloat(blockquote, "gapWidth", allowFontScaling).toInt(), olGapWidth = parseFloat(orderedList, "gapWidth", allowFontScaling).toInt(), - olMarginLeft = calculateOlMarginLeft(fontSize, parseFloat(orderedList, "marginLeft", allowFontScaling).toInt()), + olMarginLeft = parseFloat(orderedList, "marginLeft", allowFontScaling).toInt(), olMarkerFontWeight = parseOptionalFontWeight(orderedList, "markerFontWeight"), olMarkerColor = parseOptionalColor(context, orderedList, "markerColor"), ulGapWidth = parseFloat(unorderedList, "gapWidth", allowFontScaling).toInt(), @@ -169,14 +169,6 @@ data class EnrichedTextStyle( return parseFontWeight(weight) } - private fun calculateOlMarginLeft( - fontSize: Int, - userMargin: Int, - ): Int { - val leadMargin = fontSize / 2 - return leadMargin + userMargin - } - private fun parseMentionsStyle( context: ReactContext, map: ReadableMap?, diff --git a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt index 53a0a503a..aa90815fc 100644 --- a/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt +++ b/android/src/main/java/com/swmansion/enriched/text/EnrichedTextView.kt @@ -27,6 +27,7 @@ import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.common.GumboNormalizer import com.swmansion.enriched.common.parser.EnrichedParser import com.swmansion.enriched.common.pixelFromSpOrDp +import com.swmansion.enriched.common.updateOrderedListColumnMargins import com.swmansion.enriched.text.spans.EnrichedTextImageSpan import com.swmansion.enriched.text.spans.interfaces.EnrichedTextClickableSpan import com.swmansion.enriched.text.spans.interfaces.EnrichedTextSpan @@ -179,6 +180,7 @@ class EnrichedTextView : AppCompatTextView { val parsed = parseText(text, style) if (parsed != null) { parsedText = parsed + (parsed as? Spanned)?.let { updateOrderedListColumnMargins(it, paint) } setText(parsed, BufferType.NORMAL) observeAsyncImages() } else { @@ -283,6 +285,7 @@ class EnrichedTextView : AppCompatTextView { } if (modified) { + updateOrderedListColumnMargins(spannable, paint) this.text = spannable } } diff --git a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt index 023cc20fc..da6181427 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt @@ -13,6 +13,7 @@ import android.text.Editable import android.text.InputType import android.text.Spannable import android.text.SpannableString +import android.text.Spanned import android.util.AttributeSet import android.util.Log import android.util.Patterns @@ -43,6 +44,7 @@ import com.swmansion.enriched.common.EnrichedSpanFlags import com.swmansion.enriched.common.GumboNormalizer import com.swmansion.enriched.common.parser.EnrichedParser import com.swmansion.enriched.common.pixelFromSpOrDp +import com.swmansion.enriched.common.updateOrderedListColumnMargins import com.swmansion.enriched.textinput.events.MentionHandler import com.swmansion.enriched.textinput.events.OnContextMenuItemPressEvent import com.swmansion.enriched.textinput.events.OnInputBlurEvent @@ -448,6 +450,9 @@ class EnrichedTextInputView : runAsATransaction { val newText = if (shouldParseHtml) parseText(value) else value + // Compute ordered-list marker column widths before setText so the first layout reserves + // the correct leading margin (getLeadingMargin runs during layout, before drawing). + (newText as? Spanned)?.let { updateOrderedListColumnMargins(it, paint) } setText(newText) applyLineSpacing() diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/HtmlStyle.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/HtmlStyle.kt index 5a772ded9..a02e8398f 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/HtmlStyle.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/HtmlStyle.kt @@ -113,9 +113,7 @@ class HtmlStyle : EnrichedStyle { blockquoteStripeWidth = parseFloat(blockquoteStyle, "borderWidth").toInt() val olStyle = style.getMap("ol") - val userDefinedMarginLeft = parseFloat(olStyle, "marginLeft").toInt() - val calculatedMarginLeft = calculateOlMarginLeft(view, userDefinedMarginLeft) - olMarginLeft = calculatedMarginLeft + olMarginLeft = parseFloat(olStyle, "marginLeft").toInt() olGapWidth = parseFloat(olStyle, "gapWidth").toInt() olMarkerColor = parseOptionalColor(olStyle, "markerColor") olMarkerFontWeight = parseOptionalFontWeight(olStyle, "markerFontWeight") @@ -213,16 +211,6 @@ class HtmlStyle : EnrichedStyle { throw Error("Specified textDecorationLine value is not supported: $underline. Supported values are 'underline' and 'none'.") } - private fun calculateOlMarginLeft( - view: EnrichedTextInputView?, - userMargin: Int, - ): Int { - val fontSize = view?.fontSize?.toInt() ?: 0 - val leadMargin = fontSize / 2 - - return leadMargin + userMargin - } - private fun ensureValueIsSet( map: ReadableMap?, key: String, diff --git a/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt b/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt index 236560f10..a8a8ed3ee 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/styles/ListStyles.kt @@ -4,8 +4,10 @@ import android.text.Editable import android.text.Spannable import android.text.SpannableStringBuilder import android.text.Spanned +import android.text.style.ParagraphStyle import com.swmansion.enriched.common.EnrichedConstants import com.swmansion.enriched.common.EnrichedSpanFlags +import com.swmansion.enriched.common.updateOrderedListColumnMargins import com.swmansion.enriched.textinput.EnrichedTextInputView import com.swmansion.enriched.textinput.spans.EnrichedInputCheckboxListSpan import com.swmansion.enriched.textinput.spans.EnrichedInputOrderedListSpan @@ -104,17 +106,36 @@ class ListStyles( return true } - fun updateOrderedListIndexes( - text: Spannable, - position: Int, - ) { - val spans = text.getSpans(position + 1, text.length, EnrichedInputOrderedListSpan::class.java) + fun updateOrderedListIndexes(text: Spannable) { + val spans = text.getSpans(0, text.length, EnrichedInputOrderedListSpan::class.java) val sortedSpans = spans.sortedBy { text.getSpanStart(it) } for (span in sortedSpans) { val spanStart = text.getSpanStart(span) val index = getOrderedListIndex(text, spanStart) span.setListIndex(index) } + + val marginsChanged = updateOrderedListColumnMargins(text, view.paint) + + // Ordered list margins got updated, so we need to force a re-layout of that list. + // Uses the same empty ParagraphStyle trick as EnrichedSpanWatcher.updateNextLineLayout. + if (marginsChanged) { + forceOrderedListRelayout(text, sortedSpans) + } + } + + private fun forceOrderedListRelayout( + text: Spannable, + sortedSpans: List, + ) { + if (sortedSpans.isEmpty()) return + + class EmptySpan : ParagraphStyle + + val start = text.getSpanStart(sortedSpans.first()) + val end = text.getSpanEnd(sortedSpans.last()) + val (safeStart, safeEnd) = text.getSafeSpanBoundaries(start, end) + text.setSpan(EmptySpan(), safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } private fun toggleStyle( diff --git a/android/src/main/java/com/swmansion/enriched/textinput/watchers/EnrichedSpanWatcher.kt b/android/src/main/java/com/swmansion/enriched/textinput/watchers/EnrichedSpanWatcher.kt index b3d7b0936..1d9bbe764 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/watchers/EnrichedSpanWatcher.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/watchers/EnrichedSpanWatcher.kt @@ -27,7 +27,7 @@ class EnrichedSpanWatcher( end: Int, ) { updateNextLineLayout(what, text, end) - updateUnorderedListSpans(what, text, end) + updateUnorderedListSpans(what, text) emitEvent(text, what) } @@ -38,7 +38,7 @@ class EnrichedSpanWatcher( end: Int, ) { updateNextLineLayout(what, text, end) - updateUnorderedListSpans(what, text, end) + updateUnorderedListSpans(what, text) emitEvent(text, what) } @@ -56,10 +56,9 @@ class EnrichedSpanWatcher( private fun updateUnorderedListSpans( what: Any, text: Spannable, - end: Int, ) { if (what is EnrichedInputOrderedListSpan) { - view.listStyles?.updateOrderedListIndexes(text, end) + view.listStyles?.updateOrderedListIndexes(text) } } diff --git a/apps/example/src/constants/editorConfig.ts b/apps/example/src/constants/editorConfig.ts index 2b9d44b0b..63043835e 100644 --- a/apps/example/src/constants/editorConfig.ts +++ b/apps/example/src/constants/editorConfig.ts @@ -111,7 +111,7 @@ export const htmlStyle = { }, ol: { gapWidth: 16, - marginLeft: 24, + marginLeft: 16, markerColor: 'navy', markerFontWeight: 'bold', }, diff --git a/ios/extensions/LayoutManagerExtension.mm b/ios/extensions/LayoutManagerExtension.mm index 72c9620eb..38d033b05 100644 --- a/ios/extensions/LayoutManagerExtension.mm +++ b/ios/extensions/LayoutManagerExtension.mm @@ -448,7 +448,7 @@ - (void)drawDecimal:(id)host indent:(CGFloat)indent { CGFloat gapWidth = [host.config orderedListGapWidth]; CGSize markerSize = [marker sizeWithAttributes:markerAttributes]; - CGFloat markerX = origin.x + indent - gapWidth - markerSize.width / 2; + CGFloat markerX = origin.x + indent - gapWidth - markerSize.width; UIFont *markerFont = markerAttributes[NSFontAttributeName]; // drawAtPoint draws from the top-left bounding box of the string. diff --git a/ios/inputAttributesManager/InputAttributesManager.mm b/ios/inputAttributesManager/InputAttributesManager.mm index fc07d871f..db2e674b4 100644 --- a/ios/inputAttributesManager/InputAttributesManager.mm +++ b/ios/inputAttributesManager/InputAttributesManager.mm @@ -62,6 +62,10 @@ - (void)clearRemovedTypingAttributes { } - (void)handleDirtyRangesStyling { + // snapshot edited ranges (including 0-length ones) before filtering, so + // we can use them when recalculating adjacent ordered lists + NSArray *editedRangesForListRecalc = [_dirtyRanges copy]; + // Filter out 0 length ranges for styling. NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(NSValue *evaluatedObject, NSDictionary *_) { @@ -122,6 +126,16 @@ - (void)handleDirtyRangesStyling { } } } + // refresh ordered lists adjacent to any edit + OrderedListStyle *orderedListStyle = + (OrderedListStyle *)_input->stylesDict[@([OrderedListStyle getType])]; + if (orderedListStyle != nil) { + for (NSValue *rangeObj in editedRangesForListRecalc) { + [orderedListStyle + recalculateListsAroundEditedRange:[rangeObj rangeValue]]; + } + } + // do the typing attributes management, with no selection [self manageTypingAttributesWithOnlySelection:NO]; diff --git a/ios/interfaces/StyleHeaders.h b/ios/interfaces/StyleHeaders.h index aca5d4a3c..d59b9c424 100644 --- a/ios/interfaces/StyleHeaders.h +++ b/ios/interfaces/StyleHeaders.h @@ -76,6 +76,7 @@ @end @interface OrderedListStyle : StyleBase +- (void)recalculateListsAroundEditedRange:(NSRange)range; @end @interface CheckboxListStyle : StyleBase diff --git a/ios/styles/OrderedListStyle.mm b/ios/styles/OrderedListStyle.mm index 717d0887c..7f0d4321b 100644 --- a/ios/styles/OrderedListStyle.mm +++ b/ios/styles/OrderedListStyle.mm @@ -22,24 +22,106 @@ - (BOOL)needsZWS { return YES; } -- (CGFloat)headIndent { - return [self.host.config orderedListMarginLeft] + - [self.host.config orderedListGapWidth]; -} - - (void)applyStyling:(NSRange)range { // lists are drawn manually - // margin before marker + gap between marker and paragraph - CGFloat listHeadIndent = [self headIndent]; + + // if the widest counter ("N.") width overflows the initially given margin, + // we expand that margin. Every item in the same contiguous list must share + // the same column width, so we expand to the full ordered list occurrence and + // re-indent all of it - even when only a single paragraph is dirty (e.g. an + // item was just added) + NSInteger itemCount = 0; + NSRange listRange = [self contiguousOrderedListRangeContaining:range + itemCount:&itemCount]; + + [self applyIndentForListRange:listRange itemCount:itemCount]; +} + +// re-styling is normally run only on dirty-ranges, but a dirty-range +// may change an ordered list structure and those lists need to be +// re-styled. E.g. it happens when we remove an ordered list +// element - it affects the adjacent lists, as their ordinals are different +// and the computed margin might be stale +- (void)recalculateListsAroundEditedRange:(NSRange)range { + NSUInteger length = self.host.textView.textStorage.string.length; + NSUInteger start = range.location; + NSUInteger end = NSMaxRange(range); + + // look for ordered lists in adjacent locations + NSMutableArray *seeds = [NSMutableArray array]; + if (start > 0) { + [seeds addObject:@(start - 1)]; + } + [seeds addObject:@(start)]; + [seeds addObject:@(end)]; + + // dedupe so each surviving contiguous list is recomputed at most once + NSMutableArray *handled = [NSMutableArray array]; + + for (NSNumber *seedNum in seeds) { + NSUInteger seed = seedNum.unsignedIntegerValue; + if (seed >= length) { + continue; + } + if (![self detect:NSMakeRange(seed, 0)]) { + continue; + } + + BOOL alreadyHandled = NO; + for (NSValue *handledRange in handled) { + if (NSLocationInRange(seed, [handledRange rangeValue])) { + alreadyHandled = YES; + break; + } + } + if (alreadyHandled) { + continue; + } + + NSInteger itemCount = 0; + NSRange listRange = + [self contiguousOrderedListRangeContaining:NSMakeRange(seed, 0) + itemCount:&itemCount]; + [handled addObject:[NSValue valueWithRange:listRange]]; + [self applyIndentForListRange:listRange itemCount:itemCount]; + } +} + +// computes the shared marker-column indent for a list of the given item +// count. The largest marker value equals the item count (numbering starts +// at 1); if its width overflows the configured margin we expand to fit it +- (CGFloat)headIndentForItemCount:(NSInteger)itemCount { + NSString *widestMarker = + [NSString stringWithFormat:@"%d.", (int)MAX(itemCount, 1)]; + CGFloat widestMarkerWidth = + [widestMarker sizeWithAttributes:@{ + NSFontAttributeName : [self.host.config orderedListMarkerFont] + }] + .width; + + CGFloat markerColumnWidth = + MAX([self.host.config orderedListMarginLeft], widestMarkerWidth); + return markerColumnWidth + [self.host.config orderedListGapWidth]; +} + +- (void)applyIndentForListRange:(NSRange)listRange + itemCount:(NSInteger)itemCount { + CGFloat listHeadIndent = [self headIndentForItemCount:itemCount]; [self.host.textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName - inRange:range + inRange:listRange options:0 usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { - NSMutableParagraphStyle *pStyle = - [(NSParagraphStyle *)value mutableCopy]; + NSParagraphStyle *existing = (NSParagraphStyle *)value; + // skip re-styling paragraphs that don't require it + if (existing != nullptr && + existing.headIndent == listHeadIndent && + existing.firstLineHeadIndent == listHeadIndent) { + return; + } + NSMutableParagraphStyle *pStyle = [existing mutableCopy]; pStyle.headIndent = listHeadIndent; pStyle.firstLineHeadIndent = listHeadIndent; [self.host.textView.textStorage @@ -58,10 +140,86 @@ - (void)applyStylingToTypingAttrs:(NSMutableDictionary *)attributes { [attributes[NSParagraphStyleAttributeName] mutableCopy]; if (pStyle == nil) return; - CGFloat indent = [self headIndent]; - pStyle.headIndent = indent; - pStyle.firstLineHeadIndent = indent; + + NSUInteger location = self.host.textView.selectedRange.location; + NSUInteger length = self.host.textView.textStorage.length; + + if (location < length) { + // applying styling to typing attributes always happen after applying + // the styles, so we can lookup the existing style for the indent + NSParagraphStyle *existingStyle = + [self.host.textView.textStorage attribute:NSParagraphStyleAttributeName + atIndex:location + effectiveRange:NULL]; + + if (existingStyle) { + pStyle.headIndent = existingStyle.headIndent; + pStyle.firstLineHeadIndent = existingStyle.firstLineHeadIndent; + } + } else { + CGFloat fallbackIndent = [self headIndentForItemCount:1]; + pStyle.headIndent = fallbackIndent; + pStyle.firstLineHeadIndent = fallbackIndent; + } + attributes[NSParagraphStyleAttributeName] = pStyle; } +// walks paragraphs backward and forward from the given range to find the full +// contiguous run of ordered-list items it belongs to, and counts them +- (NSRange)contiguousOrderedListRangeContaining:(NSRange)range + itemCount:(NSInteger *)outCount { + NSString *fullText = self.host.textView.textStorage.string; + NSUInteger length = fullText.length; + if (length == 0) { + if (outCount != nullptr) { + *outCount = 0; + } + return NSMakeRange(range.location, 0); + } + + NSUInteger seedLocation = MIN(range.location, length - 1); + NSRange initialParagraph = + [fullText paragraphRangeForRange:NSMakeRange(seedLocation, 0)]; + NSRange firstParagraph = initialParagraph; + + NSInteger precedingCount = 0; + + // seek backward over preceding ordered-list paragraphs, counting items + while (firstParagraph.location > 0) { + NSRange previous = [fullText + paragraphRangeForRange:NSMakeRange(firstParagraph.location - 1, 0)]; + if (![self detect:NSMakeRange(previous.location, 0)]) { + break; + } + firstParagraph = previous; + precedingCount += 1; + } + + // seek forward over following ordered-list paragraphs, counting items + NSInteger followingCount = 0; + NSRange lastParagraph = initialParagraph; + NSRange cursor = initialParagraph; + while (true) { + lastParagraph = cursor; + NSUInteger nextLocation = NSMaxRange(cursor); + if (nextLocation >= length) { + break; + } + NSRange next = + [fullText paragraphRangeForRange:NSMakeRange(nextLocation, 0)]; + if (![self detect:NSMakeRange(next.location, 0)]) { + break; + } + cursor = next; + followingCount += 1; + } + + if (outCount != nullptr) { + *outCount = precedingCount + followingCount + 1; + } + return NSMakeRange(firstParagraph.location, + NSMaxRange(lastParagraph) - firstParagraph.location); +} + @end diff --git a/ios/styles/UnorderedListStyle.mm b/ios/styles/UnorderedListStyle.mm index 160ac946b..0e94894ac 100644 --- a/ios/styles/UnorderedListStyle.mm +++ b/ios/styles/UnorderedListStyle.mm @@ -24,6 +24,7 @@ - (BOOL)needsZWS { - (CGFloat)headIndent { return [self.host.config unorderedListMarginLeft] + + [self.host.config unorderedListBulletSize] + [self.host.config unorderedListGapWidth]; }