diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index 14cc2f7..1035c32 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `hidden` attribute to internal `` component when the `Features.Hidden` feature is used ([#2955](https://github.com/tailwindlabs/headlessui/pull/2955)) - Attempt form submission when pressing `Enter` on `Checkbox` component ([#2962](https://github.com/tailwindlabs/headlessui/pull/2962)) - 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)) ### Changed diff --git a/packages/@headlessui-react/src/components/checkbox/checkbox.tsx b/packages/@headlessui-react/src/components/checkbox/checkbox.tsx index aaa7ad2..c67e965 100644 --- a/packages/@headlessui-react/src/components/checkbox/checkbox.tsx +++ b/packages/@headlessui-react/src/components/checkbox/checkbox.tsx @@ -173,7 +173,12 @@ function CheckboxFn {name != null && ( - + )} {render({ ourProps, diff --git a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx index 525f345..562a33e 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.test.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.test.tsx @@ -5747,6 +5747,48 @@ describe('Form compatibility', () => { expect(submits).toHaveBeenLastCalledWith([['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 8bfd8a7..7971322 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -907,6 +907,7 @@ function ComboboxFn {name != null && ( { expect(submits).toHaveBeenLastCalledWith([['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 63f26e3..129dd74 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -670,7 +670,12 @@ function ListboxFn< })} > {name != null && value != null && ( - + )} {render({ ourProps, 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 85f0fd4..436b2e3 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 @@ -1539,6 +1539,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 2ab3a7a..2fabaa2 100644 --- a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx +++ b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx @@ -314,6 +314,7 @@ function RadioGroupFn {name != null && ( { // Verify that the form has been submitted expect(submits).toHaveBeenLastCalledWith([['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 76a4ab8..eedf7a9 100644 --- a/packages/@headlessui-react/src/components/switch/switch.tsx +++ b/packages/@headlessui-react/src/components/switch/switch.tsx @@ -237,7 +237,12 @@ function SwitchFn( return ( <> {name != null && ( - + )} {render({ ourProps, theirProps, slot, defaultTag: DEFAULT_SWITCH_TAG, name: 'Switch' })} diff --git a/packages/@headlessui-react/src/internal/form-fields.tsx b/packages/@headlessui-react/src/internal/form-fields.tsx index 3592c57..ab94d98 100644 --- a/packages/@headlessui-react/src/internal/form-fields.tsx +++ b/packages/@headlessui-react/src/internal/form-fields.tsx @@ -31,10 +31,12 @@ export function HoistFormFields({ children }: React.PropsWithChildren<{}>) { export function FormFields({ data, form: formId, + disabled, onReset, }: { data: Record form?: string + disabled?: boolean onReset?: (e: Event) => void }) { let [form, setForm] = useState(null) @@ -61,6 +63,7 @@ export function FormFields({ hidden: true, readOnly: true, form: formId, + disabled, name, value, })} diff --git a/packages/@headlessui-react/src/test-utils/scenarios.tsx b/packages/@headlessui-react/src/test-utils/scenarios.tsx index 31ee14f..c576a43 100644 --- a/packages/@headlessui-react/src/test-utils/scenarios.tsx +++ b/packages/@headlessui-react/src/test-utils/scenarios.tsx @@ -202,6 +202,35 @@ export function commonFormScenarios( expect(formDataMock.mock.calls[0][0].has('foo')).toBe(true) }) + it('should not submit the data if the control is disabled', async () => { + let submits = jest.fn() + + function Example() { + return ( +
{ + event.preventDefault() + submits([...new FormData(event.currentTarget).entries()]) + }} + > + + + + + ) + } + + render() + + // Submit the form + await click(screen.getByText('Submit')) + + // Verify that the form has been submitted + expect(submits).toHaveBeenLastCalledWith([ + ['foo', 'bar'], // The only available field + ]) + }) + it( 'should reset the control when the form is reset', suppressConsoleLogs(async () => { diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md index 25b14de..044bbd1 100644 --- a/packages/@headlessui-vue/CHANGELOG.md +++ b/packages/@headlessui-vue/CHANGELOG.md @@ -19,6 +19,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 a3b638f..3222bf1 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 238c92e..e3e4cc3 100644 --- a/packages/@headlessui-vue/src/components/combobox/combobox.ts +++ b/packages/@headlessui-vue/src/components/combobox/combobox.ts @@ -735,6 +735,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 5beb32b..b09c3c3 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 f49c438..20b1ce7 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 f491ae1..42d671a 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 9bf8417..cd63c39 100644 --- a/packages/@headlessui-vue/src/components/radio-group/radio-group.ts +++ b/packages/@headlessui-vue/src/components/radio-group/radio-group.ts @@ -262,6 +262,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 c4008b4..ce39576 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 4746dee..239e0df 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: () => `headlessui-switch-${useId()}` }, + disabled: { type: Boolean, default: false }, tabIndex: { type: Number, default: 0 }, }, inheritAttrs: false, @@ -172,6 +173,7 @@ export let Switch = defineComponent({ readOnly: true, checked: checked.value, form, + disabled: theirProps.disabled, name, value, })