Let's create our first wrapper. For this, we will use the va-input from Vuestic UI:
<template>
<ElementLayout>
<template #element>
<VaInput />
</template>
<template v-for="(component, slot) in elementSlots" #[slot]>
<slot :name="slot" :el$="el$">
<component :is="component" :el$="el$" />
</slot>
</template>
</ElementLayout>
</template>
<script>
import { defineElement } from '@vueform/vueform'
import { VaInput } from 'vuestic-ui'
export default defineElement({
name: 'VaInputElement',
components: [VaInput],
setup(props, { element }) {
// Initial setup
},
})
</script>
<template v-for="(component, slot) in elementSlots" #[slot]>
<slot :name="slot" :el$="el$">
<component :is="component" :el$="el$" />
</slot>
</template>
import en from '@vueform/vueform/locales/en'
import vueform from '@vueform/vueform/dist/vueform'
import { defineConfig } from '@vueform/vueform'
import '@vueform/vueform/dist/vueform.css';
import VaInputElement from './src/VaInputElement.vue';
export default defineConfig({
theme: vueform,
locales: { en },
locale: 'en',
elements: [VaInputElement]
})
<template>
<Vueform>
<VaInputElement name="MyCustomInput"/>
</Vueform>
</template>
<script setup lang="ts">
import VaInputElement from './VaInputElement.vue'
</script>
<template>
<ElementLayout>
<template #element>
<VaInput :model-value="value" @update:model-value="handleInput" />
</template>
<template v-for="(component, slot) in elementSlots" #[slot]>
<slot :name="slot" :el$="el$">
<component :is="component" :el$="el$" />
</slot>
</template>
</ElementLayout>
</template>
<script>
import { defineElement } from '@vueform/vueform'
import { VaInput } from 'vuestic-ui'
export default defineElement({
name: 'VaInputElement',
components: [VaInput],
setup(props, { element }) {
const { value, update } = element
const handleInput = (val) => update(val)
return {
value,
handleInput,
}
},
})
</script>
<script>
import { defineElement } from '@vueform/vueform'
import { VaInput } from 'vuestic-ui'
import { omit } from './omit'
import { computed } from 'vue'
const propsToOmit = ['rules']
export default defineElement({
name: 'VaInputElement',
components: [VaInput],
props: {
...omit(VaInput.props, propsToOmit),
},
setup(props, { element }) {
const { value, update } = element
const handleInput = (val) => update(val)
const omittedProps = computed(() => omit(props, propsToOmit))
return {
value,
props: omittedProps,
handleInput,
}
},
})
</script>
<template>
<Vueform>
<VaInputElement name="MyCustomInput" rules="required|email|min:5" />
</Vueform>
</template>
<script setup lang="ts">
import VaInputElement from './VaInputElement.vue';
</script>
const listeners = this.emits.reduce((acc, curr) => {
acc[curr] = (...args) => {
fire(curr, ...args)
}
return acc
}, {})
<VaInput v-bind="props" :model-value="value" @update:model-value="handleInput" v-on="listeners" />
<template>
<ElementLayout>
<template #element>
<VaInput v-bind="props" :model-value="value" @update:model-value="handleInput" v-on="listeners">
<template v-for="slotKey in vuesticSlotKeys" #[slotKey]="slotProps">
<slot :name="slotKey" v-bind="slotProps" />
</template>
<template v-for="(component, slot) in schemaSlots" #[slot]="slotProps">
<slot :name="slot" :el$="el$" v-bind="slotProps">
<component :is="component" :el$="el$" />
</slot>
</template>
</VaInput>
</template>
<template v-for="(component, slot) in elementSlots" #[slot]>
<slot :name="slot" :el$="el$">
<component :is="component" :el$="el$" />
</slot>
</template>
</ElementLayout>
</template>
<template>
<Vueform>
<VaInputElement name="MyCustomInput" rules="required|email|min:5" @update:modelValue="console.log($event)">
<template #appendInner>
appendInner
</template>
</VaInputElement>
</Vueform>
</template>
<script setup lang="ts">
import VaInputElement from './VaInputElement.vue';
</script>
import { defineElement } from '@vueform/vueform'
import { computed } from 'vue'
import { omit } from './omit'
export function defineVuesticElement({ name, components, props, emits, propsToOmit }) {
return defineElement({
name,
components,
props,
emits,
setup(props, { element, slots }) {
const { value, update, fire, elementSlots } = element
const handleInput = (val) => update(val)
const omittedProps = computed(() => omit(props, propsToOmit))
const listeners = this.emits.reduce((acc, curr) => {
acc[curr] = (...args) => {
fire(curr, ...args)
}
return acc
}, {})
const vueFormSlotNames = Object.keys(elementSlots.value)
const allSlotNames = Object.keys(slots)
const vuesticSlotKeys = allSlotNames.filter(
name => !vueFormSlotNames.includes(name)
)
const schemaSlots = computed(() => {
const result = {}
for (const key in props.slots) {
if (!vueFormSlotNames.includes(key)) {
result[key] = props.slots[key]
}
}
return result
})
return {
value,
props: omittedProps,
listeners,
vuesticSlotKeys,
schemaSlots,
handleInput,
}
},
})
}
<template>
<ElementLayout>
<template #element>
<VaInput v-bind="props" :model-value="value" @update:model-value="handleInput" v-on="listeners" ref="input">
<template v-for="slotKey in vuesticSlotKeys" #[slotKey]="slotProps">
<slot :name="slotKey" v-bind="slotProps" />
</template>
<template v-for="(component, slot) in schemaSlots" #[slot]="slotProps">
<slot :name="slot" :el$="el$" v-bind="slotProps">
<component :is="component" :el$="el$" />
</slot>
</template>
</VaInput>
</template>
<template v-for="(component, slot) in elementSlots" #[slot]>
<slot :name="slot" :el$="el$">
<component :is="component" :el$="el$" />
</slot>
</template>
</ElementLayout>
</template>
<script>
import { defineVuesticElement } from './defineVuesticElement';
import { VaInput } from 'vuestic-ui';
import { omit } from './omit';
const propsToOmit = ['rules', 'label']
const props = {
...omit(VaInput.props, propsToOmit)
}
export default defineVuesticElement({
name: 'VaInputElement',
components: [VaInput],
props,
emits: VaInput.emits,
propsToOmit
})
</script>
<template>
<Vueform ref="form$">
<VaInputElement name="custom" />
</Vueform>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import VaInputElement from './VaInputElement.vue'
const form$ = ref(null)
onMounted(() => {
console.log(form$.value.el$('custom').input)
})
</script>
<template>
<Vueform :schema="schema" />
</template>
<script setup>
const schema = {
MyCustomInput: {
type: 'VaInput',
rules: "required|email|min:5",
['onUpdate:modelValue']: (v) => console.log(v),
slots: {
appendInner: () => 'appendInner',
}
}
}
</script>
slots: {
appendInner: () => 'appendInner',
}
import { h } from 'vue';
const schema = {
MyCustomInput: {
type: 'va-input',
slots: {
appendInner: {
props: ['el$'],
render() {
console.log(this.el$)
return h('div', 'Schema Slot')
}
}
}
}
}
import { h } from 'vue';
import TestComponent from './TestComponent.vue';
const schema = {
MyCustomInput: {
type: 'va-input',
slots: {
appendInner: () => h(TestComponent, {
label: 'hello',
onSomeEvent: () => console.log('onSomeEvent')
}, {
icon: () => '👋'
})
}
}
}
In this guide, we walked through building a custom wrapper around Vuestic UI components for use inside Vueform. We covered everything you need for full compatibility:
This approach gives you the flexibility of Vueform with the design power of Vuestic. If you're working with many components, abstract your wrapper logic for easier maintenance.
For a full-featured example and out-of-the-box support, check out our ready-made integration package.
Happy building! 🚀