unwrapTarget
Resolve a MaybeRefOrGetter to an HTMLElement, Window, Document or null. Like unwrapElement, it unwraps refs and follows component-instance proxies via instance.$el, but it additionally passes Window and Document through unchanged. Because it uses toValue, a raw value, a ref and a getter are all accepted.
This is the building block behind useEventListener and useScrollPosition. Reach for it whenever you author a composable that may bind to the viewport as well as to an element.
Demo
element ref
unwrapTarget →—
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { unwrapTarget } from '@basmilius/common';
const box = useTemplateRef<HTMLDivElement>('box');
const result = ref('—');
function describe(value: HTMLElement | Window | Document | null): string {
if (value === null) {
return 'null';
}
if (value instanceof Window) {
return 'Window';
}
if (value instanceof Document) {
return 'Document';
}
return `<${value.tagName.toLowerCase()}>`;
}
function resolveElement(): void {
result.value = describe(unwrapTarget(box));
}
function resolveWindow(): void {
result.value = describe(unwrapTarget(window));
}
function resolveDocument(): void {
result.value = describe(unwrapTarget(document));
}
function resolveNull(): void {
result.value = describe(unwrapTarget(null));
}
</script>
<template>
<div class="demo">
<div ref="box" class="box">element ref</div>
<div class="actions">
<button @click="resolveElement">element ref</button>
<button @click="resolveWindow">window</button>
<button @click="resolveDocument">document</button>
<button @click="resolveNull">null</button>
</div>
<p class="result">
<span>unwrapTarget →</span>
<code>{{ result }}</code>
</p>
</div>
</template>
<style scoped>
.demo {
padding: 24px;
border: 1px solid var(--vp-c-border);
border-radius: 12px;
background: var(--vp-c-bg-soft);
}
.box {
display: grid;
place-items: center;
height: 64px;
margin-bottom: 16px;
border-radius: 8px;
background: var(--vp-c-bg);
box-shadow: 0 0 0 1px var(--vp-c-border) inset;
font-family: var(--vp-font-family-mono);
font-size: 13px;
color: var(--vp-c-text-3);
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.actions button {
padding: 6px 14px;
border: 1px solid var(--vp-c-border);
border-radius: 8px;
background: var(--vp-c-bg);
font-family: var(--vp-font-family-mono);
font-size: 13px;
color: var(--vp-c-text-1);
cursor: pointer;
transition: border-color .2s, color .2s;
}
.actions button:hover {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
}
.result {
display: flex;
align-items: center;
gap: 10px;
margin: 0;
color: var(--vp-c-text-2);
font-size: 14px;
}
.result code {
font-size: 16px;
font-weight: 600;
color: var(--vp-c-brand-1);
}
</style>Importing
ts
import { unwrapTarget } from '@basmilius/common';Usage
vue
<script setup lang="ts">
import { onMounted, useTemplateRef } from 'vue';
import { unwrapTarget } from '@basmilius/common';
const target = useTemplateRef<HTMLDivElement>('target');
onMounted(() => {
const element = unwrapTarget(target);
element?.scrollTo({top: 0});
});
</script>
<template>
<div ref="target">…</div>
</template>Passing window or document returns the value as-is, while element and component refs are flattened to their underlying DOM node. The element check leans on isHtmlElement from @basmilius/utils.
Type signature
ts
type EligibleTarget = HTMLElement | ComponentPublicInstance | Window | Document;
declare function unwrapTarget(
target: MaybeRefOrGetter<EligibleTarget | null | undefined>
): HTMLElement | Window | Document | null;