skip the Provider component and simplify context (#315)
I was on a walk, and I realised that in Vue you can just call provide(Symbol, context), which means that a hook like `useLabels` can just provide context... This simplifies a lot!
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { defineComponent, h, nextTick } from 'vue'
|
||||
import { defineComponent, h, nextTick, ref } from 'vue'
|
||||
import prettier from 'prettier'
|
||||
|
||||
import { render } from '../../test-utils/vue-testing-library'
|
||||
import { Description, useDescriptions } from './description'
|
||||
|
||||
import { html } from '../../test-utils/html'
|
||||
import { click } from '../../test-utils/interactions'
|
||||
import { getByText } from '../../test-utils/accessibility-assertions'
|
||||
|
||||
function format(input: Element | string) {
|
||||
let contents = (typeof input === 'string' ? input : (input as HTMLElement).outerHTML).trim()
|
||||
@@ -36,18 +38,14 @@ function renderTemplate(input: string | Partial<Parameters<typeof defineComponen
|
||||
)
|
||||
}
|
||||
|
||||
it('should be possible to use a DescriptionProvider without using a Description', async () => {
|
||||
it('should be possible to use useDescriptions without using a Description', async () => {
|
||||
let { container } = renderTemplate({
|
||||
render() {
|
||||
return h('div', [
|
||||
h(this.DescriptionProvider, () => [
|
||||
h('div', { 'aria-describedby': this.describedby }, ['No description']),
|
||||
]),
|
||||
])
|
||||
return h('div', [h('div', { 'aria-describedby': this.describedby }, ['No description'])])
|
||||
},
|
||||
setup() {
|
||||
let [describedby, DescriptionProvider] = useDescriptions()
|
||||
return { describedby, DescriptionProvider }
|
||||
let describedby = useDescriptions()
|
||||
return { describedby }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -60,21 +58,19 @@ it('should be possible to use a DescriptionProvider without using a Description'
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to use a DescriptionProvider and a single Description, and have them linked', async () => {
|
||||
it('should be possible to use useDescriptions and a single Description, and have them linked', async () => {
|
||||
let { container } = renderTemplate({
|
||||
render() {
|
||||
return h('div', [
|
||||
h(this.DescriptionProvider, () => [
|
||||
h('div', { 'aria-describedby': this.describedby }, [
|
||||
h(Description, () => 'I am a description'),
|
||||
h('span', 'Contents'),
|
||||
]),
|
||||
h('div', { 'aria-describedby': this.describedby }, [
|
||||
h(Description, () => 'I am a description'),
|
||||
h('span', 'Contents'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
setup() {
|
||||
let [describedby, DescriptionProvider] = useDescriptions()
|
||||
return { describedby, DescriptionProvider }
|
||||
let describedby = useDescriptions()
|
||||
return { describedby }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -94,22 +90,20 @@ it('should be possible to use a DescriptionProvider and a single Description, an
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to use a DescriptionProvider and multiple Description components, and have them linked', async () => {
|
||||
it('should be possible to use useDescriptions and multiple Description components, and have them linked', async () => {
|
||||
let { container } = renderTemplate({
|
||||
render() {
|
||||
return h('div', [
|
||||
h(this.DescriptionProvider, () => [
|
||||
h('div', { 'aria-describedby': this.describedby }, [
|
||||
h(Description, () => 'I am a description'),
|
||||
h('span', 'Contents'),
|
||||
h(Description, () => 'I am also a description'),
|
||||
]),
|
||||
h('div', { 'aria-describedby': this.describedby }, [
|
||||
h(Description, () => 'I am a description'),
|
||||
h('span', 'Contents'),
|
||||
h(Description, () => 'I am also a description'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
setup() {
|
||||
let [describedby, DescriptionProvider] = useDescriptions()
|
||||
return { describedby, DescriptionProvider }
|
||||
let describedby = useDescriptions()
|
||||
return { describedby }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -131,3 +125,47 @@ it('should be possible to use a DescriptionProvider and multiple Description com
|
||||
`)
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to update a prop from the parent and it should reflect in the Description component', async () => {
|
||||
let { container } = renderTemplate({
|
||||
render() {
|
||||
return h('div', [
|
||||
h('div', { 'aria-describedby': this.describedby }, [
|
||||
h(Description, () => 'I am a description'),
|
||||
h('button', { onClick: () => this.count++ }, '+1'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
setup() {
|
||||
let count = ref(0)
|
||||
let describedby = useDescriptions({ props: { 'data-count': count } })
|
||||
return { count, describedby }
|
||||
},
|
||||
})
|
||||
|
||||
await new Promise<void>(nextTick)
|
||||
|
||||
expect(format(container.firstElementChild)).toEqual(
|
||||
format(html`
|
||||
<div aria-describedby="headlessui-description-1">
|
||||
<p data-count="0" id="headlessui-description-1">
|
||||
I am a description
|
||||
</p>
|
||||
<button>+1</button>
|
||||
</div>
|
||||
`)
|
||||
)
|
||||
|
||||
await click(getByText('+1'))
|
||||
|
||||
expect(format(container.firstElementChild)).toEqual(
|
||||
format(html`
|
||||
<div aria-describedby="headlessui-description-1">
|
||||
<p data-count="1" id="headlessui-description-1">
|
||||
I am a description
|
||||
</p>
|
||||
<button>+1</button>
|
||||
</div>
|
||||
`)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -6,11 +6,11 @@ import {
|
||||
onUnmounted,
|
||||
provide,
|
||||
ref,
|
||||
unref,
|
||||
|
||||
// Types
|
||||
ComputedRef,
|
||||
InjectionKey,
|
||||
Ref,
|
||||
} from 'vue'
|
||||
|
||||
import { useId } from '../../hooks/use-id'
|
||||
@@ -20,9 +20,9 @@ import { render } from '../../utils/render'
|
||||
|
||||
let DescriptionContext = Symbol('DescriptionContext') as InjectionKey<{
|
||||
register(value: string): () => void
|
||||
slot: Ref<Record<string, any>>
|
||||
name: Ref<string>
|
||||
props: Ref<Record<string, any>>
|
||||
slot: Record<string, any>
|
||||
name: string
|
||||
props: Record<string, any>
|
||||
}>
|
||||
|
||||
function useDescriptionContext() {
|
||||
@@ -33,42 +33,33 @@ function useDescriptionContext() {
|
||||
return context
|
||||
}
|
||||
|
||||
export function useDescriptions(): [
|
||||
ComputedRef<string | undefined>,
|
||||
ReturnType<typeof defineComponent>
|
||||
] {
|
||||
export function useDescriptions({
|
||||
slot = {},
|
||||
name = 'Description',
|
||||
props = {},
|
||||
}: {
|
||||
slot?: Record<string, unknown>
|
||||
name?: string
|
||||
props?: Record<string, unknown>
|
||||
} = {}): ComputedRef<string | undefined> {
|
||||
let descriptionIds = ref<string[]>([])
|
||||
|
||||
return [
|
||||
// The actual id's as string or undefined.
|
||||
computed(() => (descriptionIds.value.length > 0 ? descriptionIds.value.join(' ') : undefined)),
|
||||
function register(value: string) {
|
||||
descriptionIds.value.push(value)
|
||||
|
||||
// The provider component
|
||||
defineComponent({
|
||||
name: 'DescriptionProvider',
|
||||
props: ['slot', 'name', 'props'],
|
||||
setup(props, { slots }) {
|
||||
function register(value: string) {
|
||||
descriptionIds.value.push(value)
|
||||
return () => {
|
||||
let idx = descriptionIds.value.indexOf(value)
|
||||
if (idx === -1) return
|
||||
descriptionIds.value.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
let idx = descriptionIds.value.indexOf(value)
|
||||
if (idx === -1) return
|
||||
descriptionIds.value.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
provide(DescriptionContext, { register, slot, name, props })
|
||||
|
||||
provide(DescriptionContext, {
|
||||
register,
|
||||
slot: computed(() => props.slot),
|
||||
name: computed(() => props.name),
|
||||
props: computed(() => props.props),
|
||||
})
|
||||
|
||||
return () => slots.default!()
|
||||
},
|
||||
}),
|
||||
]
|
||||
// The actual id's as string or undefined.
|
||||
return computed(() =>
|
||||
descriptionIds.value.length > 0 ? descriptionIds.value.join(' ') : undefined
|
||||
)
|
||||
}
|
||||
|
||||
// ---
|
||||
@@ -79,23 +70,30 @@ export let Description = defineComponent({
|
||||
as: { type: [Object, String], default: 'p' },
|
||||
},
|
||||
render() {
|
||||
let { name = 'Description', slot = {}, props = {} } = this.context
|
||||
let passThroughProps = this.$props
|
||||
let propsWeControl = { ...this.props, id: this.id }
|
||||
let propsWeControl = {
|
||||
...Object.entries(props).reduce(
|
||||
(acc, [key, value]) => Object.assign(acc, { [key]: unref(value) }),
|
||||
{}
|
||||
),
|
||||
id: this.id,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.props, ...passThroughProps, ...propsWeControl },
|
||||
slot: this.slot || {},
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: this.name || 'Description',
|
||||
name,
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
let { register, slot, name, props } = useDescriptionContext()
|
||||
let context = useDescriptionContext()
|
||||
let id = `headlessui-description-${useId()}`
|
||||
|
||||
onMounted(() => onUnmounted(register(id)))
|
||||
onMounted(() => onUnmounted(context.register(id)))
|
||||
|
||||
return { id, slot, name, props }
|
||||
return { id, context }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -109,17 +109,15 @@ export let Dialog = defineComponent({
|
||||
h(Portal, {}, () => [
|
||||
h(PortalGroup, { target: this.dialogRef }, () => [
|
||||
h(ForcePortalRoot, { force: false }, () => [
|
||||
h(this.DescriptionProvider, { slot }, () => [
|
||||
render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
visible: open,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
name: 'Dialog',
|
||||
}),
|
||||
]),
|
||||
render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
visible: open,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
name: 'Dialog',
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
@@ -182,7 +180,10 @@ export let Dialog = defineComponent({
|
||||
|
||||
useFocusTrap(containers, enabled, focusTrapOptions)
|
||||
useInertOthers(internalDialogRef, enabled)
|
||||
let [describedby, DescriptionProvider] = useDescriptions()
|
||||
let describedby = useDescriptions({
|
||||
name: 'DialogDescription',
|
||||
slot: { open: props.open },
|
||||
})
|
||||
|
||||
let titleId = ref<StateDefinition['titleId']['value']>(null)
|
||||
|
||||
@@ -271,7 +272,6 @@ export let Dialog = defineComponent({
|
||||
dialogState,
|
||||
titleId,
|
||||
describedby,
|
||||
DescriptionProvider,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { defineComponent, h, nextTick } from 'vue'
|
||||
import { defineComponent, h, nextTick, ref } from 'vue'
|
||||
import prettier from 'prettier'
|
||||
|
||||
import { render } from '../../test-utils/vue-testing-library'
|
||||
import { Label, useLabels } from './label'
|
||||
|
||||
import { html } from '../../test-utils/html'
|
||||
import { click } from '../../test-utils/interactions'
|
||||
import { getByText } from '../../test-utils/accessibility-assertions'
|
||||
|
||||
function format(input: Element | string) {
|
||||
let contents = (typeof input === 'string' ? input : (input as HTMLElement).outerHTML).trim()
|
||||
@@ -36,18 +38,14 @@ function renderTemplate(input: string | Partial<Parameters<typeof defineComponen
|
||||
)
|
||||
}
|
||||
|
||||
it('should be possible to use a LabelProvider without using a Label', async () => {
|
||||
it('should be possible to use useLabels without using a Label', async () => {
|
||||
let { container } = renderTemplate({
|
||||
render() {
|
||||
return h('div', [
|
||||
h(this.LabelProvider, () => [
|
||||
h('div', { 'aria-labelledby': this.labelledby }, ['No label']),
|
||||
]),
|
||||
])
|
||||
return h('div', [h('div', { 'aria-labelledby': this.labelledby }, ['No label'])])
|
||||
},
|
||||
setup() {
|
||||
let [labelledby, LabelProvider] = useLabels()
|
||||
return { labelledby, LabelProvider }
|
||||
let labelledby = useLabels()
|
||||
return { labelledby }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -60,21 +58,19 @@ it('should be possible to use a LabelProvider without using a Label', async () =
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to use a LabelProvider and a single Label, and have them linked', async () => {
|
||||
it('should be possible to use useLabels and a single Label, and have them linked', async () => {
|
||||
let { container } = renderTemplate({
|
||||
render() {
|
||||
return h('div', [
|
||||
h(this.LabelProvider, () => [
|
||||
h('div', { 'aria-labelledby': this.labelledby }, [
|
||||
h(Label, () => 'I am a label'),
|
||||
h('span', 'Contents'),
|
||||
]),
|
||||
h('div', { 'aria-labelledby': this.labelledby }, [
|
||||
h(Label, () => 'I am a label'),
|
||||
h('span', 'Contents'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
setup() {
|
||||
let [labelledby, LabelProvider] = useLabels()
|
||||
return { labelledby, LabelProvider }
|
||||
let labelledby = useLabels()
|
||||
return { labelledby }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -94,22 +90,20 @@ it('should be possible to use a LabelProvider and a single Label, and have them
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to use a LabelProvider and multiple Label components, and have them linked', async () => {
|
||||
it('should be possible to use useLabels and multiple Label components, and have them linked', async () => {
|
||||
let { container } = renderTemplate({
|
||||
render() {
|
||||
return h('div', [
|
||||
h(this.LabelProvider, () => [
|
||||
h('div', { 'aria-labelledby': this.labelledby }, [
|
||||
h(Label, () => 'I am a label'),
|
||||
h('span', 'Contents'),
|
||||
h(Label, () => 'I am also a label'),
|
||||
]),
|
||||
h('div', { 'aria-labelledby': this.labelledby }, [
|
||||
h(Label, () => 'I am a label'),
|
||||
h('span', 'Contents'),
|
||||
h(Label, () => 'I am also a label'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
setup() {
|
||||
let [labelledby, LabelProvider] = useLabels()
|
||||
return { labelledby, LabelProvider }
|
||||
let labelledby = useLabels()
|
||||
return { labelledby }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -131,3 +125,47 @@ it('should be possible to use a LabelProvider and multiple Label components, and
|
||||
`)
|
||||
)
|
||||
})
|
||||
|
||||
it('should be possible to update a prop from the parent and it should reflect in the Label component', async () => {
|
||||
let { container } = renderTemplate({
|
||||
render() {
|
||||
return h('div', [
|
||||
h('div', { 'aria-labelledby': this.labelledby }, [
|
||||
h(Label, () => 'I am a label'),
|
||||
h('button', { onClick: () => this.count++ }, '+1'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
setup() {
|
||||
let count = ref(0)
|
||||
let labelledby = useLabels({ props: { 'data-count': count } })
|
||||
return { count, labelledby }
|
||||
},
|
||||
})
|
||||
|
||||
await new Promise<void>(nextTick)
|
||||
|
||||
expect(format(container.firstElementChild)).toEqual(
|
||||
format(html`
|
||||
<div aria-labelledby="headlessui-label-1">
|
||||
<label data-count="0" id="headlessui-label-1">
|
||||
I am a label
|
||||
</label>
|
||||
<button>+1</button>
|
||||
</div>
|
||||
`)
|
||||
)
|
||||
|
||||
await click(getByText('+1'))
|
||||
|
||||
expect(format(container.firstElementChild)).toEqual(
|
||||
format(html`
|
||||
<div aria-labelledby="headlessui-label-1">
|
||||
<label data-count="1" id="headlessui-label-1">
|
||||
I am a label
|
||||
</label>
|
||||
<button>+1</button>
|
||||
</div>
|
||||
`)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -6,11 +6,11 @@ import {
|
||||
onUnmounted,
|
||||
provide,
|
||||
ref,
|
||||
unref,
|
||||
|
||||
// Types
|
||||
ComputedRef,
|
||||
InjectionKey,
|
||||
Ref,
|
||||
} from 'vue'
|
||||
|
||||
import { useId } from '../../hooks/use-id'
|
||||
@@ -20,9 +20,9 @@ import { render } from '../../utils/render'
|
||||
|
||||
let LabelContext = Symbol('LabelContext') as InjectionKey<{
|
||||
register(value: string): () => void
|
||||
slot: Ref<Record<string, unknown>>
|
||||
name: Ref<string>
|
||||
props: Ref<Record<string, unknown>>
|
||||
slot: Record<string, unknown>
|
||||
name: string
|
||||
props: Record<string, unknown>
|
||||
}>
|
||||
|
||||
function useLabelContext() {
|
||||
@@ -35,44 +35,30 @@ function useLabelContext() {
|
||||
return context
|
||||
}
|
||||
|
||||
export function useLabels(): [ComputedRef<string | undefined>, ReturnType<typeof defineComponent>] {
|
||||
export function useLabels({
|
||||
slot = {},
|
||||
name = 'Label',
|
||||
props = {},
|
||||
}: {
|
||||
slot?: Record<string, unknown>
|
||||
name?: string
|
||||
props?: Record<string, unknown>
|
||||
} = {}): ComputedRef<string | undefined> {
|
||||
let labelIds = ref<string[]>([])
|
||||
function register(value: string) {
|
||||
labelIds.value.push(value)
|
||||
|
||||
return [
|
||||
// The actual id's as string or undefined.
|
||||
computed(() => (labelIds.value.length > 0 ? labelIds.value.join(' ') : undefined)),
|
||||
return () => {
|
||||
let idx = labelIds.value.indexOf(value)
|
||||
if (idx === -1) return
|
||||
labelIds.value.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// The provider component
|
||||
// @ts-expect-error The DefineComponent of Vue is just too confusing
|
||||
defineComponent({
|
||||
name: 'LabelProvider',
|
||||
props: {
|
||||
slot: { type: Object, default: undefined },
|
||||
name: { type: String, default: undefined },
|
||||
props: { type: Object, default: undefined },
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
function register(value: string) {
|
||||
labelIds.value.push(value)
|
||||
provide(LabelContext, { register, slot, name, props })
|
||||
|
||||
return () => {
|
||||
let idx = labelIds.value.indexOf(value)
|
||||
if (idx === -1) return
|
||||
labelIds.value.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
provide(LabelContext, {
|
||||
register,
|
||||
slot: computed(() => props.slot),
|
||||
name: computed(() => props.name),
|
||||
props: computed(() => props.props),
|
||||
})
|
||||
|
||||
return () => slots.default!()
|
||||
},
|
||||
}),
|
||||
]
|
||||
// The actual id's as string or undefined.
|
||||
return computed(() => (labelIds.value.length > 0 ? labelIds.value.join(' ') : undefined))
|
||||
}
|
||||
|
||||
// ---
|
||||
@@ -84,8 +70,15 @@ export let Label = defineComponent({
|
||||
clickable: { type: [Boolean], default: false },
|
||||
},
|
||||
render() {
|
||||
let { name = 'Label', slot = {}, props = {} } = this.context
|
||||
let { clickable, ...passThroughProps } = this.$props
|
||||
let propsWeControl = { ...this.props, id: this.id }
|
||||
let propsWeControl = {
|
||||
...Object.entries(props).reduce(
|
||||
(acc, [key, value]) => Object.assign(acc, { [key]: unref(value) }),
|
||||
{}
|
||||
),
|
||||
id: this.id,
|
||||
}
|
||||
let allProps = { ...passThroughProps, ...propsWeControl }
|
||||
|
||||
// @ts-expect-error props are dynamic via context, some components will
|
||||
@@ -94,18 +87,18 @@ export let Label = defineComponent({
|
||||
|
||||
return render({
|
||||
props: allProps,
|
||||
slot: this.slot || {},
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: this.name || 'Label',
|
||||
name,
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
let { register, slot, name, props } = useLabelContext()
|
||||
let context = useLabelContext()
|
||||
let id = `headlessui-label-${useId()}`
|
||||
|
||||
onMounted(() => onUnmounted(register(id)))
|
||||
onMounted(() => onUnmounted(context.register(id)))
|
||||
|
||||
return { id, slot, name, props }
|
||||
return { id, context }
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
h,
|
||||
inject,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
@@ -60,7 +59,6 @@ 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 },
|
||||
@@ -70,9 +68,6 @@ 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',
|
||||
@@ -81,23 +76,19 @@ export let RadioGroup = defineComponent({
|
||||
onKeydown: this.handleKeyDown,
|
||||
}
|
||||
|
||||
return h(this.DescriptionProvider, () => [
|
||||
h(this.LabelProvider, () => [
|
||||
render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot: {},
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'RadioGroup',
|
||||
}),
|
||||
]),
|
||||
])
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot: {},
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'RadioGroup',
|
||||
})
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
let radioGroupRef = ref<HTMLElement | null>(null)
|
||||
let options = ref<StateDefinition['options']['value']>([])
|
||||
let [labelledby, LabelProvider] = useLabels()
|
||||
let [describedby, DescriptionProvider] = useDescriptions()
|
||||
let labelledby = useLabels({ name: 'RadioGroupLabel' })
|
||||
let describedby = useDescriptions({ name: 'RadioGroupDescription' })
|
||||
|
||||
let value = computed(() => props.modelValue)
|
||||
|
||||
@@ -214,8 +205,6 @@ export let RadioGroup = defineComponent({
|
||||
describedby,
|
||||
el: radioGroupRef,
|
||||
handleKeyDown,
|
||||
LabelProvider,
|
||||
DescriptionProvider,
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -229,7 +218,6 @@ enum OptionState {
|
||||
|
||||
export let RadioGroupOption = defineComponent({
|
||||
name: 'RadioGroupOption',
|
||||
inheritAttrs: false, // Manually handling this
|
||||
props: {
|
||||
as: { type: [Object, String], default: 'div' },
|
||||
value: { type: [Object, String] },
|
||||
@@ -251,9 +239,6 @@ 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',
|
||||
@@ -267,23 +252,19 @@ export let RadioGroupOption = defineComponent({
|
||||
onBlur: this.handleBlur,
|
||||
}
|
||||
|
||||
return h(this.DescriptionProvider, () => [
|
||||
h(this.LabelProvider, () => [
|
||||
render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'RadioGroupOption',
|
||||
}),
|
||||
]),
|
||||
])
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'RadioGroupOption',
|
||||
})
|
||||
},
|
||||
setup(props) {
|
||||
let api = useRadioGroupContext('RadioGroupOption')
|
||||
let id = `headlessui-radiogroup-option-${useId()}`
|
||||
let [labelledby, LabelProvider] = useLabels()
|
||||
let [describedby, DescriptionProvider] = useDescriptions()
|
||||
let labelledby = useLabels({ name: 'RadioGroupLabel' })
|
||||
let describedby = useDescriptions({ name: 'RadioGroupDescription' })
|
||||
|
||||
let optionRef = ref<HTMLElement | null>(null)
|
||||
let propsRef = computed(() => ({ value: props.value }))
|
||||
@@ -298,8 +279,6 @@ export let RadioGroupOption = defineComponent({
|
||||
labelledby,
|
||||
describedby,
|
||||
state,
|
||||
LabelProvider,
|
||||
DescriptionProvider,
|
||||
checked: computed(() => toRaw(api.value.value) === toRaw(props.value)),
|
||||
handleClick() {
|
||||
let value = props.value
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
inject,
|
||||
provide,
|
||||
ref,
|
||||
@@ -30,49 +29,28 @@ let GroupContext = Symbol('GroupContext') as InjectionKey<StateDefinition>
|
||||
|
||||
export let SwitchGroup = defineComponent({
|
||||
name: 'SwitchGroup',
|
||||
inheritAttrs: false, // Manually handling this
|
||||
props: {
|
||||
as: { type: [Object, String], default: 'template' },
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
let switchRef = ref<StateDefinition['switchRef']['value']>(null)
|
||||
let [labelledby, LabelProvider] = useLabels()
|
||||
let [describedby, DescriptionProvider] = useDescriptions()
|
||||
let labelledby = useLabels({
|
||||
name: 'SwitchLabel',
|
||||
props: {
|
||||
onClick() {
|
||||
if (!switchRef.value) return
|
||||
switchRef.value.click()
|
||||
switchRef.value.focus({ preventScroll: true })
|
||||
},
|
||||
},
|
||||
})
|
||||
let describedby = useDescriptions({ name: 'SwitchDescription' })
|
||||
|
||||
let api = { switchRef, labelledby, describedby }
|
||||
|
||||
provide(GroupContext, api)
|
||||
|
||||
return () =>
|
||||
h(DescriptionProvider, { name: 'SwitchDescription' }, () => [
|
||||
h(
|
||||
LabelProvider,
|
||||
{
|
||||
name: 'SwitchLabel',
|
||||
props: {
|
||||
onClick() {
|
||||
if (!switchRef.value) return
|
||||
switchRef.value.click()
|
||||
switchRef.value.focus({ preventScroll: true })
|
||||
},
|
||||
},
|
||||
},
|
||||
() => [
|
||||
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',
|
||||
}),
|
||||
]
|
||||
),
|
||||
])
|
||||
return () => render({ props, slot: {}, slots, attrs, name: 'SwitchGroup' })
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user