diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 41f8949a72..a1bab7ab88 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Prevent default behaviour when clicking outside of a `Dialog.Panel` ([#2919](https://github.com/tailwindlabs/headlessui/pull/2919)) - Add `hidden` attribute to internal `` component when the `Features.Hidden` feature is used ([#2955](https://github.com/tailwindlabs/headlessui/pull/2955)) - Allow setting custom `tabIndex` on the `` component ([#2966](https://github.com/tailwindlabs/headlessui/pull/2966)) +- Forward `disabled` state to hidden inputs in form-like components ([#3004](https://github.com/tailwindlabs/headlessui/pull/3004)) ## [1.7.18] - 2024-01-08 diff --git a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx index 505c85f1ad..38a91a12e5 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx @@ -5655,6 +5655,48 @@ describe('Form compatibility', () => { expect(submits).lastCalledWith([['delivery', 'pickup']]) }) + it('should not submit the data if the Combobox is disabled', async () => { + let submits = jest.fn() + + function Example() { + let [value, setValue] = useState('home-delivery') + return ( +
{ + event.preventDefault() + submits([...new FormData(event.currentTarget).entries()]) + }} + > + + + + Trigger + Pizza Delivery + + Pickup + Home delivery + Dine in + + + +
+ ) + } + + render() + + // Open combobox + await click(getComboboxButton()) + + // Submit the form + await click(getByText('Submit')) + + // Verify that the form has been submitted + expect(submits).toHaveBeenLastCalledWith([ + ['foo', 'bar'], // The only available field + ]) + }) + it('should be possible to submit a form with a complex value object', async () => { let submits = jest.fn() let options = [ diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index bdacf6d1ca..1e43f8f0e7 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -942,6 +942,7 @@ function ComboboxFn { expect(submits).lastCalledWith([['delivery', 'pickup']]) }) + it('should not submit the data if the Listbox is disabled', async () => { + let submits = jest.fn() + + function Example() { + let [value, setValue] = useState('home-delivery') + return ( +
{ + event.preventDefault() + submits([...new FormData(event.currentTarget).entries()]) + }} + > + + + Trigger + Pizza Delivery + + Pickup + Home delivery + Dine in + + + +
+ ) + } + + render() + + // Open listbox + await click(getListboxButton()) + + // Submit the form + await click(getByText('Submit')) + + // Verify that the form has been submitted + expect(submits).toHaveBeenLastCalledWith([ + ['foo', 'bar'], // The only available field + ]) + }) + it('should be possible to submit a form with a complex value object', async () => { let submits = jest.fn() let options = [ diff --git a/packages/@headlessui-react/src/components/listbox/listbox.tsx b/packages/@headlessui-react/src/components/listbox/listbox.tsx index 154883fabf..e97cb2f8e1 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -566,6 +566,7 @@ function ListboxFn< hidden: true, readOnly: true, form: formName, + disabled, name, value, })} diff --git a/packages/@headlessui-react/src/components/radio-group/radio-group.test.tsx b/packages/@headlessui-react/src/components/radio-group/radio-group.test.tsx index 4b182c5986..c7a4adae39 100644 --- a/packages/@headlessui-react/src/components/radio-group/radio-group.test.tsx +++ b/packages/@headlessui-react/src/components/radio-group/radio-group.test.tsx @@ -1485,6 +1485,41 @@ describe('Form compatibility', () => { }) ) + it('should not submit the data if the RadioGroup is disabled', async () => { + let submits = jest.fn() + + function Example() { + let [value, setValue] = useState('home-delivery') + return ( +
{ + event.preventDefault() + submits([...new FormData(event.currentTarget).entries()]) + }} + > + + + Pizza Delivery + Pickup + Home delivery + Dine in + + +
+ ) + } + + render() + + // Submit the form + await click(getByText('Submit')) + + // Verify that the form has been submitted + expect(submits).toHaveBeenLastCalledWith([ + ['foo', 'bar'], // The only available field + ]) + }) + it( 'should be possible to submit a form with a complex value object', suppressConsoleLogs(async () => { diff --git a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx index f6251e9461..f8441e1a07 100644 --- a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx +++ b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx @@ -350,6 +350,7 @@ function RadioGroupFn { // Verify that the form has been submitted expect(submits).lastCalledWith([['fruit', 'apple']]) }) + + it('should not submit the data if the Switch is disabled', async () => { + let submits = jest.fn() + + function Example() { + let [state, setState] = useState(true) + return ( +
{ + event.preventDefault() + submits([...new FormData(event.currentTarget).entries()]) + }} + > + + + + Apple + + +
+ ) + } + + render() + + // Submit the form + await click(getByText('Submit')) + + // Verify that the form has been submitted + expect(submits).toHaveBeenLastCalledWith([ + ['foo', 'bar'], // The only available field + ]) + }) }) diff --git a/packages/@headlessui-react/src/components/switch/switch.tsx b/packages/@headlessui-react/src/components/switch/switch.tsx index a4d6393f65..6aa48b021c 100644 --- a/packages/@headlessui-react/src/components/switch/switch.tsx +++ b/packages/@headlessui-react/src/components/switch/switch.tsx @@ -114,6 +114,7 @@ export type SwitchProps = Props< onChange?(checked: boolean): void name?: string value?: string + disabled?: boolean form?: string tabIndex?: number } @@ -129,6 +130,7 @@ function SwitchFn( checked: controlledChecked, defaultChecked = false, onChange: controlledOnChange, + disabled = false, name, value, form, @@ -172,6 +174,7 @@ function SwitchFn( 'aria-checked': checked, 'aria-labelledby': groupContext?.labelledby, 'aria-describedby': groupContext?.describedby, + disabled, onClick: handleClick, onKeyUp: handleKeyUp, onKeyPress: handleKeyPress, @@ -198,6 +201,7 @@ function SwitchFn( type: 'checkbox', hidden: true, readOnly: true, + disabled, form, checked, name, diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md index 21d72dd3a6..06db4c9cb7 100644 --- a/packages/@headlessui-vue/CHANGELOG.md +++ b/packages/@headlessui-vue/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Don’t override explicit `disabled` prop for components inside `` ([#2929](https://github.com/tailwindlabs/headlessui/pull/2929)) - Add `hidden` attribute to internal `` component when the `Features.Hidden` feature is used ([#2955](https://github.com/tailwindlabs/headlessui/pull/2955)) - Allow setting custom `tabIndex` on the `` component ([#2966](https://github.com/tailwindlabs/headlessui/pull/2966)) +- Forward `disabled` state to hidden inputs in form-like components ([#3004](https://github.com/tailwindlabs/headlessui/pull/3004)) ## [1.7.19] - 2024-02-07 diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.test.ts b/packages/@headlessui-vue/src/components/combobox/combobox.test.ts index 0b2cdbbc62..8d2ca7cf77 100644 --- a/packages/@headlessui-vue/src/components/combobox/combobox.test.ts +++ b/packages/@headlessui-vue/src/components/combobox/combobox.test.ts @@ -6146,6 +6146,49 @@ describe('Form compatibility', () => { expect(submits).lastCalledWith([['delivery', 'pickup']]) }) + it('should not submit the data if the Combobox is disabled', async () => { + let submits = jest.fn() + + renderTemplate({ + template: html` +
+ + + + Trigger + + Pickup + Home delivery + Dine in + + + +
+ `, + setup: () => { + let value = ref('home-delivery') + return { + value, + handleSubmit(event: SubmitEvent) { + event.preventDefault() + submits([...new FormData(event.currentTarget as HTMLFormElement).entries()]) + }, + } + }, + }) + + // Open combobox + await click(getComboboxButton()) + + // Submit the form + await click(getByText('Submit')) + + // Verify that the form has been submitted + expect(submits).toHaveBeenLastCalledWith([ + ['foo', 'bar'], // The only available field + ]) + }) + it('should be possible to submit a form with a complex value object', async () => { let submits = jest.fn() diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.ts b/packages/@headlessui-vue/src/components/combobox/combobox.ts index 8bb21da4c8..c44da0ca9c 100644 --- a/packages/@headlessui-vue/src/components/combobox/combobox.ts +++ b/packages/@headlessui-vue/src/components/combobox/combobox.ts @@ -742,6 +742,7 @@ export let Combobox = defineComponent({ hidden: true, readOnly: true, form, + disabled, name, value, }) diff --git a/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx b/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx index d2ccbf026c..58ce55dbd6 100644 --- a/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx +++ b/packages/@headlessui-vue/src/components/listbox/listbox.test.tsx @@ -5071,6 +5071,48 @@ describe('Form compatibility', () => { expect(submits).lastCalledWith([['delivery', 'pickup']]) }) + it('should not submit the data if the Listbox is disabled', async () => { + let submits = jest.fn() + + renderTemplate({ + template: html` +
+ + + Trigger + + Pickup + Home delivery + Dine in + + + +
+ `, + setup: () => { + let value = ref('home-delivery') + return { + value, + handleSubmit(event: SubmitEvent) { + event.preventDefault() + submits([...new FormData(event.currentTarget as HTMLFormElement).entries()]) + }, + } + }, + }) + + // Open listbox + await click(getListboxButton()) + + // Submit the form + await click(getByText('Submit')) + + // Verify that the form has been submitted + expect(submits).toHaveBeenLastCalledWith([ + ['foo', 'bar'], // The only available field + ]) + }) + it('should be possible to submit a form with a complex value object', async () => { let submits = jest.fn() diff --git a/packages/@headlessui-vue/src/components/listbox/listbox.ts b/packages/@headlessui-vue/src/components/listbox/listbox.ts index d797ec3e15..fe3dbdda7e 100644 --- a/packages/@headlessui-vue/src/components/listbox/listbox.ts +++ b/packages/@headlessui-vue/src/components/listbox/listbox.ts @@ -392,6 +392,7 @@ export let Listbox = defineComponent({ hidden: true, readOnly: true, form, + disabled, name, value, }) diff --git a/packages/@headlessui-vue/src/components/radio-group/radio-group.test.ts b/packages/@headlessui-vue/src/components/radio-group/radio-group.test.ts index 8b9b8e50f1..dbea2be035 100644 --- a/packages/@headlessui-vue/src/components/radio-group/radio-group.test.ts +++ b/packages/@headlessui-vue/src/components/radio-group/radio-group.test.ts @@ -1680,6 +1680,43 @@ describe('Form compatibility', () => { expect(submits).lastCalledWith([['delivery', 'pickup']]) }) + it('should not submit the data if the RadioGroup is disabled', async () => { + let submits = jest.fn() + + renderTemplate({ + template: html` +
+ + + Pizza Delivery + Pickup + Home delivery + Dine in + + +
+ `, + setup: () => { + let value = ref('home-delivery') + return { + value, + handleSubmit(event: SubmitEvent) { + event.preventDefault() + submits([...new FormData(event.currentTarget as HTMLFormElement).entries()]) + }, + } + }, + }) + + // Submit the form + await click(getByText('Submit')) + + // Verify that the form has been submitted + expect(submits).toHaveBeenLastCalledWith([ + ['foo', 'bar'], // The only available field + ]) + }) + it('should be possible to submit a form with a complex value object', async () => { let submits = jest.fn() diff --git a/packages/@headlessui-vue/src/components/radio-group/radio-group.ts b/packages/@headlessui-vue/src/components/radio-group/radio-group.ts index 00aeb472ae..c4f28f936b 100644 --- a/packages/@headlessui-vue/src/components/radio-group/radio-group.ts +++ b/packages/@headlessui-vue/src/components/radio-group/radio-group.ts @@ -264,6 +264,7 @@ export let RadioGroup = defineComponent({ hidden: true, readOnly: true, form, + disabled, name, value, }) diff --git a/packages/@headlessui-vue/src/components/switch/switch.test.tsx b/packages/@headlessui-vue/src/components/switch/switch.test.tsx index e6f6e7092f..d326ebfbbb 100644 --- a/packages/@headlessui-vue/src/components/switch/switch.test.tsx +++ b/packages/@headlessui-vue/src/components/switch/switch.test.tsx @@ -929,4 +929,39 @@ describe('Form compatibility', () => { // Verify that the form has been submitted expect(submits).lastCalledWith([['fruit', 'apple']]) }) + + it('should not submit the data if the Switch is disabled', async () => { + let submits = jest.fn() + + renderTemplate({ + template: html` +
+ + + + Apple + + +
+ `, + setup: () => { + let checked = ref(true) + return { + checked, + handleSubmit(event: SubmitEvent) { + event.preventDefault() + submits([...new FormData(event.currentTarget as HTMLFormElement).entries()]) + }, + } + }, + }) + + // Submit the form + await click(getByText('Submit')) + + // Verify that the form has been submitted + expect(submits).toHaveBeenLastCalledWith([ + ['foo', 'bar'], // The only available field + ]) + }) }) diff --git a/packages/@headlessui-vue/src/components/switch/switch.ts b/packages/@headlessui-vue/src/components/switch/switch.ts index abfb677838..66a77f421f 100644 --- a/packages/@headlessui-vue/src/components/switch/switch.ts +++ b/packages/@headlessui-vue/src/components/switch/switch.ts @@ -78,6 +78,7 @@ export let Switch = defineComponent({ name: { type: String, optional: true }, value: { type: String, optional: true }, id: { type: String, default: null }, + disabled: { type: Boolean, default: false }, tabIndex: { type: Number, default: 0 }, }, inheritAttrs: false, @@ -174,6 +175,7 @@ export let Switch = defineComponent({ readOnly: true, checked: checked.value, form, + disabled: theirProps.disabled, name, value, })