-
Notifications
You must be signed in to change notification settings - Fork 257
/
find.ts
159 lines (137 loc) · 4.32 KB
/
find.ts
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import {
ComponentInternalInstance,
VNode,
VNodeChild,
VNodeArrayChildren,
VNodeNormalizedChildren,
VNodeTypes
} from 'vue'
import { getOriginalComponentFromStub } from '../stubs'
import { FindAllComponentsSelector } from '../types'
import { isComponent } from '../utils'
import { matchName } from './matchName'
import { unwrapLegacyVueExtendComponent } from './vueCompatSupport'
import { getComponentName, getComponentRegisteredName } from './componentName'
/**
* Detect whether a selector matches a VNode
* @param node
* @param selector
* @return {boolean | ((value: any) => boolean)}
*/
export function matches(
node: VNode,
rawSelector: FindAllComponentsSelector
): boolean {
const selector = unwrapLegacyVueExtendComponent(rawSelector)
// do not return none Vue components
if (!node.component) return false
const nodeType = node.type
if (!isComponent(nodeType)) return false
if (typeof selector === 'string') {
return node.el?.matches?.(selector)
}
// When we're using stubs we want user to be able to
// find stubbed components both by original component
// or stub definition. That's why we are trying to
// extract original component and also stub, which was
// used to create specialized stub for render
const nodeTypeCandidates: VNodeTypes[] = [
nodeType,
getOriginalComponentFromStub(nodeType)
].filter(Boolean) as VNodeTypes[]
// our selector might be a stub itself
const target = getOriginalComponentFromStub(selector) ?? selector
if (nodeTypeCandidates.includes(target)) {
return true
}
let componentName: string | undefined
componentName = getComponentName(node.component, nodeType)
let selectorName = selector.name
// the component and selector both have a name
if (componentName && selectorName) {
return matchName(selectorName, componentName)
}
componentName =
getComponentRegisteredName(node.component, nodeType) || undefined
// if a name is missing, then check the locally registered components in the parent
if (node.component.parent) {
const registry = (node.component.parent as any).type.components
for (const key in registry) {
// is it the selector
if (!selectorName && registry[key] === selector) {
selectorName = key
}
// is it the component
if (!componentName && registry[key] === nodeType) {
componentName = key
}
}
}
if (selectorName && componentName) {
return matchName(selectorName, componentName)
}
return false
}
/**
* Filters out the null, undefined and primitive values,
* to only keep VNode and VNodeArrayChildren values
* @param value
*/
function nodesAsObject(
value: VNodeChild | VNodeArrayChildren
): value is VNodeArrayChildren | VNode {
return !!value && typeof value === 'object'
}
/**
* Collect all children
* @param nodes
* @param children
*/
function aggregateChildren(nodes: VNode[], children: VNodeNormalizedChildren) {
if (children && Array.isArray(children)) {
const reversedNodes = [...children].reverse().filter(nodesAsObject)
reversedNodes.forEach((node: VNodeArrayChildren | VNode) => {
if (Array.isArray(node)) {
aggregateChildren(nodes, node)
} else {
nodes.unshift(node)
}
})
}
}
function findAllVNodes(
vnode: VNode,
selector: FindAllComponentsSelector
): VNode[] {
const matchingNodes: VNode[] = []
const nodes: VNode[] = [vnode]
while (nodes.length) {
const node = nodes.shift()!
aggregateChildren(nodes, node.children)
if (node.component) {
aggregateChildren(nodes, [node.component.subTree])
}
if (node.suspense) {
// match children if component is Suspense
const { activeBranch } = node.suspense
aggregateChildren(nodes, [activeBranch])
}
if (matches(node, selector) && !matchingNodes.includes(node)) {
matchingNodes.push(node)
}
}
return matchingNodes
}
export function find(
root: VNode,
selector: FindAllComponentsSelector
): ComponentInternalInstance[] {
let matchingVNodes = findAllVNodes(root, selector)
if (typeof selector === 'string') {
// When searching by CSS selector we want only one (topmost) vnode for each el`
matchingVNodes = matchingVNodes.filter(
(vnode: VNode) => vnode.component!.parent?.vnode.el !== vnode.el
)
}
return matchingVNodes.map((vnode: VNode) => vnode.component!)
}