Skip to content

feat!: replace decoy-textarea clipboard handling with the async Clipboard API - #1270

Draft
6pac-ai wants to merge 2 commits into
masterfrom
feat/modern-clipboard-api
Draft

feat!: replace decoy-textarea clipboard handling with the async Clipboard API#1270
6pac-ai wants to merge 2 commits into
masterfrom
feat/modern-clipboard-api

Conversation

@6pac-ai

@6pac-ai 6pac-ai commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Warning

Breaking change — targeted at the next breaking release, per the discussion on #1261 ("we will update the code and examples to use the modern clipboard API next breaking release"). Please do not merge into the current minor line.

Replaces CellExternalCopyManager's decoy-textarea clipboard handling with the asynchronous Clipboard API, following slickgrid-universal's implementation.

What changed

  • Copy serializes the selected ranges in memory (loop unchanged) and calls await navigator.clipboard.writeText(clipText). The IE window.clipboardData branch, the decoy textarea, and the focus steal/restore dance are gone.

  • Paste calls await navigator.clipboard.readText() and feeds the text straight to _decodeTabularData. No decoy, no input listener, no delay — the paste-delivery race fixed by fix: decode clipboard paste on delivery instead of a fixed delay #1261 is now structurally impossible, so that interim machinery is deleted with the decoy.

  • handleKeyDown is async, wrapped in try/catch: an unavailable Clipboard API or denied permission surfaces as a console error, never a throw. e.preventDefault() is issued synchronously before the first await (the old flow deliberately allowed the native default because the decoy needed it; the new flow must suppress it so a stray DOM selection can't race the writeText).

  • Key detection modernized to e.key (case-insensitive on the letters, so Caps Lock behaves as before).

  • New plugin options as an escape hatch for non-secure contexts or app-managed clipboards:

    • clipboardWriteOverride?: (text: string) => void | Promise<void>
    • clipboardReadOverride?: () => string | Promise<string>

    These are plugin options rather than grid options (where slickgrid-universal put its clipboardWriteOverride) — the concern is entirely the plugin's.

CRLF fix beyond the straight port

The decoy textarea silently normalized CRLF→LF when the browser delivered a paste into it, so _decodeTabularData's split(/[\n\f\r]/) never actually met a \r\n pair. With readText() the raw \r\n survives, and that split treats it as two delimiters — injecting a phantom blank row after every line (a 2×2 Excel paste lands as 4 rows). The split is now /\r\n|[\n\f\r]/, restoring the old effective behavior for both LF and CRLF payloads.

Heads-up @ghiscoding: slickgrid-universal has the identical split with the readText transport, so it looks like it carries this latently — multi-row pastes from Windows Excel would gain interleaved blank rows there too. (The stale 2024 draft branch feat/clipboard-api in this repo spotted the CRLF issue, but its .replace('\r\n', '\n') takes a string argument and so only fixes the first row boundary.)

Note

This PR supersedes the stale 2024 draft branch feat/clipboard-api (8a7b407, plugin-only — no override hooks, interface cleanup, example updates, or tests); that branch can be deleted once this merges.

Breaking surface (migration notes)

Change Migration
navigator.clipboard requires a secure context (https or localhost) and clipboard-read permission for paste (Firefox shows a paste prompt) Serve over https/localhost, or supply clipboardWriteOverride/clipboardReadOverride
bodyElement option removed (existed only to place the decoy inside modal dialogs) Delete — no decoy exists
clipboardPasteDelay option removed (no delay exists) Delete
_decodeTabularData(grid, ta: HTMLTextAreaElement)_decodeTabularData(grid, clipText: string) (protected, visible to subclassers) Pass the text
Paste completes asynchronously (microtask/permission timing) instead of after a fixed delay Rely on onPasteCells rather than timing

Examples

  • example-plugin-contextmenu.html, example-plugin-hybridselectionmodel.html, example-plugin-hybridselectionmodel-esm.html: the copyCellValue helper now uses navigator.clipboard.writeText (per the "code and examples" scope). Incidentally this revives a dead path — the old fallback called tmpElem.get(0) (jQuery residue) on a plain DOM node and threw into an empty catch, so execCommand copy never actually ran.
  • examples/example-clipboard-api.html is a temporary demo page for this review — do not merge (delete before merging). Grid 1 uses the real clipboard; Grid 2 routes both hooks through a visible "app-managed clipboard" box.

Tests

  • Removed cypress/e2e/quirk-clipboard-paste-event-driven.cy.ts: it pinned the fixed-delay race via the clipboardPasteDelay option, and both the option and the mechanism no longer exist.
  • Added cypress/e2e/clipboard-api.cy.ts (self-hosting, 4 tests): copy serialization through a stubbed writeText (asserting the exact tab/CRLF text + Escape cancel + no decoy textarea in the DOM), async paste decode through stubbed readText (editor path), both override hooks with navigator.clipboard untouched (raw field-assignment path), and the console-error surface when the API is unavailable. Stub pattern mirrors slickgrid-universal's unit tests. Validated bidirectionally: all 4 tests fail on the pre-port build, all pass after.
  • example-excel-compatible-spreadsheet.cy.ts updated for the string signature; its realPress Ctrl+C → Ctrl+V test drives the full real-keystroke pipeline with the transport stubbed. (The first CI run of this PR demonstrated why: local desktop Electron passes through the real clipboard, but the headless Linux runner denies the clipboard read — focus/permission — so the paste silently lands in the plugin's console-error path. The real-hardware path is a manual check via the demo page.)
  • Full cypress suite: all specs passed — 650 tests, 648 passing, 2 pending (pre-existing skips), 0 failing (3m31s).

🤖 Generated with Claude Code

…oard API

BREAKING CHANGE: CellExternalCopyManager now copies via
navigator.clipboard.writeText and pastes via navigator.clipboard.readText
(secure context required). The bodyElement and clipboardPasteDelay options
are removed, _decodeTabularData takes the clipboard text instead of a
textarea, and paste completes asynchronously. New clipboardWriteOverride /
clipboardReadOverride plugin options replace the transport where the
Clipboard API is unavailable. The row split in _decodeTabularData now
treats CRLF as one delimiter (the decoy textarea normalized CRLF away;
readText does not).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ghiscoding

Copy link
Copy Markdown
Collaborator

oh ok so this change is to use the new Clipboard API like I do in my repo. However as discussed this should be tagged as a Major Version (aka breaking changes), so I added the label tag and I converted the PR to draft to avoid merging it by mistake

@ghiscoding
ghiscoding marked this pull request as draft August 7, 2026 17:53
@6pac

6pac commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Great. Note there is a warning at the start, and also note that it claims to have found and fixed a bug in the slickgrid-universal implementation.

Headless CI runners deny real clipboard access (focus/permission), so the
realPress Ctrl+C/Ctrl+V test now stubs navigator.clipboard while keeping
the real keystroke pipeline; the real-hardware path stays a manual check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ghiscoding

Copy link
Copy Markdown
Collaborator

oh thanks, I didn't notice that comment, will check it out on Monday

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants