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

Code before:
      shouldShowRemoveIcon = (idx: U, tag: S) => {
        if (hoveredTagIdx === null) return false
        // Check if same tag as hovered and first mark of an annotation.
        if (tokens[hoveredTagIdx!]?.tag !== tag || tokens[idx - 1]?.tag === tag) return false
        // Check if annotation consists of a single tag.
        if (idx === hoveredTagIdx) return true
        for (let i = idx; tokens[i]?.tag === tag; i++) if (i === hoveredTagIdx) return true

        return false
      },
      getMark = (text: S, idx: U, tag: S) => {
        const color = tagColorMap.get(tag)
        // Handle invalid tags entered by user.
        if (!color) return text
        const
          removeIconStyle = { visibility: shouldShowRemoveIcon(idx, tag) ? 'visible' : 'hidden' },
          isFirst = tokens[idx - 1]?.tag !== tag,
          isLast = tokens[idx + 1]?.tag !== tag
        return (
          <mark
            onMouseOver={onMarkHover(idx)}
            onMouseOut={onMarkMouseOut}
            className={clas(css.mark, isFirst ? css.firstMark : '', isLast ? css.lastMark : '')}
            style={{ backgroundColor: cssVar(color), color: getContrast(color) }}>
            {text}
            <Fluent.Icon iconName='CircleAdditionSolid' data-test={'remove-icon'} styles={{ root: removeIconStyle }} className={clas(css.removeIcon, 'wave-w6')} onMouseUp={removeAnnotation(idx)} />
            {/* HACK: Put color underlay under remove icon because its glyph is transparent and doesn't look good on tags. */}
            <span style={removeIconStyle as React.CSSProperties} className={css.iconUnderlay}></span>
          </mark>
        )
      },
      handleMouseDown = (startElProps: TokenMouseEventProps) => () => startElPropsRef.current = startElProps,
      handleMouseUp = (endElProps: TokenMouseEventProps) => () => annotate(endElProps),
      handleMouseLeave = (endElProps: TokenMouseEventProps) => (ev: React.MouseEvent<HTMLSpanElement>) => {
        const nodeName = (ev.relatedTarget as any)?.nodeName
        if (startElPropsRef.current !== undefined && nodeName !== 'P' && nodeName !== 'MARK') annotate(endElProps) // nodeName prop is not typed yet
      }

    // eslint-disable-next-line react-hooks/exhaustive-deps
    React.useEffect(() => { wave.args[name] = items as unknown as Rec[] }, [])

    return (
      <div data-test={name} className={readonly ? css.readonly : ''}>
        <div className={clas('wave-s16 wave-w6', css.title)}>{title}</div>
        <AnnotatorTags tags={tags} activateTag={activateTag} activeTag={activeTag} />
        <div style={{ display: 'flex' }}>
          <Fluent.Checkbox
            label='Smart selection'
            checked={smartSelection}
            onChange={() => setSmartSelection(prevSelection => !prevSelection)}
            className={css.checkbox}

Review comment (suggestion):
Nit
```suggestion
            <Fluent.Icon iconName='CircleAdditionSolid' data-test='remove-icon' styles={{ root: removeIconStyle }} className={clas(css.removeIcon, 'wave-w6')} onMouseUp={removeAnnotation(idx)} />
```

Code after:
      shouldShowRemoveIcon = (idx: U, tag: S) => {
        if (hoveredTagIdx === null) return false
        // Check if same tag as hovered and first mark of an annotation.
        if (tokens[hoveredTagIdx!]?.tag !== tag || tokens[idx - 1]?.tag === tag) return false
        // Check if annotation consists of a single tag.
        if (idx === hoveredTagIdx) return true
        for (let i = idx; tokens[i]?.tag === tag; i++) if (i === hoveredTagIdx) return true

        return false
      },
      getMark = (text: S, idx: U, tag: S) => {
        const color = tagColorMap.get(tag)
        // Handle invalid tags entered by user.
        if (!color) return text
        const
          removeIconStyle = { visibility: shouldShowRemoveIcon(idx, tag) ? 'visible' : 'hidden' },
          isFirst = tokens[idx - 1]?.tag !== tag,
          isLast = tokens[idx + 1]?.tag !== tag
        return (
          <mark
            onMouseOver={onMarkHover(idx)}
            onMouseOut={onMarkMouseOut}
            className={clas(css.mark, isFirst ? css.firstMark : '', isLast ? css.lastMark : '')}
            style={{ backgroundColor: cssVar(color), color: getContrast(color) }}>
            {text}
            <Fluent.Icon iconName='CircleAdditionSolid' data-test='remove-icon' styles={{ root: removeIconStyle }} className={clas(css.removeIcon, 'wave-w6')} onMouseUp={removeAnnotation(idx)} />
            {/* HACK: Put color underlay under remove icon because its glyph is transparent and doesn't look good on tags. */}
            <span style={removeIconStyle as React.CSSProperties} className={css.iconUnderlay}></span>
          </mark>
        )
      },
      handleMouseDown = (startElProps: TokenMouseEventProps) => () => startElPropsRef.current = startElProps,
      handleMouseUp = (endElProps: TokenMouseEventProps) => () => annotate(endElProps),
      handleMouseLeave = (endElProps: TokenMouseEventProps) => (ev: React.MouseEvent<HTMLSpanElement>) => {
        const nodeName = (ev.relatedTarget as any)?.nodeName
        if (startElPropsRef.current !== undefined && nodeName !== 'P' && nodeName !== 'MARK') annotate(endElProps) // nodeName prop is not typed yet
      }

    // eslint-disable-next-line react-hooks/exhaustive-deps
    React.useEffect(() => { wave.args[name] = items as unknown as Rec[] }, [])

    return (
      <div data-test={name} className={readonly ? css.readonly : ''}>
        <div className={clas('wave-s16 wave-w6', css.title)}>{title}</div>
        <AnnotatorTags tags={tags} activateTag={activateTag} activeTag={activeTag} />
        <div style={{ display: 'flex' }}>
          <Fluent.Checkbox
            label='Smart selection'
            checked={smartSelection}
            onChange={() => setSmartSelection(prevSelection => !prevSelection)}
            className={css.checkbox}