70 lines
1.6 KiB
Vue
70 lines
1.6 KiB
Vue
<script lang="ts">
|
|
import {
|
|
Comment,
|
|
Fragment,
|
|
Text,
|
|
cloneVNode,
|
|
defineComponent,
|
|
h,
|
|
withDirectives,
|
|
} from 'vue'
|
|
|
|
// eslint-disable-next-line vue/prefer-import-from-vue
|
|
import { isObject } from '@vue/shared'
|
|
import type { VNode } from 'vue'
|
|
import useClassname from '../../composables/use-classname'
|
|
|
|
export default defineComponent({
|
|
name: 'UiRenderless',
|
|
setup(_, { slots, attrs }) {
|
|
return () => {
|
|
const defaultSlot = slots.default?.(attrs)
|
|
if (!defaultSlot)
|
|
return null
|
|
|
|
if (defaultSlot.length > 1)
|
|
return null
|
|
|
|
const firstLegitNode = findFirstLegitChild(defaultSlot)
|
|
if (!firstLegitNode)
|
|
return null
|
|
|
|
return withDirectives(cloneVNode(firstLegitNode!, attrs, true), [])
|
|
}
|
|
},
|
|
})
|
|
|
|
function findFirstLegitChild(node: VNode[] | undefined): VNode | null {
|
|
if (!node)
|
|
return null
|
|
const children = node as VNode[]
|
|
for (const child of children) {
|
|
/**
|
|
* when user uses h(Fragment, [text]) to render plain string,
|
|
* this switch case just cannot handle, when the value is primitives
|
|
* we should just return the wrapped string
|
|
*/
|
|
if (isObject(child)) {
|
|
switch (child.type) {
|
|
case Comment:
|
|
continue
|
|
case Text:
|
|
case 'svg':
|
|
return wrapTextContent(child)
|
|
case Fragment:
|
|
return findFirstLegitChild(child.children as VNode[])
|
|
default:
|
|
return child
|
|
}
|
|
}
|
|
return wrapTextContent(child)
|
|
}
|
|
return null
|
|
}
|
|
|
|
function wrapTextContent(s: string | VNode) {
|
|
const cn = useClassname('ui-renderless')
|
|
return h('span', { class: cn.e('content') }, [s])
|
|
}
|
|
</script>
|