Skip to content

Commit f19f559

Browse files
committed
feat: Change '.root' selector to ':scope'.
1 parent cec9dfa commit f19f559

File tree

71 files changed

+517
-493
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+517
-493
lines changed

packages/css-blocks/README.md

+18-18
Original file line numberDiff line numberDiff line change
@@ -246,14 +246,14 @@ A state is attached to a single class in the block and describes how that
246246
class changes when the element is in that state. The names of states are
247247
scoped to their class and the styles they provide can target only elements
248248
assigned to that classname. The exception to this is for states that belong
249-
to the block `.root` class. In general, CSS Blocks restricts the use of
249+
to the block `:scope` class. In general, CSS Blocks restricts the use of
250250
combinators in selectors with a preference for unscoped selectors that are
251251
fast and optimization friendly (See Rules & Constraints below). One of the
252252
primary exceptions to this is that root states can be used in selectors as a
253253
scope for classes and other states within the block. So when you find
254254
yourself thinking that you absolutely need to use a descendant or child
255255
combinator then you should imagine that use case as a state for a block's
256-
`.root` class.
256+
`:scope` class.
257257

258258
It is important to think of States as part of the public API that describe
259259
how a block and the classes within it can vary. By keeping all the
@@ -371,10 +371,10 @@ Syntax
371371

372372
### The root element
373373

374-
Styles can be attached to a block's root element with the class `.root`. The
375-
`.root` class can be used in selectors in combination with itself as well as
374+
Styles can be attached to a block's root element with the class `:scope`. The
375+
`:scope` class can be used in selectors in combination with itself as well as
376376
in media queries and other @-rules. However, some block-specific syntax may
377-
only be put into a simple ruleset where the selector is exactly `.root`; it
377+
only be put into a simple ruleset where the selector is exactly `:scope`; it
378378
is convention to have only one such ruleset near the top of your block's
379379
file.
380380

@@ -458,7 +458,7 @@ automatically generate a unique name for BEM output mode.
458458
`shared.block.css`
459459

460460
```css
461-
.root {
461+
:scope {
462462
block-name: my-block;
463463
color: blue;
464464
}
@@ -472,7 +472,7 @@ automatically generate a unique name for BEM output mode.
472472
```css
473473
@block-reference shared from "../shared.block.css";
474474

475-
.root {
475+
:scope {
476476
block-name: my-block;
477477
background-color: blue;
478478
}
@@ -541,10 +541,10 @@ with a specific selector in the block. In fact, it is an abstraction that can ca
541541
the CSS blocks compiler to consider many selectors in a block that involve the specific
542542
block object.
543543

544-
- `.root` represents the block root for the current block.
544+
- `:scope` represents the block root for the current block.
545545
- `a-block-reference.root` represents the block root for the
546546
block that has a `@block-reference` as `a-block-reference` from the current
547-
block. In many cases, the `.root` can be safely omitted.
547+
block. In many cases, the `:scope` can be safely omitted.
548548
- `[state|foo]` or `[state|foo=bar]` represent the
549549
root state named `foo` or the state named `foo` with the substate of `bar`.
550550
- `a-block-reference[state|foo]` or `a-block-reference[state|foo=bar]`
@@ -602,7 +602,7 @@ Rather than having to specify the block root, a block class can declare itself t
602602

603603
```css
604604
// tab.block.css
605-
.root {
605+
:scope {
606606
background-color: gray;
607607
color: black;
608608
flex: 1 1 content;
@@ -631,7 +631,7 @@ Rather than having to specify the block root, a block class can declare itself t
631631
632632
// tabs.block.css
633633
@block-reference tab from "tab.block.css";
634-
.root {
634+
:scope {
635635
display: flex;
636636
}
637637
@@ -693,13 +693,13 @@ other block:
693693
And now that block can be referenced within this file by the name
694694
`another`.
695695

696-
To inherit, you must set the property `extends` inside the block's `.root`
696+
To inherit, you must set the property `extends` inside the block's `:scope`
697697
class to the name of the block you wish to inherit.
698698

699699
```css
700700
@block-refererence another from "./another-block.block.css";
701701

702-
.root {
702+
:scope {
703703
extends: another;
704704
}
705705
```
@@ -727,7 +727,7 @@ a block `implements` one or more blocks.
727727
```css
728728
@block-reference base from "./base.block.css";
729729
@block-reference other from "./other.block.css";
730-
.root { implements: base, other; color: red; }
730+
:scope { implements: base, other; color: red; }
731731
```
732732

733733
Now if there are any states, classes or substates in those other blocks
@@ -1231,7 +1231,7 @@ of the same value in the key selector the following resolution is created:
12311231

12321232
```css
12331233
@block-reference base from "./base.block.css";
1234-
.root { extends: base; }
1234+
:scope { extends: base; }
12351235
.foo {
12361236
color: resolve("base.foo");
12371237
color: blue; // conflicts with color value(s) in a selector targeting base.foo
@@ -1340,7 +1340,7 @@ change the source authoring. Here's our form example from above written with Sas
13401340

13411341
```scss
13421342
$base-size: 1em;
1343-
.root {
1343+
:scope {
13441344
block-name: my-form;
13451345
margin: 2 * $base-size 0;
13461346
padding: $base-size $base-size / 2;
@@ -1438,7 +1438,7 @@ the current component's styles:
14381438
```css
14391439
@block-reference icons from "../../shared/styles/icons/dark.block.css";
14401440

1441-
.root {
1441+
:scope {
14421442
border: 1px solid black;
14431443
overflow: auto;
14441444
}
@@ -1453,7 +1453,7 @@ the current component's styles:
14531453
`my-component/template.hbs`
14541454

14551455
```hbs
1456-
<div class="icons.root" state:icons.hoverable state:icons.dark>
1456+
<div class="icons:scope" state:icons.hoverable state:icons.dark>
14571457
<div class="icon icons.new">New File</div>
14581458
<div class="icon icons.save">Save File</div>
14591459
<div class="icon icons.undo">Undo</div>

packages/css-blocks/src/Block/Block.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
isStateNode,
1515
NodeAndType,
1616
} from "../BlockParser";
17-
import { stateName, stateValue } from "../BlockParser/block-intermediates";
17+
import { isRootNode, stateName, stateValue } from "../BlockParser/block-intermediates";
1818
import { BlockPath, CLASS_NAME_IDENT, ROOT_CLASS } from "../BlockSyntax";
1919
import { OptionsReader } from "../OptionsReader";
2020
import { SourceLocation } from "../SourceLocation";
@@ -301,7 +301,7 @@ export class Block
301301
}
302302

303303
nodeAsStyle(node: selectorParser.Node): [Styles, number] | null {
304-
if (node.type === selectorParser.CLASS && node.value === ROOT_CLASS) {
304+
if (isRootNode(node)) {
305305
return [this.rootClass, 0];
306306
} else if (node.type === selectorParser.TAG) {
307307
let otherBlock = this.getReferencedBlock(node.value);
@@ -412,7 +412,7 @@ export class Block
412412
let sourceNames = new Set<string>(this.all().map(s => s.asSource()));
413413
let sortedNames = [...sourceNames].sort();
414414
for (let n of sortedNames) {
415-
if (n !== `.${ROOT_CLASS}`) {
415+
if (n !== ROOT_CLASS) {
416416
let o = this.find(n) as Styles;
417417
result.push(o.asDebug(opts));
418418
}

packages/css-blocks/src/Block/BlockClass.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Attribute, AttributeValue, attrValues } from "@opticss/element-analysis";
22

3-
import { UNIVERSAL_STATE } from "../BlockSyntax";
3+
import { ROOT_CLASS, UNIVERSAL_STATE } from "../BlockSyntax";
44
import { OptionsReader } from "../OptionsReader";
55
import { OutputMode } from "../OutputMode";
66

@@ -33,7 +33,7 @@ export class BlockClass extends Style<BlockClass, Block, Block, StateGroup> {
3333

3434
protected newChild(name: string): StateGroup { return new StateGroup(name, this); }
3535

36-
get isRoot(): boolean { return this.name === "root"; }
36+
get isRoot(): boolean { return this.name === ROOT_CLASS; }
3737

3838
public getGroups(): StateGroup[] { return this.children(); }
3939
public getGroup(name: string): StateGroup | null { return this.getChild(name); }
@@ -48,7 +48,7 @@ export class BlockClass extends Style<BlockClass, Block, Block, StateGroup> {
4848
* Ensure that a `StateGroup` with the given name exists. If the `StateGroup`
4949
* does not exist, it is created.
5050
* @param name The State group to ensure exists.
51-
* @param vallue The State's value to ensure exists.
51+
* @param value The State's value to ensure exists.
5252
* @returns The State object.
5353
*/
5454
public ensureGroup(name: string): StateGroup { return this.ensureChild(name); }
@@ -108,7 +108,7 @@ export class BlockClass extends Style<BlockClass, Block, Block, StateGroup> {
108108
* Export as original class name.
109109
* @returns String representing original class.
110110
*/
111-
public asSource(): string { return `.${this.name}`; }
111+
public asSource(): string { return this.isRoot ? ROOT_CLASS : `.${this.name}`; }
112112

113113
/**
114114
* Emit analysis attributes for the class value this

packages/css-blocks/src/BlockCompiler/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as postcss from "postcss";
22

33
import { Block } from "../Block";
4+
import { ROOT_CLASS } from "../BlockSyntax";
45
import { OptionsReader } from "../OptionsReader";
56
import { StyleAnalysis } from "../TemplateAnalysis/StyleAnalysis";
67
import { PluginOptions } from "../options";
@@ -33,7 +34,7 @@ export class BlockCompiler {
3334
root.walkAtRules("block-reference", (atRule) => {
3435
atRule.remove();
3536
});
36-
root.walkRules(/\.root/, (rule) => {
37+
root.walkRules(ROOT_CLASS, (rule) => {
3738
rule.walkDecls(/^(extends|implements|block-name)$/, (decl) => {
3839
decl.remove();
3940
});

packages/css-blocks/src/BlockParser/block-intermediates.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { assertNever, firstOfType } from "@opticss/util";
22
import { CompoundSelector } from "opticss";
33
import selectorParser = require("postcss-selector-parser");
44

5+
import { ROOT_CLASS } from "../BlockSyntax";
6+
57
export enum BlockType {
68
root = 1,
79
state,
@@ -14,7 +16,7 @@ export type NodeAndType = {
1416
node: selectorParser.Attribute;
1517
} | {
1618
blockType: BlockType.root | BlockType.class;
17-
node: selectorParser.ClassName;
19+
node: selectorParser.ClassName | selectorParser.Pseudo;
1820
};
1921

2022
export type BlockNodeAndType = NodeAndType & {
@@ -73,8 +75,8 @@ export function isClassLevelObject(object: NodeAndType): boolean {
7375
/**
7476
* Check if given selector node is targeting the root block node
7577
*/
76-
export function isRootNode(node: selectorParser.Node): node is selectorParser.ClassName {
77-
return node.type === selectorParser.CLASS && node.value === "root";
78+
export function isRootNode(node: selectorParser.Node): node is selectorParser.Pseudo {
79+
return node.type === selectorParser.PSEUDO && node.value === ROOT_CLASS;
7880
}
7981

8082
/**

packages/css-blocks/src/BlockParser/features/discover-name.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as errors from "../../errors";
77
export async function discoverName(root: postcss.Root, defaultName: string, file: string): Promise<string> {
88

99
// Eagerly fetch custom `block-name` from the root block rule.
10-
root.walkRules(".root", (rule) => {
10+
root.walkRules(":scope", (rule) => {
1111
rule.walkDecls(BLOCK_NAME, (decl) => {
1212
if (!CLASS_NAME_IDENT.test(decl.value)) {
1313
throw new errors.InvalidBlockSyntax(

packages/css-blocks/src/BlockParser/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class BlockParser {
8989
await constructBlock(root, block, debugIdent);
9090
// Verify that external blocks referenced have been imported, have defined the state being selected, and have marked it as a global state.
9191
await assertForeignGlobalState(root, block, debugIdent);
92-
// Construct block extentions and validate.
92+
// Construct block extensions and validate.
9393
await extendBlock(root, block, debugIdent);
9494
// Validate that all required Styles are implemented.
9595
await implementBlock(root, block, debugIdent);

packages/css-blocks/src/BlockSyntax/BlockPath.ts

+27-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { BlockPathError, ErrorLocation } from "../errors";
22

3-
import { CLASS_NAME_IDENT as CSS_IDENT } from "./BlockSyntax";
3+
import {
4+
CLASS_NAME_IDENT as CSS_IDENT,
5+
ROOT_CLASS,
6+
} from "./BlockSyntax";
47

58
interface BlockToken {
69
type: "block";
@@ -42,8 +45,9 @@ const NAMESPACE_END = "|";
4245
const VALUE_START = "=";
4346
const SINGLE_QUOTE = `'`;
4447
const DOUBLE_QUOTE = `"`;
48+
const PSEUDO_BEGIN = ":";
4549
const WHITESPACE_REGEXP = /\s/g;
46-
const SEPARATORS = new Set([CLASS_BEGIN, STATE_BEGIN]);
50+
const SEPARATORS = new Set([CLASS_BEGIN, STATE_BEGIN, PSEUDO_BEGIN]);
4751

4852
export const ERRORS = {
4953
whitespace: "Whitespace is only allowed in quoted state values",
@@ -62,7 +66,7 @@ function stringify(tokens: Token[]): string {
6266
let out = "";
6367
for (let token of tokens) {
6468
if (isBlock(token)) { out += token.name; }
65-
else if (isClass(token)) { out += `.${token.name}`; }
69+
else if (isClass(token)) { out += token.name === ROOT_CLASS ? token.name : `.${token.name}`; }
6670
else if (isState(token)) { out += `[${token.namespace}|${token.name}${token.value ? `="${token.value}"` : ""}]`; }
6771
}
6872
return out;
@@ -115,6 +119,8 @@ export class BlockPath {
115119
private _tokens: Token[] = [];
116120
private parts: Token[] = [];
117121

122+
public readonly original: string;
123+
118124
/**
119125
* Throw a new BlockPathError with the given message.
120126
* @param msg The error message.
@@ -152,7 +158,7 @@ export class BlockPath {
152158
if (isState(token)) {
153159
this._state = this._state ? this.throw(ERRORS.multipleOfType(token.type)) : token;
154160
// If no class has been added yet, automatically inject the root class.
155-
if (!this._class) { this.addToken({ type: "class", name: "root" }, false); }
161+
if (!this._class) { this.addToken({ type: "class", name: ROOT_CLASS }, false); }
156162
}
157163

158164
// Add the token.
@@ -175,6 +181,20 @@ export class BlockPath {
175181

176182
switch (true) {
177183

184+
case char === PSEUDO_BEGIN:
185+
if (!isBlock(token)) { return this.throw(ERRORS.invalidIdent(PSEUDO_BEGIN), working.length); }
186+
token.name = working;
187+
this.addToken(token, true);
188+
working = `${PSEUDO_BEGIN}${walker.consume(SEPARATORS)}`;
189+
if (working === ROOT_CLASS) {
190+
this.addToken({ type: "class", name: ROOT_CLASS }, true);
191+
working = "";
192+
break;
193+
}
194+
else {
195+
return this.throw(ERRORS.invalidIdent(working), working.length);
196+
}
197+
178198
// If a period, we've finished the previous token and are now building a class name.
179199
case char === CLASS_BEGIN:
180200
if (isState(token)) { this.throw(ERRORS.illegalCharInState(char)); }
@@ -265,7 +285,7 @@ export class BlockPath {
265285
token.name = working;
266286
this.addToken(token, true);
267287
}
268-
if (!this._class) { this.addToken({ type: "class", name: "root" }, false); }
288+
if (!this._class) { this.addToken({ type: "class", name: ROOT_CLASS }, false); }
269289
}
270290

271291
/**
@@ -275,6 +295,7 @@ export class BlockPath {
275295
*/
276296
constructor(path: string | BlockPath, location?: ErrorLocation) {
277297
this._location = location;
298+
this.original = path.toString();
278299
if (path instanceof BlockPath) {
279300
this.parts = path.parts;
280301
}
@@ -308,7 +329,7 @@ export class BlockPath {
308329
* Get the parsed class name of this Block Path
309330
*/
310331
get class(): string {
311-
return this._class && this._class.name || "root";
332+
return this._class && this._class.name || ROOT_CLASS;
312333
}
313334

314335
/**

packages/css-blocks/src/BlockSyntax/BlockSyntax.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,4 @@ export const UNIVERSAL_STATE = "::universal";
5353

5454
// Internally use the invented `root` class represents the root element styling for a block. By interpreting the
5555
// root selector as just another class we no longer have to store styling information it on the `Block` object..
56-
export const ROOT_CLASS = "root";
56+
export const ROOT_CLASS = ":scope";

packages/css-blocks/test/Block/lookup-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Block } from "../../src/Block";
77
export class LookupTests {
88
@test "finds the block"() {
99
let block = new Block("test", "test.block.css");
10-
let found = block.lookup(".root");
10+
let found = block.lookup(":scope");
1111
assert.deepEqual(block.rootClass, found);
1212
}
1313
@test "finds a state"() {
@@ -48,7 +48,7 @@ export class LookupTests {
4848
block.addBlockReference("asdf", otherBlock);
4949
let found = block.lookup("asdf");
5050
assert.deepEqual(otherBlock.rootClass, found);
51-
found = block.lookup("asdf.root");
51+
found = block.lookup("asdf:scope");
5252
assert.deepEqual(otherBlock.rootClass, found);
5353
}
5454
@test "finds referenced block class"() {

0 commit comments

Comments
 (0)