Skip to content

Commit

Permalink
fix(useForwardExpose): forward native element (#1632)
Browse files Browse the repository at this point in the history
  • Loading branch information
felissi authored Feb 19, 2025
1 parent 3f9aad0 commit c052f22
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 2 deletions.
165 changes: 165 additions & 0 deletions packages/radix-vue/src/shared/useForwardExpose.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { flushPromises, mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import { Fragment, computed, defineComponent, h, ref } from 'vue'

import { useForwardExpose } from './useForwardExpose'

describe('useForwardRef', async () => {
it('should forward plain DOM element ref', async () => {
const comp = defineComponent({
setup() {
const { forwardRef } = useForwardExpose()
return { forwardRef }
},
template: `
<div>
<span :ref="forwardRef"> inner element </span>
</div>
`,
})
const wrapper = mount(
defineComponent(() => {
const el = ref()
const forwardedRef = computed(() => el.value?.$el)
return () =>
h('div', { test: forwardedRef.value }, [h(comp, { ref: el })])
}),
)
await flushPromises()
expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]')
})
it('should forward plain DOM element ref - 2', async () => {
const comp = defineComponent({
setup() {
const { forwardRef } = useForwardExpose()
return { forwardRef }
},
template: `
<div>
<span :ref="_=>forwardRef({$el: _})"> inner element </span>
</div>
`,
})
const wrapper = mount(
defineComponent(() => {
const el = ref()
const forwardedRef = computed(() => el.value?.$el)
return () =>
h('div', { test: forwardedRef.value }, [h(comp, { ref: el })])
}),
)
await flushPromises()
expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]')
})
it('should forward plain DOM element ref - fragment', async () => {
const comp = defineComponent({
setup() {
const { forwardRef } = useForwardExpose()
return { forwardRef }
},
template: `
<div>multiple node root</div>
<div>
<span :ref="forwardRef"> inner element </span>
</div>
`,
})
const wrapper = mount(
defineComponent(() => {
const el = ref()
const forwardedRef = computed(() => el.value?.$el)
return () =>
h('div', { test: forwardedRef.value }, [h(comp, { ref: el })])
}),
)
await flushPromises()
expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]')
})
it('should forward plain DOM element ref - fragment - 2', async () => {
const Frag = defineComponent(
(props, { slots }) =>
() =>
h(Fragment, {}, [slots.default?.()]),
)
const comp = defineComponent({
components: { Frag },
setup() {
const { forwardRef } = useForwardExpose()
return { forwardRef }
},
template: `
<Frag>
<Frag>
<div>
<span :ref="forwardRef"> inner element </span>
</div>
</Frag>
</Frag>
`,
})
const wrapper = mount(
defineComponent(() => {
const el = ref()
const forwardedRef = computed(() => el.value?.$el)
return () =>
h('div', { test: forwardedRef.value }, [h(comp, { ref: el })])
}),
)
await flushPromises()
expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]')
})
it('should forward plain DOM element ref - fragment - 3', async () => {
const comp = defineComponent({
setup() {
const { forwardRef } = useForwardExpose()
return { forwardRef }
},
template: `
<div>
<template>
<div>
<span :ref="forwardRef"> inner element </span>
</div>
</template>
</div>
`,
})
const wrapper = mount(
defineComponent(() => {
const el = ref()
const forwardedRef = computed(() => el.value?.$el)
return () =>
h('div', { test: forwardedRef.value }, [h(comp, { ref: el })])
}),
)
await flushPromises()
expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]')
})
it('should forward component instance for component', async () => {
const InnerComp = defineComponent(() => {
return () => h('span', {}, 'inner component')
})
const comp = defineComponent({
setup() {
const { forwardRef } = useForwardExpose()
return { forwardRef }
},
components: { InnerComp },
template: `
<div>
<InnerComp :ref="forwardRef" />
</div>
`,
})
const wrapper = mount(
defineComponent(() => {
const el = ref()
const forwardedRef = computed(() => el.value?.$el)
return () =>
h('div', { test: forwardedRef.value }, [h(comp, { ref: el })])
}),
)
await flushPromises()
expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]')
})
})
4 changes: 2 additions & 2 deletions packages/radix-vue/src/shared/useForwardExpose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ export function useForwardExpose() {
function forwardRef(ref: Element | ComponentPublicInstance | null) {
currentRef.value = ref

if (ref instanceof Element || !ref)
if (!ref)
return

// retrieve the forwarded element
Object.defineProperty(ret, '$el', {
enumerable: true,
configurable: true,
get: () => ref.$el,
get: () => (ref instanceof Element ? ref : ref.$el),
})

instance.exposed = ret
Expand Down

0 comments on commit c052f22

Please sign in to comment.