Skip to content

Commit

Permalink
feat(useCurrentElement): Allow get current element from a specific co…
Browse files Browse the repository at this point in the history
…mponent (#3750)

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
linspw and antfu committed Feb 20, 2024
1 parent 12c09a1 commit 0a9aabd
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 2 deletions.
24 changes: 24 additions & 0 deletions packages/core/useCurrentElement/index.md
Expand Up @@ -14,6 +14,30 @@ import { useCurrentElement } from '@vueuse/core'
const el = useCurrentElement() // ComputedRef<Element>
```

Or pass a specific vue component

```vue
<script setup>
import { ref } from 'vue'
import { useCurrentElement } from '@vueuse/core'
const componentRef = ref()
const el = useCurrentElement(componentRef) // ComputedRef<Element>
</script>
<template>
<div>
<OtherVueComponent ref="componentRef" />
<p>Hello world</p>
</div>
</template>
```

::: info
Only works for Vue 3 because it uses [computedWithControl](https://vueuse.org/shared/computedWithControl/#manual-triggering) under the hood
:::

## Caveats

This functions uses [`$el` under the hood](https://vuejs.org/api/component-instance.html#el).
Expand Down
58 changes: 58 additions & 0 deletions packages/core/useCurrentElement/index.test.ts
@@ -0,0 +1,58 @@
import { defineComponent, isVue2, shallowRef } from 'vue-demi'
import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import { useCurrentElement } from '.'

// Manual triggering only works for Vue 3 - https://vueuse.org/shared/computedWithControl/#manual-triggering
describe.skipIf(isVue2)('useCurrentElement', () => {
it('should be defined', () => {
expect(useCurrentElement).toBeDefined()
})

it('should return the root element from the current component', () => {
const wrapper = mount({
template: '<p ref="el">test</p>',
setup() {
const el = shallowRef<HTMLElement>()
const currentElement = useCurrentElement()

return { el, currentElement }
},
})
const vm = wrapper.vm

expect(vm.currentElement).toBe(vm.el)
wrapper.unmount()
})

it('should return the root element from the passed component', () => {
const TestVueComponent = defineComponent({
setup() {
const rootEl = shallowRef<HTMLElement>()

return { rootEl }
},
template: '<div ref="rootEl">Hello world</div>',
})
const wrapper = mount({
components: {
TestVueComponent,
},
template: `<p>
Testing
<TestVueComponent ref="el" />
</p>`,
setup() {
const el = shallowRef()
const currentElementEl = useCurrentElement(el)

return { el, currentElementEl }
},
})
const vm = wrapper.vm

expect(vm.currentElementEl).toBe((vm.el as typeof TestVueComponent).rootEl)
expect((vm.currentElementEl as HTMLElement).textContent).toBe('Hello world')
wrapper.unmount()
})
})
11 changes: 9 additions & 2 deletions packages/core/useCurrentElement/index.ts
@@ -1,12 +1,19 @@
// eslint-disable-next-line no-restricted-imports
import { getCurrentInstance, onMounted, onUpdated } from 'vue-demi'
import { computedWithControl } from '@vueuse/shared'
import type { MaybeElement, MaybeElementRef, VueInstance } from '../unrefElement'
import { unrefElement } from '../unrefElement'

export function useCurrentElement<T extends Element = Element>() {
export function useCurrentElement<
T extends MaybeElement = MaybeElement,
R extends VueInstance = VueInstance,
>(
rootComponent?: MaybeElementRef<R>,
) {
const vm = getCurrentInstance()!
const currentElement = computedWithControl(
() => null,
() => vm.proxy!.$el as T,
() => (rootComponent ? unrefElement(rootComponent) : vm.proxy!.$el) as T,
)

onUpdated(currentElement.trigger)
Expand Down

0 comments on commit 0a9aabd

Please sign in to comment.