Skip to content

Commit 3086c0f

Browse files
Merge pull request #814 from thejackshelton/fix-validation
Fix validation
2 parents c3ed0d4 + 6917287 commit 3086c0f

File tree

4 files changed

+58
-23
lines changed

4 files changed

+58
-23
lines changed

.changeset/late-ravens-smoke.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik-ui/headless': patch
3+
---
4+
5+
fix: select validates correctly with modular forms
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: 8 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 = {
@@ -27,28 +27,15 @@ export const HHiddenNativeSelect = component$(
2727
const context = useContext(SelectContextId);
2828

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

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-
4432
// TODO: make conditional logic to show either input or select based on the size of the options.
4533
return (
4634
<VisuallyHidden>
4735
<div aria-hidden="true">
4836
<label>
4937
{label}
5038
<select
51-
onFocus$={() => context.triggerRef.value?.focus()}
5239
ref={(element: HTMLSelectElement) => {
5340
nativeSelectRef.value = element;
5441
// @ts-expect-error modular forms ref function
@@ -66,13 +53,13 @@ export const HHiddenNativeSelect = component$(
6653
>
6754
<option />
6855
{Array.from(context.itemsMapSig.value.entries()).map(([index, item]) => (
69-
<option
70-
value={item.value}
71-
selected={context.selectedIndexSetSig.value.has(index)}
56+
<HiddenSelectOption
7257
key={item.value}
73-
>
74-
{item.displayValue}
75-
</option>
58+
value={item.value}
59+
displayValue={item.displayValue}
60+
nativeSelectRef={nativeSelectRef}
61+
index={index}
62+
/>
7663
))}
7764
</select>
7865
</label>

packages/kit-headless/src/components/select/select.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,7 @@ test.describe('Props', () => {
10661066
THEN the selected value matches the 5th option's value`, async ({ page }) => {
10671067
const { getTrigger } = await setup(page, 'controlled-value');
10681068

1069-
await expect(getTrigger()).toHaveText('Select an option');
1069+
await expect(getTrigger()).toHaveText('Ryan');
10701070
await page.getByRole('button', { name: 'Change to Abby' }).click();
10711071

10721072
await expect(getTrigger()).toHaveText(`Abby`);
@@ -1081,7 +1081,7 @@ test.describe('Props', () => {
10811081
}) => {
10821082
const { driver: d } = await setup(page, 'controlled-value');
10831083

1084-
await expect(d.getTrigger()).toHaveText('Select an option');
1084+
await expect(d.getTrigger()).toHaveText('Ryan');
10851085
// setup
10861086
await page.getByRole('button', { name: 'Change to Abby' }).click();
10871087
await expect(d.getTrigger()).toHaveText(`Abby`);

0 commit comments

Comments
 (0)