useAttrs().style and useAttrs().class - How to bind attrs.style and attrs.class to external HTMLDivElement instance?
#14555
-
|
I have created a native div element: const div = document.createElement('div');I need to create an element like this because of the third-party mapping library that I use. I would like to use Vue's const div = ref<HTMLDivElement>();
onMounted(() => {
div.value = document.createElement('div');
});
defineOptions({
inheritAttrs: false,
});
const attrs = useAttrs();The export type StyleValue = false | null | undefined | string | CSSProperties | Array<StyleValue>;
export type ClassValue = false | null | undefined | string | Record<string, any> | Array<ClassValue>;I have not found a way to easily take Alternatively, is there a utility function that would convert these rather complex types (which are not the easiest to work with manually), and turn them into something easier to digest, eg. a simple |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 3 replies
-
|
@rtcpw Nice integration scenario. Since that div is created outside Vue template rendering, fallthrough attrs (class / style) are not auto-applied there. Practical approach:
That keeps Vue as source-of-truth while still supporting third-party DOM ownership. References: |
Beta Was this translation helpful? Give feedback.
-
|
Vue does have normalization helpers for this, but I would be careful about treating them as a public “apply this to any DOM element” API. For classes, import { normalizeClass } from "vue";
div.className = normalizeClass(attrs.class);For styles, import { normalizeStyle } from "vue";
const normalized = normalizeStyle(attrs.style);Then you can apply it manually: import { normalizeClass, normalizeStyle, watchEffect } from "vue";
watchEffect(() => {
const el = div.value;
if (!el) {
return;
}
el.className = normalizeClass(attrs.class);
const style = normalizeStyle(attrs.style);
el.removeAttribute("style");
if (typeof style === "string") {
el.style.cssText = style;
} else if (style) {
for (const [key, value] of Object.entries(style)) {
if (value == null) {
continue;
}
el.style.setProperty(
key.startsWith("--")
? key
: key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`),
String(value),
);
}
}
});That said, the more Vue native approach is to let Vue render a small wrapper element and pass that actual DOM element to the mapping library: <script setup lang="ts">
import { ref, onMounted } from "vue";
const el = ref<HTMLDivElement>();
onMounted(() => {
if (!el.value) {
return;
}
mapLibrary.useElement(el.value);
});
</script>
<template>
<div ref="el" />
</template>Then consumers can use normal Vue bindings: <MapDiv
:class="{ active: isActive }"
:style="{ width: '100px', height: '100px' }"
/>If the library absolutely requires you to create the element yourself with The short version is: use |
Beta Was this translation helpful? Give feedback.
-
|
Since the element lives outside Vue's render tree, you mirror the attrs yourself in a import { ref, onMounted, useAttrs, watchEffect, normalizeClass, normalizeStyle } from 'vue'
defineOptions({ inheritAttrs: false })
const div = ref<HTMLDivElement>()
const attrs = useAttrs()
onMounted(() => {
div.value = document.createElement('div')
// hand div.value to your mapping library here
})
watchEffect(() => {
const el = div.value
if (!el) return
el.className = normalizeClass(attrs.class)
const style = normalizeStyle(attrs.style)
el.style.cssText =
typeof style === 'string'
? style
: Object.entries(style ?? {}).map(([k, v]) => `${k}:${v}`).join(';')
})
One caveat: these are normalization helpers, not a public "apply to any DOM node" API, so the surface could shift across minor versions. For a single third-party element that's a fair trade. If you want zero coupling, the same two functions are trivial to inline. |
Beta Was this translation helpful? Give feedback.
Vue does have normalization helpers for this, but I would be careful about treating them as a public “apply this to any DOM element” API.
For classes,
normalizeClassis the closest fit:For styles,
normalizeStylecan normalize Vue’s accepted:styleshapes into an object or string form:Then you can apply it manually: