Skip to content

Commit 5c70983

Browse files
refactor(merge current version of autocomplete): autocomplete functionality
1 parent 09689c0 commit 5c70983

File tree

3 files changed

+207
-17
lines changed

3 files changed

+207
-17
lines changed

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

Lines changed: 190 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,98 @@ import {
88
AutocompleteListbox,
99
AutocompleteOption,
1010
} from './autocomplete';
11+
import './autocompleteTest.css';
12+
13+
const fruits = [
14+
'Apple',
15+
'Apricot',
16+
'Avocado 🥑',
17+
'Banana',
18+
'Bilberry',
19+
'Blackberry',
20+
'Blackcurrant',
21+
'Blueberry',
22+
'Boysenberry',
23+
'Currant',
24+
'Cherry',
25+
'Coconut',
26+
'Cranberry',
27+
'Cucumber',
28+
'Custard apple',
29+
'Damson',
30+
'Date',
31+
'Dragonfruit',
32+
'Durian',
33+
'Elderberry',
34+
'Feijoa',
35+
'Fig',
36+
'Gooseberry',
37+
'Grape',
38+
'Raisin',
39+
'Grapefruit',
40+
'Guava',
41+
'Honeyberry',
42+
'Huckleberry',
43+
'Jabuticaba',
44+
'Jackfruit',
45+
'Jambul',
46+
'Juniper berry',
47+
'Kiwifruit',
48+
'Kumquat',
49+
'Lemon',
50+
'Lime',
51+
'Loquat',
52+
'Longan',
53+
'Lychee',
54+
'Mango',
55+
'Mangosteen',
56+
'Marionberry',
57+
'Melon',
58+
'Cantaloupe',
59+
'Honeydew',
60+
'Watermelon',
61+
'Miracle fruit',
62+
'Mulberry',
63+
'Nectarine',
64+
'Nance',
65+
'Olive',
66+
'Orange',
67+
'Clementine',
68+
'Mandarine',
69+
'Tangerine',
70+
'Papaya',
71+
'Passionfruit',
72+
'Peach',
73+
'Pear',
74+
'Persimmon',
75+
'Plantain',
76+
'Plum',
77+
'Pineapple',
78+
'Pomegranate',
79+
'Pomelo',
80+
'Quince',
81+
'Raspberry',
82+
'Salmonberry',
83+
'Rambutan',
84+
'Redcurrant',
85+
'Salak',
86+
'Satsuma',
87+
'Soursop',
88+
'Star fruit',
89+
'Strawberry',
90+
'Tamarillo',
91+
'Tamarind',
92+
'Yuzu',
93+
];
1194

1295
const RegularAutocomplete = component$(() => {
1396
return (
1497
<>
15-
<AutocompleteLabel>Label</AutocompleteLabel>
16-
<AutocompleteRoot>
98+
<AutocompleteRoot style="width: fit-content">
99+
<AutocompleteLabel>Label</AutocompleteLabel>
17100
<AutocompleteTrigger>
18101
<AutocompleteInput />
19-
<AutocompleteButton class="test">
102+
<AutocompleteButton>
20103
<svg
21104
xmlns="http://www.w3.org/2000/svg"
22105
viewBox="0 0 24 24"
@@ -31,10 +114,12 @@ const RegularAutocomplete = component$(() => {
31114
</svg>
32115
</AutocompleteButton>
33116
</AutocompleteTrigger>
34-
<AutocompleteListbox>
35-
<AutocompleteOption>Option 1</AutocompleteOption>
36-
<AutocompleteOption>Option 2</AutocompleteOption>
37-
<AutocompleteOption>Option 3</AutocompleteOption>
117+
<AutocompleteListbox class="listboxStyle">
118+
{fruits.map((fruit, index) => (
119+
<AutocompleteOption optionValue={fruit} key={index}>
120+
{fruit}
121+
</AutocompleteOption>
122+
))}
38123
</AutocompleteListbox>
39124
</AutocompleteRoot>
40125
</>
@@ -47,4 +132,102 @@ describe('Autocomplete', () => {
47132

48133
cy.checkA11yForComponent();
49134
});
135+
136+
it('Should open the listbox and aria-expanded is true on the button', () => {
137+
cy.mount(<RegularAutocomplete />);
138+
139+
cy.get('button').click().should('have.attr', 'aria-expanded', 'true');
140+
141+
cy.findByRole('listbox').should('be.visible');
142+
});
143+
144+
it('Should close the listbox and aria-expanded is false on the button', () => {
145+
cy.mount(<RegularAutocomplete />);
146+
147+
cy.get('button')
148+
.click()
149+
.click()
150+
.should('have.attr', 'aria-expanded', 'false');
151+
152+
cy.findByRole('listbox').should('not.exist');
153+
});
154+
155+
it('Should input a value and the listbox should open', () => {
156+
cy.mount(<RegularAutocomplete />);
157+
158+
cy.get('input').type(`Ap`);
159+
160+
cy.findByRole('listbox').should('be.visible');
161+
});
162+
163+
it('Should input a value and select a value, with the input value as the option', () => {
164+
cy.mount(<RegularAutocomplete />);
165+
166+
cy.get('input').type(`Ap`);
167+
168+
cy.findByRole('listbox').should('be.visible');
169+
170+
cy.get('li').first().click();
171+
172+
cy.get('input').should('have.value', 'Apple');
173+
});
174+
175+
it('Should close the listbox when a value is selected', () => {
176+
cy.mount(<RegularAutocomplete />);
177+
178+
cy.get('input').type(`Ap`);
179+
180+
cy.findByRole('listbox').should('be.visible');
181+
182+
cy.get('li').first().click();
183+
184+
cy.findByRole('listbox').should('not.exist');
185+
});
186+
187+
it('Should focus the first filtered option when the down arrow is prssed', () => {
188+
cy.mount(<RegularAutocomplete />);
189+
190+
cy.get('input').type(`Ba`);
191+
192+
cy.findByRole('listbox').should('be.visible');
193+
194+
cy.get('input').type(`{downarrow}`);
195+
196+
cy.get('li').filter(':visible').first().should('have.focus');
197+
});
198+
199+
it('Should select an option using the enter key, closing the listbox', () => {
200+
cy.mount(<RegularAutocomplete />);
201+
202+
cy.get('input').type(`Ba`);
203+
204+
cy.findByRole('listbox').should('be.visible');
205+
206+
cy.get('input').type(`{downarrow}`);
207+
208+
cy.get('li').filter(':visible').first().type(`{enter}`);
209+
210+
cy.get('input').should('have.value', 'Banana');
211+
212+
cy.findByRole('listbox').should('not.exist');
213+
});
214+
215+
it('Should go down an option and back up, using the up & down arrow keys', () => {
216+
cy.mount(<RegularAutocomplete />);
217+
218+
cy.get('input').type(`A`);
219+
220+
cy.findByRole('listbox').should('be.visible');
221+
222+
cy.get('input').type(`{downarrow}`);
223+
224+
cy.get('li').filter(':visible').first().type(`{downarrow}`);
225+
226+
// grabs the 2nd element because the index is 1
227+
cy.get('li').filter(':visible').eq(1).should('have.focus');
228+
229+
cy.get('li').filter(':visible').eq(1).type(`{uparrow}`);
230+
231+
cy.get('li').filter(':visible').first().should('have.focus');
232+
});
50233
});

packages/kit-headless/src/components/Autocomplete/autocomplete.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ const RegularAutocomplete = () => (
139139
))}
140140
</AutocompleteListbox>
141141
</AutocompleteRoot>
142-
<AutocompleteRoot style="width: fit-content">
142+
{/* <AutocompleteRoot style="width: fit-content">
143143
<AutocompleteLabel>Label</AutocompleteLabel>
144144
<AutocompleteTrigger>
145145
<AutocompleteInput />
@@ -192,7 +192,7 @@ const RegularAutocomplete = () => (
192192
</AutocompleteOption>
193193
))}
194194
</AutocompleteListbox>
195-
</AutocompleteRoot>
195+
</AutocompleteRoot> */}
196196
</>
197197
);
198198

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { computePosition, flip } from '@floating-ui/dom';
5757
5858
- Get it working
5959
- Context - 🏗️
60-
- Key events work
60+
- Key events work - ✅
6161
- Listbox toggles - ✅
6262
- Floating UI anchor working - ✅
6363
- Listbox is anchored to a wrapper containing the input and button - ✅
@@ -281,18 +281,24 @@ export const AutocompleteInput = component$((props: InputProps) => {
281281
useVisibleTask$(({ track }) => {
282282
track(() => contextService.inputValue.value);
283283

284-
if (
285-
contextService.inputValue.value.length > 0 &&
286-
document.activeElement === ref.value
287-
) {
288-
contextService.isExpanded.value = true;
289-
}
290-
291284
contextService.filteredOptions = contextService.options.filter(
292285
(option: Signal) => {
293286
const optionValue = option.value.getAttribute('optionValue');
294287
const inputValue = contextService.inputValue.value;
295288

289+
if (
290+
contextService.inputValue.value.length > 0 &&
291+
document.activeElement === ref.value
292+
) {
293+
// issue we need to look at. Still gives an array if any keyword is a match.
294+
//For example, apple returns 3 options after hitting enter.
295+
if (optionValue.match(new RegExp(inputValue, 'i'))) {
296+
contextService.isExpanded.value = true;
297+
} else {
298+
contextService.isExpanded.value = false;
299+
}
300+
}
301+
296302
return optionValue.match(new RegExp(inputValue, 'i'));
297303
}
298304
);
@@ -439,6 +445,7 @@ export const AutocompleteOption = component$((props: OptionProps) => {
439445
if (e.key === 'Enter' || e.key === ' ') {
440446
contextService.inputValue.value = props.optionValue;
441447
contextService.isExpanded.value = false;
448+
console.log('inside option!');
442449
const inputElement = contextService.triggerRef.value
443450
?.firstElementChild as HTMLElement;
444451
inputElement?.focus();

0 commit comments

Comments
 (0)