Skip to content

Instantly share code, notes, and snippets.

@drbr
Last active August 4, 2024 00:29
Show Gist options
  • Save drbr/ea9e643e536b77243fca060815c9af3d to your computer and use it in GitHub Desktop.
Save drbr/ea9e643e536b77243fca060815c9af3d to your computer and use it in GitHub Desktop.
useScopedSelectionCallback
import { useEffect } from 'react';
/**
* Using the browser's Selection API, sets up a listener to detect when the user selects text that
* resides completely within a specified element.
*
* @param containerRef The element to detect selections within.
* @param callback The function to call when the selection changes. It may be called additional
* times, particularly with `null`. If the selected text resides only partially within the container
* element, the callback will be called with `null`.
*/
export function useScopedSelectionCallback(
containerRef: React.RefObject<Node>,
callback: (text: string | null) => void
) {
useEffect(() => {
const eventHandler = () => {
const selection = document.getSelection();
if (!selection) {
callback(null);
return;
}
const selectionIsValid =
containerRef.current?.contains(selection.anchorNode) &&
containerRef.current?.contains(selection.focusNode) &&
!selection.isCollapsed;
if (!selectionIsValid) {
callback(null);
return;
}
callback(selection.toString());
};
document.addEventListener('selectionchange', eventHandler);
return () => {
document.removeEventListener('selectionchange', eventHandler);
};
}, [callback, containerRef]);
}
@Swahvay
Copy link

Swahvay commented Aug 2, 2024

Could you keep a ref of the last selection value and then only call callback(null) if the selection went from a value to no value?

@drbr
Copy link
Author

drbr commented Aug 4, 2024

Yes, I suppose you could!

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