switch to removing descendant combinator

pull/12560/head
Simon Holthausen 2 years ago
parent d11556c248
commit 1f8bf93ffa

@ -94,6 +94,8 @@ To apply all styles after a certain point to a selector globally, use the `:glob
The difference between `:global` and `:global(...)` is that `:global(...)` only makes all styles within its braces global, whereas `:global` makes all styles coming after it global, including those in nested CSS.
If `:global` is preceeded by a descendant combinator, the combinator is removed from the output. That means that `div :global.x` is equivalent to `div.svelte-hash.x`.
## Nested style tags
There should only be 1 top-level `<style>` tag per component.

@ -18,9 +18,9 @@
> A :global {...} block cannot be part of a selector list with more than one item
## css_global_block_invalid_placement
## css_global_block_invalid_modifier
> :global cannot be at the end of a selector with children starting with a `&` (aka nesting) selector. Either remove those nested child selectors, or append the :global selector to the end of the previous selector (e.g. `div:global` instead of `div :global`)
> A :global {...} block cannot modify an existing selector
## css_global_invalid_placement

@ -462,12 +462,12 @@ export function css_global_block_invalid_list(node) {
}
/**
* :global cannot be at the end of a selector with children starting with a `&` (aka nesting) selector. Either remove those nested child selectors, or append the :global selector to the end of the previous selector (e.g. `div:global` instead of `div :global`)
* A :global {...} block cannot modify an existing selector
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_global_block_invalid_placement(node) {
e(node, "css_global_block_invalid_placement", ":global cannot be at the end of a selector with children starting with a `&` (aka nesting) selector. Either remove those nested child selectors, or append the :global selector to the end of the previous selector (e.g. `div:global` instead of `div :global`)");
export function css_global_block_invalid_modifier(node) {
e(node, "css_global_block_invalid_modifier", "A :global {...} block cannot modify an existing selector");
}
/**

@ -142,7 +142,12 @@ const validation_visitors = {
const complex_selector = node.prelude.children[0];
const global_selector = complex_selector.children.find((r) => {
return r.selectors.some(is_global_block_selector);
const idx = r.selectors.findIndex(is_global_block_selector);
if (idx === 0) {
return true;
} else if (idx !== -1) {
e.css_global_block_invalid_modifier(r.selectors[idx]);
}
});
if (!global_selector) {
@ -228,22 +233,9 @@ const validation_visitors = {
},
NestingSelector(node, context) {
const rule = /** @type {Css.Rule} */ (context.state.rule);
if (!rule.metadata.parent_rule) {
e.css_nesting_selector_invalid_placement(node);
}
if (rule.metadata.parent_rule.metadata.is_global_block) {
const last = rule.metadata.parent_rule.prelude.children[0].children.at(-1);
if (
last &&
is_global(last) &&
last.selectors[0].args === null &&
last.selectors.length === 1
) {
e.css_global_block_invalid_placement(last);
}
}
}
};

@ -216,10 +216,18 @@ const visitors = {
ComplexSelector(node, context) {
const before_bumped = context.state.specificity.bumped;
/** @param {Css.PseudoClassSelector} selector */
function remove_global_pseudo_class(selector) {
/**
* @param {Css.PseudoClassSelector} selector
* @param {Css.Combinator | null} combinator
*/
function remove_global_pseudo_class(selector, combinator) {
if (selector.args === null) {
context.state.code.remove(selector.start, selector.start + ':global'.length);
let start = selector.start;
if (combinator?.name === ' ') {
// div :global.x becomes div.x
while (/\s/.test(context.state.code.original[start - 1])) start--;
}
context.state.code.remove(start, selector.start + ':global'.length);
} else {
context.state.code
.remove(selector.start, selector.start + ':global('.length)
@ -229,9 +237,17 @@ const visitors = {
for (const relative_selector of node.children) {
if (relative_selector.metadata.is_global) {
remove_global_pseudo_class(
/** @type {Css.PseudoClassSelector} */ (relative_selector.selectors[0])
);
const global = /** @type {Css.PseudoClassSelector} */ (relative_selector.selectors[0]);
remove_global_pseudo_class(global, relative_selector.combinator);
if (
node.metadata.rule?.metadata.parent_rule &&
global.args === null &&
relative_selector.combinator === null
) {
// div { :global.x { ... } } becomes div { &.x { ... } }
context.state.code.prependRight(global.start, '&');
}
continue;
}
@ -250,7 +266,7 @@ const visitors = {
// for any :global() or :global at the middle of compound selector
for (const selector of relative_selector.selectors) {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
remove_global_pseudo_class(selector);
remove_global_pseudo_class(selector, null);
}
}

@ -82,7 +82,7 @@ export namespace Css {
metadata: {
/**
* `true` if the whole selector is unscoped, e.g. `:global(...)` or `:global` or `:global.x`.
* Selectors like `:global(...).x` or `div:global` are not considered global, because they still need scoping.
* Selectors like `:global(...).x` are not considered global, because they still need scoping.
*/
is_global: boolean;
/** `:root`, `:host`, `::view-transition`, or selectors after a `:global` */

@ -4,6 +4,6 @@ export default test({
error: {
code: 'css_global_block_invalid_declaration',
message: 'A :global {...} block can only contain rules, not declarations',
position: [109, 119]
position: [140, 150]
}
});

@ -8,6 +8,10 @@
color: red;
}
:global.y {
color: red;
}
/* not ok */
:global {
color: red;

@ -0,0 +1,9 @@
import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_modifier',
message: 'A :global {...} block cannot modify an existing selector',
position: [100, 107]
}
});

@ -0,0 +1,14 @@
<style>
/* ok */
div :global.x {
color: red;
}
:global.x {
color: red;
}
/* not ok */
.x:global {
color: red;
}
</style>

@ -1,11 +0,0 @@
import { test } from '../../test';
export default test({
error: {
code: 'css_global_block_invalid_placement',
message:
':global cannot be at the end of a selector with children starting with a `&` (aka nesting) selector. ' +
'Either remove those nested child selectors, or append the :global selector to the end of the previous selector (e.g. `div:global` instead of `div :global`)',
position: [184, 192]
}
});

@ -1,27 +0,0 @@
<style>
/* ok */
div :global {
p {
color: green;
}
}
div {
:global {
p {
color: green;
}
}
}
div:global {
&.x {
color: green;
}
}
/* not ok */
div :global {
&.x {
color: red;
}
}
</style>

@ -7,14 +7,14 @@ export default test({
code: 'css_unused_selector',
message: 'Unused CSS selector ".unused :global"',
start: {
line: 39,
line: 63,
column: 1,
character: 492
character: 871
},
end: {
line: 39,
line: 63,
column: 16,
character: 507
character: 886
}
}
]

@ -26,9 +26,39 @@
}
}
div.svelte-xyz .x {
/* `div { :global { &.x { ...} } }` is allowed ... */
div.svelte-xyz {
/* :global {*/
&.x {
color: green;
}
/*}*/
}
/* ...wich is equivalent to `div :global { &.x { ...} }` ... */
div.svelte-xyz {
&.x {
color: green;
}
}
/* ...so `div :global.x` must be, too ... */
div.svelte-xyz.x {
color: green;
}
/* ...and therefore `div { :global.x { ... }` aswell */
div.svelte-xyz {
&.x {
color: green;
}
}
div.svelte-xyz {
&.x {
color: green;
}
}
div.svelte-xyz:is(html.dark-mode *) {
color: green;

@ -28,11 +28,41 @@
}
}
/* `div { :global { &.x { ...} } }` is allowed ... */
div {
:global {
&.x {
color: green;
}
}
}
/* ...wich is equivalent to `div :global { &.x { ...} }` ... */
div :global {
&.x {
color: green;
}
}
/* ...so `div :global.x` must be, too ... */
div :global.x {
color: green;
}
div:global:is(html.dark-mode *) {
/* ...and therefore `div { :global.x { ... }` aswell */
div {
:global.x {
color: green;
}
}
div {
& :global.x {
color: green;
}
}
div :global:is(html.dark-mode *) {
color: green;
}

@ -208,7 +208,7 @@ Previously, Svelte left `:is(...)` and `:where(...)` selectors untouched - it si
```diff
- main {
+ main:global {
+ main :global {
@apply bg-blue-100 dark:bg-blue-900
}
```

Loading…
Cancel
Save