I have the following:
<script setup>
import { ref } from 'vue'
import Input from './Input.js'
import Text from './Text.vue'
const msg = ref('Hello World!')
</script>
<template>
<Input label="Message">
<Text v-model="msg">
<template #icon>❤️</template>
</Text>
</Input>
</template>
Input.js:
import {h} from 'vue';
export default function Input({label}, {slots}){
return slots.default().map(vnode => h(vnode, {}, {label: () => label}))
};
Text.vue:
<script setup>
const model = defineModel();
</script>
<template>
<div :class="{iconed: $slots.icon}">
<div v-if="$slots.label"><slot name="label"/></div>
<span v-if="$slots.icon" class="icon"><slot name="icon"/></span>
<input v-model="model"/>
</div>
</template>
Any way keep the icon
slot for Text
?
I know that I can put the slot into Input
and forward it the Text
but I would rather keep it like in the template to state explicitly that the slot belongs to Text
.
h(vnode)
is a reliable but not documented alternative to cloneVNode
which merges props but not children.
{ label: ... }
overrides VNode's children, this needs to be additionally taken care of:
return slots.default().map(vnode => {
const children = Object.fromEntries(
Object.entries(vnode.children || {})
.filter(([name]) => !name.includes('_'))
);
return h(vnode, {}, { ...children, label: () => label });
});
Not filtering out internal underscored properties from children may result in bugs.