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:
Jordan Pittman
2022-01-27 13:49:26 -05:00
committed by GitHub
parent fdd2629795
commit 89fd4b202e
20 changed files with 1124 additions and 1168 deletions
+4 -4
View File
@@ -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',
})
}
},
})
-3
View File
@@ -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) {
+101
View File
@@ -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"