From fefa10e7ec75c0c406c33188f56752cd2feb54fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Furga=C5=82a?= <83299832+00200200@users.noreply.github.com> Date: Tue, 29 Sep 2026 14:33:12 +0200 Subject: [PATCH] fix(theme): prevent code blocks painting over next paragraph Pair overflow-x: auto with overflow-y: hidden so the wrapper does not become a vertical scrollport. That stops WebKit painting the block and its scrollbar over the following paragraph. --- .../markdown-extensions.test.ts | 34 +++++++++++++++++++ .../e2e/markdown-extensions/wide-code.md | 9 +++++ .../theme-default/code-overflow.test.ts | 31 +++++++++++++++++ .../styles/components/vp-doc.css | 4 +++ 4 files changed, 78 insertions(+) create mode 100644 __tests__/e2e/markdown-extensions/wide-code.md create mode 100644 __tests__/unit/client/theme-default/code-overflow.test.ts diff --git a/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts b/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts index 000ede051..bce1a8653 100644 --- a/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts +++ b/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts @@ -362,3 +362,37 @@ describe('Image Lazy Loading', () => { expect(await img.getAttribute('loading')).toBe('lazy') }) }) + +describe('Wide code blocks', () => { + beforeEach(async () => { + await goto('/markdown-extensions/wide-code') + }) + + test('do not paint over the following paragraph', async () => { + const block = page.locator('.vp-doc div[class*="language-"]').first() + const paragraph = page.locator('#after-wide-code') + + expect(await block.count()).toBe(1) + expect(await paragraph.count()).toBe(1) + + const overflowY = await block.evaluate( + (el) => getComputedStyle(el).overflowY + ) + expect(overflowY).toBe('hidden') + + const pre = block.locator('pre') + expect(await pre.evaluate((el) => el.scrollWidth > el.clientWidth)).toBe( + true + ) + + const [blockBox, paragraphBox] = await Promise.all([ + block.boundingBox(), + paragraph.boundingBox() + ]) + expect(blockBox).toBeTruthy() + expect(paragraphBox).toBeTruthy() + expect(paragraphBox!.y).toBeGreaterThanOrEqual( + blockBox!.y + blockBox!.height - 1 + ) + }) +}) diff --git a/__tests__/e2e/markdown-extensions/wide-code.md b/__tests__/e2e/markdown-extensions/wide-code.md new file mode 100644 index 000000000..3c0b874e0 --- /dev/null +++ b/__tests__/e2e/markdown-extensions/wide-code.md @@ -0,0 +1,9 @@ +# Wide Code Block + +A paragraph before the code. + +```js +const wide = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' +``` + +
A paragraph after the wide code block.
diff --git a/__tests__/unit/client/theme-default/code-overflow.test.ts b/__tests__/unit/client/theme-default/code-overflow.test.ts new file mode 100644 index 000000000..594b5a4d3 --- /dev/null +++ b/__tests__/unit/client/theme-default/code-overflow.test.ts @@ -0,0 +1,31 @@ +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' + +const vpDocCss = readFileSync( + fileURLToPath( + new URL( + '../../../../src/client/theme-default/styles/components/vp-doc.css', + import.meta.url + ) + ), + 'utf8' +) + +function ruleFor(css: string, selector: string): string { + const start = css.indexOf(selector) + if (start === -1) { + throw new Error(`missing selector ${selector}`) + } + const open = css.indexOf('{', start) + const close = css.indexOf('}', open) + return css.slice(open + 1, close) +} + +describe('client/theme-default/code-overflow', () => { + test('code block wrappers clip vertical overflow', () => { + const rule = ruleFor(vpDocCss, ".vp-doc div[class*='language-']") + + expect(rule).toMatch(/overflow-x:\s*auto/) + expect(rule).toMatch(/overflow-y:\s*hidden/) + }) +}) diff --git a/src/client/theme-default/styles/components/vp-doc.css b/src/client/theme-default/styles/components/vp-doc.css index a5ba52f7c..6fafd0dfb 100644 --- a/src/client/theme-default/styles/components/vp-doc.css +++ b/src/client/theme-default/styles/components/vp-doc.css @@ -354,7 +354,11 @@ position: relative; margin: 1rem -1.5rem; background-color: var(--vp-code-block-bg); + /* pair overflow-x so overflow-y does not compute to auto; otherwise the + wrapper becomes a vertical scrollport and WebKit can paint the block + (and its scrollbar) over the following paragraph */ overflow-x: auto; + overflow-y: hidden; transition: background-color 0.5s; }