• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alma-oss / spirit-design-system / 29920932531

22 Jul 2026 12:38PM UTC coverage: 85.616% (-0.8%) from 86.381%
29920932531

Pull #2814

github

pavelklibani
fixup! feat(web-react): introduce `UNSTABLE_Combobox` component #DS-2304
Pull Request #2814: 🚧 [WIP] feat(web-react): introduce `UNSTABLE_Combobox` component #DS-2304

2869 of 3878 branches covered (73.98%)

Branch coverage included in aggregate %.

401 of 492 new or added lines in 16 files covered. (81.5%)

7738 of 8511 relevant lines covered (90.92%)

226.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.79
/packages/web-react/src/components/UNSTABLE_Combobox/useComboboxOpenState.ts
1
'use client';
2

3
import { type RefObject, useCallback, useEffect, useState } from 'react';
175✔
4

5
export interface UseComboboxOpenStateProps {
6
  inputRef: RefObject<HTMLInputElement | null>;
7
  isDisabled?: boolean;
8
  isOpen: boolean;
9
  onToggle: () => void;
10
}
11

12
export interface UseComboboxOpenStateReturn {
13
  activeDescendantId: string | undefined;
14
  close: () => void;
15
  focusInput: () => void;
16
  handleDropdownToggle: () => void;
17
  open: () => void;
18
  setActiveDescendantId: (id: string | undefined) => void;
19
}
20

21
/**
22
 * Open/close controls and `aria-activedescendant` lifecycle for Combobox.
23
 *
24
 * @param props Hook configuration
25
 * @param props.inputRef Combobox input
26
 * @param props.isDisabled Whether the Combobox is disabled
27
 * @param props.isOpen Whether the popover is open
28
 * @param props.onToggle Toggle open state
29
 * @returns {{ open: () => void, close: () => void, handleDropdownToggle: () => void, activeDescendantId: string | undefined, setActiveDescendantId: (id: string | undefined) => void, focusInput: () => void }}
30
 */
31
export const useComboboxOpenState = ({
175✔
32
  inputRef,
33
  isDisabled = false,
7✔
34
  isOpen,
35
  onToggle,
36
}: UseComboboxOpenStateProps): UseComboboxOpenStateReturn => {
37
  const [activeDescendantId, setActiveDescendantId] = useState<string | undefined>();
58✔
38

39
  const open = useCallback(() => {
58✔
40
    if (!isOpen && !isDisabled) {
3!
41
      onToggle();
3✔
42
    }
43
  }, [isDisabled, isOpen, onToggle]);
44

45
  const close = useCallback(() => {
58✔
46
    if (isOpen) {
2!
47
      onToggle();
2✔
48
    }
49

50
    setActiveDescendantId(undefined);
2✔
51
    inputRef.current?.removeAttribute('aria-activedescendant');
2✔
52
  }, [inputRef, isOpen, onToggle]);
53

54
  const handleDropdownToggle = useCallback(() => {
58✔
NEW
55
    if (isOpen) {
×
NEW
56
      close();
×
57
    } else {
NEW
58
      onToggle();
×
59
    }
60
  }, [close, isOpen, onToggle]);
61

62
  // Safety net for any close path that does not go through `close()` (e.g. parent-driven `isOpen`).
63
  useEffect(() => {
58✔
64
    if (!isOpen) {
44✔
65
      setActiveDescendantId(undefined);
31✔
66
      inputRef.current?.removeAttribute('aria-activedescendant');
31✔
67
    }
68
  }, [inputRef, isOpen]);
69

70
  const focusInput = useCallback(() => {
58✔
71
    inputRef.current?.focus();
1✔
72
  }, [inputRef]);
73

74
  return {
58✔
75
    activeDescendantId,
76
    close,
77
    focusInput,
78
    handleDropdownToggle,
79
    open,
80
    setActiveDescendantId,
81
  };
82
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc