Skip to content

Commit e2f2d0e

Browse files
fix: select validation
1 parent 13e0d53 commit e2f2d0e

File tree

3 files changed

+56
-22
lines changed

3 files changed

+56
-22
lines changed

apps/website/src/routes/docs/headless/select/examples/validation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default component$(() => {
2929
{(field, props) => {
3030
return (
3131
<Select.Root class="select" required>
32-
<Select.HiddenNativeSelect {...props} />
32+
<Select.HiddenNativeSelect field={field} {...props} />
3333
<Select.Label>Logged in users</Select.Label>
3434
<Select.Trigger class="select-trigger">
3535
<Select.DisplayValue placeholder="Select an option" />
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
PropsOf,
3+
Signal,
4+
component$,
5+
useContext,
6+
useSignal,
7+
useTask$,
8+
} from '@builder.io/qwik';
9+
import { isServer } from '@builder.io/qwik/build';
10+
import SelectContextId from './select-context';
11+
12+
type HiddenSelectOptionProps = {
13+
value: string;
14+
displayValue: string;
15+
nativeSelectRef: Signal<HTMLSelectElement | undefined>;
16+
index: number;
17+
} & PropsOf<'option'>;
18+
19+
export const HiddenSelectOption = component$(
20+
({ value, displayValue, nativeSelectRef, index, ...rest }: HiddenSelectOptionProps) => {
21+
const optionRef = useSignal<HTMLOptionElement>();
22+
const context = useContext(SelectContextId);
23+
24+
useTask$(async function modularFormsValidation({ track }) {
25+
track(() => context.selectedIndexSetSig.value);
26+
27+
if (isServer || !nativeSelectRef.value || !optionRef.value) return;
28+
29+
// modular forms expects the input event fired after interaction
30+
const inputEvent = new Event('input', { bubbles: false });
31+
nativeSelectRef.value?.dispatchEvent(inputEvent);
32+
33+
// make sure to programmatically select the option after the input event has fired
34+
optionRef.value.selected = context.selectedIndexSetSig.value.has(index);
35+
});
36+
37+
return (
38+
<option ref={optionRef} value={value} {...rest}>
39+
{displayValue}
40+
</option>
41+
);
42+
},
43+
);

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

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { PropsOf, component$, useContext, useSignal, useTask$ } from '@builder.io/qwik';
1+
import { PropsOf, component$, useContext, useSignal } from '@builder.io/qwik';
22
import SelectContextId from './select-context';
3-
import { isServer } from '@builder.io/qwik/build';
3+
import { HiddenSelectOption } from './hidden-select-option';
44
import { VisuallyHidden } from '../../utils/visually-hidden';
55

66
export type AriaHiddenSelectProps = {
@@ -19,6 +19,10 @@ export type AriaHiddenSelectProps = {
1919
disabled?: boolean;
2020

2121
required?: boolean;
22+
23+
// from modular forms
24+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25+
field?: any;
2226
};
2327

2428
export const HHiddenNativeSelect = component$(
@@ -27,28 +31,15 @@ export const HHiddenNativeSelect = component$(
2731
const context = useContext(SelectContextId);
2832

2933
// modular forms does something with refs, doesn't seem we need it, and it overrides the ref we define here.
30-
ref;
31-
3234
const nativeSelectRef = useSignal<HTMLSelectElement>();
3335

34-
useTask$(function modularFormsValidation({ track }) {
35-
track(() => context.selectedIndexSetSig.value);
36-
37-
if (isServer) return;
38-
39-
// modular forms expects the input event fired after interaction
40-
const inputEvent = new Event('input', { bubbles: false });
41-
nativeSelectRef.value?.dispatchEvent(inputEvent);
42-
});
43-
4436
// TODO: make conditional logic to show either input or select based on the size of the options.
4537
return (
4638
<VisuallyHidden>
4739
<div aria-hidden="true">
4840
<label>
4941
{label}
5042
<select
51-
onFocus$={() => context.triggerRef.value?.focus()}
5243
ref={(element: HTMLSelectElement) => {
5344
nativeSelectRef.value = element;
5445
// @ts-expect-error modular forms ref function
@@ -66,13 +57,13 @@ export const HHiddenNativeSelect = component$(
6657
>
6758
<option />
6859
{Array.from(context.itemsMapSig.value.entries()).map(([index, item]) => (
69-
<option
70-
value={item.value}
71-
selected={context.selectedIndexSetSig.value.has(index)}
60+
<HiddenSelectOption
7261
key={item.value}
73-
>
74-
{item.displayValue}
75-
</option>
62+
value={item.value}
63+
displayValue={item.displayValue}
64+
nativeSelectRef={nativeSelectRef}
65+
index={index}
66+
/>
7667
))}
7768
</select>
7869
</label>

0 commit comments

Comments
 (0)