diff --git a/packages/marknative/src/layout/block/layout-document.ts b/packages/marknative/src/layout/block/layout-document.ts index 9837163..d5916ac 100644 --- a/packages/marknative/src/layout/block/layout-document.ts +++ b/packages/marknative/src/layout/block/layout-document.ts @@ -225,6 +225,25 @@ function layoutList(node: ListNode, context: LayoutContext): LayoutResult 0 ? width : containerWidth + } + if (width === 'fit-content') { + return -1 + } + // percentage, e.g. "50%", "80%" + const match = width.match(/^(\d+(?:\.\d+)?)%$/) + if (match && match[1]) { + const percentage = parseFloat(match[1]) + if (!isNaN(percentage) && percentage > 0) { + return (containerWidth * percentage) / 100 + } + } + + return containerWidth +} + function layoutBlockquote(node: BlockquoteNode, context: LayoutContext): LayoutResult { const padding = context.theme.blocks.quote.padding // The line box has ~32% of lineHeight as dead space above the first visible glyph @@ -261,6 +280,7 @@ function layoutBlockquote(node: BlockquoteNode, context: LayoutContext): LayoutR function layoutCode(node: CodeBlockNode, context: LayoutContext): LayoutResult { const padding = context.theme.blocks.code.padding + const width = context.theme.blocks.code.width const lineHeight = context.theme.typography.code.lineHeight const lineWidth = Math.max(1, context.width - padding * 2) const codeTheme = withBodyTypography(context.theme, context.theme.typography.code) @@ -269,6 +289,7 @@ function layoutCode(node: CodeBlockNode, context: LayoutContext): LayoutResult { const hlLine = highlighted?.lines[sourceLineIndex] @@ -281,6 +302,7 @@ function layoutCode(node: CodeBlockNode, context: LayoutContext): LayoutResult 0) { for (const line of laidOut) { + if (line.width > boxWidth) boxWidth = line.width lines.push(offsetLineBox(line, context.x + padding, cursorY - line.y)) lineSourceMap.push(sourceLineIndex) cursorY += line.height @@ -293,10 +315,13 @@ function layoutCode(node: CodeBlockNode, context: LayoutContext): LayoutResult 0 ? width : containerWidth + } + if (width === 'fit-content') { + return -1 // Special marker for caller to handle + } + // Support percentage format like "50%", "80%" + const match = width.match(/^(\d+(?:\.\d+)?)%$/) + if (match && match[1]) { + const percentage = parseFloat(match[1]) + if (!isNaN(percentage) && percentage > 0) { + return (containerWidth * percentage) / 100 + } + } + // Default to container width + return containerWidth +} + +test("parseWidth: number > 0 returns the number itself", () => { + expect(parseWidth(100, 500)).toBe(100) + expect(parseWidth(300, 500)).toBe(300) +}) + +test("parseWidth: number <= 0 returns container width", () => { + expect(parseWidth(0, 500)).toBe(500) + expect(parseWidth(-10, 500)).toBe(500) +}) + +test("parseWidth: fit-content returns -1", () => { + expect(parseWidth('fit-content', 500)).toBe(-1) +}) + +test("parseWidth: percentage strings", () => { + expect(parseWidth('50%', 500)).toBe(250) + expect(parseWidth('100%', 500)).toBe(500) + expect(parseWidth('25%', 400)).toBe(100) +}) + +test("parseWidth: decimal percentages", () => { + expect(parseWidth('33.33%', 300)).toBeCloseTo(99.99) + expect(parseWidth('66.66%', 300)).toBeCloseTo(199.98) +}) + +test("parseWidth: invalid percentages return container width", () => { + expect(parseWidth('abc', 500)).toBe(500) + expect(parseWidth('50', 500)).toBe(500) + expect(parseWidth('50 px', 500)).toBe(500) + expect(parseWidth('', 500)).toBe(500) +})