Skip to content

Commit 2fd9d1c

Browse files
committed
Forward disabled state to hidden inputs in form-like components (#3004)
* make hidden inputs disabled if the wrapping component is disabled * add tests to verify disabled hidden form elements * update changelog
1 parent e9df8dd commit 2fd9d1c

File tree

18 files changed

+322
-0
lines changed

18 files changed

+322
-0
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
- Prevent default behaviour when clicking outside of a `Dialog.Panel` ([#2919](https://github.com/tailwindlabs/headlessui/pull/2919))
2020
- Add `hidden` attribute to internal `<Hidden />` component when the `Features.Hidden` feature is used ([#2955](https://github.com/tailwindlabs/headlessui/pull/2955))
2121
- Allow setting custom `tabIndex` on the `<Switch />` component ([#2966](https://github.com/tailwindlabs/headlessui/pull/2966))
22+
- Forward `disabled` state to hidden inputs in form-like components ([#3004](https://github.com/tailwindlabs/headlessui/pull/3004))
2223

2324
## [1.7.18] - 2024-01-08
2425

packages/@headlessui-react/src/components/combobox/combobox.test.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5655,6 +5655,48 @@ describe('Form compatibility', () => {
56555655
expect(submits).lastCalledWith([['delivery', 'pickup']])
56565656
})
56575657

5658+
it('should not submit the data if the Combobox is disabled', async () => {
5659+
let submits = jest.fn()
5660+
5661+
function Example() {
5662+
let [value, setValue] = useState('home-delivery')
5663+
return (
5664+
<form
5665+
onSubmit={(event) => {
5666+
event.preventDefault()
5667+
submits([...new FormData(event.currentTarget).entries()])
5668+
}}
5669+
>
5670+
<input type="hidden" name="foo" value="bar" />
5671+
<Combobox value={value} onChange={setValue} name="delivery" disabled>
5672+
<Combobox.Input onChange={NOOP} />
5673+
<Combobox.Button>Trigger</Combobox.Button>
5674+
<Combobox.Label>Pizza Delivery</Combobox.Label>
5675+
<Combobox.Options>
5676+
<Combobox.Option value="pickup">Pickup</Combobox.Option>
5677+
<Combobox.Option value="home-delivery">Home delivery</Combobox.Option>
5678+
<Combobox.Option value="dine-in">Dine in</Combobox.Option>
5679+
</Combobox.Options>
5680+
</Combobox>
5681+
<button>Submit</button>
5682+
</form>
5683+
)
5684+
}
5685+
5686+
render(<Example />)
5687+
5688+
// Open combobox
5689+
await click(getComboboxButton())
5690+
5691+
// Submit the form
5692+
await click(getByText('Submit'))
5693+
5694+
// Verify that the form has been submitted
5695+
expect(submits).toHaveBeenLastCalledWith([
5696+
['foo', 'bar'], // The only available field
5697+
])
5698+
})
5699+
56585700
it('should be possible to submit a form with a complex value object', async () => {
56595701
let submits = jest.fn()
56605702
let options = [

packages/@headlessui-react/src/components/combobox/combobox.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,7 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
942942
hidden: true,
943943
readOnly: true,
944944
form: formName,
945+
disabled,
945946
name,
946947
value,
947948
})}

packages/@headlessui-react/src/components/listbox/listbox.test.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4864,6 +4864,47 @@ describe('Form compatibility', () => {
48644864
expect(submits).lastCalledWith([['delivery', 'pickup']])
48654865
})
48664866

4867+
it('should not submit the data if the Listbox is disabled', async () => {
4868+
let submits = jest.fn()
4869+
4870+
function Example() {
4871+
let [value, setValue] = useState('home-delivery')
4872+
return (
4873+
<form
4874+
onSubmit={(event) => {
4875+
event.preventDefault()
4876+
submits([...new FormData(event.currentTarget).entries()])
4877+
}}
4878+
>
4879+
<input type="hidden" name="foo" value="bar" />
4880+
<Listbox value={value} onChange={setValue} name="delivery" disabled>
4881+
<Listbox.Button>Trigger</Listbox.Button>
4882+
<Listbox.Label>Pizza Delivery</Listbox.Label>
4883+
<Listbox.Options>
4884+
<Listbox.Option value="pickup">Pickup</Listbox.Option>
4885+
<Listbox.Option value="home-delivery">Home delivery</Listbox.Option>
4886+
<Listbox.Option value="dine-in">Dine in</Listbox.Option>
4887+
</Listbox.Options>
4888+
</Listbox>
4889+
<button>Submit</button>
4890+
</form>
4891+
)
4892+
}
4893+
4894+
render(<Example />)
4895+
4896+
// Open listbox
4897+
await click(getListboxButton())
4898+
4899+
// Submit the form
4900+
await click(getByText('Submit'))
4901+
4902+
// Verify that the form has been submitted
4903+
expect(submits).toHaveBeenLastCalledWith([
4904+
['foo', 'bar'], // The only available field
4905+
])
4906+
})
4907+
48674908
it('should be possible to submit a form with a complex value object', async () => {
48684909
let submits = jest.fn()
48694910
let options = [

packages/@headlessui-react/src/components/listbox/listbox.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ function ListboxFn<
566566
hidden: true,
567567
readOnly: true,
568568
form: formName,
569+
disabled,
569570
name,
570571
value,
571572
})}

packages/@headlessui-react/src/components/radio-group/radio-group.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,41 @@ describe('Form compatibility', () => {
14851485
})
14861486
)
14871487

1488+
it('should not submit the data if the RadioGroup is disabled', async () => {
1489+
let submits = jest.fn()
1490+
1491+
function Example() {
1492+
let [value, setValue] = useState('home-delivery')
1493+
return (
1494+
<form
1495+
onSubmit={(event) => {
1496+
event.preventDefault()
1497+
submits([...new FormData(event.currentTarget).entries()])
1498+
}}
1499+
>
1500+
<input type="hidden" name="foo" value="bar" />
1501+
<RadioGroup value={value} onChange={setValue} name="delivery" disabled>
1502+
<RadioGroup.Label>Pizza Delivery</RadioGroup.Label>
1503+
<RadioGroup.Option value="pickup">Pickup</RadioGroup.Option>
1504+
<RadioGroup.Option value="home-delivery">Home delivery</RadioGroup.Option>
1505+
<RadioGroup.Option value="dine-in">Dine in</RadioGroup.Option>
1506+
</RadioGroup>
1507+
<button>Submit</button>
1508+
</form>
1509+
)
1510+
}
1511+
1512+
render(<Example />)
1513+
1514+
// Submit the form
1515+
await click(getByText('Submit'))
1516+
1517+
// Verify that the form has been submitted
1518+
expect(submits).toHaveBeenLastCalledWith([
1519+
['foo', 'bar'], // The only available field
1520+
])
1521+
})
1522+
14881523
it(
14891524
'should be possible to submit a form with a complex value object',
14901525
suppressConsoleLogs(async () => {

packages/@headlessui-react/src/components/radio-group/radio-group.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ function RadioGroupFn<TTag extends ElementType = typeof DEFAULT_RADIO_GROUP_TAG,
350350
hidden: true,
351351
readOnly: true,
352352
form: formName,
353+
disabled,
353354
name,
354355
value,
355356
})}

packages/@headlessui-react/src/components/switch/switch.test.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,4 +810,37 @@ describe('Form compatibility', () => {
810810
// Verify that the form has been submitted
811811
expect(submits).lastCalledWith([['fruit', 'apple']])
812812
})
813+
814+
it('should not submit the data if the Switch is disabled', async () => {
815+
let submits = jest.fn()
816+
817+
function Example() {
818+
let [state, setState] = useState(true)
819+
return (
820+
<form
821+
onSubmit={(event) => {
822+
event.preventDefault()
823+
submits([...new FormData(event.currentTarget).entries()])
824+
}}
825+
>
826+
<input type="hidden" name="foo" value="bar" />
827+
<Switch.Group>
828+
<Switch checked={state} onChange={setState} name="fruit" value="apple" disabled />
829+
<Switch.Label>Apple</Switch.Label>
830+
</Switch.Group>
831+
<button>Submit</button>
832+
</form>
833+
)
834+
}
835+
836+
render(<Example />)
837+
838+
// Submit the form
839+
await click(getByText('Submit'))
840+
841+
// Verify that the form has been submitted
842+
expect(submits).toHaveBeenLastCalledWith([
843+
['foo', 'bar'], // The only available field
844+
])
845+
})
813846
})

packages/@headlessui-react/src/components/switch/switch.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export type SwitchProps<TTag extends ElementType> = Props<
114114
onChange?(checked: boolean): void
115115
name?: string
116116
value?: string
117+
disabled?: boolean
117118
form?: string
118119
tabIndex?: number
119120
}
@@ -129,6 +130,7 @@ function SwitchFn<TTag extends ElementType = typeof DEFAULT_SWITCH_TAG>(
129130
checked: controlledChecked,
130131
defaultChecked = false,
131132
onChange: controlledOnChange,
133+
disabled = false,
132134
name,
133135
value,
134136
form,
@@ -172,6 +174,7 @@ function SwitchFn<TTag extends ElementType = typeof DEFAULT_SWITCH_TAG>(
172174
'aria-checked': checked,
173175
'aria-labelledby': groupContext?.labelledby,
174176
'aria-describedby': groupContext?.describedby,
177+
disabled,
175178
onClick: handleClick,
176179
onKeyUp: handleKeyUp,
177180
onKeyPress: handleKeyPress,
@@ -198,6 +201,7 @@ function SwitchFn<TTag extends ElementType = typeof DEFAULT_SWITCH_TAG>(
198201
type: 'checkbox',
199202
hidden: true,
200203
readOnly: true,
204+
disabled,
201205
form,
202206
checked,
203207
name,

packages/@headlessui-vue/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Don’t override explicit `disabled` prop for components inside `<MenuItem>` ([#2929](https://github.com/tailwindlabs/headlessui/pull/2929))
1515
- Add `hidden` attribute to internal `<Hidden />` component when the `Features.Hidden` feature is used ([#2955](https://github.com/tailwindlabs/headlessui/pull/2955))
1616
- Allow setting custom `tabIndex` on the `<Switch />` component ([#2966](https://github.com/tailwindlabs/headlessui/pull/2966))
17+
- Forward `disabled` state to hidden inputs in form-like components ([#3004](https://github.com/tailwindlabs/headlessui/pull/3004))
1718

1819
## [1.7.19] - 2024-02-07
1920

0 commit comments

Comments
 (0)