From 65b4c765137ba4d013c773679bd7603d354a374e Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Tue, 9 Jun 2026 15:41:48 +0200 Subject: [PATCH] fix: preserve selected chat text on copy --- .changeset/fix-firefox-selection-copy.md | 5 +++++ src/client/src/components/FormattedText.ts | 23 +--------------------- 2 files changed, 6 insertions(+), 22 deletions(-) create mode 100644 .changeset/fix-firefox-selection-copy.md diff --git a/.changeset/fix-firefox-selection-copy.md b/.changeset/fix-firefox-selection-copy.md new file mode 100644 index 0000000..d15697a --- /dev/null +++ b/.changeset/fix-firefox-selection-copy.md @@ -0,0 +1,5 @@ +--- +"@jmfederico/pi-web": patch +--- + +Let Firefox copy only the selected chat text instead of replacing selections with the full message. diff --git a/src/client/src/components/FormattedText.ts b/src/client/src/components/FormattedText.ts index 1369307..259204b 100644 --- a/src/client/src/components/FormattedText.ts +++ b/src/client/src/components/FormattedText.ts @@ -9,7 +9,7 @@ export class FormattedText extends LitElement { @property() text = ""; override render() { - return html`
${unsafeHTML(toSafeMarkdownHtml(this.text))}
`; + return html`
${unsafeHTML(toSafeMarkdownHtml(this.text))}
`; } override updated(): void { @@ -48,13 +48,6 @@ export class FormattedText extends LitElement { void this.copyCode(code.textContent, button); }; - private readonly onFormattedCopy = (event: ClipboardEvent): void => { - if (event.clipboardData === null || !hasSelectedText(currentSelection(this))) return; - event.clipboardData.setData("text/plain", this.text); - event.preventDefault(); - event.stopPropagation(); - }; - private async copyCode(text: string, button: HTMLButtonElement): Promise { const ok = await writeClipboard(text); this.setCopyButtonState(button, ok ? "copied" : "failed"); @@ -74,20 +67,6 @@ export class FormattedText extends LitElement { static override styles = formattedTextStyles; } -function currentSelection(element: Element): Selection | null { - const root = element.getRootNode(); - if (typeof ShadowRoot !== "undefined" && root instanceof ShadowRoot && hasRootSelection(root)) return root.getSelection(); - return element.ownerDocument.getSelection(); -} - -function hasRootSelection(root: Node): root is Node & { getSelection: () => Selection | null } { - return "getSelection" in root && typeof root.getSelection === "function"; -} - -function hasSelectedText(selection: Selection | null): boolean { - return selection !== null && !selection.isCollapsed && selection.toString() !== ""; -} - async function writeClipboard(text: string): Promise { try { await navigator.clipboard.writeText(text);