Skip to content

Commit ebfd315

Browse files
committed
feat: Generisize StateGroup and State to Attribute and AttrValue.
1 parent 06dc141 commit ebfd315

40 files changed

+707
-636
lines changed

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

+24-25
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,43 @@ import {
55
} from "@opticss/element-analysis";
66
import { assertNever, assertNeverCalled } from "@opticss/util";
77

8-
import { UNIVERSAL_STATE } from "../BlockSyntax";
8+
import { UNIVERSAL_ATTR_VALUE } from "../BlockSyntax";
99
import { OptionsReader } from "../OptionsReader";
1010
import { OutputMode } from "../OutputMode";
1111

12+
import { Attribute } from "./Attribute";
1213
import { Block } from "./Block";
1314
import { BlockClass } from "./BlockClass";
1415
import { RulesetContainer } from "./RulesetContainer";
15-
import { StateGroup } from "./StateGroup";
1616
import { Style } from "./Style";
1717

1818
/**
19-
* States represent a state attribute selector in a particular Block.
20-
* A State can have sub-states that are considered to be mutually exclusive.
21-
* States can be designated as "global";
19+
* AttrValue represent the value of an Attribute in a particular Block.
20+
* An Attribute can have AttrValue that are considered to be mutually exclusive.
21+
* Attributes can be designated as "global";
2222
*/
23-
export class State extends Style<State, Block, StateGroup, never> {
23+
export class AttrValue extends Style<AttrValue, Block, Attribute, never> {
2424
isGlobal = false;
2525

2626
private _sourceAttributes: AttributeNS[] | undefined;
27-
public readonly rulesets: RulesetContainer<State>;
27+
public readonly rulesets: RulesetContainer<AttrValue>;
2828

2929
/**
30-
* State constructor. Provide a local name for this State, an optional group name,
30+
* AttrValue constructor. Provide a local name for this AttrValue, an optional group name,
3131
* and the parent container.
3232
* @param name The local name for this state.
3333
* @param group An optional parent group name.
34-
* @param container The parent container of this State.
34+
* @param parent The parent Attribute of this AttrValue.
3535
*/
36-
constructor(name: string, parent: StateGroup) {
36+
constructor(name: string, parent: Attribute) {
3737
super(name, parent);
3838
this.rulesets = new RulesetContainer(this);
3939
}
4040

41-
newChild(): never {
42-
return assertNeverCalled();
43-
}
41+
protected get ChildConstructor(): never { return assertNeverCalled(); }
42+
newChild(): never { return assertNeverCalled(); }
4443

45-
get isUniversal(): boolean { return this.name === UNIVERSAL_STATE; }
44+
get isUniversal(): boolean { return this.uid === UNIVERSAL_ATTR_VALUE; }
4645

4746
/**
4847
* Retrieve the BlockClass that this state belongs to.
@@ -51,49 +50,49 @@ export class State extends Style<State, Block, StateGroup, never> {
5150
get blockClass(): BlockClass { return this.parent.parent; }
5251

5352
/**
54-
* Retrieve this State's local name, including the optional BlockClass and group designations.
55-
* @returns The State's local name.
53+
* Retrieve this AttrValue's local name, including the optional BlockClass and group designations.
54+
* @returns The AttrValue's local name.
5655
*/
5756
localName(): string {
58-
return `${this.parent.localName()}${this.isUniversal ? "" : `-${this.name}`}`;
57+
return `${this.parent.localName()}${this.isUniversal ? "" : `-${this.uid}`}`;
5958
}
6059

6160
asSourceAttributes(): Attr[] {
6261
if (!this._sourceAttributes) {
6362
let blockClass = this.blockClass;
6463
let rootIsOptional = true;
6564
this._sourceAttributes = blockClass.asSourceAttributes(rootIsOptional);
66-
let value = this.isUniversal ? attrValues.absent() : attrValues.constant(this.name);
67-
this._sourceAttributes.push(new AttributeNS("state", this.parent.name, value));
65+
let value = this.isUniversal ? attrValues.absent() : attrValues.constant(this.uid);
66+
this._sourceAttributes.push(new AttributeNS(this.parent.namespace, this.parent.name, value));
6867
}
6968
return this._sourceAttributes.slice();
7069
}
7170

7271
asSource(): string {
73-
return this.parent.asSource(this.name);
72+
return this.parent.asSource(this.uid);
7473
}
7574

7675
public cssClass(opts: OptionsReader): string {
7776
switch (opts.outputMode) {
7877
case OutputMode.BEM:
79-
return `${this.parent.cssClass(opts)}${ this.isUniversal ? "" : `-${this.name}`}`;
78+
return `${this.parent.cssClass(opts)}${ this.isUniversal ? "" : `-${this.uid}`}`;
8079
default:
8180
return assertNever(opts.outputMode);
8281
}
8382
}
8483

85-
// TODO: Implement lookup relative to State.
84+
// TODO: Implement lookup relative to AttrValue.
8685
public lookup(): undefined { return undefined; }
8786

8887
/**
8988
* Return array self and all children.
9089
* @returns Array of Styles.
9190
*/
92-
all(): State[] {
91+
all(): AttrValue[] {
9392
return [this];
9493
}
9594
}
9695

97-
export function isState(o: object): o is State {
98-
return o instanceof State;
96+
export function isAttrValue(o: object): o is AttrValue {
97+
return o instanceof AttrValue;
9998
}
+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import {
2+
Attr,
3+
AttributeNS,
4+
AttributeValueChoice,
5+
ValueAbsent,
6+
ValueConstant,
7+
} from "@opticss/element-analysis";
8+
import { ObjectDictionary } from "@opticss/util";
9+
10+
import { STATE_NAMESPACE, UNIVERSAL_ATTR_VALUE } from "../BlockSyntax";
11+
import { OptionsReader } from "../OptionsReader";
12+
import { OutputMode } from "../OutputMode";
13+
14+
import { AttrValue } from "./AttrValue";
15+
import { Block } from "./Block";
16+
import { BlockClass } from "./BlockClass";
17+
import { Inheritable } from "./Inheritable";
18+
19+
export interface AttrToken {
20+
namespace: string;
21+
name: string;
22+
}
23+
24+
function isAttrToken(o: AttrToken | string): o is AttrToken {
25+
return typeof o !== "string";
26+
}
27+
28+
export function attrTokenToString(token: AttrToken): string {
29+
if (!isAttrToken(token)) { return String(token); }
30+
return `${token.namespace}|${token.name}`;
31+
}
32+
33+
export class Attribute extends Inheritable<Attribute, Block, BlockClass, AttrValue, AttrToken>
34+
{
35+
36+
private _hasValues = false;
37+
private _universalValue: AttrValue | undefined;
38+
private _sourceAttributes: Attr[] | undefined;
39+
40+
protected get ChildConstructor(): typeof AttrValue { return AttrValue; }
41+
protected tokenToUid(token: AttrToken): string { return attrTokenToString(token); }
42+
43+
public get name(): string { return this.token.name; }
44+
public get namespace(): string { return this.token.namespace; }
45+
46+
/**
47+
* @returns If this Attribute contains anything but the "Universal" AttrValue.
48+
**/
49+
get hasValues(): boolean { return this._hasValues; }
50+
51+
/**
52+
* @returns If this Attribute only contains the "Universal" AttrValue.
53+
**/
54+
get isBooleanAttribute(): boolean { return !this._hasValues; }
55+
56+
/**
57+
* @returns The "Universal" Value, or `undefined`.
58+
**/
59+
get universalValue(): AttrValue | undefined { return this._universalValue; }
60+
61+
/**
62+
* @returns This Attribute's parent `BlockClass`
63+
**/
64+
get blockClass(): BlockClass { return this.parent; }
65+
66+
/**
67+
* @returns An array of all `AttrValue`s contained in this `Attribute`.
68+
**/
69+
values(): AttrValue[] { return this.children(); }
70+
71+
/**
72+
* @returns A hash of all `Value`s contained in this `Attribute`.
73+
**/
74+
valuesHash(): ObjectDictionary<AttrValue> { return this.childrenHash(); }
75+
resolveValuesHash(): ObjectDictionary<AttrValue> { return this.resolveChildrenHash(); }
76+
77+
/**
78+
* @returns An Map of all `Value`s contained in this `Attribute`.
79+
**/
80+
valuesMap(): Map<string, AttrValue> { return this.childrenMap(); }
81+
resolveValuesMap(): Map<string, AttrValue> { return this.resolveChildrenMap(); }
82+
83+
/**
84+
* Ensures that a AttrValue of name `name` exists in this Attribute. If no
85+
* `AttrValue` exists, one is created. If no name is passed, it ensures the
86+
* "Universal" AttrValue.
87+
* @param name string The `AttrValue` name to ensure.
88+
* @returns The `AttrValue`
89+
**/
90+
ensureValue(name: string = UNIVERSAL_ATTR_VALUE) {
91+
let value = this.ensureChild(name);
92+
if (name !== UNIVERSAL_ATTR_VALUE) { this._hasValues = true; }
93+
else { this._universalValue = value; }
94+
return value;
95+
}
96+
97+
/**
98+
* Get am Attribute's own (read: non-inherited) `AttrValue` of name
99+
* `name` from this `Attribute`. If no name is passed, it tries
100+
* to retrieve the "Universal" AttrValue.
101+
* @param name string The name of the `AttrValue` to retrieve.
102+
* @returns The `AttrValue` or `undefined`.
103+
**/
104+
getValue(name: string = UNIVERSAL_ATTR_VALUE): AttrValue | null { return this.getChild(name); }
105+
106+
/**
107+
* Get am Attribute's own or inherited `AttrValue` of name `name` from this
108+
* `Attribute` or its base. If no name is passed, it tries to retrieve
109+
* the "Universal" AttrValue.
110+
* @param name string The name of the `AttrValue` to retrieve.
111+
* @returns The `AttrValue` or `undefined`.
112+
**/
113+
resolveValue(name: string = UNIVERSAL_ATTR_VALUE): AttrValue | null { return this.resolveChild(name); }
114+
115+
/**
116+
* @returns whether this Attribute has any Values defined, directly or inherited.
117+
*/
118+
hasResolvedValues(): boolean {
119+
return !!(this.hasValues || this.base && this.base.hasResolvedValues());
120+
}
121+
122+
/**
123+
* Resolves all AttrValues from this Attribute's inheritance
124+
* chain.
125+
* @returns All AttrValues this Attribute contains.
126+
*/
127+
resolveValues(): Map<string, AttrValue> {
128+
let resolved: Map<string, AttrValue> = new Map();
129+
for (let base of this.resolveInheritance()) {
130+
if (base.values()) {
131+
resolved = new Map([...resolved, ...base._children]);
132+
}
133+
}
134+
Object.assign(resolved, this._children);
135+
return resolved;
136+
}
137+
138+
/**
139+
* @returns The bare Attribute selector with no qualifying `BlockClass` name.
140+
*/
141+
unqualifiedSource(value?: string): string {
142+
return `[${this.token.namespace}|${this.token.name}${(value && value !== UNIVERSAL_ATTR_VALUE) ? `=${value}` : ""}]`;
143+
}
144+
145+
/**
146+
* Retrieve this Attribute's selector as it appears in the Block source code.
147+
*
148+
* @param value If provided, it is used as the Attribute's value whether or not
149+
* it is allowed by the known AttrValues (this is useful for constructing
150+
* error messages).
151+
* @returns The Attribute's attribute selector.
152+
*/
153+
asSource(value?: string): string {
154+
return (this.blockClass.isRoot ? "" : this.blockClass.asSource()) + this.unqualifiedSource(value);
155+
}
156+
157+
/**
158+
* Emit analysis attributes for the `AttrValue`s this
159+
* `Attribute` represents in their authored source format.
160+
*/
161+
asSourceAttributes(): Attr[] {
162+
if (!this._sourceAttributes) {
163+
this._sourceAttributes = [];
164+
this._sourceAttributes.push(...this.blockClass.asSourceAttributes());
165+
let value: AttributeValueChoice | ValueAbsent;
166+
if (this.hasValues) {
167+
let values = new Array<ValueConstant>();
168+
for (let value of this.values()) {
169+
values.push({ constant: value.uid });
170+
}
171+
value = { oneOf: values };
172+
} else {
173+
value = { absent: true };
174+
}
175+
this._sourceAttributes.push(new AttributeNS(STATE_NAMESPACE, this.name, value));
176+
}
177+
return this._sourceAttributes;
178+
}
179+
180+
/**
181+
* Retrieve this AttrValue's local name, including the optional BlockClass and group designations.
182+
* @returns The AttrValue's local name.
183+
*/
184+
localName(): string {
185+
if (this.blockClass.isRoot) {
186+
return this.uid;
187+
} else {
188+
return `${this.blockClass.localName()}--${this.token.namespace}-${this.token.name}`;
189+
}
190+
}
191+
192+
/**
193+
* Export as new class name.
194+
* @param opts Option hash configuring output mode.
195+
* @returns String representing output class.
196+
*/
197+
cssClass(opts: OptionsReader) {
198+
switch (opts.outputMode) {
199+
case OutputMode.BEM:
200+
let cssClassName = this.blockClass.cssClass(opts);
201+
return `${cssClassName}--${this.token.name}`;
202+
default:
203+
throw new Error("this never happens");
204+
}
205+
}
206+
207+
/**
208+
* Return array self and all children.
209+
* @returns Array of Styles.
210+
*/
211+
all(): Attribute[] {
212+
return [this];
213+
}
214+
}
215+
216+
/**
217+
* @param o object The object to test.
218+
* @returns If the supplied object `o` is a `Attribute`.
219+
*/
220+
export function isAttribute(o: object): o is Attribute {
221+
return o instanceof Attribute;
222+
}

0 commit comments

Comments
 (0)