diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index 95eeaaf8cd..ab19ebd1e1 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -4,6 +4,12 @@ import { gather_possible_values, UNKNOWN } from './gather_possible_values'; import { CssNode } from './interfaces'; import Component from '../Component'; +enum BlockAppliesToNode { + NotPossible, + Possible, + UnknownSelectorType +} + export default class Selector { node: CssNode; stylesheet: Stylesheet; @@ -31,10 +37,10 @@ export default class Selector { apply(node: CssNode, stack: CssNode[]) { const to_encapsulate: CssNode[] = []; - apply_selector(this.stylesheet, this.local_blocks.slice(), node, stack.slice(), to_encapsulate); + apply_selector(this.local_blocks.slice(), node, stack.slice(), to_encapsulate); if (to_encapsulate.length > 0) { - to_encapsulate.filter((_, i) => i === 0 || i === to_encapsulate.length - 1).forEach(({ node, block }) => { + to_encapsulate.forEach(({ node, block }) => { this.stylesheet.nodes_with_css_class.add(node); block.should_encapsulate = true; }); @@ -126,7 +132,7 @@ export default class Selector { } } -function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: CssNode, stack: CssNode[], to_encapsulate: any[]): boolean { +function apply_selector(blocks: Block[], node: CssNode, stack: CssNode[], to_encapsulate: any[]): boolean { const block = blocks.pop(); if (!block) return false; @@ -134,49 +140,30 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: CssNode, return blocks.every(block => block.global); } - let i = block.selectors.length; - - while (i--) { - const selector = block.selectors[i]; - const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1'); - - if (selector.type === 'PseudoClassSelector' && name === 'global') { - // TODO shouldn't see this here... maybe we should enforce that :global(...) - // cannot be sandwiched between non-global selectors? + switch (block_might_apply_to_node(block, node)) { + case BlockAppliesToNode.NotPossible: return false; - } - - if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') { - continue; - } - - if (selector.type === 'ClassSelector') { - if (!attribute_matches(node, 'class', name, '~=', false) && !node.classes.some(c => c.name === name)) return false; - } - - else if (selector.type === 'IdSelector') { - if (!attribute_matches(node, 'id', name, '=', false)) return false; - } - else if (selector.type === 'AttributeSelector') { - if (!attribute_matches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) return false; - } - - else if (selector.type === 'TypeSelector') { - if (node.name.toLowerCase() !== name.toLowerCase() && name !== '*') return false; - } - - else { + case BlockAppliesToNode.UnknownSelectorType: // bail. TODO figure out what these could be to_encapsulate.push({ node, block }); return true; - } } if (block.combinator) { if (block.combinator.type === 'WhiteSpace') { - while (stack.length) { - if (apply_selector(stylesheet, blocks.slice(), stack.pop(), stack, to_encapsulate)) { + for (const ancestor_block of blocks) { + if (ancestor_block.global) { + continue; + } + + for (const stack_node of stack) { + if (block_might_apply_to_node(ancestor_block, stack_node) !== BlockAppliesToNode.NotPossible) { + to_encapsulate.push({ node: stack_node, block: ancestor_block }); + } + } + + if (to_encapsulate.length) { to_encapsulate.push({ node, block }); return true; } @@ -189,7 +176,7 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: CssNode, return false; } else if (block.combinator.name === '>') { - if (apply_selector(stylesheet, blocks, stack.pop(), stack, to_encapsulate)) { + if (apply_selector(blocks, stack.pop(), stack, to_encapsulate)) { to_encapsulate.push({ node, block }); return true; } @@ -206,6 +193,47 @@ function apply_selector(stylesheet: Stylesheet, blocks: Block[], node: CssNode, return true; } +function block_might_apply_to_node(block, node): BlockAppliesToNode { + let i = block.selectors.length; + + while (i--) { + const selector = block.selectors[i]; + const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1'); + + if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') { + continue; + } + + if (selector.type === 'PseudoClassSelector' && name === 'global') { + // TODO shouldn't see this here... maybe we should enforce that :global(...) + // cannot be sandwiched between non-global selectors? + return BlockAppliesToNode.NotPossible; + } + + if (selector.type === 'ClassSelector') { + if (!attribute_matches(node, 'class', name, '~=', false) && !node.classes.some(c => c.name === name)) return BlockAppliesToNode.NotPossible; + } + + else if (selector.type === 'IdSelector') { + if (!attribute_matches(node, 'id', name, '=', false)) return BlockAppliesToNode.NotPossible; + } + + else if (selector.type === 'AttributeSelector') { + if (!attribute_matches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) return BlockAppliesToNode.NotPossible; + } + + else if (selector.type === 'TypeSelector') { + if (node.name.toLowerCase() !== name.toLowerCase() && name !== '*') return BlockAppliesToNode.NotPossible; + } + + else { + return BlockAppliesToNode.UnknownSelectorType; + } + } + + return BlockAppliesToNode.Possible; +} + function test_attribute(operator, expected_value, case_insensitive, value) { if (case_insensitive) { expected_value = expected_value.toLowerCase(); diff --git a/test/css/samples/omit-scoping-attribute-global-children/expected.css b/test/css/samples/omit-scoping-attribute-global-children/expected.css new file mode 100644 index 0000000000..2a7a510449 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-global-children/expected.css @@ -0,0 +1 @@ +.root.svelte-xyz p{color:red} \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-global-children/expected.html b/test/css/samples/omit-scoping-attribute-global-children/expected.html new file mode 100644 index 0000000000..f7ee02fd83 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-global-children/expected.html @@ -0,0 +1,4 @@ +
+
+
+
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-global-children/input.svelte b/test/css/samples/omit-scoping-attribute-global-children/input.svelte new file mode 100644 index 0000000000..5e52d48738 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-global-children/input.svelte @@ -0,0 +1,16 @@ + + + + +
+
+ +
+
diff --git a/test/css/samples/omit-scoping-attribute-global-descendants/expected.css b/test/css/samples/omit-scoping-attribute-global-descendants/expected.css new file mode 100644 index 0000000000..c1fd7da897 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-global-descendants/expected.css @@ -0,0 +1 @@ +html body .root.svelte-xyz p.svelte-xyz{color:red} \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-global-descendants/expected.html b/test/css/samples/omit-scoping-attribute-global-descendants/expected.html new file mode 100644 index 0000000000..3750091bc7 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-global-descendants/expected.html @@ -0,0 +1,5 @@ +
+
+

hello

+
+
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-global-descendants/input.svelte b/test/css/samples/omit-scoping-attribute-global-descendants/input.svelte new file mode 100644 index 0000000000..7c9782ebad --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-global-descendants/input.svelte @@ -0,0 +1,16 @@ + + + + +
+
+

hello

+
+
diff --git a/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.css b/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.css new file mode 100644 index 0000000000..5452f68073 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.css @@ -0,0 +1 @@ +.root.svelte-xyz p.svelte-xyz{color:red} \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.html b/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.html new file mode 100644 index 0000000000..3750091bc7 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-multiple-descendants/expected.html @@ -0,0 +1,5 @@ +
+
+

hello

+
+
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-multiple-descendants/input.svelte b/test/css/samples/omit-scoping-attribute-multiple-descendants/input.svelte new file mode 100644 index 0000000000..dc77a6c794 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-multiple-descendants/input.svelte @@ -0,0 +1,16 @@ + + + + +
+
+

hello

+
+