useFocusManagement.ts 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { useEffect } from "react"
  2. import { useUIStateStore } from "../stores/uiStateStore.js"
  3. import type { PendingAsk } from "../types.js"
  4. export interface UseFocusManagementOptions {
  5. showApprovalPrompt: boolean
  6. pendingAsk: PendingAsk | null
  7. }
  8. export interface UseFocusManagementReturn {
  9. /** Whether focus can be toggled between scroll and input areas */
  10. canToggleFocus: boolean
  11. /** Whether scroll area should capture keyboard input */
  12. isScrollAreaActive: boolean
  13. /** Whether input area is active (for visual focus indicator) */
  14. isInputAreaActive: boolean
  15. /** Manual focus override */
  16. manualFocus: "scroll" | "input" | null
  17. /** Set manual focus override */
  18. setManualFocus: (focus: "scroll" | "input" | null) => void
  19. /** Toggle focus between scroll and input */
  20. toggleFocus: () => void
  21. }
  22. /**
  23. * Hook to manage focus state between scroll area and input area.
  24. *
  25. * Focus can be toggled when text input is available (not showing approval prompt).
  26. * The hook automatically resets manual focus when the view changes.
  27. */
  28. export function useFocusManagement({
  29. showApprovalPrompt,
  30. pendingAsk,
  31. }: UseFocusManagementOptions): UseFocusManagementReturn {
  32. const { showCustomInput, manualFocus, setManualFocus } = useUIStateStore()
  33. // Determine if we're in a mode where focus can be toggled (text input is available)
  34. const canToggleFocus =
  35. !showApprovalPrompt &&
  36. (!pendingAsk || // Initial input or task complete or loading
  37. pendingAsk.type === "followup" || // Followup question with suggestions or custom input
  38. showCustomInput) // Custom input mode
  39. // Determine if scroll area should capture keyboard input
  40. const isScrollAreaActive: boolean =
  41. manualFocus === "scroll" ? true : manualFocus === "input" ? false : Boolean(showApprovalPrompt)
  42. // Determine if input area is active (for visual focus indicator)
  43. const isInputAreaActive: boolean =
  44. manualFocus === "input" ? true : manualFocus === "scroll" ? false : !showApprovalPrompt
  45. // Reset manual focus when view changes (e.g., agent starts responding)
  46. useEffect(() => {
  47. if (!canToggleFocus) {
  48. setManualFocus(null)
  49. }
  50. }, [canToggleFocus, setManualFocus])
  51. /**
  52. * Toggle focus between scroll and input areas
  53. */
  54. const toggleFocus = () => {
  55. if (!canToggleFocus) {
  56. return
  57. }
  58. const prev = manualFocus
  59. if (prev === "scroll") {
  60. setManualFocus("input")
  61. } else if (prev === "input") {
  62. setManualFocus("scroll")
  63. } else {
  64. setManualFocus(isScrollAreaActive ? "input" : "scroll")
  65. }
  66. }
  67. return {
  68. canToggleFocus,
  69. isScrollAreaActive,
  70. isInputAreaActive,
  71. manualFocus,
  72. setManualFocus,
  73. toggleFocus,
  74. }
  75. }