Skip to content

Commit 6824fa8

Browse files
committed
feat(css-blocks): Broke up Block.ts, refactored foundational BlockObject constructs, added StateGroup concept.
This is a BREAKING CHANGES (obvs).
1 parent 9145568 commit 6824fa8

19 files changed

+930
-877
lines changed

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

+82-833
Large diffs are not rendered by default.
+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { Block } from "./Block";
2+
import { NodeStyle } from "./Style";
3+
import { StateGroup } from "./StateGroup";
4+
import { State } from "./State";
5+
import { Attribute } from "@opticss/element-analysis";
6+
import { OptionsReader } from "../OptionsReader";
7+
import { OutputMode } from "../OutputMode";
8+
import { UNIVERSAL_STATE } from "../BlockSyntax";
9+
10+
/**
11+
* Holds state values to be passed to the StateContainer.
12+
*/
13+
export interface StateInfo {
14+
group?: string;
15+
name: string;
16+
}
17+
18+
/**
19+
* Represents a Class present in the Block.
20+
*/
21+
export class BlockClass extends NodeStyle<BlockClass, Block, StateGroup> {
22+
private _sourceAttribute: Attribute;
23+
24+
protected newChild(name: string): StateGroup { return new StateGroup(name, this); }
25+
26+
get isRoot(): boolean { return this.name === "root"; }
27+
28+
public getGroups(): StateGroup[] { return this.children(); }
29+
public getGroup(name: string, filter?: string): State[] {
30+
let group = this.getChild(name);
31+
if (!group) { return []; }
32+
let states = group.states();
33+
return filter ? states.filter(s => s.name === filter) : states;
34+
}
35+
public ensureGroup(name: string): StateGroup { return this.ensureChild(name); }
36+
public resolveGroup(name: string): StateGroup | null { return this.resolveChild(name); }
37+
public stateGroups(): StateGroup[] { return this.children(); }
38+
public resolveState(group: string, state: string): State | null {
39+
let parent = this.resolveChild(group);
40+
if (parent) {
41+
parent.resolveChild(state);
42+
}
43+
return null;
44+
}
45+
46+
public booleanStates(): State[]{
47+
let res: State[] = [];
48+
for (let group of this.getGroups()) {
49+
let state = group.getState(UNIVERSAL_STATE);
50+
if (!group.hasSubStates && state) {
51+
res.push(state);
52+
}
53+
}
54+
return res;
55+
}
56+
57+
public localName(): string { return this.name; }
58+
59+
/**
60+
* Export as original class name.
61+
* @returns String representing original class.
62+
*/
63+
public asSource(): string { return `.${this.name}`; }
64+
65+
public asSourceAttributes(): Attribute[] {
66+
if (!this._sourceAttribute) {
67+
this._sourceAttribute = new Attribute("class", { constant: this.name });
68+
}
69+
return [this._sourceAttribute];
70+
}
71+
72+
/**
73+
* Export as new class name.
74+
* @param opts Option hash configuring output mode.
75+
* @returns String representing output class.
76+
*/
77+
public cssClass(opts: OptionsReader): string {
78+
switch (opts.outputMode) {
79+
case OutputMode.BEM:
80+
if (this.isRoot) {
81+
return `${this.block.name}`;
82+
} else {
83+
return `${this.block.name}__${this.name}`;
84+
}
85+
default:
86+
throw "this never happens";
87+
}
88+
}
89+
90+
/**
91+
* Return array self and all children.
92+
* @param shallow Pass false to not include children.
93+
* @returns Array of Styles.
94+
*/
95+
all(shallow?: boolean): (State | BlockClass)[] {
96+
let result: (State | BlockClass)[] = [this];
97+
if (!shallow) {
98+
result = result.concat(this.allStates());
99+
}
100+
return result;
101+
}
102+
103+
/**
104+
* Returns all concrete states defined against this class.
105+
* Does not take inheritance into account.
106+
*/
107+
allStates(): State[] {
108+
let result: State[] = [];
109+
for (let stateContainer of this.stateGroups()){
110+
result.push(...stateContainer.states());
111+
}
112+
return result;
113+
}
114+
115+
/**
116+
* Ensure that a `SubState` with the given `subStateName` exists belonging to
117+
* the state named `stateName`. If the state does not exist, it is created.
118+
* @param stateName The State's name to ensure.
119+
* @param subStateName Optional state group for lookup/registration
120+
*/
121+
ensureSubState(groupName: string, subStateName: string): State {
122+
return this.ensureGroup(groupName).ensureState(subStateName);
123+
}
124+
125+
/**
126+
* Group getter. Returns a list of State objects in the requested group that are defined
127+
* against this specific class. This does not take inheritance into account.
128+
* @param stateName State group for lookup or a boolean state name if substate is not provided.
129+
* @param subStateName Optional substate to filter states by.
130+
* @returns An array of all States that were requested.
131+
*/
132+
getState(groupName: string, stateName = UNIVERSAL_STATE): State | null {
133+
let group = this.getChild(groupName);
134+
return group ? group.getState(stateName) || null : null;
135+
}
136+
137+
getGroupsNames(): Set<string> {
138+
return new Set<string>([...this._children.keys()]);
139+
}
140+
141+
/**
142+
* Legacy State getter
143+
* @param info The StateInfo type to lookup, contains `name` and `group`
144+
* @returns The State that was requested, or undefined
145+
*/
146+
_getState(info: StateInfo): State | null {
147+
return info.group ? this.getState(info.group, info.name) : this.getState(info.name);
148+
}
149+
150+
/**
151+
* Legacy state ensurer. Ensure that a `State` with the given `StateInfo` is
152+
* registered with this Block.
153+
* @param info `StateInfo` to verify exists on this `Block`
154+
* @return The `State` object on this `Block`
155+
*/
156+
_ensureState(info: StateInfo): State {
157+
let state = this.ensureGroup(info.group || info.name);
158+
return info.group ? state.ensureState(info.name) : state.ensureState(UNIVERSAL_STATE);
159+
}
160+
161+
/**
162+
* Debug utility to help test States.
163+
* @param options Options to pass to States' asDebug method.
164+
* @return Array of debug strings for these states
165+
*/
166+
debug(opts: OptionsReader): string[] {
167+
let result: string[] = [];
168+
for (let state of this.all()) {
169+
result.push(state.asDebug(opts));
170+
}
171+
return result;
172+
}
173+
174+
}
175+
176+
export function isBlockClass(o: object): o is BlockClass {
177+
return o instanceof BlockClass;
178+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { ObjectDictionary } from "@opticss/util";
2+
import { Style, Block, isBlock, isBlockClass, isState } from './index';
3+
4+
import { BlockPath } from "../BlockSyntax";
5+
import { CssBlockError } from "../errors";
6+
7+
export abstract class Inheritable<
8+
Self extends Inheritable<Self, Parent, Child>,
9+
Parent extends Inheritable<any, any, Self> | null = null,
10+
Child extends Inheritable<any, Self, any> | null = null
11+
> {
12+
13+
protected _name: string;
14+
protected _base: Self | undefined;
15+
public _block: Block;
16+
protected _baseName: string;
17+
protected _parent: Parent | undefined;
18+
protected _children: Map<string, Child> = new Map;
19+
20+
/**
21+
* Given a parent that is a base class of this style, retrieve this style's
22+
* base style from it, if it exists. This method does not traverse into base styles.
23+
*/
24+
protected abstract newChild(name: string): Child;
25+
26+
// TODO: Currently only ever returns itself if is a style. Need to get it to look other things up.
27+
public lookup(path: string | BlockPath): Style | undefined {
28+
path = new BlockPath(path);
29+
if (isBlockClass(this) || isState(this)) return this;
30+
return undefined;
31+
}
32+
33+
/**
34+
* Inheritable constructor
35+
* @param name Name for this Inheritable instance.
36+
* @param parent The parent Inheritable of this node.
37+
*/
38+
constructor(name: string, parent?: Parent,) {
39+
this._name = name;
40+
this._parent = parent;
41+
}
42+
43+
public get name(): string { return this._name; }
44+
public get baseName(): string | undefined { return this._baseName; }
45+
public get parent(): Parent | undefined { return this._parent; }
46+
47+
/**
48+
* Get the style that this style inherits from, if any.
49+
*
50+
* This walks down the declared styles of the parent's inheritance chain,
51+
* and attempts to find a matching directly declared style on each.
52+
*
53+
* The result is cached because it never changes and is decidable as soon
54+
* as the style is instantiated.
55+
*/
56+
public get base(): Self | undefined {
57+
if (this._base !== undefined || !this.parent) {
58+
return this._base || undefined;
59+
}
60+
let baseParent: Parent | undefined = this.parent.base;
61+
while (baseParent) {
62+
let cls = baseParent ? baseParent.getChild(this.name) : undefined;
63+
if (cls) {
64+
this._base = cls;
65+
return cls;
66+
}
67+
baseParent = baseParent.base;
68+
}
69+
return this._base = undefined;
70+
}
71+
72+
/**
73+
* traverse parents and return the base block object.
74+
* @returns The base block in this container tree.
75+
*/
76+
public get block(): Block {
77+
if (isBlock(this)) { return this._block = this; }
78+
if (this._block !== undefined) { return this._block; }
79+
if (this.parent) { return this._block = this.parent.block; }
80+
throw new CssBlockError("Tried to access `block` on an orphaned `Style`");
81+
}
82+
83+
setBase(baseName: string, base: Self) {
84+
this._baseName = baseName;
85+
this._base = base;
86+
}
87+
88+
/**
89+
* Compute all block objects that are implied by this block object through
90+
* inheritance. Does not include this object or the styles it implies through
91+
* other relationships to this object.
92+
*
93+
* If nothing is inherited, this returns an empty set.
94+
*/
95+
resolveInheritance(): Self[] {
96+
let inherited = new Array<Self>();
97+
let base: Self | undefined = this.base;
98+
while (base) {
99+
inherited.unshift(base);
100+
base = base.base;
101+
}
102+
return inherited;
103+
}
104+
105+
/**
106+
* Resolves the child with the given name from this node's inheritance
107+
* chain. Returns undefined if the child is not found.
108+
* @param name The name of the child to resolve.
109+
*/
110+
resolveChild(name: string): Child | null {
111+
let state: Child | null = this.getChild(name);
112+
let container: Self | undefined = this.base;
113+
while (!state && container) {
114+
state = container.getChild(name);
115+
container = container.base;
116+
}
117+
return state || null;
118+
}
119+
120+
/**
121+
* Given a parent that is a base class of this style, retrieve this style's
122+
* base style from it, if it exists. This method does not traverse into base styles.
123+
*/
124+
protected getChild(key: string): Child | null {
125+
return this._children.get(key) || null;
126+
}
127+
128+
protected setChild(key: string, value: Child): Child {
129+
this._children.set(key, value);
130+
return value;
131+
}
132+
133+
protected ensureChild(name: string): Child {
134+
if (!this._children.has(name)) {
135+
this.setChild(name, this.newChild(name));
136+
}
137+
return this._children.get(name) as Child;
138+
}
139+
140+
protected children(): Child[]{
141+
return [...this._children.values()];
142+
}
143+
144+
// TODO: Cache this maybe? Convert entire model to only use hash?...
145+
protected childrenHash(): ObjectDictionary<Child> {
146+
let out = {};
147+
for (let [key, value] of this._children.entries() ) {
148+
out[key] = value;
149+
}
150+
return out;
151+
}
152+
153+
}
154+
155+
export abstract class Source<
156+
Self extends Inheritable<Self, null, Child>,
157+
Child extends Inheritable<Child, Self, any>
158+
> extends Inheritable<Self, null, Child> {
159+
public parent: null;
160+
protected _children: Map<string, Child>;
161+
}
162+
163+
export abstract class Node<
164+
Self extends Inheritable<Self, Parent, Child>,
165+
Parent extends Inheritable<Parent, any, Self>,
166+
Child extends Inheritable<Child, Self, any>
167+
> extends Inheritable<Self, Parent, Child> {
168+
public parent: Parent;
169+
protected _children: Map<string, Child>;
170+
}
171+
172+
export abstract class Sink<
173+
Self extends Inheritable<Self, Parent, null>,
174+
Parent extends Inheritable<any, any, Self>
175+
> extends Inheritable<Self, Parent, null> {
176+
public parent: Parent;
177+
protected _children: Map<string, null>;
178+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { MultiMap, TwoKeyMultiMap } from "@opticss/util";
22
import * as propParser from "css-property-parser";
33
import * as postcss from "postcss";
44

5-
import { BLOCK_PROP_NAMES, BlockPath, RESOLVE_RE, SELF_SELECTOR } from "../BlockSyntax";
5+
import { BLOCK_PROP_NAMES, RESOLVE_RE, SELF_SELECTOR, BlockPath } from "../BlockSyntax";
66
import { sourceLocation } from "../SourceLocation";
77
import * as errors from "../errors";
88

0 commit comments

Comments
 (0)