Files
headlessui/packages/@headlessui-react/src/utils/form.ts
T
Robin Malfait c475cab451 Allow Enter for form submit in RadioGroup, Switch and Combobox improvements (#1285)
* improve rendering of hidden form fields

* add `attemptSubmit` helper

This will allow us to _try_ and submit a form based on any element you
pass it. It will try and lookup the current form and if it is
submittable it will attempt to submit it.

Instead of submitting the form directly, we try to follow the native
browser support where it looks for the first `input[type=submit]`,
`input[type=image]`, `button` or `button[type=submit]`, then it clicks
it.

This allows you to disable your submit button, or have an `onClick` that
does an `event.preventDefault()` just like the native form in a browser
would do.

* ensure we can submit a form from a closed Combobox

When the Combobox is closed, then the `Enter` keydown event will be
ignored and thus not use `event.preventDefault()`.

With recent changes where we always have an active option, it means that
you will always be able to select an option.

If we have no option at all (some edge case) or when the combobox is
closed, then the `Enter` keydown event will just bubble, allowing you to
submit a form.

Fixes: #1282

This is a continuation of a PR ([#1176](https://github.com/tailwindlabs/headlessui/pull/1176)) provided by Alexander, so wanted to include
them as a co-author because of their initial work.

Co-authored-by: Alexander Lyon <arlyon@me.com>

* ensure we can submit a form from a RadioGroup

* ensure we can submit a form from a Switch

* simplify / refactor form playground example

* update changelog

Co-authored-by: Alexander Lyon <arlyon@me.com>
2022-03-31 21:42:34 +02:00

58 lines
1.9 KiB
TypeScript

type Entries = [string, string][]
export function objectToFormEntries(
source: Record<string, any> = {},
parentKey: string | null = null,
entries: Entries = []
): Entries {
for (let [key, value] of Object.entries(source)) {
append(entries, composeKey(parentKey, key), value)
}
return entries
}
function composeKey(parent: string | null, key: string): string {
return parent ? parent + '[' + key + ']' : key
}
function append(entries: Entries, key: string, value: any): void {
if (Array.isArray(value)) {
for (let [subkey, subvalue] of value.entries()) {
append(entries, composeKey(key, subkey.toString()), subvalue)
}
} else if (value instanceof Date) {
entries.push([key, value.toISOString()])
} else if (typeof value === 'boolean') {
entries.push([key, value ? '1' : '0'])
} else if (typeof value === 'string') {
entries.push([key, value])
} else if (typeof value === 'number') {
entries.push([key, `${value}`])
} else if (value === null || value === undefined) {
entries.push([key, ''])
} else {
objectToFormEntries(value, key, entries)
}
}
export function attemptSubmit(element: HTMLElement) {
let form = (element as any)?.form ?? element.closest('form')
if (!form) return
for (let element of form.elements) {
if (
(element.tagName === 'INPUT' && element.type === 'submit') ||
(element.tagName === 'BUTTON' && element.type === 'submit') ||
(element.nodeName === 'INPUT' && element.type === 'image')
) {
// If you press `enter` in a normal input[type='text'] field, then the form will submit by
// searching for the a submit element and "click" it. We could also use the
// `form.requestSubmit()` function, but this has a downside where an `event.preventDefault()`
// inside a `click` listener on the submit button won't stop the form from submitting.
element.click()
return
}
}
}