Skip to content

Commit bb9d265

Browse files
authored
fix(compiler-sfc): handle nested :deep in selector pseudos (#14725)
close #14724
1 parent 60402cd commit bb9d265

2 files changed

Lines changed: 166 additions & 5 deletions

File tree

packages/compiler-sfc/__tests__/compileStyle.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,41 @@ color: red
151151
":where(.foo[data-v-test] .bar) { color: red;
152152
}"
153153
`)
154+
expect(compileScoped(`:is(:deep(.foo)) .bar { color: red; }`))
155+
.toMatchInlineSnapshot(`
156+
":is([data-v-test] .foo) .bar { color: red;
157+
}"
158+
`)
159+
expect(compileScoped(`:where(:deep(.foo)) .bar { color: red; }`))
160+
.toMatchInlineSnapshot(`
161+
":where([data-v-test] .foo) .bar { color: red;
162+
}"
163+
`)
164+
expect(compileScoped(`:is(:deep(.foo), .bar) .baz { color: red; }`))
165+
.toMatchInlineSnapshot(`
166+
":is([data-v-test] .foo) .baz, :is(.bar) .baz[data-v-test] { color: red;
167+
}"
168+
`)
169+
expect(compileScoped(`:where(:deep(.foo), .bar) .baz { color: red; }`))
170+
.toMatchInlineSnapshot(`
171+
":where([data-v-test] .foo) .baz, :where(.bar) .baz[data-v-test] { color: red;
172+
}"
173+
`)
174+
expect(compileScoped(`:not(:deep(.foo)) .bar { color: red; }`))
175+
.toMatchInlineSnapshot(`
176+
":not([data-v-test] .foo) .bar { color: red;
177+
}"
178+
`)
179+
expect(compileScoped(`:has(:deep(.foo)) .bar { color: red; }`))
180+
.toMatchInlineSnapshot(`
181+
":has([data-v-test] .foo) .bar { color: red;
182+
}"
183+
`)
184+
expect(compileScoped(`:has(:deep(.foo), .bar) .baz { color: red; }`))
185+
.toMatchInlineSnapshot(`
186+
":has([data-v-test] .foo) .baz, :has(.bar) .baz[data-v-test] { color: red;
187+
}"
188+
`)
154189
expect(compileScoped(`:deep(.foo) { color: red; .bar { color: red; } }`))
155190
.toMatchInlineSnapshot(`
156191
"[data-v-test] .foo { color: red;

packages/compiler-sfc/src/style/pluginScoped.ts

Lines changed: 131 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ function rewriteSelector(
101101
) {
102102
let node: selectorParser.Node | null = null
103103
let shouldInject = !deep
104+
let hasNestedDeep = false
105+
let splitForNestedDeep = false
104106
// find the last child node to insert attribute selector
105107
selector.each(n => {
106108
// DEPRECATED ">>>" and "/deep/" combinator
@@ -119,6 +121,64 @@ function rewriteSelector(
119121

120122
if (n.type === 'pseudo') {
121123
const { value } = n
124+
if (isDeepContainerPseudo(n)) {
125+
const hasDeepSelectors = n.nodes.some(selector =>
126+
selector.some(isDeepSelector),
127+
)
128+
if (hasDeepSelectors) {
129+
const hasScopeAnchor = !!node
130+
const hasMixedSelectors = n.nodes.some(
131+
selector => !selector.some(isDeepSelector),
132+
)
133+
const hasTrailingNodes = selector.index(n) < selector.length - 1
134+
if (
135+
canSplitDeepContainerPseudo(n) &&
136+
!deep &&
137+
!hasScopeAnchor &&
138+
hasMixedSelectors &&
139+
hasTrailingNodes
140+
) {
141+
splitSelectorForNestedDeep(
142+
id,
143+
rule,
144+
selector,
145+
selectorRoot,
146+
n,
147+
deep,
148+
slotted,
149+
)
150+
splitForNestedDeep = true
151+
return false
152+
}
153+
154+
if (
155+
value === ':not' &&
156+
!deep &&
157+
!hasScopeAnchor &&
158+
hasMixedSelectors &&
159+
hasTrailingNodes
160+
) {
161+
return
162+
}
163+
164+
n.nodes.forEach(selector =>
165+
rewriteSelector(
166+
id,
167+
rule,
168+
selector,
169+
selectorRoot,
170+
deep || hasScopeAnchor,
171+
slotted,
172+
),
173+
)
174+
if (!hasScopeAnchor) {
175+
node = n
176+
shouldInject = false
177+
}
178+
hasNestedDeep = true
179+
}
180+
}
181+
122182
// deep: inject [id] attribute at the node before the ::v-deep
123183
// combinator.
124184
if (value === ':deep' || value === '::v-deep') {
@@ -219,15 +279,20 @@ function rewriteSelector(
219279
}
220280

221281
if (
222-
(n.type !== 'pseudo' && n.type !== 'combinator') ||
223-
(n.type === 'pseudo' &&
224-
(n.value === ':is' || n.value === ':where') &&
225-
!node)
282+
!hasNestedDeep &&
283+
((n.type !== 'pseudo' && n.type !== 'combinator') ||
284+
(n.type === 'pseudo' &&
285+
(n.value === ':is' || n.value === ':where') &&
286+
!node))
226287
) {
227288
node = n
228289
}
229290
})
230291

292+
if (splitForNestedDeep) {
293+
return
294+
}
295+
231296
if (rule.nodes.some(node => node.type === 'rule')) {
232297
const deep = (rule as any).__deep
233298
if (!deep) {
@@ -240,7 +305,7 @@ function rewriteSelector(
240305
shouldInject = deep
241306
}
242307

243-
if (node) {
308+
if (node && !hasNestedDeep) {
244309
const { type, value } = node as selectorParser.Node
245310
if (type === 'pseudo' && (value === ':is' || value === ':where')) {
246311
;(node as selectorParser.Pseudo).nodes.forEach(value =>
@@ -279,6 +344,67 @@ function isSpaceCombinator(node: selectorParser.Node) {
279344
return node.type === 'combinator' && /^\s+$/.test(node.value)
280345
}
281346

347+
function isDeepSelector(node: selectorParser.Node): boolean {
348+
if (
349+
node.type === 'pseudo' &&
350+
(node.value === ':deep' || node.value === '::v-deep')
351+
) {
352+
return true
353+
}
354+
355+
return !!(
356+
node as selectorParser.Node & { nodes?: selectorParser.Node[] }
357+
).nodes?.some(child => isDeepSelector(child))
358+
}
359+
360+
function isDeepContainerPseudo(
361+
node: selectorParser.Node,
362+
): node is selectorParser.Pseudo {
363+
return (
364+
node.type === 'pseudo' &&
365+
(node.value === ':is' ||
366+
node.value === ':where' ||
367+
node.value === ':has' ||
368+
node.value === ':not')
369+
)
370+
}
371+
372+
function canSplitDeepContainerPseudo(node: selectorParser.Pseudo): boolean {
373+
return (
374+
node.value === ':is' || node.value === ':where' || node.value === ':has'
375+
)
376+
}
377+
378+
function splitSelectorForNestedDeep(
379+
id: string,
380+
rule: Rule,
381+
selector: selectorParser.Selector,
382+
selectorRoot: selectorParser.Root,
383+
pseudo: selectorParser.Pseudo,
384+
deep: boolean,
385+
slotted: boolean,
386+
) {
387+
const pseudoIndex = selector.index(pseudo)
388+
const selectors = pseudo.nodes.map((branch, index) => {
389+
const branchSelector = selector.clone()
390+
if (branchSelector.first) {
391+
branchSelector.first.spaces.before =
392+
index === 0 ? selector.first.spaces.before : ' '
393+
}
394+
const branchPseudo = branchSelector.at(pseudoIndex) as selectorParser.Pseudo
395+
const branchClone = branch.clone()
396+
if (branchClone.first) {
397+
branchClone.first.spaces.before = ''
398+
}
399+
branchPseudo.removeAll()
400+
branchPseudo.append(branchClone)
401+
rewriteSelector(id, rule, branchSelector, selectorRoot, deep, slotted)
402+
return branchSelector
403+
})
404+
405+
selector.replaceWith(...selectors)
406+
}
407+
282408
function extractAndWrapNodes(parentNode: Rule | AtRule) {
283409
if (!parentNode.nodes) return
284410
const nodes = parentNode.nodes.filter(

0 commit comments

Comments
 (0)