{"version":3,"sources":["../src/components/hovering-toolbar/hovering-toolbar.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { Editor, Location, Transforms } from \"slate\";\nimport { useSlate, useSlateSelection } from \"slate-react\";\nimport {\n  getFullEditorTextWithNewlines,\n  getTextAroundSelection,\n} from \"../../lib/get-text-around-cursor\";\nimport {\n  EditingEditorState,\n  InsertionEditorApiConfig,\n} from \"../../types/base/autosuggestions-bare-function\";\nimport { useHoveringEditorContext } from \"./hovering-editor-provider\";\nimport { Menu, Portal } from \"./hovering-toolbar-components\";\nimport { HoveringInsertionPromptBox } from \"./text-insertion-prompt-box\";\n\nexport interface HoveringToolbarProps {\n  apiConfig: InsertionEditorApiConfig;\n  contextCategories: string[];\n  hoverMenuClassname: string | undefined;\n}\n\nexport const HoveringToolbar = (props: HoveringToolbarProps) => {\n  const ref = useRef<HTMLDivElement>(null);\n  const editor = useSlate();\n  const selection = useSlateSelection();\n  const { isDisplayed, setIsDisplayed } = useHoveringEditorContext();\n\n  // only render on client\n  const [isClient, setIsClient] = useState(false);\n  useEffect(() => {\n    setIsClient(true);\n  }, []);\n\n  const isShown = isClient && isDisplayed && selection;\n\n  useLayoutEffect(() => {\n    const el = ref.current;\n    const { selection } = editor;\n\n    if (!el || !isShown) {\n      return;\n    }\n\n    if (!selection) {\n      el.removeAttribute(\"style\");\n      return;\n    }\n\n    const domSelection = window.getSelection();\n    if (!domSelection || domSelection.rangeCount === 0) {\n      return;\n    }\n\n    const domRange = domSelection.getRangeAt(0);\n    const rect = domRange.getBoundingClientRect();\n\n    // We use window = (0,0,0,0) as a signal that the selection is not in the original ai-textarea,\n    // but inside the hovering window.\n    //\n    // in such case, we simply do nothing.\n    if (rect.top === 0 && rect.left === 0 && rect.width === 0 && rect.height === 0) {\n      return;\n    }\n\n    const verticalOffsetFromCorner = 0;\n    const horizontalOffsetFromCorner = 0;\n\n    // position the toolbar below the selection\n    let top = rect.bottom + window.scrollY + verticalOffsetFromCorner;\n\n    // no space left at bottom, move up\n    if (rect.bottom + el.offsetHeight > window.innerHeight - verticalOffsetFromCorner) {\n      top = rect.top + window.scrollY - el.offsetHeight - verticalOffsetFromCorner;\n    }\n\n    // position the toolbar in the center of the selection\n    let left =\n      rect.left + window.scrollX - el.offsetWidth / 2 + rect.width / 2 + horizontalOffsetFromCorner;\n\n    // no space left at left, move right\n    if (left < horizontalOffsetFromCorner) {\n      left = horizontalOffsetFromCorner;\n    }\n    // no space left at right, move left\n    else if (left + el.offsetWidth > window.innerWidth - horizontalOffsetFromCorner) {\n      left = window.innerWidth - el.offsetWidth - horizontalOffsetFromCorner;\n    }\n\n    el.style.opacity = \"1\";\n    el.style.position = \"absolute\";\n\n    el.style.top = `${top}px`;\n    el.style.left = `${left}px`;\n  }, [isShown]);\n\n  useEffect(() => {\n    const handleClickOutside = (event: MouseEvent) => {\n      if (ref.current && !ref.current.contains(event.target as Node)) {\n        setIsDisplayed(false);\n      }\n    };\n\n    document.addEventListener(\"mousedown\", handleClickOutside);\n\n    return () => {\n      document.removeEventListener(\"mousedown\", handleClickOutside);\n    };\n  }, [ref, setIsDisplayed]);\n\n  if (!isShown) {\n    return null;\n  }\n\n  return (\n    <Portal>\n      <Menu\n        ref={ref}\n        className={\n          \"vn-sdk-textarea-css-scope \" +\n          (props.hoverMenuClassname ||\n            \"p-2 absolute z-10 top-[-10000px] left-[-10000px] mt-[-6px] opacity-0 transition-opacity duration-700\")\n        }\n        data-testid=\"hovering-toolbar\"\n      >\n        <HoveringInsertionPromptBox\n          editorState={editorState(editor, selection)}\n          apiConfig={props.apiConfig}\n          performInsertion={(insertedText) => {\n            // replace the selection with the inserted text\n            Transforms.delete(editor, { at: selection });\n            Transforms.insertText(editor, insertedText, {\n              at: selection,\n            });\n            setIsDisplayed(false);\n          }}\n          contextCategories={props.contextCategories}\n        />\n      </Menu>\n    </Portal>\n  );\n};\n\nfunction editorState(editor: Editor, selection: Location): EditingEditorState {\n  const textAroundCursor = getTextAroundSelection(editor);\n  if (textAroundCursor) {\n    return textAroundCursor;\n  }\n\n  return {\n    textBeforeCursor: getFullEditorTextWithNewlines(editor),\n    textAfterCursor: \"\",\n    selectedText: \"\",\n  };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,iBAAiB,QAAQ,gBAAgB;AAC7D,SAA2B,kBAAkB;AAC7C,SAAS,UAAU,yBAAyB;AA0HpC;AAvGD,IAAM,kBAAkB,CAAC,UAAgC;AAC9D,QAAM,MAAM,OAAuB,IAAI;AACvC,QAAM,SAAS,SAAS;AACxB,QAAM,YAAY,kBAAkB;AACpC,QAAM,EAAE,aAAa,eAAe,IAAI,yBAAyB;AAGjE,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,YAAU,MAAM;AACd,gBAAY,IAAI;AAAA,EAClB,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,YAAY,eAAe;AAE3C,kBAAgB,MAAM;AACpB,UAAM,KAAK,IAAI;AACf,UAAM,EAAE,WAAAA,WAAU,IAAI;AAEtB,QAAI,CAAC,MAAM,CAAC,SAAS;AACnB;AAAA,IACF;AAEA,QAAI,CAACA,YAAW;AACd,SAAG,gBAAgB,OAAO;AAC1B;AAAA,IACF;AAEA,UAAM,eAAe,OAAO,aAAa;AACzC,QAAI,CAAC,gBAAgB,aAAa,eAAe,GAAG;AAClD;AAAA,IACF;AAEA,UAAM,WAAW,aAAa,WAAW,CAAC;AAC1C,UAAM,OAAO,SAAS,sBAAsB;AAM5C,QAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,KAAK,KAAK,UAAU,KAAK,KAAK,WAAW,GAAG;AAC9E;AAAA,IACF;AAEA,UAAM,2BAA2B;AACjC,UAAM,6BAA6B;AAGnC,QAAI,MAAM,KAAK,SAAS,OAAO,UAAU;AAGzC,QAAI,KAAK,SAAS,GAAG,eAAe,OAAO,cAAc,0BAA0B;AACjF,YAAM,KAAK,MAAM,OAAO,UAAU,GAAG,eAAe;AAAA,IACtD;AAGA,QAAI,OACF,KAAK,OAAO,OAAO,UAAU,GAAG,cAAc,IAAI,KAAK,QAAQ,IAAI;AAGrE,QAAI,OAAO,4BAA4B;AACrC,aAAO;AAAA,IACT,WAES,OAAO,GAAG,cAAc,OAAO,aAAa,4BAA4B;AAC/E,aAAO,OAAO,aAAa,GAAG,cAAc;AAAA,IAC9C;AAEA,OAAG,MAAM,UAAU;AACnB,OAAG,MAAM,WAAW;AAEpB,OAAG,MAAM,MAAM,GAAG;AAClB,OAAG,MAAM,OAAO,GAAG;AAAA,EACrB,GAAG,CAAC,OAAO,CAAC;AAEZ,YAAU,MAAM;AACd,UAAM,qBAAqB,CAAC,UAAsB;AAChD,UAAI,IAAI,WAAW,CAAC,IAAI,QAAQ,SAAS,MAAM,MAAc,GAAG;AAC9D,uBAAe,KAAK;AAAA,MACtB;AAAA,IACF;AAEA,aAAS,iBAAiB,aAAa,kBAAkB;AAEzD,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,kBAAkB;AAAA,IAC9D;AAAA,EACF,GAAG,CAAC,KAAK,cAAc,CAAC;AAExB,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,SACE,oBAAC,UACC;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WACE,gCACC,MAAM,sBACL;AAAA,MAEJ,eAAY;AAAA,MAEZ;AAAA,QAAC;AAAA;AAAA,UACC,aAAa,YAAY,QAAQ,SAAS;AAAA,UAC1C,WAAW,MAAM;AAAA,UACjB,kBAAkB,CAAC,iBAAiB;AAElC,uBAAW,OAAO,QAAQ,EAAE,IAAI,UAAU,CAAC;AAC3C,uBAAW,WAAW,QAAQ,cAAc;AAAA,cAC1C,IAAI;AAAA,YACN,CAAC;AACD,2BAAe,KAAK;AAAA,UACtB;AAAA,UACA,mBAAmB,MAAM;AAAA;AAAA,MAC3B;AAAA;AAAA,EACF,GACF;AAEJ;AAEA,SAAS,YAAY,QAAgB,WAAyC;AAC5E,QAAM,mBAAmB,uBAAuB,MAAM;AACtD,MAAI,kBAAkB;AACpB,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,kBAAkB,8BAA8B,MAAM;AAAA,IACtD,iBAAiB;AAAA,IACjB,cAAc;AAAA,EAChB;AACF;","names":["selection"]}