@staszek998/v-html
Version:
Simple Vue component capable of rendering the passed-in HTML string without wrapping it within a redundant HTML tag.
99 lines (85 loc) • 2.66 kB
text/typescript
/*******************************************************************************
* @copyright 2021 IDEALIGN Stanisław Gregor
******************************************************************************/
import Vue, { VNode } from 'vue'
/**
* Extracts the contents from the passed-in `<slot>`.
*
* @param slot - The `<slot>` which contents we wont to resolve.
*
* @author Stanisław Gregor, Vitim.us
*
* @see https://stackoverflow.com/a/52749314/11869579
*/
const getSlotContents = (slot: VNode[]): string => {
if (typeof slot === 'undefined' || !Array.isArray(slot) || slot.length === 0) {
return ''
}
/**
* The contents from all the VNodes in the `<slot>`.
*/
const textContents: string[] = slot.map(vnode => {
if (typeof vnode.text === 'string') {
return vnode.text
}
if (typeof vnode.elm !== 'undefined' && typeof vnode.elm.textContent === 'string') {
return vnode.elm.textContent
}
return ''
})
return textContents.join('')
}
/**
* Simple functional component that accepts HTML string and renders it as-is.
* Why? Because `v-html` directive is not always an option 😉
*
* @author Stanisław Gregor, Thorsten Lünborg
*
* @see https://forum.vuejs.org/t/raw-html-without-a-parent-element-via-v-html/87160
* @see https://jsfiddle.net/Linusborg/mfqjk5hm/
*/
export const VHTML = Vue.extend({
functional: true,
name: 'VHTML',
props: {
/**
* The HTML string to render.
*/
html: { type: String, required: false }
},
render (h, ctx) {
/**
* Determines whether the `[html]` prop has been specified.
*/
const hasHtmlProp: boolean = typeof ctx.props.html === 'string'
/**
* Determines whether the default `<slot>` has been populated.
*/
const hasDefaultSlot: boolean =
typeof ctx.slots().default !== 'undefined' &&
Array.isArray(ctx.slots().default) &&
ctx.slots().default.length > 0
/**
* The HTML that is to be rendered.
*/
let content: string
if (hasHtmlProp && hasDefaultSlot) {
console.warn(
'VHTML: Received both [html] prop AND default <slot> - returning only the HTML from the <slot>.'
)
content = ''
} else if (hasHtmlProp) {
content = ctx.props.html
} else if (hasDefaultSlot) {
content = getSlotContents(ctx.slots().default)
} else {
console.warn(
'VHTML: Expected to find the HTML string in [html] prop OR inside the default <slot>, but none was present!'
)
content = ''
}
return new Vue({
template: `<div>${content}</div>`
}).$mount()._vnode.children as VNode[]
}
})