Skip to content

fix: size ordered list markers to their widest number - #1005

Closed
mwolson wants to merge 1 commit into
software-mansion:mainfrom
mwolson:swm/ordered-list-marker-width
Closed

mwolson wants to merge 1 commit into
software-mansion:mainfrom
mwolson:swm/ordered-list-marker-width

Conversation

@mwolson

@mwolson mwolson commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

What/Why?

Ordered lists that reach 100. clip their markers: the marker column is sized
for 99. (a fixed 20pt in the standalone iOS package) and numbers are
right-aligned against it, so 100. draws as 00. and 1000. as 00..

Closes #1003.

Each ordered list now measures its widest rendered marker in the marker font and
widens its own column to fit; markerMinWidth is still the floor. Task items
draw a checkbox, so their numbers do not count. In the React Native package and
the standalone Android package, lists that stay under 100 keep the current 99.
column, so their layout is unchanged.

Lists nested under a widened item move with it, so nested markers stay next to
their own text instead of keeping their old position. A following list gets its
own column again.

The change covers all three packages:

  • react-native-enriched-markdown, Android and iOS.
  • enriched-markdown-android: the same approach. Lists there always start at 1,
    so this matters from the 100th item.
  • enriched-markdown-ios: the column becomes the widest marker or the existing
    20pt, whichever is larger. At the default 16pt medium marker font 10. to
    99. already measure 21.5 to 24.6pt, so two-digit lists now get a column that
    fits them, about 4.6pt wider. Task items keep aligning with their numbered
    siblings, as they do today. Quotes inside a widened item, including ones a few
    list levels down and lists inside those quotes, move by the extra width once.

One related Android fix in the React Native package: toggling a task checkbox
re-added the task's span at the end of the text's span list, and Android draws
leading margins in that order, so a nested task's checkbox moved right by its
ancestors' margins on every tap. The toggle now re-adds the task's spans and the
ancestor spans after them in their original order. The standalone Android
package already toggles in place and needed no change.

The iOS docs line for markerMinWidth said 0 sizes the column to each marker,
which was not what the code did; it now describes the new behavior.

Testing

  • react-native-enriched-markdown Android: OrderedListMarkerWidthTest renders
    through the real renderer into a StaticLayout and records where markers are
    drawn. It checks marker columns for short and wide lists, starts at 0, 98, 998
    and 12345, nested and following lists, markerMinWidth, bullets and tasks
    under wide parents, three levels, RTL, anchored markers, and marker positions
    across checkbox toggles.
  • react-native-enriched-markdown iOS:
    __tests__/ios/OrderedListMarkerWidthTests.mm (the first suite in that
    directory) covers the same column and nesting rules; yarn test:ios passes on
    an iOS 26.5 Simulator.
  • enriched-markdown-android: OrderedListMarkerWidthTest (8 tests, adapted
    for lists that always start at 1). 7 of them fail without the fix. The full
    testDebugUnitTest, ktlintCheck and lintDebug pass.
  • enriched-markdown-ios: OrderedListMarkerWidthTests (13 tests). 3 fail on
    main; the others pin nesting, task and quote offsets that the widening must
    keep. The full package suite and swiftlint lint --strict (0.65.0) pass.
  • Maestro: new ordered_list_wide_markers_test for the React Native example
    (short control, 98 to 101, 998 to 1001, nested under a wide parent) and for
    the native iOS example (1 to 12 with a nested bullet), with baselines. I ran
    every list-tagged flow in capture mode on main and on this branch, in the
    React Native example on Android API 36 (Pixel 9) and iOS 26.5 (iPhone 17), and
    in both native examples. Every existing list capture is pixel-identical
    between main and this branch. My iOS environment (Xcode 27.0) does not
    reproduce the committed iOS baselines pixel for pixel even on main, so the
    new iOS baselines may need regenerating on your reference setup.
  • Reproduced with 1.1.1 in a blank Expo app:
    https://github.com/mwolson/tmp-enriched-markdown-issues/tree/main/ordered-list-marker-width

Screenshots

React Native example, ordered_list_wide_markers_test:

Before After
Android
iOS

Native iOS example, two-digit list (the markers used to reach past the column
into the container's padding):

Before After

PR Checklist

  • Code compiles and runs on iOS
  • Code compiles and runs on Android
  • Updated documentation/README if applicable
  • Ran example app to verify changes
  • E2E tests are passing (all list flows pass in capture mode; see Testing
    for the pixel-exact baselines)
  • Required E2E tests have been added (if applicable)

@mwolson
mwolson marked this pull request as ready for review October 15, 2026 16:25
juliusmarminge added a commit to pingdotgg/enriched-markdown that referenced this pull request Oct 15, 2026
* fix(lists): size ordered list markers to their widest number

Task toggles keep list span order, fixing fork-base checkbox shifts right by ancestor margins.

* fix(android): align mixed nested list indents with iOS

* fix(android): re-add only the toggled task and its ancestors, as upstream software-mansion#1005 does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@juliusmarminge

Copy link
Copy Markdown
Contributor

Heads-up for whichever of this PR and #1006 lands second: they conflict in the Android list files of both the React Native package and enriched-markdown-android. We shipped both in the pingdotgg fork (pingdotgg#2 and #3) using @mwolson's combined resolution (combined/marker-width-plus-mixed on his fork). It drops this PR's depth>0 OrderedListSpan.getLeadingMargin override in favour of #1006's parentMarkerWidth, and getBaselineMarkerWidth() returns the default 99. width. A layout probe found the extra width counted once in every combined shape: bullets under a wide parent, a wide list under a bullet, wide under wide, tasks, and RTL. Using the same resolution upstream would let us drop our fork copy of both.

@hryhoriiK97

Copy link
Copy Markdown
Collaborator

Thanks for this, the fix is right and the test coverage is thorough. I reviewed it locally on both platforms and have two follow-up commits on top of 8407374. GitHub refuses maintainer pushes to this branch because your fork's parent is pingdotgg/enriched-markdown rather than this repo, so I opened #1015, which carries your commit unchanged as its first commit plus the follow-ups. Once #1015 merges I'll close this one; it stays open until then so the discussion here is not lost.

What the follow-ups change

  • Android, both packages: the nested-item margin lives in BaseListSpan via an overridable markerExtraWidth, so fix(android): align mixed nested list indents with iOS #1006 has one formula to extend, and ListContextManager saves and restores the widening with the rest of the list state. The column is measured by a companion function instead of a throwaway span, and the widening is rounded to whole pixels so Int margins and float marker positions agree at every depth.
  • All platforms: a task item inside a widened ordered list now moves with its numbered siblings, and so do its children, matching what the Swift package already did.
  • iOS: listMarkerOffset is ancestorMarkerOffset to match Android, and the Swift default column width moved onto ListStyle.
  • Tests are named by feature (ListRenderingTest(s), TaskListInteractionTest), and the RN Android package gets a shared test/ support package instead of per-file helpers.
  • Android baselines for ordered_list_wide_markers_test were recaptured on the repo's Pixel 9 API 36 AVD, where the committed ordered-list baselines pass exact-match. Your iOS captures reproduce byte for byte on iPhone 17 / iOS 26.5, so they stayed.
  • Docs: the markerMinWidth rows for RN and Android mention the widening.

Verified

  • All unit suites pass on the four packages; ktlint, swiftlint --strict and clang-format are clean.
  • Maestro: the new flow passes on both platforms; ordered_list_test and nested_ordered_list_test pass exact-match against their main baselines on Android, so lists under 100 items render as on main.

Two notes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ordered list markers wider than 99. are clipped

3 participants