Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.
1import { contains } from './element'; 2import { getActive } from './focus'; 3 4export interface RestoreSelection { 5 element: HTMLElement; 6 restore(): void; 7} 8 9const hasSelection = (node: HTMLElement): node is HTMLInputElement => 10 typeof (node as HTMLInputElement).selectionStart === 'number' && 11 typeof (node as HTMLInputElement).selectionEnd === 'number'; 12 13/** Snapshots the current focus or selection target, optinally using a ref if it's passed. */ 14export const snapshotSelection = ( 15 node?: HTMLElement | null 16): RestoreSelection | null => { 17 const target = getActive(); 18 const element = node && target && node !== target ? node : target; 19 if (!element || !target) { 20 return null; 21 } else if (hasSelection(element)) { 22 const { selectionStart, selectionEnd, selectionDirection } = element; 23 return { 24 element, 25 restore() { 26 element.focus(); 27 element.setSelectionRange( 28 selectionStart, 29 selectionEnd, 30 selectionDirection || undefined 31 ); 32 }, 33 }; 34 } 35 36 let range: Range | undefined; 37 38 const selection = window.getSelection(); 39 if (selection && selection.rangeCount) { 40 const _range = selection.getRangeAt(0); 41 if (_range.startContainer && contains(target, _range.startContainer)) { 42 range = _range; 43 } 44 } 45 46 return { 47 element, 48 restore() { 49 element.focus(); 50 const selection = window.getSelection(); 51 if (range && selection) { 52 selection.removeAllRanges(); 53 selection.addRange(range); 54 } 55 }, 56 }; 57}; 58 59/** Restores a given snapshot of a selection, falling back to a simple focus. */ 60export const restoreSelection = (selection: RestoreSelection | null) => { 61 if (selection && selection.element.parentNode) { 62 selection.restore(); 63 } 64};