@@ -40,6 +40,23 @@ function toSelectionRect(rect: DOMRect): SelectionRect { } } +function getChatViewportEl(from: HTMLElement | null): HTMLElement | null { + return from?.closest('[data-chat-viewport]') ?? null +} + +function isRectInChatViewport(rect: SelectionRect | DOMRect, viewport: HTMLElement | null): boolean { + if (!viewport) { + return false + } + const bounds = viewport.getBoundingClientRect() + return ( + rect.bottom > bounds.top && + rect.top < bounds.bottom && + rect.right > bounds.left && + rect.left < bounds.right + ) +} + export function BotMessage(props: any) { const [anchorEl, setAnchorEl] = React.useState(null) const open = Boolean(anchorEl) @@ -89,6 +106,12 @@ export function BotMessage(props: any) { return null } + // Поповер не показываем, если выделение вне видимой области чата + const chatViewport = getChatViewportEl(container) + if (!isRectInChatViewport(rect, chatViewport)) { + return null + } + return { text, rect: toSelectionRect(rect) } }, []) @@ -172,16 +195,22 @@ export function BotMessage(props: any) { }, [getSelectionInsideContent]) useEffect(() => { - if (!selectionTooltip) { - return + let scrollEndTimeout = 0 + + const handleScroll = () => { + setSelectionTooltip((current) => (current ? null : current)) + window.clearTimeout(scrollEndTimeout) + scrollEndTimeout = window.setTimeout(() => { + showSelectionTooltip() + }, 150) } - const hideOnScroll = () => setSelectionTooltip(null) - window.addEventListener('scroll', hideOnScroll, true) + window.addEventListener('scroll', handleScroll, true) return () => { - window.removeEventListener('scroll', hideOnScroll, true) + window.clearTimeout(scrollEndTimeout) + window.removeEventListener('scroll', handleScroll, true) } - }, [selectionTooltip]) + }, [showSelectionTooltip]) useEffect(() => { if (props.message.file) { @@ -351,13 +380,13 @@ export function BotMessage(props: any) { = memo(