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:
Robin Malfait
2022-03-09 11:24:45 +01:00
committed by GitHub
parent 2414bbd127
commit 7bb89871ba
30 changed files with 1953 additions and 66 deletions
@@ -0,0 +1,25 @@
import { objectToFormEntries } from './form'
it.each([
[{ a: 'b' }, [['a', 'b']]],
[
[1, 2, 3],
[
['0', '1'],
['1', '2'],
['2', '3'],
],
],
[
{ id: 1, admin: true, name: { first: 'Jane', last: 'Doe', nickname: { preferred: 'JDoe' } } },
[
['id', '1'],
['admin', '1'],
['name[first]', 'Jane'],
['name[last]', 'Doe'],
['name[nickname][preferred]', 'JDoe'],
],
],
])('should encode an input of %j to an form data output', (input, output) => {
expect(objectToFormEntries(input)).toEqual(output)
})
@@ -0,0 +1,37 @@
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)
}
}
@@ -218,7 +218,7 @@ export function forwardRefWithAs<T extends { name: string; displayName?: string
})
}
function compact<T extends Record<any, any>>(object: T) {
export function compact<T extends Record<any, any>>(object: T) {
let clone = Object.assign({}, object)
for (let key in clone) {
if (clone[key] === undefined) delete clone[key]