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: >
+
+
+ - A
+ - B
+ - C
+ - D
+ - E
+ - F
+ - G
+ - H
+ - I
+
+
+
+- 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: >
+
+
+ - A
+ - B
+ - C
+
+
+
+ - A
+ - B
+ - C
+ - D
+ - E
+ - F
+ - G
+ - H
+ - I
+ - J
+
+
+
+- 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];
}