Skip to content

fix(core): extendMarkRange match only the current mark of a type#7628

Merged
bdbch merged 10 commits intoueberdosis:mainfrom
commoncurriculum:extend-mark-range-default
Mar 31, 2026
Merged

fix(core): extendMarkRange match only the current mark of a type#7628
bdbch merged 10 commits intoueberdosis:mainfrom
commoncurriculum:extend-mark-range-default

Conversation

@mweidner037
Copy link
Copy Markdown
Contributor

Changes Overview

Currently, If two links are adjacent with no white space between, extendMarkRange will select the range containing both the links. This PR changes it to only select the link containing the original selection.

An analogous change was already made for the underlying getMarkRange function (#5826). However, extendMarkRange's default of attributes = {} was overriding that change.

Implementation Approach

Remove the attributes = {} default, so that omitted/undefined attributes pass through to getMarkRange. getMarkRange then applies its own default, which is to use the attributes of the original mark.

Testing Done

See changes to extendMarkRange tests.

Verification Steps

In the Marks/Link preview, create two adjacent links with different hrefs. Then, place your cursor in one of the links, click Set Link, and update it to a 3rd href.

Before this PR: both adjacent links acquire the 3rd href.

After this PR: only the link containing the cursor acquires the 3rd href.

Additional Notes

You can recover the original behavior by passing explicitly passing {} as the attributes parameter.

Checklist

  • I have created a changeset for this PR if necessary.
  • My changes do not break the library.
  • I have added tests where applicable.
  • I have followed the project guidelines.
  • I have fixed any lint issues.

Related Issues

Analogous getMarkRange issue: #3872

Copilot AI review requested due to automatic review settings March 19, 2026 17:48
@netlify
Copy link
Copy Markdown

netlify Bot commented Mar 19, 2026

Deploy Preview for tiptap-embed ready!

Name Link
🔨 Latest commit 894b745
🔍 Latest deploy log https://app.netlify.com/projects/tiptap-embed/deploys/69cac783facb180008708ee1
😎 Deploy Preview https://deploy-preview-7628--tiptap-embed.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 19, 2026

🦋 Changeset detected

Latest commit: 894b745

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 72 packages
Name Type
@tiptap/core Patch
@tiptap/extension-audio Patch
@tiptap/extension-blockquote Patch
@tiptap/extension-bold Patch
@tiptap/extension-bubble-menu Patch
@tiptap/extension-code-block-lowlight Patch
@tiptap/extension-code-block Patch
@tiptap/extension-code Patch
@tiptap/extension-collaboration-caret Patch
@tiptap/extension-collaboration Patch
@tiptap/extension-details Patch
@tiptap/extension-document Patch
@tiptap/extension-drag-handle Patch
@tiptap/extension-emoji Patch
@tiptap/extension-file-handler Patch
@tiptap/extension-floating-menu Patch
@tiptap/extension-hard-break Patch
@tiptap/extension-heading Patch
@tiptap/extension-highlight Patch
@tiptap/extension-horizontal-rule Patch
@tiptap/extension-image Patch
@tiptap/extension-invisible-characters Patch
@tiptap/extension-italic Patch
@tiptap/extension-link Patch
@tiptap/extension-list Patch
@tiptap/extension-mathematics Patch
@tiptap/extension-mention Patch
@tiptap/extension-node-range Patch
@tiptap/extension-paragraph Patch
@tiptap/extension-strike Patch
@tiptap/extension-subscript Patch
@tiptap/extension-superscript Patch
@tiptap/extension-table-of-contents Patch
@tiptap/extension-table Patch
@tiptap/extension-text-align Patch
@tiptap/extension-text-style Patch
@tiptap/extension-text Patch
@tiptap/extension-twitch Patch
@tiptap/extension-typography Patch
@tiptap/extension-underline Patch
@tiptap/extension-unique-id Patch
@tiptap/extension-youtube Patch
@tiptap/extensions Patch
@tiptap/html Patch
@tiptap/markdown Patch
@tiptap/react Patch
@tiptap/starter-kit Patch
@tiptap/static-renderer Patch
@tiptap/suggestion Patch
@tiptap/vue-2 Patch
@tiptap/vue-3 Patch
@tiptap/extension-drag-handle-react Patch
@tiptap/extension-drag-handle-vue-2 Patch
@tiptap/extension-drag-handle-vue-3 Patch
@tiptap/extension-bullet-list Patch
@tiptap/extension-ordered-list Patch
@tiptap/extension-list-item Patch
@tiptap/extension-list-keymap Patch
@tiptap/extension-task-item Patch
@tiptap/extension-task-list Patch
@tiptap/extension-table-cell Patch
@tiptap/extension-table-header Patch
@tiptap/extension-table-row Patch
@tiptap/extension-color Patch
@tiptap/extension-font-family Patch
@tiptap/extension-character-count Patch
@tiptap/extension-dropcursor Patch
@tiptap/extension-focus Patch
@tiptap/extension-gapcursor Patch
@tiptap/extension-history Patch
@tiptap/extension-placeholder Patch
@tiptap/pm Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes extendMarkRange in @tiptap/core so that when attributes is omitted it only extends to the current mark instance (e.g., a single adjacent link), aligning behavior with the underlying getMarkRange change from #5826.

Changes:

  • Removed the attributes = {} default in extendMarkRange so undefined can flow through to getMarkRange’s defaulting logic.
  • Updated extendMarkRange unit tests to cover omitted attributes vs. explicitly passing {}.
  • Added a changeset to ship the behavior change as a patch release.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
packages/core/src/commands/extendMarkRange.ts Stops forcing {} attributes, allowing getMarkRange to match only the current mark by default.
packages/core/tests/extendMarkRange.spec.ts Adjusts existing expectation and adds coverage for adjacent marks with differing attributes (omitted vs {}).
.changeset/mean-islands-change.md Declares a patch release note for the user-facing selection behavior fix.

Comment thread packages/core/__tests__/extendMarkRange.spec.ts Outdated
Comment thread packages/core/src/commands/extendMarkRange.ts
mweidner037 and others added 2 commits March 19, 2026 13:58
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Comment thread packages/core/src/commands/extendMarkRange.ts
Comment thread packages/core/__tests__/extendMarkRange.spec.ts
Copy link
Copy Markdown
Member

@bdbch bdbch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for chasing this down - the adjacent-link behavior looks right, but I do not think this is safe to merge yet.

Removing the default in extendMarkRange makes omitted attributes inherit getMarkRanges current fallback, and that fallback uses the first mark on the text node rather than the requested mark type. That means extendMarkRange(type) can now regress on text carrying multiple marks depending on mark order.

I would like to see that helper/default behavior tightened up and covered by a regression test before approving. It would also help to make the changeset more explicit that omitted attributes and explicit empty-object now have different behavior.

@bdbch
Copy link
Copy Markdown
Member

bdbch commented Mar 28, 2026

One additional concern here: I think this is effectively a public API behavior change, not just an internal implementation tweak.

Today, omitting attributes in extendMarkRange(type) behaves like passing an empty object, so adjacent marks of the same type are treated as one range even if their attrs differ. With this change, omitting attributes starts meaning "match only the current mark". That is a better default for links, but it is still a semantic change for existing consumers.

Because of that, I think we should be careful about how we merge and ship this so we do not create migration surprises:

  • if we keep this behavior change, the changelog/changeset should explicitly call out that omitted attributes and {} now behave differently
  • we should document the migration path clearly: pass extendMarkRange(type, {}) to preserve the previous behavior
  • if maintainers feel this is too risky for a patch release, another option is to preserve the old extendMarkRange default for now and introduce the link-specific fix in a more targeted way, or defer the default-semantics change to the next major

I do think the new behavior is more intuitive for the link case; I just want us to be explicit about whether we are shipping a bug fix or intentionally changing the command contract for existing users.

@mweidner037
Copy link
Copy Markdown
Contributor Author

One additional concern here: I think this is effectively a public API behavior change, not just an internal implementation tweak.

Today, omitting attributes in extendMarkRange(type) behaves like passing an empty object, so adjacent marks of the same type are treated as one range even if their attrs differ. With this change, omitting attributes starts meaning "match only the current mark". That is a better default for links, but it is still a semantic change for existing consumers.

Because of that, I think we should be careful about how we merge and ship this so we do not create migration surprises:

* if we keep this behavior change, the changelog/changeset should explicitly call out that omitted `attributes` and `{}` now behave differently

* we should document the migration path clearly: pass `extendMarkRange(type, {})` to preserve the previous behavior

* if maintainers feel this is too risky for a patch release, another option is to preserve the old `extendMarkRange` default for now and introduce the link-specific fix in a more targeted way, or defer the default-semantics change to the next major

I do think the new behavior is more intuitive for the link case; I just want us to be explicit about whether we are shipping a bug fix or intentionally changing the command contract for existing users.

I agree that this is technically a breaking change, since it changes the behavior of existing code. However it would be nice to release it sooner than Tiptap v4 :)

I did a quick check for uses of extendMarkRange in the wild, by searching GitHub globally for ".extendMarkRange". Within the first five pages of code results, essentially all used the command for its "standard" purpose: extendMarkRange('link'), followed by either setLink or unsetLink. I would guess that these uses expect the updated behavior (don't expand to adjacent links) and will be happy with this change.

I've also updated the changeset to clarify the change and how to preserve the old behavior.

@mweidner037 mweidner037 requested a review from bdbch March 30, 2026 19:06
@bdbch bdbch merged commit 0c1c112 into ueberdosis:main Mar 31, 2026
17 checks passed
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.

3 participants