From 97e269dd81bf16ead13cc1b1c612723a311b4236 Mon Sep 17 00:00:00 2001 From: Evgenii Mozharovskii Date: Fri, 17 Apr 2026 23:18:53 +0400 Subject: [PATCH 1/2] feat: add list.markerWidth for aligning unordered, ordered, and task lists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ``` 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) --- .../markdown/spans/OrderedListSpan.kt | 2 +- .../enriched/markdown/spans/TaskListSpan.kt | 10 ++++-- .../markdown/spans/UnorderedListSpan.kt | 9 +++-- .../enriched/markdown/styles/ListStyle.kt | 14 ++++++++ apps/example/src/markdownStyles.ts | 1 + docs/STYLES.md | 1 + ios/internals/MeasurementCache.h | 2 +- ios/renderer/ListItemRenderer.m | 2 +- ios/styles/StyleConfig.h | 3 ++ ios/styles/StyleConfig.mm | 34 ++++++++++++++++--- ios/utils/StylePropsUtils.h | 5 +++ src/EnrichedMarkdownNativeComponent.ts | 1 + src/EnrichedMarkdownTextNativeComponent.ts | 1 + src/normalizeMarkdownStyle.ts | 1 + src/normalizeMarkdownStyle.web.ts | 1 + src/types/MarkdownStyle.ts | 1 + src/types/MarkdownStyleInternal.ts | 4 +++ 17 files changed, 80 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt index 313fb711..b74dad3f 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt @@ -52,7 +52,7 @@ class OrderedListSpan( override fun getMarkerWidth(): Float { val paint = configureMarkerPaint() - return paint.measureText("99.") + return listStyle.effectiveMarkerWidth(paint.measureText("99.")) } var itemNumber: Int = 1 diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/TaskListSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/TaskListSpan.kt index 0f0e258b..f1a1897e 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/spans/TaskListSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/TaskListSpan.kt @@ -13,7 +13,7 @@ import com.swmansion.enriched.markdown.styles.TaskListStyle class TaskListSpan( private val taskStyle: TaskListStyle, - listStyle: ListStyle, + private val listStyle: ListStyle, depth: Int, context: Context, styleCache: SpanStyleCache, @@ -34,6 +34,7 @@ class TaskListSpan( gapWidth = listStyle.gapWidth, ) { private val checkboxSize = taskStyle.checkboxSize + private val markerColumnWidth = listStyle.effectiveMarkerWidth(checkboxSize) private val cornerRadius = taskStyle.checkboxBorderRadius private val rect = RectF() private val checkPath = Path() @@ -55,7 +56,7 @@ class TaskListSpan( strokeJoin = Paint.Join.ROUND } - override fun getMarkerWidth(): Float = checkboxSize + override fun getMarkerWidth(): Float = markerColumnWidth override fun drawMarker( canvas: Canvas, @@ -71,7 +72,10 @@ class TaskListSpan( val fontMetrics = paint.fontMetrics val capHeight = -fontMetrics.ascent * CAP_HEIGHT_RATIO val centerY = baseline - capHeight / HALF_DIVISOR - val centerX = x + (depth * marginLeft + checkboxSize / HALF_DIVISOR) * dir + // Right-align the checkbox inside the reserved marker column so it hugs + // the gap before the text. At the default (markerColumnWidth == + // checkboxSize) this is identical to the previous flush-left layout. + val centerX = x + (depth * marginLeft + markerColumnWidth - checkboxSize / HALF_DIVISOR) * dir val half = checkboxSize / HALF_DIVISOR rect.set(centerX - half, centerY - half, centerX + half, centerY + half) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt index 432f1e4b..6fc33c86 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt @@ -35,13 +35,14 @@ class UnorderedListSpan( } private val radius: Float = listStyle.bulletSize / 2f + private val markerColumnWidth: Float = listStyle.effectiveMarkerWidth(radius) private fun configureBulletPaint(): Paint = sharedBulletPaint.apply { color = listStyle.bulletColor } - override fun getMarkerWidth(): Float = radius + override fun getMarkerWidth(): Float = markerColumnWidth override fun drawMarker( canvas: Canvas, @@ -55,7 +56,11 @@ class UnorderedListSpan( start: Int, ) { val bulletPaint = configureBulletPaint() - val bulletX = x + (depth * marginLeft + radius) * dir + // Center the bullet at the right edge of the reserved marker column so + // it hugs the gap before the text — matches iOS behavior and stays + // visually flush-left when the column width equals the bullet radius + // (the default). + val bulletX = x + (depth * marginLeft + markerColumnWidth) * dir val fontMetrics = paint.fontMetrics val bulletY = baseline + (fontMetrics.ascent + fontMetrics.descent) / 2f diff --git a/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt b/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt index 23fc75de..c5050910 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt @@ -12,11 +12,17 @@ data class ListStyle( override val lineHeight: Float, val bulletColor: Int, val bulletSize: Float, + val markerWidth: Float, val markerColor: Int, val markerFontWeight: String, val gapWidth: Float, val marginLeft: Float, ) : BaseBlockStyle { + + /** Floor `naturalWidth` by the consumer-configured `markerWidth`. */ + fun effectiveMarkerWidth(naturalWidth: Float): Float = + if (markerWidth > naturalWidth) markerWidth else naturalWidth + companion object { fun fromReadableMap( map: ReadableMap, @@ -32,6 +38,13 @@ data class ListStyle( val lineHeight = parser.toPixelFromSP(lineHeightRaw) val bulletColor = parser.parseColor(map, "bulletColor") val bulletSize = parser.toPixelFromDIP(map.getDouble("bulletSize").toFloat()) + val markerWidthRaw = map.getDouble("markerWidth").toFloat() + val markerWidth = + if (markerWidthRaw < 0f) { + -1f + } else { + parser.toPixelFromDIP(markerWidthRaw) + } val markerColor = parser.parseColor(map, "markerColor") val markerFontWeight = parser.parseString(map, "markerFontWeight", "normal") val gapWidth = parser.toPixelFromDIP(map.getDouble("gapWidth").toFloat()) @@ -47,6 +60,7 @@ data class ListStyle( lineHeight, bulletColor, bulletSize, + markerWidth, markerColor, markerFontWeight, gapWidth, diff --git a/apps/example/src/markdownStyles.ts b/apps/example/src/markdownStyles.ts index f741cadd..ddd95918 100644 --- a/apps/example/src/markdownStyles.ts +++ b/apps/example/src/markdownStyles.ts @@ -69,6 +69,7 @@ export const customMarkdownStyle: MarkdownStyle = { lineHeight: Platform.select({ ios: 22, android: 26, default: 26 }), bulletColor: '#6b7280', bulletSize: 6, + markerWidth: 20, markerColor: '#6b7280', markerFontWeight: '500', gapWidth: 8, diff --git a/docs/STYLES.md b/docs/STYLES.md index fdc0009a..92dc58b7 100644 --- a/docs/STYLES.md +++ b/docs/STYLES.md @@ -238,6 +238,7 @@ The library provides sensible default styles for all Markdown elements out of th |----------|------|-------------| | `bulletColor` | `string` | Bullet point color | | `bulletSize` | `number` | Bullet point size | +| `markerWidth` | `number` | Minimum reserved marker column width (floors the natural width of every list type) | | `markerColor` | `string` | Number marker color | | `markerFontWeight` | `string` | Number marker font weight | | `gapWidth` | `number` | Gap between marker and text | diff --git a/ios/internals/MeasurementCache.h b/ios/internals/MeasurementCache.h index a1ca9dc0..d2d3b139 100644 --- a/ios/internals/MeasurementCache.h +++ b/ios/internals/MeasurementCache.h @@ -103,7 +103,7 @@ template inline size_t computeStyleFingerprint(const Styl hashFields(s.blockquote.borderWidth, s.blockquote.gapWidth); hashTextLayout(s.list); - hashFields(s.list.bulletSize, s.list.markerFontWeight, s.list.gapWidth, s.list.marginLeft); + hashFields(s.list.bulletSize, s.list.markerWidth, s.list.markerFontWeight, s.list.gapWidth, s.list.marginLeft); // Code & Inlines hashFields(s.codeBlock.fontFamily, s.codeBlock.fontSize, s.codeBlock.fontWeight, s.codeBlock.marginTop, diff --git a/ios/renderer/ListItemRenderer.m b/ios/renderer/ListItemRenderer.m index 85b5a5e9..3d315585 100644 --- a/ios/renderer/ListItemRenderer.m +++ b/ios/renderer/ListItemRenderer.m @@ -73,7 +73,7 @@ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)out // currentDepth - 1 handles the horizontal offset for nested lists const NSInteger nestingLevel = currentDepth - 1; - const CGFloat baseMarkerWidth = isTask ? [_config taskListCheckboxSize] + const CGFloat baseMarkerWidth = isTask ? [_config effectiveListMarginLeftForTask] : (context.listType == ListTypeOrdered) ? [_config effectiveListMarginLeftForNumber] : [_config effectiveListMarginLeftForBullet]; diff --git a/ios/styles/StyleConfig.h b/ios/styles/StyleConfig.h index 7ad552d3..039066a4 100644 --- a/ios/styles/StyleConfig.h +++ b/ios/styles/StyleConfig.h @@ -236,6 +236,8 @@ - (void)setListStyleBulletColor:(RCTUIColor *)newValue; - (CGFloat)listStyleBulletSize; - (void)setListStyleBulletSize:(CGFloat)newValue; +- (CGFloat)listStyleMarkerWidth; +- (void)setListStyleMarkerWidth:(CGFloat)newValue; - (RCTUIColor *)listStyleMarkerColor; - (void)setListStyleMarkerColor:(RCTUIColor *)newValue; - (NSString *)listStyleMarkerFontWeight; @@ -249,6 +251,7 @@ - (CGFloat)effectiveListGapWidth; - (CGFloat)effectiveListMarginLeftForBullet; - (CGFloat)effectiveListMarginLeftForNumber; +- (CGFloat)effectiveListMarginLeftForTask; // Code block properties - (CGFloat)codeBlockFontSize; - (void)setCodeBlockFontSize:(CGFloat)newValue; diff --git a/ios/styles/StyleConfig.mm b/ios/styles/StyleConfig.mm index 3b4e668e..78990098 100644 --- a/ios/styles/StyleConfig.mm +++ b/ios/styles/StyleConfig.mm @@ -151,6 +151,7 @@ @implementation StyleConfig { CGFloat _listStyleLineHeight; RCTUIColor *_listStyleBulletColor; CGFloat _listStyleBulletSize; + CGFloat _listStyleMarkerWidth; RCTUIColor *_listStyleMarkerColor; NSString *_listStyleMarkerFontWeight; CGFloat _listStyleGapWidth; @@ -434,6 +435,7 @@ - (id)copyWithZone:(NSZone *)zone copy->_listStyleLineHeight = _listStyleLineHeight; copy->_listStyleBulletColor = [_listStyleBulletColor copy]; copy->_listStyleBulletSize = _listStyleBulletSize; + copy->_listStyleMarkerWidth = _listStyleMarkerWidth; copy->_listStyleMarkerColor = [_listStyleMarkerColor copy]; copy->_listStyleMarkerFontWeight = [_listStyleMarkerFontWeight copy]; copy->_listStyleGapWidth = _listStyleGapWidth; @@ -1706,6 +1708,16 @@ - (void)setListStyleBulletSize:(CGFloat)newValue _listStyleBulletSize = newValue; } +- (CGFloat)listStyleMarkerWidth +{ + return _listStyleMarkerWidth; +} + +- (void)setListStyleMarkerWidth:(CGFloat)newValue +{ + _listStyleMarkerWidth = newValue; +} + - (RCTUIColor *)listStyleMarkerColor { return _listStyleMarkerColor; @@ -1786,16 +1798,30 @@ - (CGFloat)effectiveListGapWidth - (CGFloat)effectiveListMarginLeftForBullet { - // Just the minimum width needed for bullet (radius) - return _listStyleBulletSize / 2.0; + // Natural width for a bullet is the bullet radius (bulletSize / 2). The + // consumer-provided `markerWidth` acts as a floor so bulleted lists can + // line up with ordered / task lists without resizing the bullet glyph. + CGFloat natural = _listStyleBulletSize / 2.0; + return _listStyleMarkerWidth > natural ? _listStyleMarkerWidth : natural; } - (CGFloat)effectiveListMarginLeftForNumber { - // Reserve width for numbers up to 99 (matching Android) + // Reserve width for numbers up to 99 (matching Android). `markerWidth` + // floors the value so consumers can widen the column uniformly. UIFont *font = [self listMarkerFont]; - return + CGFloat natural = [@"99." sizeWithAttributes:@{NSFontAttributeName : font ?: [UIFont systemFontOfSize:_listStyleFontSize]}].width; + return _listStyleMarkerWidth > natural ? _listStyleMarkerWidth : natural; +} + +- (CGFloat)effectiveListMarginLeftForTask +{ + // Task checkbox reserves at least its own size. `markerWidth` can widen + // the column so task rows align with adjacent UL/OL rows. The checkbox + // glyph itself is still drawn at its configured size. + CGFloat natural = [self taskListCheckboxSize]; + return _listStyleMarkerWidth > natural ? _listStyleMarkerWidth : natural; } // Code block properties diff --git a/ios/utils/StylePropsUtils.h b/ios/utils/StylePropsUtils.h index f74311aa..d66c06c5 100644 --- a/ios/utils/StylePropsUtils.h +++ b/ios/utils/StylePropsUtils.h @@ -713,6 +713,11 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty changed = YES; } + if (newStyle.list.markerWidth != oldStyle.list.markerWidth) { + [config setListStyleMarkerWidth:newStyle.list.markerWidth]; + changed = YES; + } + if (newStyle.list.markerColor != oldStyle.list.markerColor) { RCTUIColor *markerColor = RCTUIColorFromSharedColor(newStyle.list.markerColor); [config setListStyleMarkerColor:markerColor]; diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index e81e2d96..53c2ad59 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -34,6 +34,7 @@ interface BlockquoteStyleInternal extends BaseBlockStyleInternal { interface ListStyleInternal extends BaseBlockStyleInternal { bulletColor: ColorValue; bulletSize: CodegenTypes.Float; + markerWidth: CodegenTypes.Float; markerColor: ColorValue; markerFontWeight: string; gapWidth: CodegenTypes.Float; diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index a76a48d6..2d1f1b21 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -34,6 +34,7 @@ interface BlockquoteStyleInternal extends BaseBlockStyleInternal { interface ListStyleInternal extends BaseBlockStyleInternal { bulletColor: ColorValue; bulletSize: CodegenTypes.Float; + markerWidth: CodegenTypes.Float; markerColor: ColorValue; markerFontWeight: string; gapWidth: CodegenTypes.Float; diff --git a/src/normalizeMarkdownStyle.ts b/src/normalizeMarkdownStyle.ts index b9b1c2fa..007b59e5 100644 --- a/src/normalizeMarkdownStyle.ts +++ b/src/normalizeMarkdownStyle.ts @@ -112,6 +112,7 @@ const DEFAULT_NORMALIZED_STYLE = Object.freeze({ marginBottom: 16, bulletColor: normalizeColor('#6B7280')!, bulletSize: 6, + markerWidth: -1, markerColor: normalizeColor('#6B7280')!, markerFontWeight: '500', gapWidth: 12, diff --git a/src/normalizeMarkdownStyle.web.ts b/src/normalizeMarkdownStyle.web.ts index 5167d3f4..72385614 100644 --- a/src/normalizeMarkdownStyle.web.ts +++ b/src/normalizeMarkdownStyle.web.ts @@ -98,6 +98,7 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ marginBottom: 16, bulletColor: '#6B7280', bulletSize: 6, + markerWidth: -1, markerColor: '#6B7280', markerFontWeight: '500', gapWidth: 12, diff --git a/src/types/MarkdownStyle.ts b/src/types/MarkdownStyle.ts index 75fb5185..e6913fb0 100644 --- a/src/types/MarkdownStyle.ts +++ b/src/types/MarkdownStyle.ts @@ -28,6 +28,7 @@ interface BlockquoteStyle extends BaseBlockStyle { interface ListStyle extends BaseBlockStyle { bulletColor?: string; bulletSize?: number; + markerWidth?: number; markerColor?: string; markerFontWeight?: string; gapWidth?: number; diff --git a/src/types/MarkdownStyleInternal.ts b/src/types/MarkdownStyleInternal.ts index 8cb52ae0..0948804d 100644 --- a/src/types/MarkdownStyleInternal.ts +++ b/src/types/MarkdownStyleInternal.ts @@ -34,6 +34,10 @@ interface BlockquoteStyleInternal extends BaseBlockStyleInternal { interface ListStyleInternal extends BaseBlockStyleInternal { bulletColor: string; bulletSize: number; + // Minimum reserved marker column width. Negative = "auto" → use each + // list's natural marker width (UL: bulletSize/2, OL: width of "99.", + // task: checkbox size). + markerWidth: number; markerColor: string; markerFontWeight: string; gapWidth: number; From d505e612bf3d93490c737975aee1762224f7a3f8 Mon Sep 17 00:00:00 2001 From: Evgenii Mozharovskii Date: Sat, 18 Apr 2026 15:30:26 +0400 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20address=20PR=20review=20?= =?UTF-8?q?=E2=80=94=20rename=20markerWidth=20=E2=86=92=20markerMinWidth,?= =?UTF-8?q?=20use=200=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../enriched/markdown/styles/ListStyle.kt | 17 +++-------- apps/example/src/markdownStyles.ts | 2 +- docs/STYLES.md | 2 +- ios/internals/MeasurementCache.h | 2 +- ios/styles/StyleConfig.h | 4 +-- ios/styles/StyleConfig.mm | 28 ++++++------------- ios/utils/StylePropsUtils.h | 4 +-- src/EnrichedMarkdownNativeComponent.ts | 2 +- src/EnrichedMarkdownTextNativeComponent.ts | 2 +- src/normalizeMarkdownStyle.ts | 2 +- src/normalizeMarkdownStyle.web.ts | 2 +- src/types/MarkdownStyle.ts | 6 +++- src/types/MarkdownStyleInternal.ts | 5 +--- 13 files changed, 30 insertions(+), 48 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt b/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt index c5050910..9863a3ba 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt @@ -12,16 +12,13 @@ data class ListStyle( override val lineHeight: Float, val bulletColor: Int, val bulletSize: Float, - val markerWidth: Float, + val markerMinWidth: Float, val markerColor: Int, val markerFontWeight: String, val gapWidth: Float, val marginLeft: Float, ) : BaseBlockStyle { - - /** Floor `naturalWidth` by the consumer-configured `markerWidth`. */ - fun effectiveMarkerWidth(naturalWidth: Float): Float = - if (markerWidth > naturalWidth) markerWidth else naturalWidth + fun effectiveMarkerWidth(naturalWidth: Float): Float = naturalWidth.coerceAtLeast(markerMinWidth) companion object { fun fromReadableMap( @@ -38,13 +35,7 @@ data class ListStyle( val lineHeight = parser.toPixelFromSP(lineHeightRaw) val bulletColor = parser.parseColor(map, "bulletColor") val bulletSize = parser.toPixelFromDIP(map.getDouble("bulletSize").toFloat()) - val markerWidthRaw = map.getDouble("markerWidth").toFloat() - val markerWidth = - if (markerWidthRaw < 0f) { - -1f - } else { - parser.toPixelFromDIP(markerWidthRaw) - } + val markerMinWidth = parser.toPixelFromDIP(map.getDouble("markerMinWidth").toFloat().coerceAtLeast(0f)) val markerColor = parser.parseColor(map, "markerColor") val markerFontWeight = parser.parseString(map, "markerFontWeight", "normal") val gapWidth = parser.toPixelFromDIP(map.getDouble("gapWidth").toFloat()) @@ -60,7 +51,7 @@ data class ListStyle( lineHeight, bulletColor, bulletSize, - markerWidth, + markerMinWidth, markerColor, markerFontWeight, gapWidth, diff --git a/apps/example/src/markdownStyles.ts b/apps/example/src/markdownStyles.ts index ddd95918..8bc6f79d 100644 --- a/apps/example/src/markdownStyles.ts +++ b/apps/example/src/markdownStyles.ts @@ -69,7 +69,7 @@ export const customMarkdownStyle: MarkdownStyle = { lineHeight: Platform.select({ ios: 22, android: 26, default: 26 }), bulletColor: '#6b7280', bulletSize: 6, - markerWidth: 20, + markerMinWidth: 20, markerColor: '#6b7280', markerFontWeight: '500', gapWidth: 8, diff --git a/docs/STYLES.md b/docs/STYLES.md index 92dc58b7..8b7377a6 100644 --- a/docs/STYLES.md +++ b/docs/STYLES.md @@ -238,7 +238,7 @@ The library provides sensible default styles for all Markdown elements out of th |----------|------|-------------| | `bulletColor` | `string` | Bullet point color | | `bulletSize` | `number` | Bullet point size | -| `markerWidth` | `number` | Minimum reserved marker column width (floors the natural width of every list type) | +| `markerMinWidth` | `number` | Minimum reserved marker column width (floors the natural width of every list type) | | `markerColor` | `string` | Number marker color | | `markerFontWeight` | `string` | Number marker font weight | | `gapWidth` | `number` | Gap between marker and text | diff --git a/ios/internals/MeasurementCache.h b/ios/internals/MeasurementCache.h index d2d3b139..e669d6cc 100644 --- a/ios/internals/MeasurementCache.h +++ b/ios/internals/MeasurementCache.h @@ -103,7 +103,7 @@ template inline size_t computeStyleFingerprint(const Styl hashFields(s.blockquote.borderWidth, s.blockquote.gapWidth); hashTextLayout(s.list); - hashFields(s.list.bulletSize, s.list.markerWidth, s.list.markerFontWeight, s.list.gapWidth, s.list.marginLeft); + hashFields(s.list.bulletSize, s.list.markerMinWidth, s.list.markerFontWeight, s.list.gapWidth, s.list.marginLeft); // Code & Inlines hashFields(s.codeBlock.fontFamily, s.codeBlock.fontSize, s.codeBlock.fontWeight, s.codeBlock.marginTop, diff --git a/ios/styles/StyleConfig.h b/ios/styles/StyleConfig.h index 039066a4..f9746c4a 100644 --- a/ios/styles/StyleConfig.h +++ b/ios/styles/StyleConfig.h @@ -236,8 +236,8 @@ - (void)setListStyleBulletColor:(RCTUIColor *)newValue; - (CGFloat)listStyleBulletSize; - (void)setListStyleBulletSize:(CGFloat)newValue; -- (CGFloat)listStyleMarkerWidth; -- (void)setListStyleMarkerWidth:(CGFloat)newValue; +- (CGFloat)listStyleMarkerMinWidth; +- (void)setListStyleMarkerMinWidth:(CGFloat)newValue; - (RCTUIColor *)listStyleMarkerColor; - (void)setListStyleMarkerColor:(RCTUIColor *)newValue; - (NSString *)listStyleMarkerFontWeight; diff --git a/ios/styles/StyleConfig.mm b/ios/styles/StyleConfig.mm index 78990098..964ca1d4 100644 --- a/ios/styles/StyleConfig.mm +++ b/ios/styles/StyleConfig.mm @@ -151,7 +151,7 @@ @implementation StyleConfig { CGFloat _listStyleLineHeight; RCTUIColor *_listStyleBulletColor; CGFloat _listStyleBulletSize; - CGFloat _listStyleMarkerWidth; + CGFloat _listStyleMarkerMinWidth; RCTUIColor *_listStyleMarkerColor; NSString *_listStyleMarkerFontWeight; CGFloat _listStyleGapWidth; @@ -435,7 +435,7 @@ - (id)copyWithZone:(NSZone *)zone copy->_listStyleLineHeight = _listStyleLineHeight; copy->_listStyleBulletColor = [_listStyleBulletColor copy]; copy->_listStyleBulletSize = _listStyleBulletSize; - copy->_listStyleMarkerWidth = _listStyleMarkerWidth; + copy->_listStyleMarkerMinWidth = _listStyleMarkerMinWidth; copy->_listStyleMarkerColor = [_listStyleMarkerColor copy]; copy->_listStyleMarkerFontWeight = [_listStyleMarkerFontWeight copy]; copy->_listStyleGapWidth = _listStyleGapWidth; @@ -1708,14 +1708,14 @@ - (void)setListStyleBulletSize:(CGFloat)newValue _listStyleBulletSize = newValue; } -- (CGFloat)listStyleMarkerWidth +- (CGFloat)listStyleMarkerMinWidth { - return _listStyleMarkerWidth; + return _listStyleMarkerMinWidth; } -- (void)setListStyleMarkerWidth:(CGFloat)newValue +- (void)setListStyleMarkerMinWidth:(CGFloat)newValue { - _listStyleMarkerWidth = newValue; + _listStyleMarkerMinWidth = newValue; } - (RCTUIColor *)listStyleMarkerColor @@ -1798,30 +1798,20 @@ - (CGFloat)effectiveListGapWidth - (CGFloat)effectiveListMarginLeftForBullet { - // Natural width for a bullet is the bullet radius (bulletSize / 2). The - // consumer-provided `markerWidth` acts as a floor so bulleted lists can - // line up with ordered / task lists without resizing the bullet glyph. - CGFloat natural = _listStyleBulletSize / 2.0; - return _listStyleMarkerWidth > natural ? _listStyleMarkerWidth : natural; + return MAX(_listStyleMarkerMinWidth, _listStyleBulletSize / 2.0); } - (CGFloat)effectiveListMarginLeftForNumber { - // Reserve width for numbers up to 99 (matching Android). `markerWidth` - // floors the value so consumers can widen the column uniformly. UIFont *font = [self listMarkerFont]; CGFloat natural = [@"99." sizeWithAttributes:@{NSFontAttributeName : font ?: [UIFont systemFontOfSize:_listStyleFontSize]}].width; - return _listStyleMarkerWidth > natural ? _listStyleMarkerWidth : natural; + return MAX(_listStyleMarkerMinWidth, natural); } - (CGFloat)effectiveListMarginLeftForTask { - // Task checkbox reserves at least its own size. `markerWidth` can widen - // the column so task rows align with adjacent UL/OL rows. The checkbox - // glyph itself is still drawn at its configured size. - CGFloat natural = [self taskListCheckboxSize]; - return _listStyleMarkerWidth > natural ? _listStyleMarkerWidth : natural; + return MAX(_listStyleMarkerMinWidth, [self taskListCheckboxSize]); } // Code block properties diff --git a/ios/utils/StylePropsUtils.h b/ios/utils/StylePropsUtils.h index d66c06c5..cbf73cad 100644 --- a/ios/utils/StylePropsUtils.h +++ b/ios/utils/StylePropsUtils.h @@ -713,8 +713,8 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty changed = YES; } - if (newStyle.list.markerWidth != oldStyle.list.markerWidth) { - [config setListStyleMarkerWidth:newStyle.list.markerWidth]; + if (newStyle.list.markerMinWidth != oldStyle.list.markerMinWidth) { + [config setListStyleMarkerMinWidth:newStyle.list.markerMinWidth]; changed = YES; } diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index 53c2ad59..06024b47 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -34,7 +34,7 @@ interface BlockquoteStyleInternal extends BaseBlockStyleInternal { interface ListStyleInternal extends BaseBlockStyleInternal { bulletColor: ColorValue; bulletSize: CodegenTypes.Float; - markerWidth: CodegenTypes.Float; + markerMinWidth: CodegenTypes.Float; markerColor: ColorValue; markerFontWeight: string; gapWidth: CodegenTypes.Float; diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index 2d1f1b21..e238ecfc 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -34,7 +34,7 @@ interface BlockquoteStyleInternal extends BaseBlockStyleInternal { interface ListStyleInternal extends BaseBlockStyleInternal { bulletColor: ColorValue; bulletSize: CodegenTypes.Float; - markerWidth: CodegenTypes.Float; + markerMinWidth: CodegenTypes.Float; markerColor: ColorValue; markerFontWeight: string; gapWidth: CodegenTypes.Float; diff --git a/src/normalizeMarkdownStyle.ts b/src/normalizeMarkdownStyle.ts index 007b59e5..240c83e8 100644 --- a/src/normalizeMarkdownStyle.ts +++ b/src/normalizeMarkdownStyle.ts @@ -112,7 +112,7 @@ const DEFAULT_NORMALIZED_STYLE = Object.freeze({ marginBottom: 16, bulletColor: normalizeColor('#6B7280')!, bulletSize: 6, - markerWidth: -1, + markerMinWidth: 0, markerColor: normalizeColor('#6B7280')!, markerFontWeight: '500', gapWidth: 12, diff --git a/src/normalizeMarkdownStyle.web.ts b/src/normalizeMarkdownStyle.web.ts index 72385614..5045a9c0 100644 --- a/src/normalizeMarkdownStyle.web.ts +++ b/src/normalizeMarkdownStyle.web.ts @@ -98,7 +98,7 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ marginBottom: 16, bulletColor: '#6B7280', bulletSize: 6, - markerWidth: -1, + markerMinWidth: 0, markerColor: '#6B7280', markerFontWeight: '500', gapWidth: 12, diff --git a/src/types/MarkdownStyle.ts b/src/types/MarkdownStyle.ts index e6913fb0..c05a97ab 100644 --- a/src/types/MarkdownStyle.ts +++ b/src/types/MarkdownStyle.ts @@ -28,7 +28,11 @@ interface BlockquoteStyle extends BaseBlockStyle { interface ListStyle extends BaseBlockStyle { bulletColor?: string; bulletSize?: number; - markerWidth?: number; + /** + * Minimum reserved marker column width applied uniformly to UL/OL/task lists. + * `0` (the default) means no minimum — each list uses its natural marker width. + */ + markerMinWidth?: number; markerColor?: string; markerFontWeight?: string; gapWidth?: number; diff --git a/src/types/MarkdownStyleInternal.ts b/src/types/MarkdownStyleInternal.ts index 0948804d..b62d1337 100644 --- a/src/types/MarkdownStyleInternal.ts +++ b/src/types/MarkdownStyleInternal.ts @@ -34,10 +34,7 @@ interface BlockquoteStyleInternal extends BaseBlockStyleInternal { interface ListStyleInternal extends BaseBlockStyleInternal { bulletColor: string; bulletSize: number; - // Minimum reserved marker column width. Negative = "auto" → use each - // list's natural marker width (UL: bulletSize/2, OL: width of "99.", - // task: checkbox size). - markerWidth: number; + markerMinWidth: number; markerColor: string; markerFontWeight: string; gapWidth: number;