Skip to content

Commit 119c553

Browse files
feat(select): initial typeahead support
1 parent 7f5d8d7 commit 119c553

File tree

5 files changed

+85
-0
lines changed

5 files changed

+85
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { component$, useSignal } from '@builder.io/qwik';
2+
import {
3+
Select,
4+
SelectListbox,
5+
SelectOption,
6+
SelectTrigger,
7+
SelectValue,
8+
} from '@qwik-ui/headless';
9+
10+
export default component$(() => {
11+
const usersSig = useSignal<string[]>(['Tim', 'Ryan', 'Jim', 'Jessie', 'Abby']);
12+
13+
return (
14+
<Select class="relative min-w-40">
15+
<SelectTrigger class="w-full border-2 border-dashed border-red-400">
16+
<SelectValue placeholder="Select an option" />
17+
</SelectTrigger>
18+
<SelectListbox class="absolute w-full border-2 border-dashed border-green-400 bg-slate-900 p-2">
19+
{usersSig.value.map((user) => (
20+
<SelectOption
21+
class="border-dashed border-blue-400 data-[highlighted]:border-2"
22+
key={user}
23+
>
24+
{user}
25+
</SelectOption>
26+
))}
27+
</SelectListbox>
28+
</Select>
29+
);
30+
});

apps/website/src/routes/docs/headless/select/index.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ This element is used to create a drop-down list, it's often used in a form, to c
5858
<Showcase name="open-change" />
5959
</div>
6060

61+
## Typeahead
62+
63+
<div data-testid="select-typeahead-test">
64+
<Showcase name="typeahead" />
65+
</div>
66+
6167
## Building blocks
6268

6369
<CodeSnippet name="building-blocks" />

apps/website/src/routes/docs/headless/select/select.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,23 @@ test.describe('Keyboard Behavior', () => {
464464
expect(optStr).toEqual(await getValue());
465465
});
466466
});
467+
468+
test.describe('typeahead', () => {
469+
test(`GIVEN an open select with a typeahead support
470+
WHEN the user types in the letter "r"
471+
THEN the first option starting with the letter "r" should have data-highlighted`, async ({
472+
page,
473+
}) => {
474+
const { getRoot, getTrigger, openListbox } = await setup(
475+
page,
476+
'select-typeahead-test',
477+
);
478+
await openListbox('ArrowDown');
479+
await getTrigger().pressSequentially('j', { delay: 250 });
480+
const highlightedOpt = getRoot().locator('[data-highlighted]');
481+
await expect(highlightedOpt).toContainText('j', { ignoreCase: true });
482+
});
483+
});
467484
});
468485

469486
test.describe('Disabled', () => {

packages/kit-headless/src/components/select/select-trigger.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { component$, type PropsOf, useContext, sync$, $, Slot } from '@builder.io/qwik';
22
import SelectContextId from './select-context';
33
import { getNextEnabledOptionIndex, getPrevEnabledOptionIndex } from './utils';
4+
import { useTypeahead } from './use-select';
45
export type OpenKeys = 'ArrowUp' | 'Enter' | 'Space' | 'ArrowDown';
56

67
type SelectTriggerProps = PropsOf<'button'>;
@@ -9,6 +10,8 @@ export const SelectTrigger = component$<SelectTriggerProps>((props) => {
910
const openKeys = ['ArrowUp', 'ArrowDown'];
1011
const closedKeys = [`Escape`];
1112

13+
const { typeahead$ } = useTypeahead();
14+
1215
// Both the space and enter keys run with handleClick$
1316
const handleClick$ = $(() => {
1417
context.isListboxOpenSig.value = !context.isListboxOpenSig.value;
@@ -77,6 +80,8 @@ export const SelectTrigger = component$<SelectTriggerProps>((props) => {
7780
if (e.key === 'Enter' || e.key === ' ') {
7881
context.selectedIndexSig.value = context.highlightedIndexSig.value;
7982
}
83+
84+
typeahead$(e.key);
8085
}
8186
});
8287

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useContext, $ } from '@builder.io/qwik';
2+
import SelectContextId from './select-context';
3+
4+
export function useTypeahead() {
5+
const context = useContext(SelectContextId);
6+
7+
const typeahead$ = $((key: string): void => {
8+
if (key.length > 1) {
9+
return null;
10+
}
11+
12+
const singleInputChar = key.toLowerCase();
13+
const firstCharOptions = context.optionsSig.value?.map((opt) =>
14+
opt.value.slice(0, 1).toLowerCase(),
15+
);
16+
17+
const charIndex = firstCharOptions.indexOf(singleInputChar);
18+
19+
if (charIndex === -1) {
20+
return null;
21+
}
22+
23+
context.highlightedIndexSig.value = charIndex;
24+
});
25+
26+
return { typeahead$ };
27+
}

0 commit comments

Comments
 (0)