#7949·tiptap

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

Author: InoooooorCreated Jun 15, 2026Updated Sep 10, 2026
Labelsstatus: triagearea: corecomplexity: hardimpact: high

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:

xml
<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:

javascript
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

xml
<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.