Skip to content

Commit 2aa95f2

Browse files
committed
ensure we forward attributes when using providers
1 parent a02c818 commit 2aa95f2

File tree

5 files changed

+135
-4
lines changed

5 files changed

+135
-4
lines changed

packages/@headlessui-vue/src/components/dialog/dialog.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,15 @@ export let Dialog = defineComponent({
7575
},
7676
render() {
7777
let propsWeControl = {
78+
// Manually passthrough the attributes, because Vue can't automatically pass
79+
// it to the underlying div because of all the wrapper components below.
80+
...this.$attrs,
7881
ref: 'el',
7982
id: this.id,
8083
role: 'dialog',
8184
'aria-modal': this.dialogState === DialogStates.Open ? true : undefined,
8285
'aria-labelledby': this.titleId,
8386
'aria-describedby': this.describedby,
84-
// Manually passthrough the attributes, because Vue can't automatically pass
85-
// it to the underlying div because of all the wrapper components below.
86-
...this.$attrs,
8787
}
8888
let { open, onClose, initialFocus, ...passThroughProps } = this.$props
8989
let containers = this.containers

packages/@headlessui-vue/src/components/radio-group/radio-group.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,55 @@ describe('Rendering', () => {
244244
`Dine in - ${JSON.stringify({ checked: false, active: false })}`
245245
)
246246
})
247+
248+
it('should be possible to put classes on a RadioGroup', async () => {
249+
renderTemplate({
250+
template: html`
251+
<RadioGroup v-model="deliveryMethod" as="div" class="abc">
252+
<RadioGroupLabel>Pizza Delivery</RadioGroupLabel>
253+
<RadioGroupOption v-for="option in options" key="option.id" :value="option" v-slot="data"
254+
>{{option.label}}</RadioGroupOption
255+
>
256+
</RadioGroup>
257+
`,
258+
setup() {
259+
let deliveryMethod = ref(undefined)
260+
let options = ref([{ id: 1, label: 'Pickup' }])
261+
return { deliveryMethod, options }
262+
},
263+
})
264+
265+
await new Promise<void>(nextTick)
266+
267+
expect(document.querySelector('[id^="headlessui-radiogroup-"]')).toHaveClass('abc')
268+
})
269+
270+
it('should be possible to put classes on a RadioGroupOption', async () => {
271+
renderTemplate({
272+
template: html`
273+
<RadioGroup v-model="deliveryMethod">
274+
<RadioGroupLabel>Pizza Delivery</RadioGroupLabel>
275+
<RadioGroupOption
276+
v-for="option in options"
277+
key="option.id"
278+
:value="option"
279+
v-slot="data"
280+
class="abc"
281+
>{{option.label}}</RadioGroupOption
282+
>
283+
</RadioGroup>
284+
`,
285+
setup() {
286+
let deliveryMethod = ref(undefined)
287+
let options = ref([{ id: 1, label: 'Pickup' }])
288+
return { deliveryMethod, options }
289+
},
290+
})
291+
292+
await new Promise<void>(nextTick)
293+
294+
expect(getByText('Pickup')).toHaveClass('abc')
295+
})
247296
})
248297

249298
describe('Keyboard interactions', () => {

packages/@headlessui-vue/src/components/radio-group/radio-group.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ function useRadioGroupContext(component: string) {
6060
export let RadioGroup = defineComponent({
6161
name: 'RadioGroup',
6262
emits: ['update:modelValue'],
63+
inheritAttrs: false, // Manually handling this
6364
props: {
6465
as: { type: [Object, String], default: 'div' },
6566
disabled: { type: [Boolean], default: false },
@@ -69,6 +70,9 @@ export let RadioGroup = defineComponent({
6970
let { modelValue, disabled, ...passThroughProps } = this.$props
7071

7172
let propsWeControl = {
73+
// Manually passthrough the attributes, because Vue can't automatically pass
74+
// it to the underlying div because of all the wrapper components below.
75+
...this.$attrs,
7276
ref: 'el',
7377
id: this.id,
7478
role: 'radiogroup',
@@ -225,6 +229,7 @@ enum OptionState {
225229

226230
export let RadioGroupOption = defineComponent({
227231
name: 'RadioGroupOption',
232+
inheritAttrs: false, // Manually handling this
228233
props: {
229234
as: { type: [Object, String], default: 'div' },
230235
value: { type: [Object, String] },
@@ -246,6 +251,9 @@ export let RadioGroupOption = defineComponent({
246251

247252
let slot = { checked: this.checked, active: Boolean(this.state & OptionState.Active) }
248253
let propsWeControl = {
254+
// Manually passthrough the attributes, because Vue can't automatically pass
255+
// it to the underlying div because of all the wrapper components below.
256+
...this.$attrs,
249257
id: this.id,
250258
ref: 'el',
251259
role: 'radio',

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getSwitch,
99
assertActiveElement,
1010
getSwitchLabel,
11+
getByText,
1112
} from '../../test-utils/accessibility-assertions'
1213
import { press, click, Keys } from '../../test-utils/interactions'
1314
import { html } from '../../test-utils/html'
@@ -215,6 +216,65 @@ describe('Render composition', () => {
215216
description: 'This is an important feature',
216217
})
217218
})
219+
220+
it('should be possible to put classes on a SwitchLabel', async () => {
221+
renderTemplate({
222+
template: html`
223+
<SwitchGroup>
224+
<SwitchLabel class="abc">Label A</SwitchLabel>
225+
<Switch v-model="checked" />
226+
</SwitchGroup>
227+
`,
228+
setup: () => ({ checked: ref(false) }),
229+
})
230+
231+
await new Promise(requestAnimationFrame)
232+
233+
assertSwitch({
234+
state: SwitchState.Off,
235+
label: 'Label A',
236+
})
237+
238+
expect(getByText('Label A')).toHaveClass('abc')
239+
})
240+
241+
it('should be possible to put classes on a SwitchDescription', async () => {
242+
renderTemplate({
243+
template: html`
244+
<SwitchGroup>
245+
<SwitchDescription class="abc">Description A</SwitchDescription>
246+
<Switch v-model="checked" />
247+
</SwitchGroup>
248+
`,
249+
setup: () => ({ checked: ref(false) }),
250+
})
251+
252+
await new Promise(requestAnimationFrame)
253+
254+
assertSwitch({
255+
state: SwitchState.Off,
256+
description: 'Description A',
257+
})
258+
259+
expect(getByText('Description A')).toHaveClass('abc')
260+
})
261+
262+
it('should be possible to put classes on a SwitchGroup', async () => {
263+
renderTemplate({
264+
template: html`
265+
<SwitchGroup as="div" class="abc" id="group">
266+
<Switch v-model="checked" />
267+
</SwitchGroup>
268+
`,
269+
setup: () => ({ checked: ref(false) }),
270+
})
271+
272+
await new Promise(requestAnimationFrame)
273+
274+
assertSwitch({ state: SwitchState.Off })
275+
276+
expect(document.getElementById('group')).toHaveClass('abc')
277+
})
218278
})
219279

220280
describe('Keyboard interactions', () => {

packages/@headlessui-vue/src/components/switch/switch.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ let GroupContext = Symbol('GroupContext') as InjectionKey<StateDefinition>
3030

3131
export let SwitchGroup = defineComponent({
3232
name: 'SwitchGroup',
33+
inheritAttrs: false, // Manually handling this
3334
props: {
3435
as: { type: [Object, String], default: 'template' },
3536
},
@@ -56,7 +57,20 @@ export let SwitchGroup = defineComponent({
5657
},
5758
},
5859
},
59-
() => [render({ props, slot: {}, slots, attrs, name: 'SwitchGroup' })]
60+
() => [
61+
render({
62+
props: {
63+
// Manually passthrough the attributes, because Vue can't automatically pass
64+
// it to the underlying div because of all the wrapper components below.
65+
...attrs,
66+
...props,
67+
},
68+
slot: {},
69+
slots,
70+
attrs,
71+
name: 'SwitchGroup',
72+
}),
73+
]
6074
),
6175
])
6276
},

0 commit comments

Comments
 (0)