overleaf/services/web/frontend/js/features/review-panel-new/components/review-panel-entry.tsx
Domagoj Kriskovic 332e2a38c9 Fix review panel entry focus handler (#21075)
* Fix review panel entry focus handler

* dont check position

* remove event.target checks

* Revert "remove event.target checks"

This reverts commit 3f511e47b6922260666c6952b692f6f0a29d5912.

GitOrigin-RevId: 9271df9cdb36a040d59a97c88f21d6e923ac0404
2024-10-22 08:05:33 +00:00

133 lines
3.7 KiB
TypeScript

import { FC, useCallback, useEffect, useState } from 'react'
import { AnyOperation } from '../../../../../types/change'
import {
useCodeMirrorStateContext,
useCodeMirrorViewContext,
} from '@/features/source-editor/components/codemirror-context'
import { isSelectionWithinOp } from '../utils/is-selection-within-op'
import classNames from 'classnames'
import {
clearHighlightRanges,
highlightRanges,
} from '@/features/source-editor/extensions/ranges'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import { useLayoutContext } from '@/shared/context/layout-context'
import { EditorSelection } from '@codemirror/state'
import { EditorView } from '@codemirror/view'
export const ReviewPanelEntry: FC<{
position: number
op: AnyOperation
docId: string
top?: number
className?: string
selectLineOnFocus?: boolean
hoverRanges?: boolean
disabled?: boolean
}> = ({
children,
position,
top,
op,
className,
selectLineOnFocus = true,
docId,
hoverRanges = true,
disabled,
}) => {
const state = useCodeMirrorStateContext()
const view = useCodeMirrorViewContext()
const { openDocId, getCurrentDocId } = useEditorManagerContext()
const [focused, setFocused] = useState(false)
const { setReviewPanelOpen } = useLayoutContext()
const highlighted = isSelectionWithinOp(op, state.selection.main)
const openReviewPanel = useCallback(() => {
setReviewPanelOpen(true)
}, [setReviewPanelOpen])
const focusHandler = useCallback(
event => {
if (
event.target instanceof HTMLButtonElement ||
event.target instanceof HTMLLinkElement ||
event.target instanceof HTMLAnchorElement
) {
// Don't focus if the click was on a button/link/anchor as we
// don't want to affect its behaviour
return
}
setFocused(true)
if (!selectLineOnFocus) {
return
}
if (getCurrentDocId() !== docId) {
const focusIsOnTextarea = event.target instanceof HTMLTextAreaElement
if (focusIsOnTextarea === false) {
openDocId(docId, { gotoOffset: position, keepCurrentView: true })
}
} else {
setTimeout(() =>
view.dispatch({
selection: EditorSelection.cursor(position),
effects: EditorView.scrollIntoView(position, { y: 'center' }),
})
)
}
},
[getCurrentDocId, docId, selectLineOnFocus, view, position, openDocId]
)
// Clear op highlight on dismount
useEffect(() => {
return () => {
if (hoverRanges) {
setTimeout(() => {
view.dispatch(clearHighlightRanges(op))
})
}
}
}, []) // eslint-disable-line react-hooks/exhaustive-deps
return (
<div
onMouseDown={openReviewPanel} // Using onMouseDown rather than onClick to guarantee that it fires before onFocus
onFocus={focusHandler}
onBlur={() => setFocused(false)}
onMouseEnter={() => {
if (hoverRanges) {
view.dispatch(highlightRanges(op))
}
}}
onMouseLeave={() => {
if (hoverRanges) {
view.dispatch(clearHighlightRanges(op))
}
}}
role="button"
tabIndex={position + 1}
className={classNames(
'review-panel-entry',
{
'review-panel-entry-focused': focused,
'review-panel-entry-highlighted': highlighted,
'review-panel-entry-disabled': disabled,
},
className
)}
data-top={top}
data-pos={position}
style={{
position: top === undefined ? 'relative' : 'absolute',
visibility: top === undefined ? 'visible' : 'hidden',
transition: 'top .3s, left .1s, right .1s',
}}
>
{children}
</div>
)
}