-
-
Notifications
You must be signed in to change notification settings - Fork 14.6k
/
only-child.tsx
83 lines (74 loc) 路 2.02 KB
/
only-child.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import {
Comment,
Fragment,
Text,
cloneVNode,
defineComponent,
inject,
withDirectives,
} from 'vue'
import { NOOP, isObject } from '@vue/shared'
import {
FORWARD_REF_INJECTION_KEY,
useForwardRefDirective,
} from '@element-plus/hooks'
import { debugWarn } from '@element-plus/utils'
import type { Ref, VNode } from 'vue'
const NAME = 'ElOnlyChild'
export const OnlyChild = defineComponent({
name: NAME,
setup(_, { slots, attrs }) {
const forwardRefInjection = inject(FORWARD_REF_INJECTION_KEY)
const forwardRefDirective = useForwardRefDirective(
forwardRefInjection?.setForwardRef ?? NOOP
)
return () => {
const defaultSlot = slots.default?.(attrs)
if (!defaultSlot) return null
if (defaultSlot.length > 1) {
debugWarn(NAME, 'requires exact only one valid child.')
return null
}
const firstLegitNode = findFirstLegitChild(defaultSlot)
if (!firstLegitNode) {
debugWarn(NAME, 'no valid child node found')
return null
}
return withDirectives(cloneVNode(firstLegitNode!, attrs), [
[forwardRefDirective],
])
}
},
})
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) {
return <span class="el-only-child__content">{s}</span>
}
export type OnlyChildExpose = {
forwardRef: Ref<HTMLElement>
}