I've built a renderless base icon Vue component that just outputs whatever is in its slot but with some additional attributes added to the slotted component. One of the attributes I set is class="icon"
which allows me to have my styles for all icons in one place.
Icon.vue
<script>
import Vue from "vue";
export default {
name: "u-icon",
render(createElement) {
const svg = this.$slots.default[0];
const attrs = svg.data.attrs;
attrs.xmlns = "http://www.w3.org/2000/svg";
attrs.class = "icon";
attrs["aria-hidden"] = "true";
attrs.focusable = "false";
attrs.role = "img";
return this.$slots.default;
},
};
</script>
<style>
.icon {
// ...
}
</style>
Close-Icon.vue
I can then more easily create many different icon components such as this close icon component.
<template>
<u-icon>
<svg viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<line x1="4" y1="4" x2="20" y2="20"></line>
<line x1="4" y1="20" x2="20" y2="4"></line>
</svg>
</u-icon>
</template>
<script>
import icon from "~/icon.vue";
export default {
name: "close-icon",
components: {
"u-icon": icon,
},
};
</script>
Actual Outcome
The problem is when I use the close-icon
component and set a class
like so:
<close-icon class="foo"/>
The rendered output is not combining the two classes "icon foo"
.
<svg class="foo" ...>
...
</svg>
Expected Outcome
How can I ensure that the class is not overwritten but appended to so that the output is:
<svg class="icon foo" ...>
...
</svg>
If possible, any changes I make should be made to Icon.vue
and I'd like to avoid making changes to Close-Icon.vue
since there are a large number of other icons.
CodePudding user response:
class
and style
bindings are not part of the $attrs - source
I'm not sure why your code even work without the class
binding on close-icon
but if you want to add/modify the classes of existing VNode (coming from default slot), you should use svg.data.staticClass
instead of svg.data.attrs.class
Example below even handles the case in which static class is also placed directly on the svg
element inside close-icon
- renders <svg class="foo bar icon" ...
Vue.component("u-icon", {
render(createElement) {
const svg = this.$slots.default[0];
const attrs = svg.data.attrs;
attrs.xmlns = "http://www.w3.org/2000/svg";
attrs["aria-hidden"] = "true";
attrs.focusable = "false";
attrs.role = "img";
svg.data.staticClass = (svg.data.staticClass || "") " icon";
return this.$slots.default;
},
});
Vue.component("close-icon", {
template:`
<u-icon>
<svg viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" class="bar">
<line x1="4" y1="4" x2="20" y2="20"></line>
<line x1="4" y1="20" x2="20" y2="4"></line>
</svg>
</u-icon>
`
})
new Vue({
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
<div id="app">
<close-icon class="foo"></close-icon>
</div>