":where(.foo[data-v-test] .bar) { color: red;
}"
`)
+ expect(compileScoped(`:is(:deep(.foo)) .bar { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":is([data-v-test] .foo) .bar { color: red;
+ }"
+ `)
+ expect(compileScoped(`:where(:deep(.foo)) .bar { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":where([data-v-test] .foo) .bar { color: red;
+ }"
+ `)
+ expect(compileScoped(`:is(:deep(.foo), .bar) .baz { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":is([data-v-test] .foo) .baz, :is(.bar) .baz[data-v-test] { color: red;
+ }"
+ `)
+ expect(compileScoped(`:where(:deep(.foo), .bar) .baz { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":where([data-v-test] .foo) .baz, :where(.bar) .baz[data-v-test] { color: red;
+ }"
+ `)
+ expect(compileScoped(`:not(:deep(.foo)) .bar { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":not([data-v-test] .foo) .bar { color: red;
+ }"
+ `)
+ expect(compileScoped(`:has(:deep(.foo)) .bar { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":has([data-v-test] .foo) .bar { color: red;
+ }"
+ `)
+ expect(compileScoped(`:has(:deep(.foo), .bar) .baz { color: red; }`))
+ .toMatchInlineSnapshot(`
+ ":has([data-v-test] .foo) .baz, :has(.bar) .baz[data-v-test] { color: red;
+ }"
+ `)
expect(compileScoped(`:deep(.foo) { color: red; .bar { color: red; } }`))
.toMatchInlineSnapshot(`
"[data-v-test] .foo { color: red;
) {
let node: selectorParser.Node | null = null
let shouldInject = !deep
+ let hasNestedDeep = false
+ let splitForNestedDeep = false
// find the last child node to insert attribute selector
selector.each(n => {
// DEPRECATED ">>>" and "/deep/" combinator
if (n.type === 'pseudo') {
const { value } = n
+ if (isDeepContainerPseudo(n)) {
+ const hasDeepSelectors = n.nodes.some(selector =>
+ selector.some(isDeepSelector),
+ )
+ if (hasDeepSelectors) {
+ const hasScopeAnchor = !!node
+ const hasMixedSelectors = n.nodes.some(
+ selector => !selector.some(isDeepSelector),
+ )
+ const hasTrailingNodes = selector.index(n) < selector.length - 1
+ if (
+ canSplitDeepContainerPseudo(n) &&
+ !deep &&
+ !hasScopeAnchor &&
+ hasMixedSelectors &&
+ hasTrailingNodes
+ ) {
+ splitSelectorForNestedDeep(
+ id,
+ rule,
+ selector,
+ selectorRoot,
+ n,
+ deep,
+ slotted,
+ )
+ splitForNestedDeep = true
+ return false
+ }
+
+ if (
+ value === ':not' &&
+ !deep &&
+ !hasScopeAnchor &&
+ hasMixedSelectors &&
+ hasTrailingNodes
+ ) {
+ return
+ }
+
+ n.nodes.forEach(selector =>
+ rewriteSelector(
+ id,
+ rule,
+ selector,
+ selectorRoot,
+ deep || hasScopeAnchor,
+ slotted,
+ ),
+ )
+ if (!hasScopeAnchor) {
+ node = n
+ shouldInject = false
+ }
+ hasNestedDeep = true
+ }
+ }
+
// deep: inject [id] attribute at the node before the ::v-deep
// combinator.
if (value === ':deep' || value === '::v-deep') {
}
if (
- (n.type !== 'pseudo' && n.type !== 'combinator') ||
- (n.type === 'pseudo' &&
- (n.value === ':is' || n.value === ':where') &&
- !node)
+ !hasNestedDeep &&
+ ((n.type !== 'pseudo' && n.type !== 'combinator') ||
+ (n.type === 'pseudo' &&
+ (n.value === ':is' || n.value === ':where') &&
+ !node))
) {
node = n
}
})
+ if (splitForNestedDeep) {
+ return
+ }
+
if (rule.nodes.some(node => node.type === 'rule')) {
const deep = (rule as any).__deep
if (!deep) {
shouldInject = deep
}
- if (node) {
+ if (node && !hasNestedDeep) {
const { type, value } = node as selectorParser.Node
if (type === 'pseudo' && (value === ':is' || value === ':where')) {
;(node as selectorParser.Pseudo).nodes.forEach(value =>
return node.type === 'combinator' && /^\s+$/.test(node.value)
}
+function isDeepSelector(node: selectorParser.Node): boolean {
+ if (
+ node.type === 'pseudo' &&
+ (node.value === ':deep' || node.value === '::v-deep')
+ ) {
+ return true
+ }
+
+ return !!(
+ node as selectorParser.Node & { nodes?: selectorParser.Node[] }
+ ).nodes?.some(child => isDeepSelector(child))
+}
+
+function isDeepContainerPseudo(
+ node: selectorParser.Node,
+): node is selectorParser.Pseudo {
+ return (
+ node.type === 'pseudo' &&
+ (node.value === ':is' ||
+ node.value === ':where' ||
+ node.value === ':has' ||
+ node.value === ':not')
+ )
+}
+
+function canSplitDeepContainerPseudo(node: selectorParser.Pseudo): boolean {
+ return (
+ node.value === ':is' || node.value === ':where' || node.value === ':has'
+ )
+}
+
+function splitSelectorForNestedDeep(
+ id: string,
+ rule: Rule,
+ selector: selectorParser.Selector,
+ selectorRoot: selectorParser.Root,
+ pseudo: selectorParser.Pseudo,
+ deep: boolean,
+ slotted: boolean,
+) {
+ const pseudoIndex = selector.index(pseudo)
+ const selectors = pseudo.nodes.map((branch, index) => {
+ const branchSelector = selector.clone()
+ if (branchSelector.first) {
+ branchSelector.first.spaces.before =
+ index === 0 ? selector.first.spaces.before : ' '
+ }
+ const branchPseudo = branchSelector.at(pseudoIndex) as selectorParser.Pseudo
+ const branchClone = branch.clone()
+ if (branchClone.first) {
+ branchClone.first.spaces.before = ''
+ }
+ branchPseudo.removeAll()
+ branchPseudo.append(branchClone)
+ rewriteSelector(id, rule, branchSelector, selectorRoot, deep, slotted)
+ return branchSelector
+ })
+
+ selector.replaceWith(...selectors)
+}
+
function extractAndWrapNodes(parentNode: Rule | AtRule) {
if (!parentNode.nodes) return
const nodes = parentNode.nodes.filter(