Update minimum Vue to 3.2 (#1072)
* Remove vercel json file * Don't use provide/inject outside of setup * Upgrade minimum vue version * Mark vue as an external * Update lockfile * WIP move render functions into setup * WIP * WIP * Use setup returning render fns for tests
This commit is contained in:
@@ -33,19 +33,19 @@
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm run build",
|
||||
"build": "../../scripts/build.sh",
|
||||
"watch": "../../scripts/watch.sh",
|
||||
"build": "../../scripts/build.sh --external:vue",
|
||||
"watch": "../../scripts/watch.sh --external:vue",
|
||||
"test": "../../scripts/test.sh",
|
||||
"lint": "../../scripts/lint.sh",
|
||||
"playground": "yarn workspace playground-vue dev",
|
||||
"clean": "rimraf ./dist"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
"vue": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/vue": "^5.8.2",
|
||||
"@vue/test-utils": "^2.0.0-rc.18",
|
||||
"vue": "^3.2.27"
|
||||
"vue": "^3.2.29"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,33 +256,29 @@ export let Combobox = defineComponent({
|
||||
export let ComboboxLabel = defineComponent({
|
||||
name: 'ComboboxLabel',
|
||||
props: { as: { type: [Object, String], default: 'label' } },
|
||||
render() {
|
||||
let api = useComboboxContext('ComboboxLabel')
|
||||
|
||||
let slot = {
|
||||
open: api.ComboboxState.value === ComboboxStates.Open,
|
||||
disabled: api.disabled.value,
|
||||
}
|
||||
let propsWeControl = { id: this.id, ref: 'el', onClick: this.handleClick }
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'ComboboxLabel',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useComboboxContext('ComboboxLabel')
|
||||
let id = `headlessui-combobox-label-${useId()}`
|
||||
|
||||
return {
|
||||
id,
|
||||
el: api.labelRef,
|
||||
handleClick() {
|
||||
dom(api.inputRef)?.focus({ preventScroll: true })
|
||||
},
|
||||
function handleClick() {
|
||||
dom(api.inputRef)?.focus({ preventScroll: true })
|
||||
}
|
||||
|
||||
return () => {
|
||||
let slot = {
|
||||
open: api.ComboboxState.value === ComboboxStates.Open,
|
||||
disabled: api.disabled.value,
|
||||
}
|
||||
|
||||
let propsWeControl = { id, ref: api.labelRef, onClick: handleClick }
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name: 'ComboboxLabel',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -294,40 +290,7 @@ export let ComboboxButton = defineComponent({
|
||||
props: {
|
||||
as: { type: [Object, String], default: 'button' },
|
||||
},
|
||||
render() {
|
||||
let api = useComboboxContext('ComboboxButton')
|
||||
|
||||
let slot = {
|
||||
open: api.ComboboxState.value === ComboboxStates.Open,
|
||||
disabled: api.disabled.value,
|
||||
}
|
||||
let propsWeControl = {
|
||||
ref: 'el',
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
tabindex: '-1',
|
||||
'aria-haspopup': true,
|
||||
'aria-controls': dom(api.optionsRef)?.id,
|
||||
'aria-expanded': api.disabled.value
|
||||
? undefined
|
||||
: api.ComboboxState.value === ComboboxStates.Open,
|
||||
'aria-labelledby': api.labelRef.value
|
||||
? [dom(api.labelRef)?.id, this.id].join(' ')
|
||||
: undefined,
|
||||
disabled: api.disabled.value === true ? true : undefined,
|
||||
onKeydown: this.handleKeydown,
|
||||
onClick: this.handleClick,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'ComboboxButton',
|
||||
})
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useComboboxContext('ComboboxButton')
|
||||
let id = `headlessui-combobox-button-${useId()}`
|
||||
|
||||
@@ -394,16 +357,39 @@ export let ComboboxButton = defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
api,
|
||||
id,
|
||||
el: api.buttonRef,
|
||||
type: useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
api.buttonRef
|
||||
),
|
||||
handleClick,
|
||||
handleKeydown,
|
||||
let type = useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
api.buttonRef
|
||||
)
|
||||
|
||||
return () => {
|
||||
let slot = {
|
||||
open: api.ComboboxState.value === ComboboxStates.Open,
|
||||
disabled: api.disabled.value,
|
||||
}
|
||||
let propsWeControl = {
|
||||
ref: api.buttonRef,
|
||||
id,
|
||||
type: type.value,
|
||||
tabindex: '-1',
|
||||
'aria-haspopup': true,
|
||||
'aria-controls': dom(api.optionsRef)?.id,
|
||||
'aria-expanded': api.disabled.value
|
||||
? undefined
|
||||
: api.ComboboxState.value === ComboboxStates.Open,
|
||||
'aria-labelledby': api.labelRef.value ? [dom(api.labelRef)?.id, id].join(' ') : undefined,
|
||||
disabled: api.disabled.value === true ? true : undefined,
|
||||
onKeydown: handleKeydown,
|
||||
onClick: handleClick,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name: 'ComboboxButton',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -421,36 +407,7 @@ export let ComboboxInput = defineComponent({
|
||||
emits: {
|
||||
change: (_value: Event & { target: HTMLInputElement }) => true,
|
||||
},
|
||||
render() {
|
||||
let api = useComboboxContext('ComboboxInput')
|
||||
|
||||
let slot = { open: api.ComboboxState.value === ComboboxStates.Open }
|
||||
let propsWeControl = {
|
||||
'aria-activedescendant':
|
||||
api.activeOptionIndex.value === null
|
||||
? undefined
|
||||
: api.options.value[api.activeOptionIndex.value]?.id,
|
||||
'aria-labelledby': dom(api.labelRef)?.id ?? dom(api.buttonRef)?.id,
|
||||
'aria-orientation': api.orientation.value,
|
||||
id: this.id,
|
||||
onKeydown: this.handleKeyDown,
|
||||
onChange: this.handleChange,
|
||||
role: 'combobox',
|
||||
tabIndex: 0,
|
||||
ref: 'el',
|
||||
}
|
||||
let passThroughProps = this.$props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
name: 'ComboboxInput',
|
||||
})
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
setup(props, { emit, attrs, slots }) {
|
||||
let api = useComboboxContext('ComboboxInput')
|
||||
let id = `headlessui-combobox-input-${useId()}`
|
||||
api.inputPropsRef = computed(() => props)
|
||||
@@ -530,7 +487,33 @@ export let ComboboxInput = defineComponent({
|
||||
emit('change', event)
|
||||
}
|
||||
|
||||
return { id, el: api.inputRef, handleKeyDown, handleChange }
|
||||
return () => {
|
||||
let slot = { open: api.ComboboxState.value === ComboboxStates.Open }
|
||||
let propsWeControl = {
|
||||
'aria-activedescendant':
|
||||
api.activeOptionIndex.value === null
|
||||
? undefined
|
||||
: api.options.value[api.activeOptionIndex.value]?.id,
|
||||
'aria-labelledby': dom(api.labelRef)?.id ?? dom(api.buttonRef)?.id,
|
||||
'aria-orientation': api.orientation.value,
|
||||
id,
|
||||
onKeydown: handleKeyDown,
|
||||
onChange: handleChange,
|
||||
role: 'combobox',
|
||||
tabIndex: 0,
|
||||
ref: api.inputRef,
|
||||
}
|
||||
let passThroughProps = props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
name: 'ComboboxInput',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -543,34 +526,7 @@ export let ComboboxOptions = defineComponent({
|
||||
static: { type: Boolean, default: false },
|
||||
unmount: { type: Boolean, default: true },
|
||||
},
|
||||
render() {
|
||||
let api = useComboboxContext('ComboboxOptions')
|
||||
|
||||
let slot = { open: api.ComboboxState.value === ComboboxStates.Open }
|
||||
let propsWeControl = {
|
||||
'aria-activedescendant':
|
||||
api.activeOptionIndex.value === null
|
||||
? undefined
|
||||
: api.options.value[api.activeOptionIndex.value]?.id,
|
||||
'aria-labelledby': dom(api.labelRef)?.id ?? dom(api.buttonRef)?.id,
|
||||
'aria-orientation': api.orientation.value,
|
||||
id: this.id,
|
||||
ref: 'el',
|
||||
role: 'listbox',
|
||||
}
|
||||
let passThroughProps = this.$props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: this.visible,
|
||||
name: 'ComboboxOptions',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useComboboxContext('ComboboxOptions')
|
||||
let id = `headlessui-combobox-options-${useId()}`
|
||||
|
||||
@@ -583,7 +539,31 @@ export let ComboboxOptions = defineComponent({
|
||||
return api.ComboboxState.value === ComboboxStates.Open
|
||||
})
|
||||
|
||||
return { id, el: api.optionsRef, visible }
|
||||
return () => {
|
||||
let slot = { open: api.ComboboxState.value === ComboboxStates.Open }
|
||||
let propsWeControl = {
|
||||
'aria-activedescendant':
|
||||
api.activeOptionIndex.value === null
|
||||
? undefined
|
||||
: api.options.value[api.activeOptionIndex.value]?.id,
|
||||
'aria-labelledby': dom(api.labelRef)?.id ?? dom(api.buttonRef)?.id,
|
||||
'aria-orientation': api.orientation.value,
|
||||
id,
|
||||
ref: api.optionsRef,
|
||||
role: 'listbox',
|
||||
}
|
||||
let passThroughProps = props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: visible.value,
|
||||
name: 'ComboboxOptions',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -27,12 +27,11 @@ it('should be possible to use useDescriptions without using a Description', asyn
|
||||
let { container } = render(
|
||||
defineComponent({
|
||||
components: { Description },
|
||||
render() {
|
||||
return h('div', [h('div', { 'aria-describedby': this.describedby }, ['No description'])])
|
||||
},
|
||||
setup() {
|
||||
let describedby = useDescriptions()
|
||||
return { describedby }
|
||||
|
||||
return () =>
|
||||
h('div', [h('div', { 'aria-describedby': describedby.value }, ['No description'])])
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -50,17 +49,16 @@ it('should be possible to use useDescriptions and a single Description, and have
|
||||
let { container } = render(
|
||||
defineComponent({
|
||||
components: { Description },
|
||||
render() {
|
||||
return h('div', [
|
||||
h('div', { 'aria-describedby': this.describedby }, [
|
||||
h(Description, () => 'I am a description'),
|
||||
h('span', 'Contents'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
setup() {
|
||||
let describedby = useDescriptions()
|
||||
return { describedby }
|
||||
|
||||
return () =>
|
||||
h('div', [
|
||||
h('div', { 'aria-describedby': describedby.value }, [
|
||||
h(Description, () => 'I am a description'),
|
||||
h('span', 'Contents'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -83,18 +81,17 @@ it('should be possible to use useDescriptions and multiple Description component
|
||||
let { container } = render(
|
||||
defineComponent({
|
||||
components: { Description },
|
||||
render() {
|
||||
return h('div', [
|
||||
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 = useDescriptions()
|
||||
return { describedby }
|
||||
|
||||
return () =>
|
||||
h('div', [
|
||||
h('div', { 'aria-describedby': describedby.value }, [
|
||||
h(Description, () => 'I am a description'),
|
||||
h('span', 'Contents'),
|
||||
h(Description, () => 'I am also a description'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -118,18 +115,18 @@ it('should be possible to update a prop from the parent and it should reflect in
|
||||
let { container } = render(
|
||||
defineComponent({
|
||||
components: { Description },
|
||||
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 }
|
||||
|
||||
return () => {
|
||||
return h('div', [
|
||||
h('div', { 'aria-describedby': describedby.value }, [
|
||||
h(Description, () => 'I am a description'),
|
||||
h('button', { onClick: () => count.value++ }, '+1'),
|
||||
]),
|
||||
])
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
@@ -70,31 +70,30 @@ export let Description = defineComponent({
|
||||
props: {
|
||||
as: { type: [Object, String], default: 'p' },
|
||||
},
|
||||
render() {
|
||||
let { name = 'Description', slot = ref({}), props = {} } = this.context
|
||||
let passThroughProps = this.$props
|
||||
let propsWeControl = {
|
||||
...Object.entries(props).reduce(
|
||||
(acc, [key, value]) => Object.assign(acc, { [key]: unref(value) }),
|
||||
{}
|
||||
),
|
||||
id: this.id,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot: slot.value,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name,
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(myProps, { attrs, slots }) {
|
||||
let context = useDescriptionContext()
|
||||
let id = `headlessui-description-${useId()}`
|
||||
|
||||
onMounted(() => onUnmounted(context.register(id)))
|
||||
|
||||
return { id, context }
|
||||
return () => {
|
||||
let { name = 'Description', slot = ref({}), props = {} } = context
|
||||
let passThroughProps = myProps
|
||||
let propsWeControl = {
|
||||
...Object.entries(props).reduce(
|
||||
(acc, [key, value]) => Object.assign(acc, { [key]: unref(value) }),
|
||||
{}
|
||||
),
|
||||
id,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot: slot.value,
|
||||
attrs,
|
||||
slots,
|
||||
name,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -935,57 +935,46 @@ describe('Nesting', () => {
|
||||
components: { Dialog, DialogOverlay },
|
||||
emits: ['close'],
|
||||
props: ['level'],
|
||||
render() {
|
||||
let level = this.$props.level ?? 1
|
||||
return h(Dialog, { open: true, onClose: this.onClose }, () => [
|
||||
h(DialogOverlay),
|
||||
h('div', [
|
||||
h('p', `Level: ${level}`),
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
onClick: () => {
|
||||
this.showChild = true
|
||||
},
|
||||
},
|
||||
`Open ${level + 1} a`
|
||||
),
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
onClick: () => {
|
||||
this.showChild = true
|
||||
},
|
||||
},
|
||||
`Open ${level + 1} b`
|
||||
),
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
onClick: () => {
|
||||
this.showChild = true
|
||||
},
|
||||
},
|
||||
`Open ${level + 1} c`
|
||||
),
|
||||
]),
|
||||
this.showChild &&
|
||||
h(Nested, {
|
||||
onClose: () => {
|
||||
this.showChild = false
|
||||
},
|
||||
level: level + 1,
|
||||
}),
|
||||
])
|
||||
},
|
||||
setup(_props, { emit }) {
|
||||
setup(props, { emit }) {
|
||||
let showChild = ref(false)
|
||||
function onClose() {
|
||||
emit('close', false)
|
||||
}
|
||||
|
||||
return {
|
||||
showChild,
|
||||
onClose() {
|
||||
emit('close', false)
|
||||
},
|
||||
return () => {
|
||||
let level = props.level ?? 1
|
||||
return h(Dialog, { open: true, onClose: onClose }, () => [
|
||||
h(DialogOverlay),
|
||||
h('div', [
|
||||
h('p', `Level: ${level}`),
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
onClick: () => (showChild.value = true),
|
||||
},
|
||||
`Open ${level + 1} a`
|
||||
),
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
onClick: () => (showChild.value = true),
|
||||
},
|
||||
`Open ${level + 1} b`
|
||||
),
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
onClick: () => (showChild.value = true),
|
||||
},
|
||||
`Open ${level + 1} c`
|
||||
),
|
||||
]),
|
||||
showChild.value &&
|
||||
h(Nested, {
|
||||
onClose: () => (showChild.value = false),
|
||||
level: level + 1,
|
||||
}),
|
||||
])
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -75,42 +75,7 @@ export let Dialog = defineComponent({
|
||||
initialFocus: { type: Object as PropType<HTMLElement | null>, default: null },
|
||||
},
|
||||
emits: { close: (_close: boolean) => true },
|
||||
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,
|
||||
onClick: this.handleClick,
|
||||
}
|
||||
let { open: _, initialFocus, ...passThroughProps } = this.$props
|
||||
|
||||
let slot = { open: this.dialogState === DialogStates.Open }
|
||||
|
||||
return h(ForcePortalRoot, { force: true }, () =>
|
||||
h(Portal, () =>
|
||||
h(PortalGroup, { target: this.dialogRef }, () =>
|
||||
h(ForcePortalRoot, { force: false }, () =>
|
||||
render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
visible: this.visible,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
name: 'Dialog',
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
setup(props, { emit, attrs, slots }) {
|
||||
let containers = ref<Set<HTMLElement>>(new Set())
|
||||
|
||||
let usesOpenClosedState = useOpenClosed()
|
||||
@@ -256,19 +221,44 @@ export let Dialog = defineComponent({
|
||||
onInvalidate(() => observer.disconnect())
|
||||
})
|
||||
|
||||
return {
|
||||
id,
|
||||
el: internalDialogRef,
|
||||
dialogRef: internalDialogRef,
|
||||
containers,
|
||||
dialogState,
|
||||
titleId,
|
||||
describedby,
|
||||
visible,
|
||||
open,
|
||||
handleClick(event: MouseEvent) {
|
||||
event.stopPropagation()
|
||||
},
|
||||
function handleClick(event: MouseEvent) {
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
return () => {
|
||||
let propsWeControl = {
|
||||
// Manually passthrough the attributes, because Vue can't automatically pass
|
||||
// it to the underlying div because of all the wrapper components below.
|
||||
...attrs,
|
||||
ref: internalDialogRef,
|
||||
id,
|
||||
role: 'dialog',
|
||||
'aria-modal': dialogState.value === DialogStates.Open ? true : undefined,
|
||||
'aria-labelledby': titleId.value,
|
||||
'aria-describedby': describedby.value,
|
||||
onClick: handleClick,
|
||||
}
|
||||
let { open: _, initialFocus, ...passThroughProps } = props
|
||||
|
||||
let slot = { open: dialogState.value === DialogStates.Open }
|
||||
|
||||
return h(ForcePortalRoot, { force: true }, () =>
|
||||
h(Portal, () =>
|
||||
h(PortalGroup, { target: internalDialogRef.value }, () =>
|
||||
h(ForcePortalRoot, { force: false }, () =>
|
||||
render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
visible: visible.value,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
name: 'Dialog',
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -280,36 +270,32 @@ export let DialogOverlay = defineComponent({
|
||||
props: {
|
||||
as: { type: [Object, String], default: 'div' },
|
||||
},
|
||||
render() {
|
||||
let api = useDialogContext('DialogOverlay')
|
||||
let propsWeControl = {
|
||||
ref: 'el',
|
||||
id: this.id,
|
||||
'aria-hidden': true,
|
||||
onClick: this.handleClick,
|
||||
}
|
||||
let passThroughProps = this.$props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot: { open: api.dialogState.value === DialogStates.Open },
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'DialogOverlay',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useDialogContext('DialogOverlay')
|
||||
let id = `headlessui-dialog-overlay-${useId()}`
|
||||
|
||||
return {
|
||||
id,
|
||||
handleClick(event: MouseEvent) {
|
||||
if (event.target !== event.currentTarget) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.close()
|
||||
},
|
||||
function handleClick(event: MouseEvent) {
|
||||
if (event.target !== event.currentTarget) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.close()
|
||||
}
|
||||
|
||||
return () => {
|
||||
let propsWeControl = {
|
||||
id,
|
||||
'aria-hidden': true,
|
||||
onClick: handleClick,
|
||||
}
|
||||
let passThroughProps = props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot: { open: api.dialogState.value === DialogStates.Open },
|
||||
attrs,
|
||||
slots,
|
||||
name: 'DialogOverlay',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -321,20 +307,7 @@ export let DialogTitle = defineComponent({
|
||||
props: {
|
||||
as: { type: [Object, String], default: 'h2' },
|
||||
},
|
||||
render() {
|
||||
let api = useDialogContext('DialogTitle')
|
||||
let propsWeControl = { id: this.id }
|
||||
let passThroughProps = this.$props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot: { open: api.dialogState.value === DialogStates.Open },
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'DialogTitle',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useDialogContext('DialogTitle')
|
||||
let id = `headlessui-dialog-title-${useId()}`
|
||||
|
||||
@@ -343,7 +316,18 @@ export let DialogTitle = defineComponent({
|
||||
onUnmounted(() => api.setTitleId(null))
|
||||
})
|
||||
|
||||
return { id }
|
||||
return () => {
|
||||
let propsWeControl = { id }
|
||||
let passThroughProps = props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot: { open: api.dialogState.value === DialogStates.Open },
|
||||
attrs,
|
||||
slots,
|
||||
name: 'DialogTitle',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -133,40 +133,7 @@ export let DisclosureButton = defineComponent({
|
||||
as: { type: [Object, String], default: 'button' },
|
||||
disabled: { type: [Boolean], default: false },
|
||||
},
|
||||
render() {
|
||||
let api = useDisclosureContext('DisclosureButton')
|
||||
|
||||
let slot = { open: api.disclosureState.value === DisclosureStates.Open }
|
||||
let propsWeControl = this.isWithinPanel
|
||||
? {
|
||||
ref: 'el',
|
||||
type: this.type,
|
||||
onClick: this.handleClick,
|
||||
onKeydown: this.handleKeyDown,
|
||||
}
|
||||
: {
|
||||
id: this.id,
|
||||
ref: 'el',
|
||||
type: this.type,
|
||||
'aria-expanded': this.$props.disabled
|
||||
? undefined
|
||||
: api.disclosureState.value === DisclosureStates.Open,
|
||||
'aria-controls': dom(api.panel) ? api.panelId : undefined,
|
||||
disabled: this.$props.disabled ? true : undefined,
|
||||
onClick: this.handleClick,
|
||||
onKeydown: this.handleKeyDown,
|
||||
onKeyup: this.handleKeyUp,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'DisclosureButton',
|
||||
})
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useDisclosureContext('DisclosureButton')
|
||||
|
||||
let panelContext = useDisclosurePanelContext()
|
||||
@@ -180,58 +147,86 @@ export let DisclosureButton = defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
isWithinPanel,
|
||||
id: api.buttonId,
|
||||
el: elementRef,
|
||||
type: useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
elementRef
|
||||
),
|
||||
handleClick() {
|
||||
if (props.disabled) return
|
||||
let type = useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
elementRef
|
||||
)
|
||||
|
||||
if (isWithinPanel) {
|
||||
api.toggleDisclosure()
|
||||
dom(api.button)?.focus()
|
||||
} else {
|
||||
api.toggleDisclosure()
|
||||
}
|
||||
},
|
||||
handleKeyDown(event: KeyboardEvent) {
|
||||
if (props.disabled) return
|
||||
function handleClick() {
|
||||
if (props.disabled) return
|
||||
|
||||
if (isWithinPanel) {
|
||||
switch (event.key) {
|
||||
case Keys.Space:
|
||||
case Keys.Enter:
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.toggleDisclosure()
|
||||
dom(api.button)?.focus()
|
||||
break
|
||||
}
|
||||
} else {
|
||||
switch (event.key) {
|
||||
case Keys.Space:
|
||||
case Keys.Enter:
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.toggleDisclosure()
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
handleKeyUp(event: KeyboardEvent) {
|
||||
if (isWithinPanel) {
|
||||
api.toggleDisclosure()
|
||||
dom(api.button)?.focus()
|
||||
} else {
|
||||
api.toggleDisclosure()
|
||||
}
|
||||
}
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (props.disabled) return
|
||||
|
||||
if (isWithinPanel) {
|
||||
switch (event.key) {
|
||||
case Keys.Space:
|
||||
// Required for firefox, event.preventDefault() in handleKeyDown for
|
||||
// the Space key doesn't cancel the handleKeyUp, which in turn
|
||||
// triggers a *click*.
|
||||
case Keys.Enter:
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.toggleDisclosure()
|
||||
dom(api.button)?.focus()
|
||||
break
|
||||
}
|
||||
},
|
||||
} else {
|
||||
switch (event.key) {
|
||||
case Keys.Space:
|
||||
case Keys.Enter:
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.toggleDisclosure()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case Keys.Space:
|
||||
// Required for firefox, event.preventDefault() in handleKeyDown for
|
||||
// the Space key doesn't cancel the handleKeyUp, which in turn
|
||||
// triggers a *click*.
|
||||
event.preventDefault()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
let slot = { open: api.disclosureState.value === DisclosureStates.Open }
|
||||
let propsWeControl = isWithinPanel
|
||||
? {
|
||||
ref: elementRef,
|
||||
type: type.value,
|
||||
onClick: handleClick,
|
||||
onKeydown: handleKeyDown,
|
||||
}
|
||||
: {
|
||||
id: api.buttonId,
|
||||
ref: elementRef,
|
||||
type: type.value,
|
||||
'aria-expanded': props.disabled
|
||||
? undefined
|
||||
: api.disclosureState.value === DisclosureStates.Open,
|
||||
'aria-controls': dom(api.panel) ? api.panelId : undefined,
|
||||
disabled: props.disabled ? true : undefined,
|
||||
onClick: handleClick,
|
||||
onKeydown: handleKeyDown,
|
||||
onKeyup: handleKeyUp,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name: 'DisclosureButton',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -245,23 +240,7 @@ export let DisclosurePanel = defineComponent({
|
||||
static: { type: Boolean, default: false },
|
||||
unmount: { type: Boolean, default: true },
|
||||
},
|
||||
render() {
|
||||
let api = useDisclosureContext('DisclosurePanel')
|
||||
|
||||
let slot = { open: api.disclosureState.value === DisclosureStates.Open, close: api.close }
|
||||
let propsWeControl = { id: this.id, ref: 'el' }
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: this.visible,
|
||||
name: 'DisclosurePanel',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useDisclosureContext('DisclosurePanel')
|
||||
|
||||
provide(DisclosurePanelContext, api.panelId)
|
||||
@@ -275,10 +254,19 @@ export let DisclosurePanel = defineComponent({
|
||||
return api.disclosureState.value === DisclosureStates.Open
|
||||
})
|
||||
|
||||
return {
|
||||
id: api.panelId,
|
||||
el: api.panel,
|
||||
visible,
|
||||
return () => {
|
||||
let slot = { open: api.disclosureState.value === DisclosureStates.Open, close: api.close }
|
||||
let propsWeControl = { id: api.panelId, ref: api.panel }
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: visible.value,
|
||||
name: 'DisclosurePanel',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -17,20 +17,7 @@ export let FocusTrap = defineComponent({
|
||||
as: { type: [Object, String], default: 'div' },
|
||||
initialFocus: { type: Object as PropType<HTMLElement | null>, default: null },
|
||||
},
|
||||
render() {
|
||||
let slot = {}
|
||||
let propsWeControl = { ref: 'el' }
|
||||
let { initialFocus, ...passThroughProps } = this.$props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'FocusTrap',
|
||||
})
|
||||
},
|
||||
setup(props) {
|
||||
setup(props, { attrs, slots }) {
|
||||
let containers = ref(new Set<HTMLElement>())
|
||||
let container = ref<HTMLElement | null>(null)
|
||||
let enabled = ref(true)
|
||||
@@ -47,6 +34,18 @@ export let FocusTrap = defineComponent({
|
||||
enabled.value = false
|
||||
})
|
||||
|
||||
return { el: container }
|
||||
return () => {
|
||||
let slot = {}
|
||||
let propsWeControl = { ref: container }
|
||||
let { initialFocus, ...passThroughProps } = props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name: 'FocusTrap',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -27,12 +27,10 @@ it('should be possible to use useLabels without using a Label', async () => {
|
||||
let { container } = render(
|
||||
defineComponent({
|
||||
components: { Label },
|
||||
render() {
|
||||
return h('div', [h('div', { 'aria-labelledby': this.labelledby }, ['No label'])])
|
||||
},
|
||||
setup() {
|
||||
let labelledby = useLabels()
|
||||
return { labelledby }
|
||||
|
||||
return () => h('div', [h('div', { 'aria-labelledby': labelledby.value }, ['No label'])])
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -50,17 +48,16 @@ it('should be possible to use useLabels and a single Label, and have them linked
|
||||
let { container } = render(
|
||||
defineComponent({
|
||||
components: { Label },
|
||||
render() {
|
||||
return h('div', [
|
||||
h('div', { 'aria-labelledby': this.labelledby }, [
|
||||
h(Label, () => 'I am a label'),
|
||||
h('span', 'Contents'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
setup() {
|
||||
let labelledby = useLabels()
|
||||
return { labelledby }
|
||||
|
||||
return () =>
|
||||
h('div', [
|
||||
h('div', { 'aria-labelledby': labelledby.value }, [
|
||||
h(Label, () => 'I am a label'),
|
||||
h('span', 'Contents'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -83,18 +80,17 @@ it('should be possible to use useLabels and multiple Label components, and have
|
||||
let { container } = render(
|
||||
defineComponent({
|
||||
components: { Label },
|
||||
render() {
|
||||
return h('div', [
|
||||
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 = useLabels()
|
||||
return { labelledby }
|
||||
|
||||
return () =>
|
||||
h('div', [
|
||||
h('div', { 'aria-labelledby': labelledby.value }, [
|
||||
h(Label, () => 'I am a label'),
|
||||
h('span', 'Contents'),
|
||||
h(Label, () => 'I am also a label'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -118,18 +114,17 @@ it('should be possible to update a prop from the parent and it should reflect in
|
||||
let { container } = render(
|
||||
defineComponent({
|
||||
components: { Label },
|
||||
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 }
|
||||
|
||||
return () =>
|
||||
h('div', [
|
||||
h('div', { 'aria-labelledby': labelledby.value }, [
|
||||
h(Label, () => 'I am a label'),
|
||||
h('button', { onClick: () => count.value++ }, '+1'),
|
||||
]),
|
||||
])
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
@@ -69,36 +69,35 @@ export let Label = defineComponent({
|
||||
as: { type: [Object, String], default: 'label' },
|
||||
passive: { type: [Boolean], default: false },
|
||||
},
|
||||
render() {
|
||||
let { name = 'Label', slot = {}, props = {} } = this.context
|
||||
let { passive, ...passThroughProps } = this.$props
|
||||
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
|
||||
// provide an onClick then we can delete it.
|
||||
if (passive) delete allProps['onClick']
|
||||
|
||||
return render({
|
||||
props: allProps,
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name,
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(myProps, { slots, attrs }) {
|
||||
let context = useLabelContext()
|
||||
let id = `headlessui-label-${useId()}`
|
||||
|
||||
onMounted(() => onUnmounted(context.register(id)))
|
||||
|
||||
return { id, context }
|
||||
return () => {
|
||||
let { name = 'Label', slot = {}, props = {} } = context
|
||||
let { passive, ...passThroughProps } = myProps
|
||||
let propsWeControl = {
|
||||
...Object.entries(props).reduce(
|
||||
(acc, [key, value]) => Object.assign(acc, { [key]: unref(value) }),
|
||||
{}
|
||||
),
|
||||
id,
|
||||
}
|
||||
let allProps = { ...passThroughProps, ...propsWeControl }
|
||||
|
||||
// @ts-expect-error props are dynamic via context, some components will
|
||||
// provide an onClick then we can delete it.
|
||||
if (passive) delete allProps['onClick']
|
||||
|
||||
return render({
|
||||
props: allProps,
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -245,30 +245,28 @@ export let Listbox = defineComponent({
|
||||
export let ListboxLabel = defineComponent({
|
||||
name: 'ListboxLabel',
|
||||
props: { as: { type: [Object, String], default: 'label' } },
|
||||
render() {
|
||||
let api = useListboxContext('ListboxLabel')
|
||||
|
||||
let slot = { open: api.listboxState.value === ListboxStates.Open, disabled: api.disabled.value }
|
||||
let propsWeControl = { id: this.id, ref: 'el', onClick: this.handleClick }
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'ListboxLabel',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useListboxContext('ListboxLabel')
|
||||
let id = `headlessui-listbox-label-${useId()}`
|
||||
|
||||
return {
|
||||
id,
|
||||
el: api.labelRef,
|
||||
handleClick() {
|
||||
dom(api.buttonRef)?.focus({ preventScroll: true })
|
||||
},
|
||||
function handleClick() {
|
||||
dom(api.buttonRef)?.focus({ preventScroll: true })
|
||||
}
|
||||
|
||||
return () => {
|
||||
let slot = {
|
||||
open: api.listboxState.value === ListboxStates.Open,
|
||||
disabled: api.disabled.value,
|
||||
}
|
||||
let propsWeControl = { id, ref: api.labelRef, onClick: handleClick }
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name: 'ListboxLabel',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -280,37 +278,7 @@ export let ListboxButton = defineComponent({
|
||||
props: {
|
||||
as: { type: [Object, String], default: 'button' },
|
||||
},
|
||||
render() {
|
||||
let api = useListboxContext('ListboxButton')
|
||||
|
||||
let slot = { open: api.listboxState.value === ListboxStates.Open, disabled: api.disabled.value }
|
||||
let propsWeControl = {
|
||||
ref: 'el',
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
'aria-haspopup': true,
|
||||
'aria-controls': dom(api.optionsRef)?.id,
|
||||
'aria-expanded': api.disabled.value
|
||||
? undefined
|
||||
: api.listboxState.value === ListboxStates.Open,
|
||||
'aria-labelledby': api.labelRef.value
|
||||
? [dom(api.labelRef)?.id, this.id].join(' ')
|
||||
: undefined,
|
||||
disabled: api.disabled.value === true ? true : undefined,
|
||||
onKeydown: this.handleKeyDown,
|
||||
onKeyup: this.handleKeyUp,
|
||||
onClick: this.handleClick,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'ListboxButton',
|
||||
})
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useListboxContext('ListboxButton')
|
||||
let id = `headlessui-listbox-button-${useId()}`
|
||||
|
||||
@@ -363,16 +331,39 @@ export let ListboxButton = defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
el: api.buttonRef,
|
||||
type: useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
api.buttonRef
|
||||
),
|
||||
handleKeyDown,
|
||||
handleKeyUp,
|
||||
handleClick,
|
||||
let type = useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
api.buttonRef
|
||||
)
|
||||
|
||||
return () => {
|
||||
let slot = {
|
||||
open: api.listboxState.value === ListboxStates.Open,
|
||||
disabled: api.disabled.value,
|
||||
}
|
||||
let propsWeControl = {
|
||||
ref: api.buttonRef,
|
||||
id,
|
||||
type: type.value,
|
||||
'aria-haspopup': true,
|
||||
'aria-controls': dom(api.optionsRef)?.id,
|
||||
'aria-expanded': api.disabled.value
|
||||
? undefined
|
||||
: api.listboxState.value === ListboxStates.Open,
|
||||
'aria-labelledby': api.labelRef.value ? [dom(api.labelRef)?.id, id].join(' ') : undefined,
|
||||
disabled: api.disabled.value === true ? true : undefined,
|
||||
onKeydown: handleKeyDown,
|
||||
onKeyup: handleKeyUp,
|
||||
onClick: handleClick,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name: 'ListboxButton',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -386,36 +377,7 @@ export let ListboxOptions = defineComponent({
|
||||
static: { type: Boolean, default: false },
|
||||
unmount: { type: Boolean, default: true },
|
||||
},
|
||||
render() {
|
||||
let api = useListboxContext('ListboxOptions')
|
||||
|
||||
let slot = { open: api.listboxState.value === ListboxStates.Open }
|
||||
let propsWeControl = {
|
||||
'aria-activedescendant':
|
||||
api.activeOptionIndex.value === null
|
||||
? undefined
|
||||
: api.options.value[api.activeOptionIndex.value]?.id,
|
||||
'aria-labelledby': dom(api.labelRef)?.id ?? dom(api.buttonRef)?.id,
|
||||
'aria-orientation': api.orientation.value,
|
||||
id: this.id,
|
||||
onKeydown: this.handleKeyDown,
|
||||
role: 'listbox',
|
||||
tabIndex: 0,
|
||||
ref: 'el',
|
||||
}
|
||||
let passThroughProps = this.$props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: this.visible,
|
||||
name: 'ListboxOptions',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useListboxContext('ListboxOptions')
|
||||
let id = `headlessui-listbox-options-${useId()}`
|
||||
let searchDebounce = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||
@@ -500,7 +462,33 @@ export let ListboxOptions = defineComponent({
|
||||
return api.listboxState.value === ListboxStates.Open
|
||||
})
|
||||
|
||||
return { id, el: api.optionsRef, handleKeyDown, visible }
|
||||
return () => {
|
||||
let slot = { open: api.listboxState.value === ListboxStates.Open }
|
||||
let propsWeControl = {
|
||||
'aria-activedescendant':
|
||||
api.activeOptionIndex.value === null
|
||||
? undefined
|
||||
: api.options.value[api.activeOptionIndex.value]?.id,
|
||||
'aria-labelledby': dom(api.labelRef)?.id ?? dom(api.buttonRef)?.id,
|
||||
'aria-orientation': api.orientation.value,
|
||||
id,
|
||||
onKeydown: handleKeyDown,
|
||||
role: 'listbox',
|
||||
tabIndex: 0,
|
||||
ref: api.optionsRef,
|
||||
}
|
||||
let passThroughProps = props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: visible.value,
|
||||
name: 'ListboxOptions',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -172,6 +172,7 @@ export let Menu = defineComponent({
|
||||
|
||||
// @ts-expect-error Types of property 'dataRef' are incompatible.
|
||||
provide(MenuContext, api)
|
||||
|
||||
useOpenClosedProvider(
|
||||
computed(() =>
|
||||
match(menuState.value, {
|
||||
@@ -194,31 +195,7 @@ export let MenuButton = defineComponent({
|
||||
disabled: { type: Boolean, default: false },
|
||||
as: { type: [Object, String], default: 'button' },
|
||||
},
|
||||
render() {
|
||||
let api = useMenuContext('MenuButton')
|
||||
|
||||
let slot = { open: api.menuState.value === MenuStates.Open }
|
||||
let propsWeControl = {
|
||||
ref: 'el',
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
'aria-haspopup': true,
|
||||
'aria-controls': dom(api.itemsRef)?.id,
|
||||
'aria-expanded': this.$props.disabled ? undefined : api.menuState.value === MenuStates.Open,
|
||||
onKeydown: this.handleKeyDown,
|
||||
onKeyup: this.handleKeyUp,
|
||||
onClick: this.handleClick,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'MenuButton',
|
||||
})
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useMenuContext('MenuButton')
|
||||
let id = `headlessui-menu-button-${useId()}`
|
||||
|
||||
@@ -274,16 +251,32 @@ export let MenuButton = defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
el: api.buttonRef,
|
||||
type: useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
api.buttonRef
|
||||
),
|
||||
handleKeyDown,
|
||||
handleKeyUp,
|
||||
handleClick,
|
||||
let type = useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
api.buttonRef
|
||||
)
|
||||
|
||||
return () => {
|
||||
let slot = { open: api.menuState.value === MenuStates.Open }
|
||||
let propsWeControl = {
|
||||
ref: api.buttonRef,
|
||||
id,
|
||||
type: type.value,
|
||||
'aria-haspopup': true,
|
||||
'aria-controls': dom(api.itemsRef)?.id,
|
||||
'aria-expanded': props.disabled ? undefined : api.menuState.value === MenuStates.Open,
|
||||
onKeydown: handleKeyDown,
|
||||
onKeyup: handleKeyUp,
|
||||
onClick: handleClick,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name: 'MenuButton',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -295,36 +288,7 @@ export let MenuItems = defineComponent({
|
||||
static: { type: Boolean, default: false },
|
||||
unmount: { type: Boolean, default: true },
|
||||
},
|
||||
render() {
|
||||
let api = useMenuContext('MenuItems')
|
||||
|
||||
let slot = { open: api.menuState.value === MenuStates.Open }
|
||||
let propsWeControl = {
|
||||
'aria-activedescendant':
|
||||
api.activeItemIndex.value === null
|
||||
? undefined
|
||||
: api.items.value[api.activeItemIndex.value]?.id,
|
||||
'aria-labelledby': dom(api.buttonRef)?.id,
|
||||
id: this.id,
|
||||
onKeydown: this.handleKeyDown,
|
||||
onKeyup: this.handleKeyUp,
|
||||
role: 'menu',
|
||||
tabIndex: 0,
|
||||
ref: 'el',
|
||||
}
|
||||
let passThroughProps = this.$props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: this.visible,
|
||||
name: 'MenuItems',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useMenuContext('MenuItems')
|
||||
let id = `headlessui-menu-items-${useId()}`
|
||||
let searchDebounce = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||
@@ -430,7 +394,34 @@ export let MenuItems = defineComponent({
|
||||
return api.menuState.value === MenuStates.Open
|
||||
})
|
||||
|
||||
return { id, el: api.itemsRef, handleKeyDown, handleKeyUp, visible }
|
||||
return () => {
|
||||
let slot = { open: api.menuState.value === MenuStates.Open }
|
||||
let propsWeControl = {
|
||||
'aria-activedescendant':
|
||||
api.activeItemIndex.value === null
|
||||
? undefined
|
||||
: api.items.value[api.activeItemIndex.value]?.id,
|
||||
'aria-labelledby': dom(api.buttonRef)?.id,
|
||||
id,
|
||||
onKeydown: handleKeyDown,
|
||||
onKeyup: handleKeyUp,
|
||||
role: 'menu',
|
||||
tabIndex: 0,
|
||||
ref: api.itemsRef,
|
||||
}
|
||||
|
||||
let passThroughProps = props
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: visible.value,
|
||||
name: 'MenuItems',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -206,40 +206,7 @@ export let PopoverButton = defineComponent({
|
||||
as: { type: [Object, String], default: 'button' },
|
||||
disabled: { type: [Boolean], default: false },
|
||||
},
|
||||
render() {
|
||||
let api = usePopoverContext('PopoverButton')
|
||||
|
||||
let slot = { open: api.popoverState.value === PopoverStates.Open }
|
||||
let propsWeControl = this.isWithinPanel
|
||||
? {
|
||||
ref: 'el',
|
||||
type: this.type,
|
||||
onKeydown: this.handleKeyDown,
|
||||
onClick: this.handleClick,
|
||||
}
|
||||
: {
|
||||
ref: 'el',
|
||||
id: api.buttonId,
|
||||
type: this.type,
|
||||
'aria-expanded': this.$props.disabled
|
||||
? undefined
|
||||
: api.popoverState.value === PopoverStates.Open,
|
||||
'aria-controls': dom(api.panel) ? api.panelId : undefined,
|
||||
disabled: this.$props.disabled ? true : undefined,
|
||||
onKeydown: this.handleKeyDown,
|
||||
onKeyup: this.handleKeyUp,
|
||||
onClick: this.handleClick,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'PopoverButton',
|
||||
})
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = usePopoverContext('PopoverButton')
|
||||
|
||||
let groupContext = usePopoverGroupContext()
|
||||
@@ -271,124 +238,153 @@ export let PopoverButton = defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
isWithinPanel,
|
||||
el: elementRef,
|
||||
type: useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
elementRef
|
||||
),
|
||||
handleKeyDown(event: KeyboardEvent) {
|
||||
if (isWithinPanel) {
|
||||
if (api.popoverState.value === PopoverStates.Closed) return
|
||||
switch (event.key) {
|
||||
case Keys.Space:
|
||||
case Keys.Enter:
|
||||
event.preventDefault() // Prevent triggering a *click* event
|
||||
event.stopPropagation()
|
||||
api.closePopover()
|
||||
dom(api.button)?.focus() // Re-focus the original opening Button
|
||||
break
|
||||
}
|
||||
} else {
|
||||
switch (event.key) {
|
||||
case Keys.Space:
|
||||
case Keys.Enter:
|
||||
event.preventDefault() // Prevent triggering a *click* event
|
||||
event.stopPropagation()
|
||||
if (api.popoverState.value === PopoverStates.Closed) closeOthers?.(api.buttonId)
|
||||
api.togglePopover()
|
||||
break
|
||||
let type = useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
elementRef
|
||||
)
|
||||
|
||||
case Keys.Escape:
|
||||
if (api.popoverState.value !== PopoverStates.Open) return closeOthers?.(api.buttonId)
|
||||
if (!dom(api.button)) return
|
||||
if (!dom(api.button)?.contains(document.activeElement)) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.closePopover()
|
||||
break
|
||||
|
||||
case Keys.Tab:
|
||||
if (api.popoverState.value !== PopoverStates.Open) return
|
||||
if (!api.panel) return
|
||||
if (!api.button) return
|
||||
|
||||
// TODO: Revisit when handling Tab/Shift+Tab when using Portal's
|
||||
if (event.shiftKey) {
|
||||
// Check if the last focused element exists, and check that it is not inside button or panel itself
|
||||
if (!previousActiveElementRef.value) return
|
||||
if (dom(api.button)?.contains(previousActiveElementRef.value)) return
|
||||
if (dom(api.panel)?.contains(previousActiveElementRef.value)) return
|
||||
|
||||
// Check if the last focused element is *after* the button in the DOM
|
||||
let focusableElements = getFocusableElements()
|
||||
let previousIdx = focusableElements.indexOf(
|
||||
previousActiveElementRef.value as HTMLElement
|
||||
)
|
||||
let buttonIdx = focusableElements.indexOf(dom(api.button)!)
|
||||
if (buttonIdx > previousIdx) return
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
focusIn(dom(api.panel)!, Focus.Last)
|
||||
} else {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
focusIn(dom(api.panel)!, Focus.First)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
handleKeyUp(event: KeyboardEvent) {
|
||||
if (isWithinPanel) return
|
||||
if (event.key === Keys.Space) {
|
||||
// Required for firefox, event.preventDefault() in handleKeyDown for
|
||||
// the Space key doesn't cancel the handleKeyUp, which in turn
|
||||
// triggers a *click*.
|
||||
event.preventDefault()
|
||||
}
|
||||
if (api.popoverState.value !== PopoverStates.Open) return
|
||||
if (!api.panel) return
|
||||
if (!api.button) return
|
||||
|
||||
// TODO: Revisit when handling Tab/Shift+Tab when using Portal's
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (isWithinPanel) {
|
||||
if (api.popoverState.value === PopoverStates.Closed) return
|
||||
switch (event.key) {
|
||||
case Keys.Tab:
|
||||
// Check if the last focused element exists, and check that it is not inside button or panel itself
|
||||
if (!previousActiveElementRef.value) return
|
||||
if (dom(api.button)?.contains(previousActiveElementRef.value)) return
|
||||
if (dom(api.panel)?.contains(previousActiveElementRef.value)) return
|
||||
|
||||
// Check if the last focused element is *after* the button in the DOM
|
||||
let focusableElements = getFocusableElements()
|
||||
let previousIdx = focusableElements.indexOf(
|
||||
previousActiveElementRef.value as HTMLElement
|
||||
)
|
||||
let buttonIdx = focusableElements.indexOf(dom(api.button)!)
|
||||
if (buttonIdx > previousIdx) return
|
||||
|
||||
event.preventDefault()
|
||||
case Keys.Space:
|
||||
case Keys.Enter:
|
||||
event.preventDefault() // Prevent triggering a *click* event
|
||||
event.stopPropagation()
|
||||
focusIn(dom(api.panel)!, Focus.Last)
|
||||
api.closePopover()
|
||||
dom(api.button)?.focus() // Re-focus the original opening Button
|
||||
break
|
||||
}
|
||||
},
|
||||
handleClick() {
|
||||
if (props.disabled) return
|
||||
if (isWithinPanel) {
|
||||
api.closePopover()
|
||||
dom(api.button)?.focus() // Re-focus the original opening Button
|
||||
} else {
|
||||
if (api.popoverState.value === PopoverStates.Closed) closeOthers?.(api.buttonId)
|
||||
dom(api.button)?.focus()
|
||||
api.togglePopover()
|
||||
} else {
|
||||
switch (event.key) {
|
||||
case Keys.Space:
|
||||
case Keys.Enter:
|
||||
event.preventDefault() // Prevent triggering a *click* event
|
||||
event.stopPropagation()
|
||||
if (api.popoverState.value === PopoverStates.Closed) closeOthers?.(api.buttonId)
|
||||
api.togglePopover()
|
||||
break
|
||||
|
||||
case Keys.Escape:
|
||||
if (api.popoverState.value !== PopoverStates.Open) return closeOthers?.(api.buttonId)
|
||||
if (!dom(api.button)) return
|
||||
if (!dom(api.button)?.contains(document.activeElement)) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.closePopover()
|
||||
break
|
||||
|
||||
case Keys.Tab:
|
||||
if (api.popoverState.value !== PopoverStates.Open) return
|
||||
if (!api.panel) return
|
||||
if (!api.button) return
|
||||
|
||||
// TODO: Revisit when handling Tab/Shift+Tab when using Portal's
|
||||
if (event.shiftKey) {
|
||||
// Check if the last focused element exists, and check that it is not inside button or panel itself
|
||||
if (!previousActiveElementRef.value) return
|
||||
if (dom(api.button)?.contains(previousActiveElementRef.value)) return
|
||||
if (dom(api.panel)?.contains(previousActiveElementRef.value)) return
|
||||
|
||||
// Check if the last focused element is *after* the button in the DOM
|
||||
let focusableElements = getFocusableElements()
|
||||
let previousIdx = focusableElements.indexOf(
|
||||
previousActiveElementRef.value as HTMLElement
|
||||
)
|
||||
let buttonIdx = focusableElements.indexOf(dom(api.button)!)
|
||||
if (buttonIdx > previousIdx) return
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
focusIn(dom(api.panel)!, Focus.Last)
|
||||
} else {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
focusIn(dom(api.panel)!, Focus.First)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
if (isWithinPanel) return
|
||||
if (event.key === Keys.Space) {
|
||||
// Required for firefox, event.preventDefault() in handleKeyDown for
|
||||
// the Space key doesn't cancel the handleKeyUp, which in turn
|
||||
// triggers a *click*.
|
||||
event.preventDefault()
|
||||
}
|
||||
if (api.popoverState.value !== PopoverStates.Open) return
|
||||
if (!api.panel) return
|
||||
if (!api.button) return
|
||||
|
||||
// TODO: Revisit when handling Tab/Shift+Tab when using Portal's
|
||||
switch (event.key) {
|
||||
case Keys.Tab:
|
||||
// Check if the last focused element exists, and check that it is not inside button or panel itself
|
||||
if (!previousActiveElementRef.value) return
|
||||
if (dom(api.button)?.contains(previousActiveElementRef.value)) return
|
||||
if (dom(api.panel)?.contains(previousActiveElementRef.value)) return
|
||||
|
||||
// Check if the last focused element is *after* the button in the DOM
|
||||
let focusableElements = getFocusableElements()
|
||||
let previousIdx = focusableElements.indexOf(previousActiveElementRef.value as HTMLElement)
|
||||
let buttonIdx = focusableElements.indexOf(dom(api.button)!)
|
||||
if (buttonIdx > previousIdx) return
|
||||
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
focusIn(dom(api.panel)!, Focus.Last)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
if (props.disabled) return
|
||||
if (isWithinPanel) {
|
||||
api.closePopover()
|
||||
dom(api.button)?.focus() // Re-focus the original opening Button
|
||||
} else {
|
||||
if (api.popoverState.value === PopoverStates.Closed) closeOthers?.(api.buttonId)
|
||||
dom(api.button)?.focus()
|
||||
api.togglePopover()
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
let slot = { open: api.popoverState.value === PopoverStates.Open }
|
||||
let propsWeControl = isWithinPanel
|
||||
? {
|
||||
ref: elementRef,
|
||||
type: type.value,
|
||||
onKeydown: handleKeyDown,
|
||||
onClick: handleClick,
|
||||
}
|
||||
: {
|
||||
ref: elementRef,
|
||||
id: api.buttonId,
|
||||
type: type.value,
|
||||
'aria-expanded': props.disabled
|
||||
? undefined
|
||||
: api.popoverState.value === PopoverStates.Open,
|
||||
'aria-controls': dom(api.panel) ? api.panelId : undefined,
|
||||
disabled: props.disabled ? true : undefined,
|
||||
onKeydown: handleKeyDown,
|
||||
onKeyup: handleKeyUp,
|
||||
onClick: handleClick,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: attrs,
|
||||
slots: slots,
|
||||
name: 'PopoverButton',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -402,29 +398,9 @@ export let PopoverOverlay = defineComponent({
|
||||
static: { type: Boolean, default: false },
|
||||
unmount: { type: Boolean, default: true },
|
||||
},
|
||||
render() {
|
||||
let api = usePopoverContext('PopoverOverlay')
|
||||
|
||||
let slot = { open: api.popoverState.value === PopoverStates.Open }
|
||||
let propsWeControl = {
|
||||
id: this.id,
|
||||
ref: 'el',
|
||||
'aria-hidden': true,
|
||||
onClick: this.handleClick,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: this.visible,
|
||||
name: 'PopoverOverlay',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = usePopoverContext('PopoverOverlay')
|
||||
let id = `headlessui-popover-overlay-${useId()}`
|
||||
|
||||
let usesOpenClosedState = useOpenClosed()
|
||||
let visible = computed(() => {
|
||||
@@ -435,12 +411,27 @@ export let PopoverOverlay = defineComponent({
|
||||
return api.popoverState.value === PopoverStates.Open
|
||||
})
|
||||
|
||||
return {
|
||||
id: `headlessui-popover-overlay-${useId()}`,
|
||||
handleClick() {
|
||||
api.closePopover()
|
||||
},
|
||||
visible,
|
||||
function handleClick() {
|
||||
api.closePopover()
|
||||
}
|
||||
|
||||
return () => {
|
||||
let slot = { open: api.popoverState.value === PopoverStates.Open }
|
||||
let propsWeControl = {
|
||||
id,
|
||||
'aria-hidden': true,
|
||||
onClick: handleClick,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: visible.value,
|
||||
name: 'PopoverOverlay',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -455,31 +446,7 @@ export let PopoverPanel = defineComponent({
|
||||
unmount: { type: Boolean, default: true },
|
||||
focus: { type: Boolean, default: false },
|
||||
},
|
||||
render() {
|
||||
let api = usePopoverContext('PopoverPanel')
|
||||
|
||||
let slot = {
|
||||
open: api.popoverState.value === PopoverStates.Open,
|
||||
close: api.close,
|
||||
}
|
||||
|
||||
let propsWeControl = {
|
||||
ref: 'el',
|
||||
id: this.id,
|
||||
onKeydown: this.handleKeyDown,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: this.visible,
|
||||
name: 'PopoverPanel',
|
||||
})
|
||||
},
|
||||
setup(props) {
|
||||
setup(props, { attrs, slots }) {
|
||||
let { focus } = props
|
||||
let api = usePopoverContext('PopoverPanel')
|
||||
|
||||
@@ -563,23 +530,41 @@ export let PopoverPanel = defineComponent({
|
||||
return api.popoverState.value === PopoverStates.Open
|
||||
})
|
||||
|
||||
return {
|
||||
id: api.panelId,
|
||||
el: api.panel,
|
||||
handleKeyDown(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case Keys.Escape:
|
||||
if (api.popoverState.value !== PopoverStates.Open) return
|
||||
if (!dom(api.panel)) return
|
||||
if (!dom(api.panel)?.contains(document.activeElement)) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.closePopover()
|
||||
dom(api.button)?.focus()
|
||||
break
|
||||
}
|
||||
},
|
||||
visible,
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case Keys.Escape:
|
||||
if (api.popoverState.value !== PopoverStates.Open) return
|
||||
if (!dom(api.panel)) return
|
||||
if (!dom(api.panel)?.contains(document.activeElement)) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
api.closePopover()
|
||||
dom(api.button)?.focus()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
let slot = {
|
||||
open: api.popoverState.value === PopoverStates.Open,
|
||||
close: api.close,
|
||||
}
|
||||
|
||||
let propsWeControl = {
|
||||
ref: api.panel,
|
||||
id: api.panelId,
|
||||
onKeydown: handleKeyDown,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
features: Features.RenderStrategy | Features.Static,
|
||||
visible: visible.value,
|
||||
name: 'PopoverPanel',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -591,18 +576,7 @@ export let PopoverGroup = defineComponent({
|
||||
props: {
|
||||
as: { type: [Object, String], default: 'div' },
|
||||
},
|
||||
render() {
|
||||
let propsWeControl = { ref: 'el' }
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot: {},
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'PopoverGroup',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let groupRef = ref<HTMLElement | null>(null)
|
||||
let popovers = ref<PopoverRegisterBag[]>([])
|
||||
|
||||
@@ -645,6 +619,16 @@ export let PopoverGroup = defineComponent({
|
||||
closeOthers,
|
||||
})
|
||||
|
||||
return { el: groupRef }
|
||||
return () => {
|
||||
let propsWeControl = { ref: groupRef }
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot: {},
|
||||
attrs,
|
||||
slots,
|
||||
name: 'PopoverGroup',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@ import { dom } from '../../utils/dom'
|
||||
import { Keys } from '../../keyboard'
|
||||
import { focusIn, Focus, FocusResult } from '../../utils/focus-management'
|
||||
import { useId } from '../../hooks/use-id'
|
||||
import { render } from '../../utils/render'
|
||||
import { omit, render } from '../../utils/render'
|
||||
import { Label, useLabels } from '../label/label'
|
||||
import { Description, useDescriptions } from '../description/description'
|
||||
import { useTreeWalker } from '../../hooks/use-tree-walker'
|
||||
@@ -66,27 +66,7 @@ export let RadioGroup = defineComponent({
|
||||
disabled: { type: [Boolean], default: false },
|
||||
modelValue: { type: [Object, String, Number, Boolean] },
|
||||
},
|
||||
render() {
|
||||
let { modelValue, disabled, ...passThroughProps } = this.$props
|
||||
|
||||
let propsWeControl = {
|
||||
ref: 'el',
|
||||
id: this.id,
|
||||
role: 'radiogroup',
|
||||
'aria-labelledby': this.labelledby,
|
||||
'aria-describedby': this.describedby,
|
||||
onKeydown: this.handleKeyDown,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot: {},
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'RadioGroup',
|
||||
})
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
setup(props, { emit, attrs, slots }) {
|
||||
let radioGroupRef = ref<HTMLElement | null>(null)
|
||||
let options = ref<StateDefinition['options']['value']>([])
|
||||
let labelledby = useLabels({ name: 'RadioGroupLabel' })
|
||||
@@ -209,12 +189,25 @@ export let RadioGroup = defineComponent({
|
||||
|
||||
let id = `headlessui-radiogroup-${useId()}`
|
||||
|
||||
return {
|
||||
id,
|
||||
labelledby,
|
||||
describedby,
|
||||
el: radioGroupRef,
|
||||
handleKeyDown,
|
||||
return () => {
|
||||
let { modelValue, disabled, ...passThroughProps } = props
|
||||
|
||||
let propsWeControl = {
|
||||
ref: radioGroupRef,
|
||||
id,
|
||||
role: 'radiogroup',
|
||||
'aria-labelledby': labelledby.value,
|
||||
'aria-describedby': describedby.value,
|
||||
onKeydown: handleKeyDown,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot: {},
|
||||
attrs,
|
||||
slots,
|
||||
name: 'RadioGroup',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -233,38 +226,7 @@ export let RadioGroupOption = defineComponent({
|
||||
value: { type: [Object, String, Number, Boolean] },
|
||||
disabled: { type: Boolean, default: false },
|
||||
},
|
||||
render() {
|
||||
let { value, disabled, ...passThroughProps } = this.$props
|
||||
|
||||
let slot = {
|
||||
checked: this.checked,
|
||||
disabled: this.disabled,
|
||||
active: Boolean(this.state & OptionState.Active),
|
||||
}
|
||||
|
||||
let propsWeControl = {
|
||||
id: this.id,
|
||||
ref: 'el',
|
||||
role: 'radio',
|
||||
'aria-checked': this.checked ? 'true' : 'false',
|
||||
'aria-labelledby': this.labelledby,
|
||||
'aria-describedby': this.describedby,
|
||||
'aria-disabled': this.disabled ? true : undefined,
|
||||
tabIndex: this.tabIndex,
|
||||
onClick: this.disabled ? undefined : this.handleClick,
|
||||
onFocus: this.disabled ? undefined : this.handleFocus,
|
||||
onBlur: this.disabled ? undefined : this.handleBlur,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'RadioGroupOption',
|
||||
})
|
||||
},
|
||||
setup(props) {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useRadioGroupContext('RadioGroupOption')
|
||||
let id = `headlessui-radiogroup-option-${useId()}`
|
||||
let labelledby = useLabels({ name: 'RadioGroupLabel' })
|
||||
@@ -280,33 +242,58 @@ export let RadioGroupOption = defineComponent({
|
||||
let isFirstOption = computed(() => api.firstOption.value?.id === id)
|
||||
let disabled = computed(() => api.disabled.value || props.disabled)
|
||||
let checked = computed(() => toRaw(api.value.value) === toRaw(props.value))
|
||||
let tabIndex = computed(() => {
|
||||
if (disabled.value) return -1
|
||||
if (checked.value) return 0
|
||||
if (!api.containsCheckedOption.value && isFirstOption.value) return 0
|
||||
return -1
|
||||
})
|
||||
|
||||
return {
|
||||
id,
|
||||
el: optionRef,
|
||||
labelledby,
|
||||
describedby,
|
||||
state,
|
||||
disabled,
|
||||
checked,
|
||||
tabIndex: computed(() => {
|
||||
if (disabled.value) return -1
|
||||
if (checked.value) return 0
|
||||
if (!api.containsCheckedOption.value && isFirstOption.value) return 0
|
||||
return -1
|
||||
}),
|
||||
handleClick() {
|
||||
if (!api.change(props.value)) return
|
||||
function handleClick() {
|
||||
if (!api.change(props.value)) return
|
||||
|
||||
state.value |= OptionState.Active
|
||||
optionRef.value?.focus()
|
||||
},
|
||||
handleFocus() {
|
||||
state.value |= OptionState.Active
|
||||
},
|
||||
handleBlur() {
|
||||
state.value &= ~OptionState.Active
|
||||
},
|
||||
state.value |= OptionState.Active
|
||||
optionRef.value?.focus()
|
||||
}
|
||||
|
||||
function handleFocus() {
|
||||
state.value |= OptionState.Active
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
state.value &= ~OptionState.Active
|
||||
}
|
||||
|
||||
return () => {
|
||||
let passThroughProps = omit(props, ['value', 'disabled'])
|
||||
|
||||
let slot = {
|
||||
checked: checked.value,
|
||||
disabled: disabled.value,
|
||||
active: Boolean(state.value & OptionState.Active),
|
||||
}
|
||||
|
||||
let propsWeControl = {
|
||||
id,
|
||||
ref: optionRef,
|
||||
role: 'radio',
|
||||
'aria-checked': checked.value ? 'true' : 'false',
|
||||
'aria-labelledby': labelledby.value,
|
||||
'aria-describedby': describedby.value,
|
||||
'aria-disabled': disabled.value ? true : undefined,
|
||||
tabIndex: tabIndex.value,
|
||||
onClick: disabled.value ? undefined : handleClick,
|
||||
onFocus: disabled.value ? undefined : handleFocus,
|
||||
onBlur: disabled.value ? undefined : handleBlur,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...passThroughProps, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name: 'RadioGroupOption',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -64,31 +64,8 @@ export let Switch = defineComponent({
|
||||
as: { type: [Object, String], default: 'button' },
|
||||
modelValue: { type: Boolean, default: false },
|
||||
},
|
||||
render() {
|
||||
let slot = { checked: this.$props.modelValue }
|
||||
let propsWeControl = {
|
||||
id: this.id,
|
||||
ref: 'el',
|
||||
role: 'switch',
|
||||
type: this.type,
|
||||
tabIndex: 0,
|
||||
'aria-checked': this.$props.modelValue,
|
||||
'aria-labelledby': this.labelledby,
|
||||
'aria-describedby': this.describedby,
|
||||
onClick: this.handleClick,
|
||||
onKeyup: this.handleKeyUp,
|
||||
onKeypress: this.handleKeyPress,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'Switch',
|
||||
})
|
||||
},
|
||||
setup(props, { emit, attrs }) {
|
||||
setup(props, { emit, attrs, slots }) {
|
||||
let api = inject(GroupContext, null)
|
||||
let id = `headlessui-switch-${useId()}`
|
||||
|
||||
@@ -98,28 +75,49 @@ export let Switch = defineComponent({
|
||||
|
||||
let internalSwitchRef = ref(null)
|
||||
let switchRef = api === null ? internalSwitchRef : api.switchRef
|
||||
let type = useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
switchRef
|
||||
)
|
||||
|
||||
return {
|
||||
id,
|
||||
el: switchRef,
|
||||
type: useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
switchRef
|
||||
),
|
||||
labelledby: api?.labelledby,
|
||||
describedby: api?.describedby,
|
||||
handleClick(event: MouseEvent) {
|
||||
event.preventDefault()
|
||||
toggle()
|
||||
},
|
||||
handleKeyUp(event: KeyboardEvent) {
|
||||
if (event.key !== Keys.Tab) event.preventDefault()
|
||||
if (event.key === Keys.Space) toggle()
|
||||
},
|
||||
// This is needed so that we can "cancel" the click event when we use the `Enter` key on a button.
|
||||
handleKeyPress(event: KeyboardEvent) {
|
||||
event.preventDefault()
|
||||
},
|
||||
function handleClick(event: MouseEvent) {
|
||||
event.preventDefault()
|
||||
toggle()
|
||||
}
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
if (event.key !== Keys.Tab) event.preventDefault()
|
||||
if (event.key === Keys.Space) toggle()
|
||||
}
|
||||
|
||||
// This is needed so that we can "cancel" the click event when we use the `Enter` key on a button.
|
||||
function handleKeyPress(event: KeyboardEvent) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
return () => {
|
||||
let slot = { checked: props.modelValue }
|
||||
let propsWeControl = {
|
||||
id,
|
||||
ref: switchRef,
|
||||
role: 'switch',
|
||||
type: type.value,
|
||||
tabIndex: 0,
|
||||
'aria-checked': props.modelValue,
|
||||
'aria-labelledby': api?.labelledby.value,
|
||||
'aria-describedby': api?.describedby.value,
|
||||
onClick: handleClick,
|
||||
onKeyup: handleKeyUp,
|
||||
onKeypress: handleKeyPress,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name: 'Switch',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -181,33 +181,7 @@ export let Tab = defineComponent({
|
||||
as: { type: [Object, String], default: 'button' },
|
||||
disabled: { type: [Boolean], default: false },
|
||||
},
|
||||
render() {
|
||||
let api = useTabsContext('Tab')
|
||||
|
||||
let slot = { selected: this.selected }
|
||||
let propsWeControl = {
|
||||
ref: 'el',
|
||||
onKeydown: this.handleKeyDown,
|
||||
onFocus: api.activation.value === 'manual' ? this.handleFocus : this.handleSelection,
|
||||
onClick: this.handleSelection,
|
||||
id: this.id,
|
||||
role: 'tab',
|
||||
type: this.type,
|
||||
'aria-controls': api.panels.value[this.myIndex]?.value?.id,
|
||||
'aria-selected': this.selected,
|
||||
tabIndex: this.selected ? 0 : -1,
|
||||
disabled: this.$props.disabled ? true : undefined,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
name: 'Tab',
|
||||
})
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useTabsContext('Tab')
|
||||
let id = `headlessui-tabs-tab-${useId()}`
|
||||
|
||||
@@ -271,18 +245,34 @@ export let Tab = defineComponent({
|
||||
api.setSelectedIndex(myIndex.value)
|
||||
}
|
||||
|
||||
return {
|
||||
el: tabRef,
|
||||
id,
|
||||
selected,
|
||||
myIndex,
|
||||
type: useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
tabRef
|
||||
),
|
||||
handleKeyDown,
|
||||
handleFocus,
|
||||
handleSelection,
|
||||
let type = useResolveButtonType(
|
||||
computed(() => ({ as: props.as, type: attrs.type })),
|
||||
tabRef
|
||||
)
|
||||
|
||||
return () => {
|
||||
let slot = { selected: selected.value }
|
||||
let propsWeControl = {
|
||||
ref: tabRef,
|
||||
onKeydown: handleKeyDown,
|
||||
onFocus: api.activation.value === 'manual' ? handleFocus : handleSelection,
|
||||
onClick: handleSelection,
|
||||
id,
|
||||
role: 'tab',
|
||||
type: type.value,
|
||||
'aria-controls': api.panels.value[myIndex.value]?.value?.id,
|
||||
'aria-selected': selected.value,
|
||||
tabIndex: selected.value ? 0 : -1,
|
||||
disabled: props.disabled ? true : undefined,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
name: 'Tab',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -318,29 +308,7 @@ export let TabPanel = defineComponent({
|
||||
static: { type: Boolean, default: false },
|
||||
unmount: { type: Boolean, default: true },
|
||||
},
|
||||
render() {
|
||||
let api = useTabsContext('TabPanel')
|
||||
|
||||
let slot = { selected: this.selected }
|
||||
let propsWeControl = {
|
||||
ref: 'el',
|
||||
id: this.id,
|
||||
role: 'tabpanel',
|
||||
'aria-labelledby': api.tabs.value[this.myIndex]?.value?.id,
|
||||
tabIndex: this.selected ? 0 : -1,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...this.$props, ...propsWeControl },
|
||||
slot,
|
||||
attrs: this.$attrs,
|
||||
slots: this.$slots,
|
||||
features: Features.Static | Features.RenderStrategy,
|
||||
visible: this.selected,
|
||||
name: 'TabPanel',
|
||||
})
|
||||
},
|
||||
setup() {
|
||||
setup(props, { attrs, slots }) {
|
||||
let api = useTabsContext('TabPanel')
|
||||
let id = `headlessui-tabs-panel-${useId()}`
|
||||
|
||||
@@ -352,6 +320,25 @@ export let TabPanel = defineComponent({
|
||||
let myIndex = computed(() => api.panels.value.indexOf(panelRef))
|
||||
let selected = computed(() => myIndex.value === api.selectedIndex.value)
|
||||
|
||||
return { id, el: panelRef, selected, myIndex }
|
||||
return () => {
|
||||
let slot = { selected: selected.value }
|
||||
let propsWeControl = {
|
||||
ref: panelRef,
|
||||
id,
|
||||
role: 'tabpanel',
|
||||
'aria-labelledby': api.tabs.value[myIndex.value]?.value?.id,
|
||||
tabIndex: selected.value ? 0 : -1,
|
||||
}
|
||||
|
||||
return render({
|
||||
props: { ...props, ...propsWeControl },
|
||||
slot,
|
||||
attrs,
|
||||
slots,
|
||||
features: Features.Static | Features.RenderStrategy,
|
||||
visible: selected.value,
|
||||
name: 'TabPanel',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { useId } from '../../hooks/use-id'
|
||||
import { match } from '../../utils/match'
|
||||
|
||||
import { Features, render, RenderStrategy } from '../../utils/render'
|
||||
import { Features, omit, render, RenderStrategy } from '../../utils/render'
|
||||
import { Reason, transition } from './utils/transition'
|
||||
import { dom } from '../../utils/dom'
|
||||
import {
|
||||
@@ -151,54 +151,20 @@ export let TransitionChild = defineComponent({
|
||||
beforeLeave: () => true,
|
||||
afterLeave: () => true,
|
||||
},
|
||||
render() {
|
||||
if (this.renderAsRoot) {
|
||||
return h(
|
||||
TransitionRoot,
|
||||
{
|
||||
...this.$props,
|
||||
onBeforeEnter: () => this.$emit('beforeEnter'),
|
||||
onAfterEnter: () => this.$emit('afterEnter'),
|
||||
onBeforeLeave: () => this.$emit('beforeLeave'),
|
||||
onAfterLeave: () => this.$emit('afterLeave'),
|
||||
},
|
||||
this.$slots
|
||||
)
|
||||
}
|
||||
|
||||
let {
|
||||
appear,
|
||||
show,
|
||||
|
||||
// Class names
|
||||
enter,
|
||||
enterFrom,
|
||||
enterTo,
|
||||
entered,
|
||||
leave,
|
||||
leaveFrom,
|
||||
leaveTo,
|
||||
...rest
|
||||
} = this.$props
|
||||
|
||||
let propsWeControl = { ref: 'el' }
|
||||
let passthroughProps = rest
|
||||
|
||||
return render({
|
||||
props: { ...passthroughProps, ...propsWeControl },
|
||||
slot: {},
|
||||
slots: this.$slots,
|
||||
attrs: this.$attrs,
|
||||
features: TransitionChildRenderFeatures,
|
||||
visible: this.state === TreeStates.Visible,
|
||||
name: 'TransitionChild',
|
||||
})
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
setup(props, { emit, attrs, slots }) {
|
||||
if (!hasTransitionContext() && hasOpenClosed()) {
|
||||
return {
|
||||
renderAsRoot: true,
|
||||
}
|
||||
return () =>
|
||||
h(
|
||||
TransitionRoot,
|
||||
{
|
||||
...props,
|
||||
onBeforeEnter: () => emit('beforeEnter'),
|
||||
onAfterEnter: () => emit('afterEnter'),
|
||||
onBeforeLeave: () => emit('beforeLeave'),
|
||||
onAfterLeave: () => emit('afterLeave'),
|
||||
},
|
||||
slots
|
||||
)
|
||||
}
|
||||
|
||||
let container = ref<HTMLElement | null>(null)
|
||||
@@ -341,7 +307,35 @@ export let TransitionChild = defineComponent({
|
||||
)
|
||||
)
|
||||
|
||||
return { el: container, renderAsRoot: false, state }
|
||||
return () => {
|
||||
let {
|
||||
appear,
|
||||
show,
|
||||
|
||||
// Class names
|
||||
enter,
|
||||
enterFrom,
|
||||
enterTo,
|
||||
entered,
|
||||
leave,
|
||||
leaveFrom,
|
||||
leaveTo,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
let propsWeControl = { ref: container }
|
||||
let passthroughProps = rest
|
||||
|
||||
return render({
|
||||
props: { ...passthroughProps, ...propsWeControl },
|
||||
slot: {},
|
||||
slots,
|
||||
attrs,
|
||||
features: TransitionChildRenderFeatures,
|
||||
visible: state.value === TreeStates.Visible,
|
||||
name: 'TransitionChild',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -368,41 +362,7 @@ export let TransitionRoot = defineComponent({
|
||||
beforeLeave: () => true,
|
||||
afterLeave: () => true,
|
||||
},
|
||||
render() {
|
||||
let { show, appear, unmount, ...passThroughProps } = this.$props
|
||||
let sharedProps = { unmount }
|
||||
|
||||
return render({
|
||||
props: {
|
||||
...sharedProps,
|
||||
as: 'template',
|
||||
},
|
||||
slot: {},
|
||||
slots: {
|
||||
...this.$slots,
|
||||
default: () => [
|
||||
h(
|
||||
TransitionChild,
|
||||
{
|
||||
onBeforeEnter: () => this.$emit('beforeEnter'),
|
||||
onAfterEnter: () => this.$emit('afterEnter'),
|
||||
onBeforeLeave: () => this.$emit('beforeLeave'),
|
||||
onAfterLeave: () => this.$emit('afterLeave'),
|
||||
...this.$attrs,
|
||||
...sharedProps,
|
||||
...passThroughProps,
|
||||
},
|
||||
this.$slots.default
|
||||
),
|
||||
],
|
||||
},
|
||||
attrs: {},
|
||||
features: TransitionChildRenderFeatures,
|
||||
visible: this.state === TreeStates.Visible,
|
||||
name: 'Transition',
|
||||
})
|
||||
},
|
||||
setup(props) {
|
||||
setup(props, { emit, attrs, slots }) {
|
||||
let usesOpenClosedState = useOpenClosed()
|
||||
|
||||
let show = computed(() => {
|
||||
@@ -449,6 +409,39 @@ export let TransitionRoot = defineComponent({
|
||||
provide(NestingContext, nestingBag)
|
||||
provide(TransitionContext, transitionBag)
|
||||
|
||||
return { state, show }
|
||||
return () => {
|
||||
let passThroughProps = omit(props, ['show', 'appear', 'unmount'])
|
||||
let sharedProps = { unmount: props.unmount }
|
||||
|
||||
return render({
|
||||
props: {
|
||||
...sharedProps,
|
||||
as: 'template',
|
||||
},
|
||||
slot: {},
|
||||
slots: {
|
||||
...slots,
|
||||
default: () => [
|
||||
h(
|
||||
TransitionChild,
|
||||
{
|
||||
onBeforeEnter: () => emit('beforeEnter'),
|
||||
onAfterEnter: () => emit('afterEnter'),
|
||||
onBeforeLeave: () => emit('beforeLeave'),
|
||||
onAfterLeave: () => emit('afterLeave'),
|
||||
...attrs,
|
||||
...sharedProps,
|
||||
...passThroughProps,
|
||||
},
|
||||
slots.default
|
||||
),
|
||||
],
|
||||
},
|
||||
attrs: {},
|
||||
features: TransitionChildRenderFeatures,
|
||||
visible: state.value === TreeStates.Visible,
|
||||
name: 'Transition',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, h } from 'vue'
|
||||
import { defineComponent, h, ref, watchEffect } from 'vue'
|
||||
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
|
||||
|
||||
function classNames(...classes) {
|
||||
|
||||
@@ -929,6 +929,16 @@
|
||||
estree-walker "^2.0.2"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-core@3.2.29":
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.29.tgz#b06097ab8ff0493177c68c5ea5b63d379a061097"
|
||||
integrity sha512-RePZ/J4Ub3sb7atQw6V6Rez+/5LCRHGFlSetT3N4VMrejqJnNPXKUt5AVm/9F5MJriy2w/VudEIvgscCfCWqxw==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/shared" "3.2.29"
|
||||
estree-walker "^2.0.2"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-dom@3.2.28":
|
||||
version "3.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.28.tgz#cc32a987fee50673f25430df35ea943f252c23e6"
|
||||
@@ -937,6 +947,14 @@
|
||||
"@vue/compiler-core" "3.2.28"
|
||||
"@vue/shared" "3.2.28"
|
||||
|
||||
"@vue/compiler-dom@3.2.29":
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.29.tgz#ad0ead405bd2f2754161335aad9758aa12430715"
|
||||
integrity sha512-y26vK5khdNS9L3ckvkqJk/78qXwWb75Ci8iYLb67AkJuIgyKhIOcR1E8RIt4mswlVCIeI9gQ+fmtdhaiTAtrBQ==
|
||||
dependencies:
|
||||
"@vue/compiler-core" "3.2.29"
|
||||
"@vue/shared" "3.2.29"
|
||||
|
||||
"@vue/compiler-sfc@3.2.28":
|
||||
version "3.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.28.tgz#0a576c09abc72d6a76b153133de6fd7599c182c3"
|
||||
@@ -953,6 +971,22 @@
|
||||
postcss "^8.1.10"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-sfc@3.2.29":
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.29.tgz#f76d556cd5fca6a55a3ea84c88db1a2a53a36ead"
|
||||
integrity sha512-X9+0dwsag2u6hSOP/XsMYqFti/edvYvxamgBgCcbSYuXx1xLZN+dS/GvQKM4AgGS4djqo0jQvWfIXdfZ2ET68g==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/compiler-core" "3.2.29"
|
||||
"@vue/compiler-dom" "3.2.29"
|
||||
"@vue/compiler-ssr" "3.2.29"
|
||||
"@vue/reactivity-transform" "3.2.29"
|
||||
"@vue/shared" "3.2.29"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
postcss "^8.1.10"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-ssr@3.2.28":
|
||||
version "3.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.28.tgz#411e8b3bdc3183b2acd35e6551734b34366d64e5"
|
||||
@@ -961,6 +995,14 @@
|
||||
"@vue/compiler-dom" "3.2.28"
|
||||
"@vue/shared" "3.2.28"
|
||||
|
||||
"@vue/compiler-ssr@3.2.29":
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.29.tgz#37b15b32dcd2f6b410bb61fca3f37b1a92b7eb1e"
|
||||
integrity sha512-LrvQwXlx66uWsB9/VydaaqEpae9xtmlUkeSKF6aPDbzx8M1h7ukxaPjNCAXuFd3fUHblcri8k42lfimHfzMICA==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.2.29"
|
||||
"@vue/shared" "3.2.29"
|
||||
|
||||
"@vue/devtools-api@^6.0.0-beta.18":
|
||||
version "6.0.0-beta.21.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.21.1.tgz#f1410f53c42aa67fa3b01ca7bdba891f69d7bc97"
|
||||
@@ -977,6 +1019,17 @@
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
|
||||
"@vue/reactivity-transform@3.2.29":
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.29.tgz#a08d606e10016b7cf588d1a43dae4db2953f9354"
|
||||
integrity sha512-YF6HdOuhdOw6KyRm59+3rML8USb9o8mYM1q+SH0G41K3/q/G7uhPnHGKvspzceD7h9J3VR1waOQ93CUZj7J7OA==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/compiler-core" "3.2.29"
|
||||
"@vue/shared" "3.2.29"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
|
||||
"@vue/reactivity@3.2.28":
|
||||
version "3.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.28.tgz#1c3c7f434372edd867f937151897fca7efc4be18"
|
||||
@@ -984,6 +1037,13 @@
|
||||
dependencies:
|
||||
"@vue/shared" "3.2.28"
|
||||
|
||||
"@vue/reactivity@3.2.29":
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.29.tgz#afdc9c111d4139b14600be17ad80267212af6052"
|
||||
integrity sha512-Ryhb6Gy62YolKXH1gv42pEqwx7zs3n8gacRVZICSgjQz8Qr8QeCcFygBKYfJm3o1SccR7U+bVBQDWZGOyG1k4g==
|
||||
dependencies:
|
||||
"@vue/shared" "3.2.29"
|
||||
|
||||
"@vue/runtime-core@3.2.28":
|
||||
version "3.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.28.tgz#69d8eede42957a1660b964004aa002982ae36a41"
|
||||
@@ -992,6 +1052,14 @@
|
||||
"@vue/reactivity" "3.2.28"
|
||||
"@vue/shared" "3.2.28"
|
||||
|
||||
"@vue/runtime-core@3.2.29":
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.29.tgz#fb8577b2fcf52e8d967bd91cdf49ab9fb91f9417"
|
||||
integrity sha512-VMvQuLdzoTGmCwIKTKVwKmIL0qcODIqe74JtK1pVr5lnaE0l25hopodmPag3RcnIcIXe+Ye3B2olRCn7fTCgig==
|
||||
dependencies:
|
||||
"@vue/reactivity" "3.2.29"
|
||||
"@vue/shared" "3.2.29"
|
||||
|
||||
"@vue/runtime-dom@3.2.28":
|
||||
version "3.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.28.tgz#b5a0cf38daed5534edbc95790f4eeac97dff2003"
|
||||
@@ -1001,6 +1069,15 @@
|
||||
"@vue/shared" "3.2.28"
|
||||
csstype "^2.6.8"
|
||||
|
||||
"@vue/runtime-dom@3.2.29":
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.29.tgz#35e9a2bf04ef80b86ac2ca0e7b2ceaccf1e18f01"
|
||||
integrity sha512-YJgLQLwr+SQyORzTsBQLL5TT/5UiV83tEotqjL7F9aFDIQdFBTCwpkCFvX9jqwHoyi9sJqM9XtTrMcc8z/OjPA==
|
||||
dependencies:
|
||||
"@vue/runtime-core" "3.2.29"
|
||||
"@vue/shared" "3.2.29"
|
||||
csstype "^2.6.8"
|
||||
|
||||
"@vue/server-renderer@3.2.28":
|
||||
version "3.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.28.tgz#235944dc4d969fadd387f62acc2eb8b8d50008a2"
|
||||
@@ -1009,11 +1086,24 @@
|
||||
"@vue/compiler-ssr" "3.2.28"
|
||||
"@vue/shared" "3.2.28"
|
||||
|
||||
"@vue/server-renderer@3.2.29":
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.29.tgz#ea6afa361b9c781a868c8da18c761f9b7bc89102"
|
||||
integrity sha512-lpiYx7ciV7rWfJ0tPkoSOlLmwqBZ9FTmQm33S+T4g0j1fO/LmhJ9b9Ctl1o5xvIFVDk9QkSUWANZn7H2pXuxVw==
|
||||
dependencies:
|
||||
"@vue/compiler-ssr" "3.2.29"
|
||||
"@vue/shared" "3.2.29"
|
||||
|
||||
"@vue/shared@3.2.28":
|
||||
version "3.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.28.tgz#5b0b1840432031d0ea1adff633b356a503e87048"
|
||||
integrity sha512-eMQ8s9j8FpbGHlgUAaj/coaG3Q8YtMsoWL/RIHTsE3Ex7PUTyr7V91vB5HqWB5Sn8m4RXTHGO22/skoTUYvp0A==
|
||||
|
||||
"@vue/shared@3.2.29":
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.29.tgz#07dac7051117236431d2f737d16932aa38bbb925"
|
||||
integrity sha512-BjNpU8OK6Z0LVzGUppEk0CMYm/hKDnZfYdjSmPOs0N+TR1cLKJAkDwW8ASZUvaaSLEi6d3hVM7jnWnX+6yWnHw==
|
||||
|
||||
"@vue/test-utils@^1.1.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.3.0.tgz#d563decdcd9c68a7bca151d4179a2bfd6d5c3e15"
|
||||
@@ -5190,6 +5280,17 @@ vue@^3.2.27:
|
||||
"@vue/server-renderer" "3.2.28"
|
||||
"@vue/shared" "3.2.28"
|
||||
|
||||
vue@^3.2.29:
|
||||
version "3.2.29"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.29.tgz#3571b65dbd796d3a6347e2fd45a8e6e11c13d56a"
|
||||
integrity sha512-cFIwr7LkbtCRanjNvh6r7wp2yUxfxeM2yPpDQpAfaaLIGZSrUmLbNiSze9nhBJt5MrZ68Iqt0O5scwAMEVxF+Q==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.2.29"
|
||||
"@vue/compiler-sfc" "3.2.29"
|
||||
"@vue/runtime-dom" "3.2.29"
|
||||
"@vue/server-renderer" "3.2.29"
|
||||
"@vue/shared" "3.2.29"
|
||||
|
||||
w3c-hr-time@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
|
||||
|
||||
Reference in New Issue
Block a user