@supunlakmal/hooks
Version:
A collection of reusable React hooks
79 lines • 3.43 kB
JavaScript
import { useState, useEffect, useCallback } from 'react';
const isBrowser = typeof window !== 'undefined';
const getInitialState = () => ({
text: '',
rect: null,
startOffset: null,
endOffset: null,
startNode: null,
endNode: null,
range: null,
});
/**
* Hook to track the user's text selection within the document.
*
* @returns {TextSelectionState} An object containing details about the current text selection.
*/
export function useTextSelection() {
const [selectionState, setSelectionState] = useState(getInitialState);
const handleSelectionChange = useCallback(() => {
if (!isBrowser)
return;
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
// If no selection or selection is collapsed, reset state
// Check if current state is already reset to avoid unnecessary updates
if (selectionState.text !== '' || selectionState.range !== null) {
setSelectionState(getInitialState());
}
return;
}
const range = selection.getRangeAt(0);
const text = selection.toString();
let rect = null;
try {
rect = range.getBoundingClientRect();
}
catch (e) {
console.warn('Could not get bounding client rect for selection range:', e);
}
// Check if the new state is different from the current one before updating
if (text !== selectionState.text ||
range.startOffset !== selectionState.startOffset ||
range.endOffset !== selectionState.endOffset ||
range.startContainer !== selectionState.startNode ||
range.endContainer !== selectionState.endNode
// Note: Comparing DOMRect objects directly might be tricky due to float precision.
// A shallow comparison or comparing specific properties might be better if needed.
) {
setSelectionState({
text,
rect,
startOffset: range.startOffset,
endOffset: range.endOffset,
startNode: range.startContainer,
endNode: range.endContainer,
range: range, // Store the range itself if needed
});
}
}, [selectionState]); // Depend on selectionState to compare against new selection
useEffect(() => {
if (!isBrowser)
return;
// Listen for selection changes
document.addEventListener('selectionchange', handleSelectionChange);
// Initial check in case selection exists on mount
handleSelectionChange();
// Also listen for mouse up, as selectionchange might not fire reliably in all cases (e.g., clearing selection)
// Note: 'mouseup' might fire too often. 'selectionchange' is generally preferred.
// Consider adding mouseup only if 'selectionchange' proves insufficient for some edge cases.
// document.addEventListener('mouseup', handleSelectionChange);
// Cleanup listeners
return () => {
document.removeEventListener('selectionchange', handleSelectionChange);
// document.removeEventListener('mouseup', handleSelectionChange);
};
}, [handleSelectionChange]);
return selectionState;
}
//# sourceMappingURL=useTextSelection.js.map