Skip to content

fix: Enable user selection for math static elements - #3061

Open
tsuji-riya wants to merge 4 commits into
arnog:masterfrom
tsuji-riya:fix-math-static-elements-user-select
Open

tsuji-riya wants to merge 4 commits into
arnog:masterfrom
tsuji-riya:fix-math-static-elements-user-select

Conversation

@tsuji-riya

Copy link
Copy Markdown
Contributor

I would like to enable user selection on <math-span /> and <math-div /> elements.

To do this, I removed user-select: none from the .ML__latex CSS class in core.less, so users can now select and copy the math content.

But this had a side effect: the _mathMLContainer (a hidden MathML element used for screen readers/accessibility) also became selectable, causing the same math expression to be duplicated when copied. To fix this, I added user-select: none directly on _mathMLContainer in math-static-elements.ts, so only that element is excluded from selection/copy while the visible math stays selectable.

@arnog

arnog commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Thank you for this PR, and for the clear description. Making the content of
<math-span> and <math-div> selectable is a reasonable request, and you were
right to catch the duplicate caused by the hidden MathML container.

I tested the change in Chrome with \frac{a}{b}+x^2+\sqrt{y}, and I found some
problems that prevent me from merging it as is:

  1. The .ML__latex rule is shared. It applies to the mathfield, to
    renderMathInElement(), to the server-side rendering output and to
    mathlive-static.css, not only to the static elements. Removing
    user-select: none changes all of them.

  2. renderMathInElement() now copies the expression twice. The fix for the
    duplicate covers _mathMLContainer, but the .ML__sr-only span created in
    static-render.ts has the same problem.

  3. A page selection that crosses a <math-field> now highlights and copies
    the content of the field.
    On iOS, removing -webkit-user-select: none may
    also enable the long-press selection callout inside the field. I have not
    tested this on a device.

  4. The copied text is not usable. The selection follows the layout boxes,
    so the expression above is copied as b a +x 2 +√ y, with a line break
    between each part. The denominator comes before the numerator and the
    structure is lost. Before the change, the copy gives ab+x2+y from the
    hidden MathML, which is at least in reading order.

A narrower approach would give you what you need without these side effects:

  • Keep user-select: none on .ML__latex in core.less.
  • Enable selection only inside the shadow root of the static elements, for
    example with a rule on [part="render"] .ML__latex.
  • Add a copy event handler on the static elements that puts the LaTeX source
    on the clipboard, so that pasting gives \frac{a}{b}+x^2+\sqrt{y}.

Would you be willing to update the PR in this direction? A Playwright test for
the copy result and a CHANGELOG entry would also be welcome.

Thanks again for the contribution.

- Restore `user-select: none` on `.ML__latex` so that the mathfield,
  `renderMathInElement()` and the static CSS are unchanged
- Enable selection only for `[part="render"] .ML__latex` in the shadow
  root of `<math-span>` and `<math-div>`
- Add a `copy` handler that puts the LaTeX source on the clipboard,
  wrapped in `$...$` / `$$...$$` when copied with surrounding text
- Leave the copy behavior unchanged when the selection includes a
  `<math-field>`
- Add Playwright tests and a CHANGELOG entry
@tsuji-riya

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review! 😃
I've updated the PR as you suggested:

  • user-select: none is restored on .ML__latex. Selection is enabled only in the shadow root of the static elements ([part="render"] .ML__latex), so the mathfield, renderMathInElement() and SSR output are unchanged.
  • A copy handler puts the LaTeX source on the clipboard. When surrounding text is included, the LaTeX is wrapped in $...$ / $$...$$. If the selection includes a <math-field>, the default copy behavior is used.
  • Added Playwright tests and a CHANGELOG entry.

Let me know if you'd like any changes.

This branch has not been deployed

No deployments
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