The "Loose" CSS Object Model is a primative object model compatible with the CSS Syntax that preserves every single code point in CSS Objects without sacrificing the usability of those objects to a developer.
The core concept of the "loose" CSS object model is to separate all information into 2 kinds of objects; the CSSDetail and the CSSValue.
The CSSDetail represents all whitespace and comments in CSS.
type CSSDetail = {
value: String;
}
There are 2 kinds of CSSDetail; the CSSComment and the CSSWhitespace.
The CSSComment represents a comment in CSS. It has a value for the string content of everything between the opening /*
and the closing */
.
type CSSComment = CSSDetail & {
value: String;
}
Consider the following CSS and the equivalent Object representation:
/* 🤓 */
CSSComment {
value: " 🤓 "
}
The CSSWhitespace represents whitespace in CSS. It has a value for the string content of a consecutive group of whitespace.
type CSSWhitespace = CSSDetail & {
value: String;
}
Note: Developers may find it significant to know whether whitespace contains "block" whitespace (e.g. newlines like form feeds, line feeds, carriage returns) or "inline" whitespace (e.g. spaces, tabs).
The CSSValue represents all meaningful kinds of CSS. It has a value for the string content of any pattern or character of meaningful value associated with its subclass. It also has a detail object for any whitespace or comments that might be associated with the value.
type CSSValue = {
value: String;
detail: {
beforeValue?: CSSDetail[];
[key: String]: CSSDetail[];
}
}
A core concept of the "loose" CSS object model is to associate every CSSValue with its preceeding CSSDetail.
Consider the following CSS and the equivalent Object representation:
/* 🤓 */ initial
CSSValue {
value: "initial"
detail: {
beforeValue: [ CSSComment { value: " 🤓 " }, CSSWhitespace { value: " " } ]
}
}
The CSSFunction represents an action or purpose in CSS. It has a name for identity and a value for content.
type CSSFunction = CSSValue & {
name: String;
value: CSSValue[];
detail: {
beforeName: CSSDetail[];
beforeClosing: CSSDetail[];
}
}
Consider the following CSS and the equivalent Object representation:
/* 🤓 */ --custom-function( --custom-value )
CSSFunction {
name: "--custom-function"
value: [
CSSValue {
value: "--custom-value"
detail: {
beforeValue: [ CSSWhitespace { value: " " } ]
}
}
]
detail: [
beforeName: [ CSSComment { value: " 🤓 " }, CSSWhitespace { value: " " } ]
beforeClosing: [ CSSWhitespace { value: " " } ]
]
}
The CSSDeclaration represents a specific description of style. It has a name for identity, a value for content, an important for explicit significance, and a semicolon for separation.
type CSSDeclaration = CSSValue & {
name: String;
value: CSSValue[];
important: String;
semicolon: Boolean;
detail: {
beforeName: CSSDetail[];
beforeColon: CSSDetail[];
beforeImportant: CSSDetail[];
beforeSemicolon: CSSDetail[];
}
}
Consider the following CSS and the equivalent Object representation:
/* 🤓 */ color : blue !important ;
CSSDeclaration {
name: "color"
value: [
CSSValue {
value: "blue"
detail: {
beforeValue: [ CSSWhitespace { value: " " } ]
}
}
]
important: "important"
semicolon: true
detail: {
beforeName: [ CSSComment { value: " 🤓 " }, CSSWhitespace { value: " " } ]
beforeColon: [ CSSWhitespace { value: " " } ]
beforeImportant: [ CSSWhitespace { value: " " } ]
beforeSemicolon: [ CSSWhitespace { value: " " } ]
}
}
The CSSRule represents a collection of processing rules or values. It has a prelude for context and an optional value for content.
It also has a detail object for any whitespace or comments that might be associated with the prelude or mirrored curly braces.
type CSSRule = CSSValue & {
prelude: CSSValue[];
value?: CSSValue[];
detail: {
beforeOpening: CSSDetail[];
beforeClosing: CSSDetail[];
}
}
Consider the following CSS and the equivalent Object representation:
/* 🤓 */ body { all: initial }
CSSRule {
prelude: [
CSSValue {
value: "body"
detail: {
beforeValue: [ CSSComment { value: " 🤓 " }, CSSWhitespace { value: " " } ]
}
}
]
value: [
CSSDeclaration {
name: "all"
value: [
CSSValue {
value: "initial"
detail: {
beforeValue: [ CSSWhitespace { value: " " } ]
}
}
]
important: ""
semicolon: false
detail: {
beforeName: [ CSSWhitespace { value: " " } ]
beforeColon: []
beforeImportant: []
beforeSemicolon: []
}
}
]
detail: {
beforeOpening: [ CSSWhitespace { value: " " } ]
beforeClosing: [ CSSWhitespace { value: " " } ]
}
}
The CSSAtRule represents a collection of processing rules or values. It has a name for identity, an optional prelude for context, and a value for content.
It also has a detail object for any whitespace or comments that might be associated with the prelude or mirrored curly braces.
type CSSAtRule = CSSValue & {
name: String;
prelude?: CSSValue[];
value?: CSSValue[];
detail: {
beforeName: CSSValue[];
beforeOpening: CSSDetail[];
beforeClosing: CSSDetail[];
}
}
Consider the following CSS and the equivalent Object representation:
/* 🤓 */ @media print {}
CSSAtRule {
name: "media",
prelude: [
CSSValue {
value: "print"
detail: {
beforeValue: [ CSSWhitespace { value: " " } ]
}
}
]
value: []
detail: {
beforeName: [ CSSComment { value: " 🤓 " }, CSSWhitespace { value: " " } ]
beforeOpening: [ CSSWhitespace { value: " " } ]
beforeClosing: []
}
}
Note: A CSS At-Rule requires whitespace between the
name
andprelude
. Does this mean that space should belong to the CSSAtRule asbeforePrelude
?
The CSSSheet represents a collection of processing values or comments. It has a value for content.
It also has a detail object for any whitespace or comments that might be associated with the collection.
type CSSSheet = CSSValue & {
value: CSSValue[];
beforeClosing: String;
}
Consider the following CSS and the equivalent Object representation:
@media print {}
/* 🤓 */
CSSSheet {
value: [
CSSAtRule {
name: "media",
prelude: [
CSSValue {
value: "print"
detail: {
beforeValue: [ CSSWhitespace { value: " " } ]
}
}
]
value: []
detail: {
beforeName: []
beforeOpening: [ CSSWhitespace { value: " " } ]
beforeClosing: []
}
}
]
detail: {
beforeClosing: [ CSSWhitespace { value: "\n\n" } CSSComment { value: " 🤓 " } CSSWhitespace { value: "\n" } ]
}
}
Note: If a CSS Comment comes after a rule and before any newline, should that mean the author intended it to belong to the preceeding Rule? Consider the following CSS:
@media screen {} /* do this */ @media print {} /* and then this */In that example,
/* do this */
would belong to the@media print {}
at-rule while/* and then this */
would belong to the sheet, even though it doesn’t appear that way. This suggests that newline whitespaces matter.
The CSSBlock represents a group of content with a shared context, delineated by mirrored brackets. It has a bracket for identity and a value for content.
type CSSBlock = CSSValue & <Bracket = "{" | "[" | "(">{
bracket: Bracket;
value: CSSValue[];
detail: {
beforeOpening: CSSDetail[];
}
}