Skip to content

Instantly share code, notes, and snippets.

@jonathantneal
Last active December 2, 2019 21:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonathantneal/42d98aac8e5eb8e927f35fc4be1c5047 to your computer and use it in GitHub Desktop.
Save jonathantneal/42d98aac8e5eb8e927f35fc4be1c5047 to your computer and use it in GitHub Desktop.
CSS Loose Object Model

Loose CSS Object Model

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.

CSSDetail

The CSSDetail represents all whitespace and comments in CSS.

type CSSDetail = {
  value: String;
}

There are 2 kinds of CSSDetail; the CSSComment and the CSSWhitespace.

CSSComment

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: " 🤓 "
}

CSSWhitespace

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

CSSValue

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: " " } ]
  }
}

CSSFunction

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: "   " } ]
  ]
}

CSSDeclaration

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: "     " } ]
  }
}

CSSRule

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: " " } ]
  }
}

CSSAtRule

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 and prelude. Does this mean that space should belong to the CSSAtRule as beforePrelude?

CSSSheet

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.

CSSBlock

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[];
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment