Skip to content

Commit e0f9cef

Browse files
refactor(accordion & autocomplete): autocomplete preventdefault, accordion declaration type fix
1 parent a90e9ce commit e0f9cef

File tree

15 files changed

+201
-155
lines changed

15 files changed

+201
-155
lines changed

apps/website/src/routes/docs/headless/(components)/autocomplete/examples.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { component$, Slot } from '@builder.io/qwik';
22
import {
3-
AutocompleteButton,
4-
AutocompleteInput,
53
AutocompleteLabel,
6-
AutocompleteListbox,
7-
AutocompleteOption,
84
AutocompleteRoot,
9-
AutocompleteTrigger
5+
AutocompleteControl,
6+
AutocompleteInput,
7+
AutocompleteTrigger,
8+
AutocompleteListbox,
9+
AutocompleteOption
1010
} from '@qwik-ui/headless';
11+
1112
import { PreviewCodeExample } from '../../../_components/preview-code-example/preview-code-example';
1213

1314
const trainers = [
@@ -31,9 +32,9 @@ export const Example01 = component$(() => {
3132
<AutocompleteLabel class=" font-semibold dark:text-white text-[#333333]">
3233
Personal Trainers ⚡
3334
</AutocompleteLabel>
34-
<AutocompleteTrigger class="bg-[#1f2532] flex items-center rounded-sm border-[#7d95b3] border-[1px] relative">
35+
<AutocompleteControl class="bg-[#1f2532] flex items-center rounded-sm border-[#7d95b3] border-[1px] relative">
3536
<AutocompleteInput class="w-44 bg-inherit px-2 pr-6 text-white" />
36-
<AutocompleteButton class="w-6 h-6 group absolute right-0">
37+
<AutocompleteTrigger class="w-6 h-6 group absolute right-0">
3738
<svg
3839
xmlns="http://www.w3.org/2000/svg"
3940
viewBox="0 0 24 24"
@@ -45,8 +46,8 @@ export const Example01 = component$(() => {
4546
>
4647
<polyline points="6 9 12 15 18 9"></polyline>
4748
</svg>
48-
</AutocompleteButton>
49-
</AutocompleteTrigger>
49+
</AutocompleteTrigger>
50+
</AutocompleteControl>
5051
<AutocompleteListbox class="text-white w-full bg-[#1f2532] px-4 py-2 mt-2 rounded-sm border-[#7d95b3] border-[1px]">
5152
{trainers.map((trainer, index) => (
5253
<AutocompleteOption

apps/website/src/routes/docs/headless/(components)/autocomplete/index.mdx

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
AutocompleteLabel,
88
AutocompleteTrigger,
99
AutocompleteInput,
10-
AutocompleteButton,
1110
AutocompleteListbox,
1211
AutocompleteOption,
1312
} from '@qwik-ui/headless';
@@ -32,9 +31,9 @@ import { AlphaBanner } from '../../../_components/alpha-banner/alpha-banner';
3231
<AutocompleteLabel class="text-inherit font-semibold">
3332
Personal Trainers ⚡
3433
</AutocompleteLabel>
35-
<AutocompleteTrigger class="bg-[#1f2532] flex items-center rounded-sm border-[#7d95b3] border-[1px] relative">
34+
<AutocompleteControl class="bg-[#1f2532] flex items-center rounded-sm border-[#7d95b3] border-[1px] relative">
3635
<AutocompleteInput class="w-44 bg-inherit px-2 pr-6" />
37-
<AutocompleteButton class="w-6 h-6 group absolute right-0">
36+
<AutocompleteTrigger class="w-6 h-6 group absolute right-0">
3837
<svg
3938
xmlns="http://www.w3.org/2000/svg"
4039
viewBox="0 0 24 24"
@@ -46,8 +45,8 @@ import { AlphaBanner } from '../../../_components/alpha-banner/alpha-banner';
4645
>
4746
<polyline points="6 9 12 15 18 9"></polyline>
4847
</svg>
49-
</AutocompleteButton>
50-
</AutocompleteTrigger>
48+
</AutocompleteTrigger>
49+
</AutocompleteControl>
5150
<AutocompleteListbox class="w-full bg-[#1f2532] px-4 py-2 mt-2 rounded-sm border-[#7d95b3] border-[1px]">
5251
{trainers.map((trainer, index) => (
5352
<AutocompleteOption
@@ -68,22 +67,24 @@ import { AlphaBanner } from '../../../_components/alpha-banner/alpha-banner';
6867
<CodeExample>
6968
```tsx
7069
import { component$ } from '@builder.io/qwik';
71-
import { AutocompleteRoot, AutocompleteLabel, AutocompleteTrigger, AutocompleteInput, AutocompleteButton, AutocompleteListbox, AutocompleteOption } from '@qwik-ui/headless';
72-
73-
export default component$(() => (
74-
<AutocompleteRoot>
75-
<AutocompleteLabel>Label</AutocompleteLabel>
76-
<AutocompleteTrigger>
77-
<AutocompleteInput />
78-
<AutocompleteButton>
79-
Button Marker
80-
</AutocompleteButton>
81-
</AutocompleteTrigger>
82-
<AutocompleteListbox>
83-
<AutocompleteOption />
84-
</AutocompleteListbox>
85-
</AutocompleteRoot>
86-
))
70+
import { AutocompleteRoot, AutocompleteLabel, AutocompleteControl, AutocompleteInput, AutocompleteTrigger, AutocompleteListbox, AutocompleteOption } from '@qwik-ui/headless';
71+
72+
export default component$(() => {
73+
return (
74+
<AutocompleteRoot>
75+
<AutocompleteLabel>Label</AutocompleteLabel>
76+
<AutocompleteControl>
77+
<AutocompleteInput />
78+
<AutocompleteTrigger>
79+
Button
80+
</AutocompleteTrigger>
81+
</AutocompleteControl>
82+
<AutocompleteListbox>
83+
<AutocompleteOption>Option</AutocompleteOption>
84+
</AutocompleteListbox>
85+
</AutocompleteRoot>
86+
)
87+
})
8788
```
8889

8990
</CodeExample>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface AccordionRootContext {
88
currFocusedTriggerIndexSig: Signal<number>;
99
currSelectedTriggerIndexSig: Signal<number>;
1010
selectedTriggerIdSig: Signal;
11-
triggerStore: HTMLButtonElement[];
11+
triggerStore: HTMLElement[];
1212
collapsible: boolean;
1313
behavior?: string;
1414
animated?: boolean;

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { accordionItemContextId, accordionRootContextId } from './accordion-cont
1414

1515
import { KeyCode } from '../../utils/key-code.type';
1616

17-
export const accordionPreventedKeys = [
17+
const accordionPreventedKeys = [
1818
KeyCode.Home,
1919
KeyCode.End,
2020
KeyCode.PageDown,
@@ -23,14 +23,16 @@ export const accordionPreventedKeys = [
2323
KeyCode.ArrowUp
2424
];
2525

26-
export type AccordionTriggerProps = QwikIntrinsicElements['button'];
26+
export type AccordionTriggerProps = {
27+
disabled?: boolean;
28+
} & QwikIntrinsicElements['button'];
2729

2830
export const AccordionTrigger = component$(
2931
({ disabled, ...props }: AccordionTriggerProps) => {
3032
const contextService = useContext(accordionRootContextId);
3133
const itemContext = useContext(accordionItemContextId);
3234

33-
const ref = useSignal<HTMLButtonElement>();
35+
const ref = useSignal<HTMLElement>();
3436
const triggerElement = ref.value;
3537

3638
const behavior = contextService.behavior;

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

Lines changed: 0 additions & 38 deletions
This file was deleted.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export interface AutocompleteContext {
1010
labelRef: Signal<HTMLElement | undefined>;
1111
listBoxId: string;
1212
inputId: string;
13-
buttonId: string;
13+
triggerId: string;
1414
activeOptionId: Signal<string | null>;
1515
inputValue: Signal<string>;
1616
focusInput$: QRL<(inputId: string) => void>;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {
2+
component$,
3+
useSignal,
4+
useContext,
5+
Slot,
6+
type QwikIntrinsicElements
7+
} from '@builder.io/qwik';
8+
import AutocompleteContextId from './autocomplete-context-id';
9+
10+
export type AutocompleteControlProps = QwikIntrinsicElements['div'];
11+
12+
export const AutocompleteControl = component$((props: AutocompleteControlProps) => {
13+
const ref = useSignal<HTMLElement>();
14+
const contextService = useContext(AutocompleteContextId);
15+
contextService.triggerRef = ref;
16+
17+
return (
18+
<div ref={ref} {...props}>
19+
<Slot />
20+
</div>
21+
);
22+
});

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

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,21 @@ import {
66
$,
77
type Signal,
88
type QwikKeyboardEvent,
9-
type QwikIntrinsicElements,
9+
type QwikIntrinsicElements
1010
} from '@builder.io/qwik';
1111
import AutocompleteContextId from './autocomplete-context-id';
1212

13+
import { KeyCode } from '../../utils/key-code.type';
14+
15+
const autocompletePreventedKeys = [
16+
KeyCode.Home,
17+
KeyCode.End,
18+
KeyCode.PageDown,
19+
KeyCode.PageUp,
20+
KeyCode.ArrowDown,
21+
KeyCode.ArrowUp
22+
];
23+
1324
export type InputProps = QwikIntrinsicElements['input'];
1425

1526
// Add required context here
@@ -18,37 +29,53 @@ export const AutocompleteInput = component$((props: InputProps) => {
1829
const contextService = useContext(AutocompleteContextId);
1930
const listboxId = contextService.listBoxId;
2031
const labelRef = contextService.labelRef;
32+
const inputElement = ref.value;
33+
34+
useVisibleTask$(function preventDefaultTask({ cleanup }) {
35+
function keyHandler(e: KeyboardEvent) {
36+
if (autocompletePreventedKeys.includes(e.key as KeyCode)) {
37+
e.preventDefault();
38+
}
39+
}
40+
41+
inputElement?.addEventListener('keydown', keyHandler);
42+
cleanup(() => {
43+
inputElement?.removeEventListener('keydown', keyHandler);
44+
});
45+
});
2146

2247
/*
2348
previously had useTask here, but noticed whenever it first renders,
2449
it won't focus the first option when hitting the down arrow key to open the listbox
2550
Also, all of our tests break on useTask, BUT it seems to work fine in the browser with useTask.
2651
Very odd.
2752
*/
28-
useVisibleTask$(({ track }) => {
53+
useVisibleTask$(async ({ track }) => {
2954
track(() => contextService.inputValue.value);
3055

31-
contextService.filteredOptions = contextService.options.filter(
32-
(option: Signal) => {
33-
const optionValue = option.value.getAttribute('optionValue');
34-
const inputValue = contextService.inputValue.value;
56+
contextService.filteredOptions = contextService.options.filter((option: Signal) => {
57+
const optionValue = option.value.getAttribute('optionValue');
58+
const inputValue = contextService.inputValue.value;
3559

36-
if (
37-
contextService.inputValue.value.length >= 0 &&
38-
document.activeElement === ref.value
39-
) {
40-
if (optionValue === inputValue) {
41-
contextService.isExpanded.value = false;
42-
} else if (optionValue.match(new RegExp(inputValue, 'i'))) {
43-
contextService.isExpanded.value = true;
44-
}
45-
} else {
60+
const defaultFilterRegex = '[0-9]*';
61+
const defaultFilterPattern = inputValue + defaultFilterRegex;
62+
const defaultFilter = new RegExp(defaultFilterPattern, 'i');
63+
64+
if (
65+
contextService.inputValue.value.length >= 0 &&
66+
document.activeElement === ref.value
67+
) {
68+
if (optionValue === inputValue) {
4669
contextService.isExpanded.value = false;
70+
} else if (optionValue.match(defaultFilter)) {
71+
contextService.isExpanded.value = true;
4772
}
48-
49-
return optionValue.match(new RegExp(inputValue, 'i'));
73+
} else {
74+
contextService.isExpanded.value = false;
5075
}
51-
);
76+
77+
return optionValue.match(defaultFilter);
78+
});
5279

5380
// Probably better to refactor Signal type later
5481
contextService.options.map((option: Signal) => {
@@ -86,7 +113,7 @@ export const AutocompleteInput = component$((props: InputProps) => {
86113
contextService.filteredOptions[0]?.value?.focus();
87114
}
88115
}),
89-
props.onKeyDown$,
116+
props.onKeyDown$
90117
]}
91118
{...props}
92119
/>

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,21 @@ import {
66
Slot,
77
type QwikIntrinsicElements,
88
type QwikKeyboardEvent,
9+
useVisibleTask$
910
} from '@builder.io/qwik';
1011
import AutocompleteContextId from './autocomplete-context-id';
1112

13+
import { KeyCode } from '../../utils/key-code.type';
14+
15+
const preventedKeys = [
16+
KeyCode.Home,
17+
KeyCode.End,
18+
KeyCode.PageDown,
19+
KeyCode.PageUp,
20+
KeyCode.ArrowDown,
21+
KeyCode.ArrowUp
22+
];
23+
1224
export type ListboxProps = {
1325
isExpanded?: boolean;
1426
} & QwikIntrinsicElements['ul'];
@@ -17,7 +29,20 @@ export const AutocompleteListbox = component$((props: ListboxProps) => {
1729
const ref = useSignal<HTMLElement>();
1830
const contextService = useContext(AutocompleteContextId);
1931
const listboxId = contextService.listBoxId;
20-
contextService.listBoxRef = ref;
32+
const listboxElement = ref.value;
33+
34+
useVisibleTask$(function preventDefaultTask({ cleanup }) {
35+
function keyHandler(e: KeyboardEvent) {
36+
if (preventedKeys.includes(e.key as KeyCode)) {
37+
e.preventDefault();
38+
}
39+
}
40+
41+
listboxElement?.addEventListener('keydown', keyHandler);
42+
cleanup(() => {
43+
listboxElement?.removeEventListener('keydown', keyHandler);
44+
});
45+
});
2146

2247
return (
2348
<ul
@@ -64,7 +89,7 @@ export const AutocompleteListbox = component$((props: ListboxProps) => {
6489
availableOptions[availableOptions.length - 1]?.focus();
6590
}
6691
}),
67-
props.onKeyDown$,
92+
props.onKeyDown$
6893
]}
6994
>
7095
<Slot />

0 commit comments

Comments
 (0)