|
| 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