Skip to content

Instantly share code, notes, and snippets.

@padcom
Last active November 7, 2024 08:35
Show Gist options
  • Save padcom/f1b0f1528044a07674d3f6063062cfb4 to your computer and use it in GitHub Desktop.
Save padcom/f1b0f1528044a07674d3f6063062cfb4 to your computer and use it in GitHub Desktop.
Example how to use `defineModel()` and `useHost()` in Vue.js 3.5 in a custom webcomponent
<template>
<!--
First things first, the `:value` cannot be `v-model="value"` because it would
break reactivity. It's the same as assigning things directly to `value.value`
and as explained below, assigning the value needs to be done through the host
element (`host.value = '...'`).
Secondly, the `<select>` element emits its own `input` event that bubbles.
Unfortunately, the event is disguised under the custom element, and not the
`<select>` that actually triggered the value. This is due to the `shadowRoot`
being used here.
Therefore, to emit the right event at the right time we stop the propagation
of `input` event here and then, after the proper value has been updated
(as in `host.value`) we emit the `input` event ourselves.
-->
<select :value @input.stop="update(($event.target as HTMLSelectElement).value)">
<option value="arial">Arial</option>
<option value="monospace">monospace</option>
</select>
</template>
<script lang="ts">
import { VueElement } from 'vue'
export interface HTMLElementFontInput extends VueElement {
value: string
}
</script>
<script lang="ts" setup>
import { useHost } from 'vue'
// This defines a `value` property on the custom element that is reactive,
// but ONLY if accessed through the host element. Setting its value.value = '...'
// directly (or value = '...' in the template) breaks reactivity.
// That is exactly why we need to use the `host` element to set the new value,
// instead using `v-model` on the `<select>`.
const value = defineModel<string>('value')
const host = useHost() as HTMLElementFontInput
function update(newValue: string) {
host.value = newValue
// The `change` event is the default one GrapesJS listens to in a trait
host?.dispatchEvent(new InputEvent('change', { bubbles: true }))
}
</script>
export function pluginFontSelectTrait(editor: Editor) {
try {
customElements.define('font-input', defineCustomElement(FontInput))
} catch (e: any) {
console.warn('Unable to register webcomponent:', e.message)
}
editor.TraitManager.addType('font-select', {
createInput() {
return document.createElement('font-input') as HTMLElementFontInput
},
onUpdate({ trait, elInput }) {
elInput.value = trait.getValue()
},
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment