Skip to content

fix: keep the editor styled when editorClassName prop changes#374

Merged
ocavue merged 6 commits into
masterfrom
fix/editor-class-clobber
Jul 25, 2026
Merged

fix: keep the editor styled when editorClassName prop changes#374
ocavue merged 6 commits into
masterfrom
fix/editor-class-clobber

Conversation

@maccman

@maccman maccman commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Problem

ProseKitEditor renders the element ProseMirror mounts into as:

<div ref={editor.mount} className={clsx('meowdown-content', editorClassName)} />

ProseMirror puts ProseMirror — and ProseMirror-focused while focused — on that
same element itself, via classList, so it composes with whatever else is there.
React's className prop is a whole-attribute write, so any re-render with a
changed editorClassName wipes ProseMirror's tokens
, and they are not restored:
PM's own bookkeeping still believes they are applied. Every .ProseMirror … rule
in the stylesheet stops matching until the editor remounts.

The most visible symptom is a wikilink's raw source appearing next to its label —
.ProseMirror .md-atom-view-content { font-size: 0; opacity: 0 } is the only thing
hiding it:

Meetings
• 12:00pm met with Alice Wyatt[[Alice Wyatt]] for lunch

It reads like "live preview turned itself off", but the document is fine; only the
class is gone. .ProseMirror-focused goes the same way, which drops the virtual
caret's focused styling.

This is easy to hit from a host that sizes the editor from state. In Reflect, each
daily-stream row passes min-h-[60vh] for today and min-h-[100px] for past days;
when local midnight passes with the app open, yesterday's row re-renders with the
new class and loses its styling until it is scrolled out of the virtualizer and back.

Fix

Apply the host's classes imperatively, token by token, so both writers stay
additive — only classes Meowdown applied itself are ever removed. React no longer
sets className on that element at all.

MarkdownView is unaffected: it renders its own static tree with no ProseMirror
view, and already includes ProseMirror in its class list.

Testing

  • Two regression tests in editor.test.tsx: the class list survives an
    editorClassName change, and a wikilink's source stays at font-size: 0px
    across one. Both fail on master (they time out waiting for the class) and
    pass here.
  • pnpm typecheck, oxfmt --check and eslint clean on the changed files.
  • Full @meowdown/react suite: 279 passed in Chromium; editor +
    prosekit-editor also run green under MEOWDOWN_TEST_BROWSER=webkit.

🤖 Generated with Claude Code

ProseMirror puts `ProseMirror` (and `ProseMirror-focused` while focused) on
the editable root itself, through `classList`, so it composes with whatever
else is on the element. React's `className` prop is a whole-attribute write,
so any re-render with a changed `editorClassName` dropped those tokens: every
`.ProseMirror …` rule in the stylesheet stopped matching, and stayed broken
until the editor remounted.

The most visible symptom is a wikilink's raw `[[…]]` source appearing beside
its label, since `.ProseMirror .md-atom-view-content` is the only thing hiding
it. The virtual caret's `.ProseMirror-focused` styling went the same way.

Apply the host's classes imperatively instead, token by token, so both writers
stay additive and only classes Meowdown applied itself are ever removed.

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

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
meowdown Ready Ready Preview Jul 25, 2026 6:57pm

@pkg-pr-new

pkg-pr-new Bot commented Jul 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@meowdown/core@374
npm i https://pkg.pr.new/@meowdown/markdown@374
npm i https://pkg.pr.new/@meowdown/react@374

commit: 2df302d

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 93.13% 4423 / 4749
🔵 Statements 90.81% 4966 / 5468
🔵 Functions 90.77% 1073 / 1182
🔵 Branches 86.54% 3189 / 3685
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/core/src/index.ts 0% 0% 0% 0%
packages/core/src/extensions/extension.ts 100% 100% 100% 100%
packages/core/src/extensions/view-attributes.ts 100% 100% 100% 100%
packages/react/src/components/editor-extensions.tsx 100% 95% 100% 100%
packages/react/src/components/prosekit-editor.tsx 94.16% 89.77% 93.75% 97.16% 75, 91, 121, 132, 355, 427-430
Generated in workflow #1463 for commit 2df302d by the Vitest Coverage Report Action

@maccman
maccman requested a review from ocavue July 25, 2026 07:26
@ocavue ocavue changed the title fix(react): keep the editor styled when editorClassName changes fix(react): keep the editor styled when editorClassName changes Jul 25, 2026
@ocavue ocavue changed the title fix(react): keep the editor styled when editorClassName changes fix: keep the editor styled when editorClassName prop changes Jul 25, 2026
@ocavue

ocavue commented Jul 25, 2026

Copy link
Copy Markdown
Member

Thanks. Good catch. I've changed the implementation but kept your tests.

@ocavue
ocavue merged commit b3b63b4 into master Jul 25, 2026
16 checks passed
@ocavue
ocavue deleted the fix/editor-class-clobber branch July 25, 2026 19:00
@ocavuebot ocavuebot mentioned this pull request Jul 25, 2026
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