import { useCallback, useEffect, useLayoutEffect, useRef, useState, } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { arrowUp, chevronDown, chevronUp, Icon } from '@wordpress/icons'; import classNames from 'classnames'; export const ReplyOptions = ({ options, onSubmit }) => { const [own, setOwn] = useState(''); const [picked, setPicked] = useState(null); const optionRefs = useRef([]); const ownRef = useRef(null); const cardRef = useRef(null); const typed = own.trim().length > 0; // Without this the footer opens below the chat's visible edge. const keepInView = useCallback(() => { const el = cardRef.current; const scroller = el?.closest( '#extendify-agent-chat-scroll-area', )?.parentElement; if (!scroller) return; const overflow = el.getBoundingClientRect().bottom - scroller.getBoundingClientRect().bottom; if (overflow > 0) scroller.scrollTop += overflow + 8; }, []); // The box takes focus so typing needs no click and no option looks chosen. useEffect(() => { const target = ownRef.current; target?.focus(); if (document.activeElement === target) { keepInView(); return; } // Hidden elements refuse focus, and reloads hide the chat until first paint. const timer = setInterval(() => { const active = document.activeElement; if (active && active !== document.body && active !== target) { clearInterval(timer); return; } target?.focus(); if (document.activeElement !== target) return; clearInterval(timer); keepInView(); }, 100); const stop = setTimeout(() => clearInterval(timer), 3000); return () => { clearInterval(timer); clearTimeout(stop); }; }, [keepInView]); const adjustHeight = useCallback(() => { const el = ownRef.current; if (!el) return; el.style.height = 'auto'; el.style.height = `${el.scrollHeight}px`; keepInView(); }, [keepInView]); const submit = (value) => { const trimmed = value?.trim(); if (!trimmed) return; onSubmit(trimmed); }; // Sending would discard an answer they already typed, so it only stages. const handleOption = (option, index) => { if (!typed) return submit(option); setPicked((prev) => (prev === index ? null : index)); }; // Arrows never leave the textarea — inside it they move the caret. const focusStop = (index) => { const stops = [...optionRefs.current, ownRef.current].filter(Boolean); stops[Math.max(0, Math.min(index, stops.length - 1))]?.focus(); }; const handleArrows = (event, index) => { if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return; event.preventDefault(); focusStop(index + (event.key === 'ArrowDown' ? 1 : -1)); }; return (
{options.length ? (
{typed ? __('Press to select', 'extendify-local') : __('Press to submit', 'extendify-local')}
) : null}
{options.length ? ( options.map((option, index) => ( 0} first={index === 0} last={index === options.length - 1} onSelect={() => handleOption(option, index)} onArrow={(event) => handleArrows(event, index)} optionRef={(el) => { optionRefs.current[index] = el; }} /> )) ) : (
{__( 'The agent is asking for additional information.', 'extendify-local', )}
)}
{/* biome-ignore lint: the whole footer is the answer box's click target */}
ownRef.current?.focus()} >