Skip to content

feat: add list.markerMinWidth to align UL/OL/task lists#248

Merged
hryhoriiK97 merged 2 commits intosoftware-mansion-labs:mainfrom
mozharovsky:feat/list-marker-width
Apr 18, 2026
Merged

feat: add list.markerMinWidth to align UL/OL/task lists#248
hryhoriiK97 merged 2 commits intosoftware-mansion-labs:mainfrom
mozharovsky:feat/list-marker-width

Conversation

@mozharovsky
Copy link
Copy Markdown
Contributor

@mozharovsky mozharovsky commented Apr 17, 2026

What/Why?

Today each list kind reserves its own natural marker column width: bulletSize / 2 for unordered, the width of "99." at the marker font for ordered, and the checkbox size for tasks. Mixed content therefore looks ragged — bulleted rows sit far to the left of numbered rows, task rows to the left of both.

This PR adds one optional style, list.markerWidth, that acts as a minimum reserved marker column width applied to all three list kinds. Each list's effective column becomes max(markerWidth, natural), so consumers can line up mixed lists without shrinking ordered lists or resizing the bullet/checkbox glyphs. Values below the natural width are ignored; leaving it unset preserves existing behavior.

<EnrichedMarkdownText
  markdownStyle={{
    list: {
      bulletSize: 6,
      markerWidth: 20, // widens UL and task rows to line up with OL at 16pt
    },
  }}
  ...
/>

Implementation

  • Public API: ListStyle.markerWidth?: number (type-only; JSDoc on the internal type captures the sentinel contract).
  • Internal codegen prop: concrete Float; negative = auto. Default is set in normalizeMarkdownStyle so consumers never touch the sentinel.
  • iOS: StyleConfig gets a new ivar + getter/setter; effectiveListMarginLeftForBullet / effectiveListMarginLeftForNumber floor by markerWidth; new effectiveListMarginLeftForTask handles task rows. Wired through StylePropsUtils and included in MeasurementCache so cached sizes invalidate when the prop changes. ListItemRenderer.m routes the task case through the new accessor.
  • Android: ListStyle carries the field and exposes effectiveMarkerWidth(natural). UnorderedListSpan, OrderedListSpan, and TaskListSpan all apply the floor. Bullets and checkboxes now position themselves relative to the reserved column's right edge (matching iOS), which stays pixel-identical when the column width equals the natural glyph width.
  • Docs: new row in docs/STYLES.md.
  • Example app: apps/example/src/markdownStyles.ts sets markerWidth: 20 so the feature is visible against the existing mixed-list sample content.

Testing

  • iOS example app built and launched on iPhone 17 Pro simulator.
  • Android example app built and launched on Pixel 3a (API 34) emulator.
  • Verified before/after visually on the existing sampleMarkdown which has mixed UL, OL, and task lists on both platforms.
  • Behavior unchanged when markerWidth is undefined (floor is below natural).

Screenshots

Same mixed-list sample content (UL + OL + task rows from sampleMarkdown.ts) rendered with and without markerWidth: 20.

iOS (iPhone 17 Pro simulator)

Default markerWidth: 20
Default markerWidth: 20

Android (Pixel 3a API 34 emulator)

Default markerWidth: 20
Default Android markerWidth: 20 Android

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

…lists

Today each list type reserves its own natural marker column width — the
bullet radius (`bulletSize / 2`) for unordered, the width of `"99."` at
the marker font for ordered, and the checkbox size for tasks. Mixed
lists therefore look ragged: bullets and task boxes hang far to the left
of numbers.

The new optional `list.markerWidth` acts as a floor applied to all three
list types. Each list's effective column becomes `max(markerWidth,
natural)`, so consumers can widen the gutter uniformly without shrinking
ordered lists or resizing bullet/checkbox glyphs. Values below the
natural width are ignored.

```tsx
<EnrichedMarkdownText
  markdownStyle={{
    list: {
      bulletSize: 6,
      markerWidth: 22, // widens UL and task rows to match OL at 17pt
    },
  }}
  ...
/>
```

Implementation:

- Public `ListStyle.markerWidth?: number` (undefined = current
  per-list-natural behavior).
- Internal codegen prop is a concrete `Float`; negative = "auto". Default
  is set in `normalizeMarkdownStyle` so consumers never touch the
  sentinel.
- iOS: `StyleConfig` now floors each list kind — new
  `effectiveListMarginLeftForTask` plus updated
  `effectiveListMarginLeftForBullet` / `effectiveListMarginLeftForNumber`.
  `ListItemRenderer.m` routes the task case through the new accessor.
  Wired through `StylePropsUtils` and included in the measurement cache
  key so cached sizes invalidate when it changes.
- Android: `ListStyle.effectiveMarkerWidth(natural)` helper; each span
  (`UnorderedListSpan`, `OrderedListSpan`, `TaskListSpan`) applies the
  floor. Bullets and checkboxes now position themselves relative to the
  reserved column's right edge so they line up flush with the gap —
  behaviorally identical at the default since the column width equals
  the natural glyph width.
- Docs: replaced the old bulletSize-only row in `docs/STYLES.md` with
  the shared `markerWidth` semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@hryhoriiK97 hryhoriiK97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mozharovsky, thank you for the PR! It's a great improvement for the lists 💪 I've left a few comments. 🙂

Comment thread ios/styles/StyleConfig.mm Outdated
Comment thread src/normalizeMarkdownStyle.ts Outdated
Comment thread src/types/MarkdownStyle.ts Outdated
Comment thread android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt Outdated
Comment thread android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt Outdated
Comment thread src/types/MarkdownStyleInternal.ts Outdated
Comment thread src/types/MarkdownStyleInternal.ts Outdated
…e 0 default

Following review feedback from @hryhoriiK97:

- Rename `list.markerWidth` → `list.markerMinWidth`: the name now carries
  the floor semantic ("at least this wide"), no room to read it as a
  hard-set width.
- Default is `0` instead of a `-1` sentinel. Floor semantics make this
  trivial: `max(0, natural) === natural`, so no magic value is needed.
  Drops the three `>= 0` / `< 0` guards across TS normalize, Android
  parse, and iOS effective accessors.
- iOS `effectiveListMarginLeftFor*` collapse to single-line `MAX(...)`.
- Android `effectiveMarkerWidth` uses idiomatic `coerceAtLeast`; the
  `fromReadableMap` parse is now one line. Dropped the redundant KDoc
  (function name says it all).
- Move JSDoc onto the public `MarkdownStyle` type (where consumers hover
  it in their IDE); drop the note from `MarkdownStyleInternal` since the
  public doc now covers the contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mozharovsky
Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, @hryhoriiK97 — all seven points accepted in d505e61. Summary:

  • Rename list.markerWidthlist.markerMinWidth across public/internal types, codegen specs, iOS ivar + getter/setter, Android field, style normalizer (native + web), docs, and the example.
  • Default is now 0 instead of a -1 sentinel. Floor semantics (max(0, natural) === natural) made the sentinel unnecessary; the three < 0 guards are gone.
  • iOS effectiveListMarginLeftForBullet / …ForNumber / …ForTask are now single-line MAX(...) calls — no helper needed.
  • Android effectiveMarkerWidth uses idiomatic coerceAtLeast; fromReadableMap parsing is one line; KDoc dropped (the name speaks for itself).
  • JSDoc moved onto the public MarkdownStyle.ListStyle field (where consumers hover it); dropped from MarkdownStyleInternal.

Net diff from this commit: -48 / +30. Ready for another look whenever you have a minute.

Copy link
Copy Markdown
Collaborator

@hryhoriiK97 hryhoriiK97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thank you for the contribution. @mozharovsky, markerMinWidth will be included in tomorrow’s nightly release.

@hryhoriiK97 hryhoriiK97 changed the title feat: add list.markerWidth to align UL/OL/task lists feat: add list.markerMinWidth to align UL/OL/task lists Apr 18, 2026
@hryhoriiK97 hryhoriiK97 merged commit 17c5dda into software-mansion-labs:main Apr 18, 2026
5 checks passed
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.

2 participants