Repository: h2oai/wave
File: ui/src/text_annotator.tsx (TypeScript)

Code before:
            }
            else {
              arr.push({ text: currentText + text, tag })
            }
          }
          else if (idx !== 0 && tag !== currentTag) {
            arr.push({ text: currentText, tag: currentTag })
            currentText = text
          } else {
            currentText += text
          }
          currentTag = tag
          return arr
        }, [] as TextAnnotatorItem[]) as unknown as Rec[]
        if (trigger) wave.push()
      },
      activateTag = (tagName: S) => () => setActiveTag(tagName),
      removeAnnotation = (idx: U) => (ev: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
        ev.stopPropagation() // Stop event bubbling so that annotate is not called.
        const tagToRemove = tokens[idx].tag
        for (let i = idx; tokens[i]?.tag === tagToRemove; i++) tokens[i].tag = undefined
        setTokens([...tokens])
        submitWaveArgs()
      },
      timeoutRef = React.useRef<NodeJS.Timeout | null>(null),
      mouseDownTimeRef = React.useRef<number>(0),
      annotate = (target?: EventTarget & HTMLSpanElement) => {
        if (timeoutRef.current) {
          clearTimeout(timeoutRef.current)
          timeoutRef.current = null
        }
        const
          selectedText = window.getSelection(),
          startElData = selectedText?.anchorNode?.parentElement?.dataset,
          endElData = selectedText?.focusNode?.parentElement?.dataset

        if (!startElData || !endElData) return

        const
          selectedTextStr = selectedText.toString(),
          startTokenIdx = parseInt(smart_selection ? startElData!.key! : startElData!.start!),
          endTokenIdx = target ? (startTokenIdx + selectedTextStr.length) : parseInt(smart_selection ? endElData!.key! : endElData!.end!),
          max = Math.max(startTokenIdx, endTokenIdx),
          min = Math.min(startTokenIdx, endTokenIdx)

        if (target && selectedTextStr.length) {
          // smart selection on double click
          const targetDataIdx = parseInt(target.dataset.key!)
          let strLength = 0 // number of characters right to the selection target; if not 0, we loop to the left direction
          for (let i = 0; i <= selectedTextStr.length; i++) {
            const tokenIdx = targetDataIdx + (!strLength ? i : (strLength - i))

Review comment (suggestion):
Nit, no need for explicit type, TS can infer it.
```suggestion
      mouseDownTimeRef = React.useRef(0),
```

Code after:
            start = tokenArr.length === 0 ? 0 : tokenArr[tokenArr.length - 1].end + 1,
            end = tokenArr.length === 0 ? textItem.length - 1 : tokenArr[tokenArr.length - 1].end + textItem.length

          tokenArr.push(...textItem.split('').map(char => ({ text: char, tag, start, end })))
        })
        return tokenArr
      }, [] as TokenProps[])),
      submitWaveArgs = () => {
        let currentText = ''
        let currentTag: S | undefined
        wave.args[name] = tokens.reduce((arr, { text, tag }, idx, tokens) => {
          if (idx === tokens.length - 1) {
            if (tag !== currentTag) {
              arr.push({ text: currentText, tag: currentTag })
              arr.push({ text, tag })
            }
            else {
              arr.push({ text: currentText + text, tag })
            }
          }
          else if (idx !== 0 && tag !== currentTag) {
            arr.push({ text: currentText, tag: currentTag })
            currentText = text
          } else {
            currentText += text
          }
          currentTag = tag
          return arr
        }, [] as TextAnnotatorItem[]) as unknown as Rec[]
        if (trigger) wave.push()
      },
      activateTag = (tagName: S) => () => setActiveTag(tagName),
      removeAnnotation = (idx: U) => (ev: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
        ev.stopPropagation() // Stop event bubbling so that annotate is not called.
        const tagToRemove = tokens[idx].tag
        for (let i = idx; tokens[i]?.tag === tagToRemove; i++) tokens[i].tag = undefined
        startElPropsRef.current = undefined
        setTokens([...tokens])
        submitWaveArgs()
      },
      annotate = (endElProps: TokenMouseEventProps) => {
        if (!startElPropsRef.current) return
        const
          startElProps = startElPropsRef.current,
          max = smartSelection ? Math.max(startElProps.end, endElProps.end) : Math.max(startElProps.key, endElProps.key),
          min = smartSelection ? Math.min(startElProps.start, endElProps.start) : Math.min(startElProps.key, endElProps.key),
          // Remove new line characters because Firefox does count them.
          selectedStr = window.getSelection()?.toString().replace(/\r?\n|\r/g, "")

        for (let i = min; i <= max; i++) {
          // Ignore characters returned when user hovers over the part of the prev/next character.