Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(getElementInput): Select type number for HTMLSelectElement #259

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/preact/src/utils/getElementInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export function getElementInput<
? [...((field.value.peek() || []) as string[]), value]
: ((field.value.peek() || []) as string[]).filter((v) => v !== value)
: type === 'number'
? valueAsNumber
? element instanceof HTMLSelectElement?
value ? parseFloat(value) : undefined
: valueAsNumber
: type === 'boolean'
? checked
: type === 'File' && files
Expand Down
4 changes: 3 additions & 1 deletion packages/qwik/src/utils/getElementInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export function getElementInput<
? [...((field.value || []) as string[]), value]
: ((field.value || []) as string[]).filter((v) => v !== value)
: type === 'number'
? valueAsNumber
? element instanceof HTMLSelectElement?
value ? parseFloat(value) : undefined
: valueAsNumber
: type === 'boolean'
? checked
: type === 'File' && files
Expand Down
4 changes: 3 additions & 1 deletion packages/react/src/utils/getElementInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export function getElementInput<
? [...((field.value.peek() || []) as string[]), value]
: ((field.value.peek() || []) as string[]).filter((v) => v !== value)
: type === 'number'
? valueAsNumber
? element instanceof HTMLSelectElement?
value ? parseFloat(value) : undefined
: valueAsNumber
: type === 'boolean'
? checked
: type === 'File' && files
Expand Down
55 changes: 35 additions & 20 deletions packages/solid/src/utils/getElementInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,50 @@ import type {
*/
export function getElementInput<
TFieldValues extends FieldValues,
TFieldName extends FieldPath<TFieldValues>
TFieldName extends FieldPath<TFieldValues>,
>(
element: FieldElement,
field: InternalFieldStore<TFieldValues, TFieldName>,
type: Maybe<FieldType<any>>
): FieldPathValue<TFieldValues, TFieldName> {
const { checked, files, options, value, valueAsDate, valueAsNumber } =
element as HTMLInputElement & HTMLSelectElement & HTMLTextAreaElement;
return untrack(() =>
!type || type === 'string'
? value
: type === 'string[]'
? options
return untrack(() => {
if (!type || type === 'string') {
return value;
}
if (type === 'string[]') {
return options
? [...options]
.filter((e) => e.selected && !e.disabled)
.map((e) => e.value)
: checked
? [...((field.value.get() || []) as string[]), value]
: ((field.value.get() || []) as string[]).filter((v) => v !== value)
: type === 'number'
? valueAsNumber
: type === 'boolean'
? checked
: type === 'File' && files
? files[0]
: type === 'File[]' && files
? [...files]
: type === 'Date' && valueAsDate
? valueAsDate
: field.value.get()
) as FieldPathValue<TFieldValues, TFieldName>;
? [...((field.value.get() || []) as string[]), value]
: ((field.value.get() || []) as string[]).filter((v) => v !== value);
}

if (type === 'number') {
if (element instanceof HTMLSelectElement) {
return value ? parseFloat(value) : undefined;
}

return valueAsNumber;
}

if (type === 'boolean') {
return checked;
}

if (type === 'File') {
return files ? files[0] : undefined;
}

if (type === 'File[]') {
return files ? [...files] : undefined;
}

if (type === 'Date') {
return valueAsDate;
}
}) as FieldPathValue<TFieldValues, TFieldName>;
}
10 changes: 5 additions & 5 deletions playgrounds/preact/src/components/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { AngleDownIcon } from '../icons';
import { InputError } from './InputError';
import { InputLabel } from './InputLabel';

type SelectProps = {
type SelectProps<T> = {
name: string;
value: ReadonlySignal<string | string[] | null | undefined>;
value: ReadonlySignal<T | T[] | null | undefined>;
ref: Ref<HTMLSelectElement>;
onInput: JSX.GenericEventHandler<HTMLSelectElement>;
onChange: JSX.GenericEventHandler<HTMLSelectElement>;
onBlur: JSX.FocusEventHandler<HTMLSelectElement>;
options: { label: string; value: string }[];
options: { label: string; value: T }[];
multiple?: boolean;
size?: number;
placeholder?: string;
Expand All @@ -29,15 +29,15 @@ type SelectProps = {
* decorations can be displayed in or around the field to communicate the
* entry requirements.
*/
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
export const Select = forwardRef<HTMLSelectElement, SelectProps<string | number>>(
({ value, options, label, error, ...props }, ref) => {
const { name, required, multiple, placeholder } = props;

// Create computed value of selected values
const values = useComputed(() =>
Array.isArray(value.value)
? value.value
: value.value && typeof value.value === 'string'
: (typeof props.value === 'string' || typeof props.value === 'number')
? [value.value]
: []
);
Expand Down
18 changes: 18 additions & 0 deletions playgrounds/preact/src/routes/special.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type SpecialForm = {
select: {
array: string[];
string: string;
number: number;
};
file: {
list: File[];
Expand Down Expand Up @@ -130,6 +131,23 @@ export default function SpecialPage() {
)}
</Field>

<Field name="select.number" type="number">
{(field, props) => (
<Select
{...props}
value={field.value}
options={[
{ label: 'Option 1', value: 1 },
{ label: 'Option 2', value: 2 },
{ label: 'Option 3', value: 3 },
]}
error={field.error}
label="Select number"
/>
)}
</Field>


<Field name="file.list" type="File[]">
{(field, props) => (
<FileInput
Expand Down
12 changes: 6 additions & 6 deletions playgrounds/qwik/src/components/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { AngleDownIcon } from '~/icons';
import { InputError } from './InputError';
import { InputLabel } from './InputLabel';

type SelectProps = {
type SelectProps<T> = {
ref: QRL<(element: HTMLSelectElement) => void>;
name: string;
value: string | string[] | null | undefined;
value: T | T[] | null | undefined;
onInput$: (event: Event, element: HTMLSelectElement) => void;
onChange$: (event: Event, element: HTMLSelectElement) => void;
onBlur$: (event: Event, element: HTMLSelectElement) => void;
options: { label: string; value: string }[];
options: { label: string; value: T }[];
multiple?: boolean;
size?: number;
placeholder?: string;
Expand All @@ -27,16 +27,16 @@ type SelectProps = {
* entry requirements.
*/
export const Select = component$(
({ value, options, label, error, ...props }: SelectProps) => {
({ value, options, label, error, ...props }: SelectProps<string | number>) => {
const { name, required, multiple, placeholder } = props;

// Create computed value of selected values
const values = useSignal<string[]>();
const values = useSignal<(string | number)[]>();
useTask$(({ track }) => {
track(() => value);
values.value = Array.isArray(value)
? value
: value && typeof value === 'string'
: value && (typeof value === 'string' || typeof value === 'number')
? [value]
: [];
});
Expand Down
18 changes: 18 additions & 0 deletions playgrounds/qwik/src/routes/(default)/special/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type SpecialForm = {
select: {
array: string[];
string?: string;
number?: number;
};
file: {
list: NoSerialize<File>[];
Expand All @@ -38,6 +39,7 @@ export const useFormLoader = routeLoader$<InitialValues<SpecialForm>>(() => ({
select: {
array: [],
string: undefined,
number: undefined,
},
file: {
list: [],
Expand Down Expand Up @@ -150,6 +152,22 @@ export default component$(() => {
)}
</Field>

<Field name="select.number" type="number">
{(field, props) => (
<Select
{...props}
value={field.value}
options={[
{ label: 'Option 1', value: 1 },
{ label: 'Option 2', value: 2 },
{ label: 'Option 3', value: 3 },
]}
error={field.error}
label="Select number"
/>
)}
</Field>

<Field name="file.list" type="File[]">
{(field, props) => (
<FileInput
Expand Down
19 changes: 19 additions & 0 deletions playgrounds/qwik/src/routes/actions/special/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const SpecialSchema = v.object({
select: v.object({
array: v.array(v.string()),
string: v.optional(v.string()),
number: v.optional(v.number()),
}),
file: v.object({
list: v.array(v.custom<NoSerialize<Blob>>(isBlob)),
Expand All @@ -53,6 +54,7 @@ const getInitFormValues = (): InitialValues<SpecialForm> => ({
select: {
array: [],
string: undefined,
number: undefined,
},
file: {
list: [],
Expand Down Expand Up @@ -206,6 +208,23 @@ export default component$(() => {
)}
</Field>

<Field name="select.number" type="number">
{(field, props) => (
<Select
{...props}
value={field.value}
options={[
{ label: 'Option 1', value: 1 },
{ label: 'Option 2', value: 2 },
{ label: 'Option 3', value: 3 },
]}
error={field.error}
label="Select number"
/>
)}
</Field>


<Field name="file.list" type="File[]">
{(field, props) => (
<FileInput
Expand Down
21 changes: 14 additions & 7 deletions playgrounds/react/src/components/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { AngleDownIcon } from '../icons';
import { InputError } from './InputError';
import { InputLabel } from './InputLabel';

type SelectProps = {
type SelectProps<T> = {
name: string;
value: ReadonlySignal<string | string[] | null | undefined>;
value: ReadonlySignal<T | T[] | null | undefined>;
ref: Ref<HTMLSelectElement>;
onChange: ChangeEventHandler<HTMLSelectElement>;
onBlur: FocusEventHandler<HTMLSelectElement>;
options: { label: string; value: string }[];
options: { label: string; value: T }[];
multiple?: boolean;
size?: number;
placeholder?: string;
Expand All @@ -26,9 +26,17 @@ type SelectProps = {
* decorations can be displayed in or around the field to communicate the
* entry requirements.
*/
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
export const Select = forwardRef<HTMLSelectElement, SelectProps<string | number>>(
({ className, value, options, label, error, ...props }, ref) => {
const { name, required, multiple, placeholder } = props;
// Create computed value of selected values
const values = Array.isArray(value.value)
? value.value
: (typeof value === 'string' || typeof value === 'number')
? [value]
: [];


return (
<div className={clsx('px-8 lg:px-10', className)}>
<InputLabel name={name} label={label} required={required} />
Expand All @@ -42,18 +50,17 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
? 'border-red-600/50 dark:border-red-400/50'
: 'border-slate-200 hover:border-slate-300 focus:border-sky-600/50 dark:border-slate-800 dark:hover:border-slate-700 dark:focus:border-sky-400/50',
multiple ? 'py-5' : 'h-14 md:h-16 lg:h-[70px]',
placeholder && !value.value?.length && 'text-slate-500'
placeholder && !value.value && 'text-slate-500'
)}
id={name}
value={value.value || (multiple ? [] : '')}
aria-invalid={!!error}
aria-errormessage={`${name}-error`}
>
<option value="" disabled hidden>
{placeholder}
</option>
{options.map(({ label, value }) => (
<option key={value} value={value}>
<option key={value} value={value} selected={values?.includes(value)}>
{label}
</option>
))}
Expand Down
18 changes: 18 additions & 0 deletions playgrounds/react/src/routes/special.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type SpecialForm = {
select: {
array: string[];
string: string;
number: number;
};
file: {
list: File[];
Expand Down Expand Up @@ -130,6 +131,23 @@ export default function SpecialPage() {
)}
</Field>

<Field name="select.number">
{(field, props) => (
<Select
{...props}
value={field.value}
options={[
{ label: 'Option 1', value: 1 },
{ label: 'Option 2', value: 2 },
{ label: 'Option 3', value: 3 },
]}
error={field.error}
label="Select string"
/>
)}
</Field>


<Field name="file.list" type="File[]">
{(field, props) => (
<FileInput
Expand Down
Loading