From 2aa95f28c6755b4fb2c67953ee2928f42e2db89c Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 8 Apr 2021 18:13:28 +0200 Subject: [PATCH] ensure we forward attributes when using providers --- .../src/components/dialog/dialog.ts | 6 +- .../radio-group/radio-group.test.ts | 49 +++++++++++++++ .../src/components/radio-group/radio-group.ts | 8 +++ .../src/components/switch/switch.test.tsx | 60 +++++++++++++++++++ .../src/components/switch/switch.ts | 16 ++++- 5 files changed, 135 insertions(+), 4 deletions(-) diff --git a/packages/@headlessui-vue/src/components/dialog/dialog.ts b/packages/@headlessui-vue/src/components/dialog/dialog.ts index 60e382b..c0d64f7 100644 --- a/packages/@headlessui-vue/src/components/dialog/dialog.ts +++ b/packages/@headlessui-vue/src/components/dialog/dialog.ts @@ -75,15 +75,15 @@ export let Dialog = defineComponent({ }, render() { let propsWeControl = { + // Manually passthrough the attributes, because Vue can't automatically pass + // it to the underlying div because of all the wrapper components below. + ...this.$attrs, ref: 'el', id: this.id, role: 'dialog', 'aria-modal': this.dialogState === DialogStates.Open ? true : undefined, 'aria-labelledby': this.titleId, 'aria-describedby': this.describedby, - // Manually passthrough the attributes, because Vue can't automatically pass - // it to the underlying div because of all the wrapper components below. - ...this.$attrs, } let { open, onClose, initialFocus, ...passThroughProps } = this.$props let containers = this.containers 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 9f2a64c..29a6a30 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 @@ -244,6 +244,55 @@ describe('Rendering', () => { `Dine in - ${JSON.stringify({ checked: false, active: false })}` ) }) + + it('should be possible to put classes on a RadioGroup', async () => { + renderTemplate({ + template: html` + + Pizza Delivery + {{option.label}} + + `, + setup() { + let deliveryMethod = ref(undefined) + let options = ref([{ id: 1, label: 'Pickup' }]) + return { deliveryMethod, options } + }, + }) + + await new Promise(nextTick) + + expect(document.querySelector('[id^="headlessui-radiogroup-"]')).toHaveClass('abc') + }) + + it('should be possible to put classes on a RadioGroupOption', async () => { + renderTemplate({ + template: html` + + Pizza Delivery + {{option.label}} + + `, + setup() { + let deliveryMethod = ref(undefined) + let options = ref([{ id: 1, label: 'Pickup' }]) + return { deliveryMethod, options } + }, + }) + + await new Promise(nextTick) + + expect(getByText('Pickup')).toHaveClass('abc') + }) }) describe('Keyboard interactions', () => { 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 52e013d..f867577 100644 --- a/packages/@headlessui-vue/src/components/radio-group/radio-group.ts +++ b/packages/@headlessui-vue/src/components/radio-group/radio-group.ts @@ -60,6 +60,7 @@ function useRadioGroupContext(component: string) { export let RadioGroup = defineComponent({ name: 'RadioGroup', emits: ['update:modelValue'], + inheritAttrs: false, // Manually handling this props: { as: { type: [Object, String], default: 'div' }, disabled: { type: [Boolean], default: false }, @@ -69,6 +70,9 @@ export let RadioGroup = defineComponent({ let { modelValue, disabled, ...passThroughProps } = this.$props let propsWeControl = { + // Manually passthrough the attributes, because Vue can't automatically pass + // it to the underlying div because of all the wrapper components below. + ...this.$attrs, ref: 'el', id: this.id, role: 'radiogroup', @@ -225,6 +229,7 @@ enum OptionState { export let RadioGroupOption = defineComponent({ name: 'RadioGroupOption', + inheritAttrs: false, // Manually handling this props: { as: { type: [Object, String], default: 'div' }, value: { type: [Object, String] }, @@ -246,6 +251,9 @@ export let RadioGroupOption = defineComponent({ let slot = { checked: this.checked, active: Boolean(this.state & OptionState.Active) } let propsWeControl = { + // Manually passthrough the attributes, because Vue can't automatically pass + // it to the underlying div because of all the wrapper components below. + ...this.$attrs, id: this.id, ref: 'el', role: 'radio', diff --git a/packages/@headlessui-vue/src/components/switch/switch.test.tsx b/packages/@headlessui-vue/src/components/switch/switch.test.tsx index 910c0df..c7198ad 100644 --- a/packages/@headlessui-vue/src/components/switch/switch.test.tsx +++ b/packages/@headlessui-vue/src/components/switch/switch.test.tsx @@ -8,6 +8,7 @@ import { getSwitch, assertActiveElement, getSwitchLabel, + getByText, } from '../../test-utils/accessibility-assertions' import { press, click, Keys } from '../../test-utils/interactions' import { html } from '../../test-utils/html' @@ -215,6 +216,65 @@ describe('Render composition', () => { description: 'This is an important feature', }) }) + + it('should be possible to put classes on a SwitchLabel', async () => { + renderTemplate({ + template: html` + + Label A + + + `, + setup: () => ({ checked: ref(false) }), + }) + + await new Promise(requestAnimationFrame) + + assertSwitch({ + state: SwitchState.Off, + label: 'Label A', + }) + + expect(getByText('Label A')).toHaveClass('abc') + }) + + it('should be possible to put classes on a SwitchDescription', async () => { + renderTemplate({ + template: html` + + Description A + + + `, + setup: () => ({ checked: ref(false) }), + }) + + await new Promise(requestAnimationFrame) + + assertSwitch({ + state: SwitchState.Off, + description: 'Description A', + }) + + expect(getByText('Description A')).toHaveClass('abc') + }) + + it('should be possible to put classes on a SwitchGroup', async () => { + renderTemplate({ + template: html` + + + + `, + setup: () => ({ checked: ref(false) }), + }) + + await new Promise(requestAnimationFrame) + + assertSwitch({ state: SwitchState.Off }) + + expect(document.getElementById('group')).toHaveClass('abc') + }) }) describe('Keyboard interactions', () => { diff --git a/packages/@headlessui-vue/src/components/switch/switch.ts b/packages/@headlessui-vue/src/components/switch/switch.ts index c46a907..ba29216 100644 --- a/packages/@headlessui-vue/src/components/switch/switch.ts +++ b/packages/@headlessui-vue/src/components/switch/switch.ts @@ -30,6 +30,7 @@ let GroupContext = Symbol('GroupContext') as InjectionKey export let SwitchGroup = defineComponent({ name: 'SwitchGroup', + inheritAttrs: false, // Manually handling this props: { as: { type: [Object, String], default: 'template' }, }, @@ -56,7 +57,20 @@ export let SwitchGroup = defineComponent({ }, }, }, - () => [render({ props, slot: {}, slots, attrs, name: 'SwitchGroup' })] + () => [ + render({ + props: { + // Manually passthrough the attributes, because Vue can't automatically pass + // it to the underlying div because of all the wrapper components below. + ...attrs, + ...props, + }, + slot: {}, + slots, + attrs, + name: 'SwitchGroup', + }), + ] ), ]) },