{
  "name": "use-text-selection",
  "author": "Brendan Dash (https://shadcn-hooks.com)",
  "description": "A hook to get the text selection and its bounding rect from an element",
  "registryDependencies": [
    "@shadcnhooks/create-effect-with-target",
    "@shadcnhooks/use-effect-with-target"
  ],
  "files": [
    {
      "path": "registry/hooks/use-text-selection.ts",
      "content": "import { useRef, useState } from 'react'\nimport { useEffectWithTarget } from '@/registry/hooks/use-effect-with-target'\nimport { getTargetElement } from '@/registry/lib/create-effect-with-target'\nimport type { BasicTarget } from '@/registry/lib/create-effect-with-target'\n\ninterface Rect {\n  top: number\n  left: number\n  bottom: number\n  right: number\n  height: number\n  width: number\n}\nexport interface State extends Rect {\n  text: string\n}\n\nconst initRect: Rect = {\n  top: Number.NaN,\n  left: Number.NaN,\n  bottom: Number.NaN,\n  right: Number.NaN,\n  height: Number.NaN,\n  width: Number.NaN,\n}\n\nconst initState: State = {\n  text: '',\n  ...initRect,\n}\n\nfunction getRectFromSelection(selection: Selection | null): Rect {\n  if (!selection) {\n    return initRect\n  }\n\n  if (selection.rangeCount < 1) {\n    return initRect\n  }\n  const range = selection.getRangeAt(0)\n  const { height, width, top, left, right, bottom } =\n    range.getBoundingClientRect()\n  return {\n    height,\n    width,\n    top,\n    left,\n    right,\n    bottom,\n  }\n}\n\nexport function useTextSelection(\n  target?: BasicTarget<Document | Element>,\n): State {\n  const [state, setState] = useState(initState)\n\n  const stateRef = useRef(state)\n  const isInRangeRef = useRef(false)\n  stateRef.current = state\n\n  useEffectWithTarget(\n    () => {\n      const el = getTargetElement(target, document)\n      if (!el) {\n        return\n      }\n\n      const mouseupHandler = () => {\n        let selObj: Selection | null = null\n        let text = ''\n        let rect = initRect\n        if (!window.getSelection) {\n          return\n        }\n        selObj = window.getSelection()\n        text = selObj ? selObj.toString() : ''\n        if (text && isInRangeRef.current) {\n          rect = getRectFromSelection(selObj)\n          setState({ ...state, text, ...rect })\n        }\n      }\n\n      // clear the previous range on any click\n      const mousedownHandler = (e: MouseEvent) => {\n        // if the mouse button is right, skip it because the selection will be cleared\n        if (e.button === 2) {\n          return\n        }\n        if (!window.getSelection) {\n          return\n        }\n        if (stateRef.current.text) {\n          setState({ ...initState })\n        }\n        isInRangeRef.current = false\n        const selObj = window.getSelection()\n        if (!selObj) {\n          return\n        }\n        selObj.removeAllRanges()\n        isInRangeRef.current = el.contains(e.target as Node)\n      }\n\n      el.addEventListener('mouseup', mouseupHandler)\n\n      document.addEventListener('mousedown', mousedownHandler)\n\n      return () => {\n        el.removeEventListener('mouseup', mouseupHandler)\n        document.removeEventListener('mousedown', mousedownHandler)\n      }\n    },\n    [],\n    target,\n  )\n\n  return state\n}\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:hook"
}
