From 94ab53b0fd36fc373b245d83a431fa95786d8c58 Mon Sep 17 00:00:00 2001 From: Amadeus Demarzi Date: Tue, 9 Jun 2026 16:05:00 -0700 Subject: [PATCH] Update docs for onPostRender to show a more applicable example --- .../app/(diffs)/docs/ReactAPI/constants.ts | 37 ++++++++++++++++--- .../app/(diffs)/docs/ReactAPI/content.mdx | 8 ++-- .../app/(diffs)/docs/VanillaAPI/constants.ts | 37 ++++++++++++++++--- .../app/(diffs)/docs/VanillaAPI/content.mdx | 8 ++-- 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/apps/docs/app/(diffs)/docs/ReactAPI/constants.ts b/apps/docs/app/(diffs)/docs/ReactAPI/constants.ts index 1af759945..d9288c4d0 100644 --- a/apps/docs/app/(diffs)/docs/ReactAPI/constants.ts +++ b/apps/docs/app/(diffs)/docs/ReactAPI/constants.ts @@ -20,11 +20,34 @@ const cleanupByNode = new WeakMap void>(); options={{ onPostRender(node, _instance, phase) { if (phase === 'mount') { - const observer = new ResizeObserver(() => { - console.log(node.getBoundingClientRect().height); + const selectionRoot = node.shadowRoot ?? node; + + const handleSelectStart = () => { + console.log('selection started in diff'); + }; + + const handleSelectionChange = () => { + const selection = document.getSelection(); + if (selection == null || selection.isCollapsed) { + return; + } + + if ( + !containsSelectionNode(selectionRoot, selection.anchorNode) && + !containsSelectionNode(selectionRoot, selection.focusNode) + ) { + return; + } + + console.log('selected text', selection.toString()); + }; + + selectionRoot.addEventListener('selectstart', handleSelectStart); + document.addEventListener('selectionchange', handleSelectionChange); + cleanupByNode.set(node, () => { + selectionRoot.removeEventListener('selectstart', handleSelectStart); + document.removeEventListener('selectionchange', handleSelectionChange); }); - observer.observe(node); - cleanupByNode.set(node, () => observer.disconnect()); return; } @@ -34,7 +57,11 @@ const cleanupByNode = new WeakMap void>(); } }, }} -/>`, +/> + +function containsSelectionNode(root: Node, node: Node | null) { + return node != null && root.contains(node); +}`, }, options, }; diff --git a/apps/docs/app/(diffs)/docs/ReactAPI/content.mdx b/apps/docs/app/(diffs)/docs/ReactAPI/content.mdx index 8255b07f3..5638949a8 100644 --- a/apps/docs/app/(diffs)/docs/ReactAPI/content.mdx +++ b/apps/docs/app/(diffs)/docs/ReactAPI/content.mdx @@ -72,10 +72,10 @@ a container node, `phase: 'update'` after later DOM-committing renders, and `phase: 'unmount'` before a mounted container node is removed, replaced, cleaned up, or recycled. -Use this callback for DOM-node-associated work such as observers, measurements, -or imperative integrations. Treat `unmount` as node-centric: if you need -teardown state, capture it by `node` when the node mounts instead of depending -on mutable instance fields. +Use this callback when native DOM selection listeners need access to the +rendered diff node or its shadow DOM. Attach listeners such as `selectstart` and +`selectionchange` during `mount`, and remove them during `unmount` with teardown +state captured by `node`. diff --git a/apps/docs/app/(diffs)/docs/VanillaAPI/constants.ts b/apps/docs/app/(diffs)/docs/VanillaAPI/constants.ts index 093e78306..9763cf965 100644 --- a/apps/docs/app/(diffs)/docs/VanillaAPI/constants.ts +++ b/apps/docs/app/(diffs)/docs/VanillaAPI/constants.ts @@ -19,11 +19,34 @@ const cleanupByNode = new WeakMap void>(); const instance = new FileDiff({ onPostRender(node, _instance, phase) { if (phase === 'mount') { - const observer = new ResizeObserver(() => { - console.log(node.getBoundingClientRect().height); + const selectionRoot = node.shadowRoot ?? node; + + const handleSelectStart = () => { + console.log('selection started in diff'); + }; + + const handleSelectionChange = () => { + const selection = document.getSelection(); + if (selection == null || selection.isCollapsed) { + return; + } + + if ( + !containsSelectionNode(selectionRoot, selection.anchorNode) && + !containsSelectionNode(selectionRoot, selection.focusNode) + ) { + return; + } + + console.log('selected text', selection.toString()); + }; + + selectionRoot.addEventListener('selectstart', handleSelectStart); + document.addEventListener('selectionchange', handleSelectionChange); + cleanupByNode.set(node, () => { + selectionRoot.removeEventListener('selectstart', handleSelectStart); + document.removeEventListener('selectionchange', handleSelectionChange); }); - observer.observe(node); - cleanupByNode.set(node, () => observer.disconnect()); return; } @@ -32,7 +55,11 @@ const instance = new FileDiff({ cleanupByNode.delete(node); } }, -});`, +}); + +function containsSelectionNode(root: Node, node: Node | null) { + return node != null && root.contains(node); +}`, }, options, }; diff --git a/apps/docs/app/(diffs)/docs/VanillaAPI/content.mdx b/apps/docs/app/(diffs)/docs/VanillaAPI/content.mdx index 0eb310570..1ecc32f27 100644 --- a/apps/docs/app/(diffs)/docs/VanillaAPI/content.mdx +++ b/apps/docs/app/(diffs)/docs/VanillaAPI/content.mdx @@ -66,10 +66,10 @@ container node, `phase: 'update'` after later DOM-committing renders, and `phase: 'unmount'` before a mounted container node is removed, replaced, cleaned up, or recycled. -Use this callback for DOM-node-associated work such as observers, measurements, -or imperative integrations. Treat `unmount` as node-centric: if you need -teardown state, capture it by `node` when the node mounts instead of depending -on mutable instance fields. +Use this callback when native DOM selection listeners need access to the +rendered diff node or its shadow DOM. Attach listeners such as `selectstart` and +`selectionchange` during `mount`, and remove them during `unmount` with teardown +state captured by `node`.