Add <form> compatibility (#1214)
* implement `objetToFormEntries` functionality
If we are working with more complex data structures then we have to
encode those data structures into a syntax that the HTML can understand.
This means that we have to use `<input type="hidden" name="..." value="...">` syntax.
To convert a simple array we can use the following syntax:
```js
// Assuming we have a `name` of `person`
let input = ['Alice', 'Bob', 'Charlie']
```
Results in:
```html
<input type="hidden" name="person[]" value="Alice" />
<input type="hidden" name="person[]" value="Bob" />
<input type="hidden" name="person[]" value="Charlie" />
```
Note: the additional `[]` in the name attribute.
---
A more complex object (even deeply nested) can be encoded like this:
```js
// Assuming we have a `name` of `person`
let input = {
id: 1,
name: {
first: 'Jane',
last: 'Doe'
}
}
```
Results in:
```html
<input type="hidden" name="person[id]" value="1" />
<input type="hidden" name="person[name][first]" value="Jane" />
<input type="hidden" name="person[name][last]" value="Doe" />
```
* implement VisuallyHidden component
* implement and export some extra helper utilities
* implement form element for Switch
* implement form element for Combobox
* implement form element for RadioGroup
* implement form element for Listbox
* add combined forms example to the playground
* update changelog
* enable support for iterators
* ensure to compile dom iterables
* remove unused imports
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
getSwitch,
|
||||
assertActiveElement,
|
||||
getSwitchLabel,
|
||||
getByText,
|
||||
} from '../../test-utils/accessibility-assertions'
|
||||
import { press, click, Keys } from '../../test-utils/interactions'
|
||||
|
||||
@@ -395,3 +396,83 @@ describe('Mouse interactions', () => {
|
||||
assertSwitch({ state: SwitchState.Off })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Form compatibility', () => {
|
||||
it('should be possible to submit a form with an boolean value', async () => {
|
||||
let submits = jest.fn()
|
||||
|
||||
function Example() {
|
||||
let [state, setState] = useState(false)
|
||||
return (
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
submits([...new FormData(event.currentTarget).entries()])
|
||||
}}
|
||||
>
|
||||
<Switch.Group>
|
||||
<Switch checked={state} onChange={setState} name="notifications" />
|
||||
<Switch.Label>Enable notifications</Switch.Label>
|
||||
</Switch.Group>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
render(<Example />)
|
||||
|
||||
// Submit the form
|
||||
await click(getByText('Submit'))
|
||||
|
||||
// Verify that the form has been submitted
|
||||
expect(submits).lastCalledWith([]) // no data
|
||||
|
||||
// Toggle
|
||||
await click(getSwitchLabel())
|
||||
|
||||
// Submit the form again
|
||||
await click(getByText('Submit'))
|
||||
|
||||
// Verify that the form has been submitted
|
||||
expect(submits).lastCalledWith([['notifications', 'on']])
|
||||
})
|
||||
|
||||
it('should be possible to submit a form with a provided string value', async () => {
|
||||
let submits = jest.fn()
|
||||
|
||||
function Example() {
|
||||
let [state, setState] = useState(false)
|
||||
return (
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
submits([...new FormData(event.currentTarget).entries()])
|
||||
}}
|
||||
>
|
||||
<Switch.Group>
|
||||
<Switch checked={state} onChange={setState} name="fruit" value="apple" />
|
||||
<Switch.Label>Apple</Switch.Label>
|
||||
</Switch.Group>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
render(<Example />)
|
||||
|
||||
// Submit the form
|
||||
await click(getByText('Submit'))
|
||||
|
||||
// Verify that the form has been submitted
|
||||
expect(submits).lastCalledWith([]) // no data
|
||||
|
||||
// Toggle
|
||||
await click(getSwitchLabel())
|
||||
|
||||
// Submit the form again
|
||||
await click(getByText('Submit'))
|
||||
|
||||
// Verify that the form has been submitted
|
||||
expect(submits).lastCalledWith([['fruit', 'apple']])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,17 +5,17 @@ import React, {
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
useRef,
|
||||
|
||||
// Types
|
||||
ElementType,
|
||||
KeyboardEvent as ReactKeyboardEvent,
|
||||
MouseEvent as ReactMouseEvent,
|
||||
useRef,
|
||||
Ref,
|
||||
} from 'react'
|
||||
|
||||
import { Props } from '../../types'
|
||||
import { forwardRefWithAs, render } from '../../utils/render'
|
||||
import { forwardRefWithAs, render, compact } from '../../utils/render'
|
||||
import { useId } from '../../hooks/use-id'
|
||||
import { Keys } from '../keyboard'
|
||||
import { isDisabledReactIssue7711 } from '../../utils/bugs'
|
||||
@@ -23,6 +23,7 @@ import { Label, useLabels } from '../label/label'
|
||||
import { Description, useDescriptions } from '../description/description'
|
||||
import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
|
||||
import { useSyncRefs } from '../../hooks/use-sync-refs'
|
||||
import { VisuallyHidden } from '../../internal/visually-hidden'
|
||||
|
||||
interface StateDefinition {
|
||||
switch: HTMLButtonElement | null
|
||||
@@ -88,13 +89,19 @@ type SwitchPropsWeControl =
|
||||
let SwitchRoot = forwardRefWithAs(function Switch<
|
||||
TTag extends ElementType = typeof DEFAULT_SWITCH_TAG
|
||||
>(
|
||||
props: Props<TTag, SwitchRenderPropArg, SwitchPropsWeControl | 'checked' | 'onChange'> & {
|
||||
props: Props<
|
||||
TTag,
|
||||
SwitchRenderPropArg,
|
||||
SwitchPropsWeControl | 'checked' | 'onChange' | 'name' | 'value'
|
||||
> & {
|
||||
checked: boolean
|
||||
onChange(checked: boolean): void
|
||||
name?: string
|
||||
value?: string
|
||||
},
|
||||
ref: Ref<HTMLElement>
|
||||
) {
|
||||
let { checked, onChange, ...passThroughProps } = props
|
||||
let { checked, onChange, name, value, ...passThroughProps } = props
|
||||
let id = `headlessui-switch-${useId()}`
|
||||
let groupContext = useContext(GroupContext)
|
||||
let internalSwitchRef = useRef<HTMLButtonElement | null>(null)
|
||||
@@ -143,12 +150,33 @@ let SwitchRoot = forwardRefWithAs(function Switch<
|
||||
onKeyPress: handleKeyPress,
|
||||
}
|
||||
|
||||
return render({
|
||||
let renderConfiguration = {
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
defaultTag: DEFAULT_SWITCH_TAG,
|
||||
name: 'Switch',
|
||||
})
|
||||
}
|
||||
|
||||
if (name != null && checked) {
|
||||
return (
|
||||
<>
|
||||
<VisuallyHidden
|
||||
{...compact({
|
||||
as: 'input',
|
||||
type: 'checkbox',
|
||||
hidden: true,
|
||||
readOnly: true,
|
||||
checked,
|
||||
name,
|
||||
value,
|
||||
})}
|
||||
/>
|
||||
{render(renderConfiguration)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return render(renderConfiguration)
|
||||
})
|
||||
|
||||
// ---
|
||||
|
||||
Reference in New Issue
Block a user