feat!: replace decoy-textarea clipboard handling with the async Clipboard API - #1270
Conversation
|
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 |
|
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. |
|
oh thanks, I didn't notice that comment, will check it out on Monday |
…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>
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>
…transport The multi-range gap test arrived with the `hidden` column work on next-v6 and stubbed `window.clipboardData` - the legacy IE transport this branch removes - then asserted synchronously. Against the async Clipboard API it captured nothing and the copied text read back as empty. It now stubs `navigator.clipboard` the same way the other tests in this spec do, and retries the assertion because the copy handler awaits the write. The expectation is unchanged, so it still covers what it was written for: gaps preserved between two non-contiguous ranges. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a8b14c2 to
b994876
Compare
|
Thishas been rebased. Comment from Cluade: The one judgement call worth your eye: next-v6's new multi-range gap test stubbed window.clipboardData — the legacy transport this PR deletes — and asserted synchronously, so it captured nothing. I moved it onto the navigator.clipboard stub the rest of that spec uses and made it retry for the awaited write, leaving the expected string untouched. It's commit b994876 on its own rather than folded into an amend, so it's visible in review. That test passing is also the best evidence the two features compose correctly. |
|
WIP is doing something strange - seems to be related to being in Draft mode - but CI is good. |
|
@6pac I took a quick look and it seems fine by me, ready to merge? |
|
Let me know if you want the SortableJS removal stuff rebased as well. |
@6pac no not yet, I'll create the Pinning/Sticky PR (which on my side also doesn't have SortableJS removed, so it's better to be on the same base) |
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 IEwindow.clipboardDatabranch, 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, noinputlistener, 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.handleKeyDownisasync, 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 firstawait(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 thewriteText).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'ssplit(/[\n\f\r]/)never actually met a\r\npair. WithreadText()the raw\r\nsurvives, 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
readTexttransport, 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 branchfeat/clipboard-apiin 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)
navigator.clipboardrequires a secure context (https or localhost) and clipboard-read permission for paste (Firefox shows a paste prompt)clipboardWriteOverride/clipboardReadOverridebodyElementoption removed (existed only to place the decoy inside modal dialogs)clipboardPasteDelayoption removed (no delay exists)_decodeTabularData(grid, ta: HTMLTextAreaElement)→_decodeTabularData(grid, clipText: string)(protected, visible to subclassers)onPasteCellsrather than timingExamples
example-plugin-contextmenu.html,example-plugin-hybridselectionmodel.html,example-plugin-hybridselectionmodel-esm.html: thecopyCellValuehelper now usesnavigator.clipboard.writeText(per the "code and examples" scope). Incidentally this revives a dead path — the old fallback calledtmpElem.get(0)(jQuery residue) on a plain DOM node and threw into an empty catch, soexecCommandcopy never actually ran.examples/example-clipboard-api.htmlis 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
cypress/e2e/quirk-clipboard-paste-event-driven.cy.ts: it pinned the fixed-delay race via theclipboardPasteDelayoption, and both the option and the mechanism no longer exist.cypress/e2e/clipboard-api.cy.ts(self-hosting, 4 tests): copy serialization through a stubbedwriteText(asserting the exact tab/CRLF text + Escape cancel + no decoy textarea in the DOM), async paste decode through stubbedreadText(editor path), both override hooks withnavigator.clipboarduntouched (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.tsupdated for the string signature; itsrealPressCtrl+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.)🤖 Generated with Claude Code