Skip to content

TableKit: Pasting tables with merged cells from clipboard crashes with RangeError #7949

Description

@Inoooooor

Affected Packages

tiptap/extension-table

Version(s)

3.13.0

Bug Description

Bug description

Pasting a table with merged cells into an existing TipTap table crashes with
an uncaught RangeError: No cell with offset found when the bottom-right
corner of the copied selection
is covered by a rowspan or colspan cell
that starts in an earlier row or column.

On Windows, an additional issue causes the crash to happen even for simpler
tables (see Root cause → Issue 1 below).


Steps to reproduce

  1. Create a TipTap editor with TableKit enabled and insert a table.
  2. In any application (Google Sheets, Excel, Word), create a table where
    the last column of the selection ends inside a merged cell:
┌──────┬──────┬──────────┐
│  A   │  B   │    C     │  ← C has rowspan=2
├──────┼──────┤  (spans  │
│  D   │  E   │  2 rows) │  ← selection ends here
└──────┴──────┴──────────┘
  1. Copy the table and paste it into the TipTap table.

Expected behavior

The table content is pasted, preserving merged cells.


Actual behavior

Uncaught RangeError: No cell with offset 495 found
    at TableMap.findCell
    at TableMap.rectBetween
    at new CellSelection
    at insertCells
    at Plugin.handlePaste

The paste fails entirely on both macOS and Windows.


Root cause

Two issues combine to cause the crash:

Issue 1 — Windows clipboard HTML wrapping (Windows only)

Google Sheets and Excel on Windows wrap clipboard HTML in custom elements:

<google-sheets-html-origin>
  <style>...</style>
  <table>...</table>
</google-sheets-html-origin>

pastedCells(slice) expects the slice content to start with a table row.
When it receives the outer wrapper instead, it returns null, the built-in
handlePaste exits early, and the next handler crashes (Issue 2).

On macOS the same applications put a clean <table> directly in the clipboard,
so Issue 1 is skipped — but Issue 2 can still trigger depending on table structure.


Issue 2 — positionAt returns an invalid offset (all platforms)

insertCells creates a CellSelection after inserting cells:

new CellSelection(
  tr.doc.resolve(tableStart + map.positionAt(top, left, table)),
  tr.doc.resolve(tableStart + map.positionAt(bottom - 1, right - 1, table))
)

When (bottom-1, right-1) falls inside a cell covered by rowspan or colspan
from an earlier row/column, positionAt returns rowEnd - 1 (an end-of-row
sentinel, not a valid cell offset). TableMap.findCell then throws RangeError.


Workaround

We worked around both issues with a custom extension:

  1. Detect clipboard HTML with rowspan/colspan ≥ 2 (including unquoted attributes from Windows: colspan=3).
  2. Extract just the <table> element from the clipboard HTML, discarding the outer wrapper (fixes Issue 1).
  3. Append a temporary extra empty column to every row so that positionAt(bottom-1, right-1) always resolves to an explicit cell (fixes Issue 2).
  4. Call __insertCells with the modified slice.
  5. Delete the extra column afterwards with deleteColumn.

Environment

TipTap 3.x
prosemirror-tables 1.8.1
Clipboard source Google Sheets / Microsoft Excel (Windows and macOS)
Browser Chrome (Chromium)

Minimal reproducing table

<table>
  <tr><td>A</td><td>B</td><td rowspan="2">C</td></tr>
  <tr><td>D</td><td>E</td></tr>
</table>
Image Image Image

Browser Used

Chrome

Code Example URL

No response

Expected Behavior

The table content is pasted, preserving merged cells.

Additional Context (Optional)

No response

Dependency Updates

  • Yes, I've updated all my dependencies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreCore editor engine and ProseMirror integrationcomplexity: hardMultiple components, research neededimpact: highSignificant user or business impactstatus: triageNeeds initial review and categorization

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions