Skip to content

Commit c30ccee

Browse files
authored
Merge pull request #352 from thejackshelton/add-headless-autocomplete
2 parents 1f986de + eecf9a5 commit c30ccee

File tree

6 files changed

+26
-8
lines changed

6 files changed

+26
-8
lines changed

packages/kit-headless/src/components/autocomplete/autocomplete-button.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,17 @@ export type ButtonProps = QwikIntrinsicElements['button'];
1111

1212
export const AutocompleteButton = component$((props: ButtonProps) => {
1313
const contextService = useContext(AutocompleteContextId);
14+
const buttonId = contextService.buttonId;
15+
const listboxId = contextService.listBoxId;
1416

1517
return (
1618
<button
1719
{...props}
20+
id={buttonId}
1821
aria-expanded={contextService.isExpanded.value}
22+
aria-label="listbox toggle button"
23+
aria-haspopup="listbox"
24+
aria-controls={listboxId}
1925
// add their own custom onClick with our onClick functionality
2026
onClick$={[
2127
$(

packages/kit-headless/src/components/autocomplete/autocomplete-context.type.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface AutocompleteContext {
1010
labelRef: Signal<HTMLElement | undefined>;
1111
listBoxId: string;
1212
inputId: string;
13+
buttonId: string;
1314
activeOptionId: Signal<string | null>;
1415
inputValue: Signal<string>;
1516
focusInput$: QRL<(inputId: string) => void>;

packages/kit-headless/src/components/autocomplete/autocomplete-input.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export type InputProps = QwikIntrinsicElements['input'];
1616
export const AutocompleteInput = component$((props: InputProps) => {
1717
const ref = useSignal<HTMLElement>();
1818
const contextService = useContext(AutocompleteContextId);
19+
const listboxId = contextService.listBoxId;
20+
const labelRef = contextService.labelRef;
1921

2022
/*
2123
previously had useTask here, but noticed whenever it first renders,
@@ -67,9 +69,15 @@ export const AutocompleteInput = component$((props: InputProps) => {
6769
data-autocomplete-input-id={contextService.inputId}
6870
ref={ref}
6971
role="combobox"
72+
aria-invalid={props['aria-invalid'] || false}
7073
id={contextService.inputId}
7174
aria-autocomplete="list"
72-
aria-controls={contextService.listBoxId}
75+
aria-haspopup="listbox"
76+
aria-controls={listboxId}
77+
aria-expanded={contextService.isExpanded.value}
78+
aria-disabled={props['aria-disabled'] || false}
79+
aria-label={labelRef.value ? undefined : contextService.inputValue.value}
80+
aria-required={props['aria-required'] || false}
7381
bind:value={contextService.inputValue}
7482
onKeyDown$={[
7583
$((e: QwikKeyboardEvent) => {

packages/kit-headless/src/components/autocomplete/autocomplete-listbox.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ export type ListboxProps = {
1616
export const AutocompleteListbox = component$((props: ListboxProps) => {
1717
const ref = useSignal<HTMLElement>();
1818
const contextService = useContext(AutocompleteContextId);
19+
const listboxId = contextService.listBoxId;
1920
contextService.listBoxRef = ref;
2021

2122
return (
2223
<ul
23-
id={contextService.listBoxId}
24+
id={listboxId}
2425
ref={ref}
2526
style={`
2627
display: ${

packages/kit-headless/src/components/autocomplete/autocomplete-root.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,19 @@ export const AutocompleteRoot = component$(
130130
const isExpanded = useSignal(false);
131131
const triggerRef = useSignal<HTMLElement>();
132132
const listBoxRef = useSignal<HTMLElement>();
133+
const rootRef = useSignal<HTMLElement>();
133134
const labelRef = useSignal<HTMLElement>();
134135
const inputValue = useSignal(defaultValue ? defaultValue : '');
135136
const listBoxId = useId();
136137
const inputId = useId();
138+
const buttonId = useId();
137139
const activeOptionId = useSignal(null);
138140
const focusInput$ = $((inputId: string) => {
139-
triggerRef.value
141+
rootRef.value
140142
?.querySelector<HTMLElement>(
141143
`[data-autocomplete-input-id="${inputId}"]`
142144
)
143145
?.focus();
144-
145-
console.log(triggerRef.value);
146146
});
147147

148148
const contextService: AutocompleteContext = {
@@ -156,6 +156,7 @@ export const AutocompleteRoot = component$(
156156
inputValue,
157157
listBoxId,
158158
inputId,
159+
buttonId,
159160
activeOptionId,
160161
focusInput$,
161162
};
@@ -226,6 +227,7 @@ export const AutocompleteRoot = component$(
226227
props.onKeyDown$,
227228
]}
228229
{...props}
230+
ref={rootRef}
229231
>
230232
<Slot />
231233
</div>

packages/kit-headless/src/components/autocomplete/autocomplete.spec.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,11 +296,11 @@ describe('Autocomplete', () => {
296296
THEN focus should move back to the previous option in the list.`, () => {
297297
cy.mount(<RegularAutocomplete />);
298298

299-
cy.get('input').type(`A`);
299+
cy.get('input').type(`Ba`);
300300

301-
cy.findByRole('listbox').should('be.visible');
301+
cy.findByRole('listbox');
302302

303-
cy.findByRole('option', { name: 'Apricot' }).focus().type(`{uparrow}`);
303+
cy.findByRole('option', { name: 'Jabuticaba' }).focus().type(`{uparrow}`);
304304

305305
cy.get('li').filter(':visible').first().should('have.focus');
306306
});

0 commit comments

Comments
 (0)