This is an alternative site for discovering Elm packages. You may be looking for the official Elm package site instead.

ModularDesign.Stylesheet

A CSS implementation for the Modular Design framework

This library allows you to embed a stylesheet within an HTML document using the <style> tag. In a browser that fully complies with the HTML 5 specification, you would be able to "scope" the stylesheet so that it is only applied to its parent element and all of that element's children, allowing you to embed multiple stylesheets that apply to different sections of the document. Unfortunately, as of September 2016, Firefox is the only browser that has implemented this scoping feature. Thus, for the time being, recommended use of this library is to follow standard practice and create one stylesheet that applies globally to the document. Instead of linking to an external CSS file, however, this library allows you to embed CSS code between <style> tags in the body of the HTML DOM.

Whereas it is standard practice to include <style> tags in the <head> of the document, Elm does not currently provide an interface to insert code into the document's <head>. This seems not to be a problem, as no difference in performance is apparent when <style> tags are inserted in the <body> of the document instead. Thus, the recommended use of this library is to embed a global stylesheet at the root node of the HTML tree prior to calling assembleHtml to render the view.

The approach to generating CSS used in this library is based on the elm-css package by Jeffrey Massung, version 1.1, licensed under BSD-3. I retained Massung's basic approach to "compiling" a stylesheet that will be applied when rendering a view, but rewrote much of the code so that it would be compatible with my Modular Design framework and would conform to my stylistic preferences for Elm. I also made some changes in implementation. Most significantly, I chose to encode CSS id and class variables as strings rather than union types in order to simplify the representation. Finally, I added support for attribute selectors and custom at-rule expressions.

The basic workflow for using this library is (1) create your rule sets, consisting of selectors (identifying elements) and declarations (defining styles), (2) add your rule sets to a new stylesheet along with any import URLs needed to access external resources (e.g., Google fonts), and (3) embed the stylesheet at the root level of your HTML tree. The constructor functions included with this library allow for semantically pleasing code that uses functional operators to chain expressions, making it easy to read and modify your CSS as you iterate your UI design.

See examples/Stylesheet.elm for a full working example.

CSS Representation

type Selector = Any | Tag String | Id String | Class String | Attribute (Selector, String, MatchValue) | Descendant (Selector, Selector) | Child (Selector, Selector) | Sibling (Selector, Selector) | Adjacent (Selector, Selector) | PseudoClass (Selector, List String) | PseudoElement (Selector, String) | At (String, String) | CssCode String

Represents a CSS selector, which defines the set of elements (and/or pseudo-elements) to which a set of style declarations apply. Specifications for each of the selector types may be found here. Here are some use examples:

Tag "div"   --> div

Id "identifier"   --> #identifier

Class "class-name"   --> .class-name

Attribute (Tag "a", "href", StartsWith "#")   --> a[href^="#"]

Descendant (Tag "article", Tag "p")    --> article p

Child (Class "content", Child (Tag "ul", Tag "li"))   --> .content > ul > li

Sibling (Tag "p", Child (Tag "ul", Tag "li"))   --> p ~ ul > li

Adjacent (Tag "p", Child (Tag "ul", Tag "li"))    --> p + ul > li

PseudoClass (Tag "li", [ "nth-child(1)", "hover" ])    --> li:nth-child(1):hover

PseudoElement (PseudoClass (Tag "li", [ "hover" ]), "after")   --> li:hover::after

At ("media", "screen and (min-width: 700px)")   --> @media screen and (min-width: 700px)

On occasion, it might be simpler and more readable just to define the selector using CSS code. That is what the CssCode type is for:

CssCode "article p ~ ul > li:nth-child(1)"   --> article p ~ ul > li:nth-child(1)
type MatchValue = IsDefined | Exactly String | Includes String | StartsWith String | EndsWith String | Contains String | Prefix String

Represents an expression that defines a set of matching values for a given HTML attribute. Specifications for attribute selectors may be found here. IsDefined corresponds to [attr], Exactly corresponds to [attr=value], Includes corresponds to [attr~=value], StartsWith corresponds to [attr^=value], EndsWith corresponds to [attr$=value], Contains corresponds to [attr*=value], and Prefix corresponds to [attr|=value].

type alias RuleSet = { selectors : List Selector , declarations : List (String, String) }

A rule set consists of one or more selectors that define a set of elements (and/or pseudo-elements) and one or more style declarations that apply to those elements.

type alias Stylesheet = { imports : List String , prepends : List String , rules : List RuleSet , scoped : Bool }

A stylesheet consists of one or more rule sets and, optionally: (1) a list of external style resources to import; (2) a list of inline CSS code snippets to prepend above the stylesheet's rule statements. By default, a stylesheet applies globally to the HTML document, but it also contains a scoped attribute that can be set to True to take advantage of CSS scoping in HTML 5 (currently only implemented in the Firefox browser).

Constructing Rule Sets

newRuleSet : RuleSet

Initialize a new rule set

withSelectors : List Selector -> RuleSet -> RuleSet

Add a list of selectors to a rule set, replacing any existing selectors

addSelector : Selector -> RuleSet -> RuleSet

Add a new selector to a rule set, retaining any existing selectors

withDeclarations : List (String, String) -> RuleSet -> RuleSet

Add a list of style declarations to a rule set, replacing any existing declarations

addDeclaration : (String, String) -> RuleSet -> RuleSet

Add a new style declaration to a rule set, retaining any existing declarations

Constructing a Stylesheet

newStylesheet : Stylesheet

Initialize a new stylesheet

withImports : List String -> Stylesheet -> Stylesheet

Add a list of imports to a stylesheet, replacing any existing imports

addImport : String -> Stylesheet -> Stylesheet

Add a new import to a stylesheet, retaining any existing imports

withPrepends : List String -> Stylesheet -> Stylesheet

Insert one or more strings of CSS code into a stylesheet after its imports and before its rule statements; this constructor will replace any existing prepends

addPrepend : String -> Stylesheet -> Stylesheet

Add a string of CSS code into a stylesheet after its imports (and after any existing prepends) and before its rule statements; this constructor will retain any existing prepends

withRules : List RuleSet -> Stylesheet -> Stylesheet

Add a list of rule sets to a stylesheet, replacing any existing rule sets

addRuleSet : RuleSet -> Stylesheet -> Stylesheet

Add a new rule set to a stylesheet, retaining any existing rule sets

scoped : Stylesheet -> Stylesheet

Set the stylesheet's scoped attribute to True. In an HTML 5 compliant browser, the stylesheet will only be applied to the element on which embedStylesheet is called and all of that element's children. As of September 2016 this scoping feature is only embedded in the Firefox browser.

Embedding a Stylesheet in an HTML Tree

embedStylesheet : Stylesheet -> HtmlTree msg -> HtmlTree msg

Using <style> tags, embed the stylesheet above the root node of an HTML tree as its sibling, inserting a new <div> as the common parent

Helpers for Constructing Combinator Selectors

descendantOf : Selector -> Selector -> Selector

Constructor function to create a descendent selector; intended to be used semantically as an infix function

Tag "p" `descendantOf` Tag "article"
childOf : Selector -> Selector -> Selector

Constructor function to create a child selector; intended to be used semantically as an infix function

Tag "li" `childOf` Tag "ul"
siblingOf : Selector -> Selector -> Selector

Constructor function to create a sibling selector; intended to be used semantically as an infix function

Tag "ul" `siblingOf` Tag "p"
adjacentTo : Selector -> Selector -> Selector

Constructor function to create an adjacent selector; intended to be used semantically as an infix function

Tag "ul" `adjacentTo` Tag "p"
pseudoClass : Selector -> String -> Selector

Constructor function to create a pseudo class selector; intended to be used semantically as an infix function

Tag "li" `pseudoClass` "hover"
pseudoElement : Selector -> String -> Selector

Constructor function to create a pseudo element selector; intended to be used semantically as an infix function

Tag "li" `pseudoElement` "after"

Helpers for Importing Google Fonts

type alias FontFamily = { name : String , variants : List String , subsets : List String }

Represents a family of fonts that may have multiple variants and character subsets. Used to construct import directives for Google Fonts.

newFontFamily : String -> FontFamily

Initialize a new font family; the string argument provides the font name

withVariants : List String -> FontFamily -> FontFamily

Add a list of variants to a font family, replacing any existing variants

addVariant : String -> FontFamily -> FontFamily

Add a new variant to a font family, retaining any existing variants

withSubsets : List String -> FontFamily -> FontFamily

Add a list of character subsets (e.g., "cyrillic") to a font family, replacing any existing subsets

addSubset : String -> FontFamily -> FontFamily

Add a new character subset to a font family, retaining any existing subsets

importGoogleFonts : List FontFamily -> Stylesheet -> Stylesheet

Given a list of FontFamily records and a stylesheet, add an import directive containing an API query that will retreive the specified families/variants from Google Fonts

module ModularDesign.Stylesheet exposing
  ( Selector(..), MatchValue(..), RuleSet, Stylesheet, newRuleSet, withSelectors
  , addSelector, withDeclarations, addDeclaration, newStylesheet, withImports
  , addImport, withPrepends, addPrepend, withRules, addRuleSet, scoped
  , embedStylesheet, descendantOf, childOf, siblingOf, adjacentTo, pseudoClass
  , pseudoElement, FontFamily, newFontFamily, withVariants, addVariant, withSubsets
  , addSubset, importGoogleFonts
  )


{-|

## A CSS implementation for the Modular Design framework

This library allows you to embed a stylesheet within an HTML document using
the `<style>` tag. In a browser that fully complies with the HTML 5
specification, you would be able to "scope" the stylesheet so that it is only
applied to its parent element and all of that element's children, allowing you
to embed multiple stylesheets that apply to different sections of the document.
Unfortunately, as of September 2016, Firefox is the only browser that has
implemented this scoping feature. Thus, for the time being, recommended use of
this library is to follow standard practice and create one stylesheet that
applies globally to the document. Instead of linking to an external CSS file,
however, this library allows you to embed CSS code between `<style>` tags in the
body of the HTML DOM.

Whereas it is standard practice to include `<style>` tags in the `<head>` of the
document, Elm does not currently provide an interface to insert code into the
document's `<head>`. This seems not to be a problem, as no difference in
performance is apparent when `<style>` tags are inserted in the `<body>` of the
document instead. Thus, the recommended use of this library is to embed a
global stylesheet at the root node of the HTML tree prior to calling
`assembleHtml` to render the view.

The approach to generating CSS used in this library is based on the
[elm-css package](https://github.com/massung/elm-css) by Jeffrey Massung,
version 1.1, licensed under BSD-3. I retained Massung's basic approach to
"compiling" a stylesheet that will be applied when rendering a view, but
rewrote much of the code so that it would be compatible with my Modular Design
framework and would conform to my stylistic preferences for Elm. I also made
some changes in implementation. Most significantly, I chose to encode CSS id and
class variables as strings rather than union types in order to simplify the
representation. Finally, I added support for attribute selectors and custom
at-rule expressions.

The basic workflow for using this library is (1) create your rule sets,
consisting of selectors (identifying elements) and declarations (defining
styles), (2) add your rule sets to a new stylesheet along with any import URLs
needed to access external resources (e.g., Google fonts), and (3) embed the
stylesheet at the root level of your HTML tree. The constructor functions
included with this library allow for semantically pleasing code that uses
functional operators to chain expressions, making it easy to read and modify
your CSS as you iterate your UI design.

See
[examples/Stylesheet.elm](https://github.com/danielnarey/elm-modular-design/tree/master/examples)
for a full working example.


# CSS Representation
@docs Selector, MatchValue, RuleSet, Stylesheet

# Constructing Rule Sets
@docs newRuleSet, withSelectors, addSelector, withDeclarations, addDeclaration

# Constructing a Stylesheet
@docs newStylesheet, withImports, addImport, withPrepends, addPrepend
@docs withRules, addRuleSet, scoped

# Embedding a Stylesheet in an HTML Tree
@docs embedStylesheet

# Helpers for Constructing Combinator Selectors
@docs descendantOf, childOf, siblingOf, adjacentTo, pseudoClass
@docs pseudoElement

# Helpers for Importing Google Fonts
@docs FontFamily, newFontFamily, withVariants, addVariant, withSubsets
@docs addSubset, importGoogleFonts
-}

import ModularDesign exposing (..)
import ModularDesign.Operators exposing (..)
import String


{-| Represents a CSS selector, which defines the set of elements (and/or
pseudo-elements) to which a set of style declarations apply. Specifications
for each of the selector types may be found
[here](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors).
Here are some use examples:

    Tag "div"   --> div

    Id "identifier"   --> #identifier

    Class "class-name"   --> .class-name

    Attribute (Tag "a", "href", StartsWith "#")   --> a[href^="#"]

    Descendant (Tag "article", Tag "p")    --> article p

    Child (Class "content", Child (Tag "ul", Tag "li"))   --> .content > ul > li

    Sibling (Tag "p", Child (Tag "ul", Tag "li"))   --> p ~ ul > li

    Adjacent (Tag "p", Child (Tag "ul", Tag "li"))    --> p + ul > li

    PseudoClass (Tag "li", [ "nth-child(1)", "hover" ])    --> li:nth-child(1):hover

    PseudoElement (PseudoClass (Tag "li", [ "hover" ]), "after")   --> li:hover::after

    At ("media", "screen and (min-width: 700px)")   --> @media screen and (min-width: 700px)

On occasion, it might be simpler and more readable just to define the selector
using CSS code. That is what the `CssCode` type is for:

    CssCode "article p ~ ul > li:nth-child(1)"   --> article p ~ ul > li:nth-child(1)

-}
type Selector
  = Any
  | Tag String
  | Id String
  | Class String
  | Attribute (Selector, String, MatchValue)
  | Descendant (Selector, Selector)
  | Child (Selector, Selector)
  | Sibling (Selector, Selector)
  | Adjacent (Selector, Selector)
  | PseudoClass (Selector, List String)
  | PseudoElement (Selector, String)
  | At (String, String)
  | CssCode String


{-| Represents an expression that defines a set of matching values for a given
HTML attribute. Specifications for attribute selectors may be found
[here](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors).
`IsDefined` corresponds to [attr], `Exactly` corresponds to [attr=value],
`Includes` corresponds to [attr~=value], `StartsWith` corresponds to
[attr^=value], `EndsWith` corresponds to [attr$=value], `Contains`
corresponds to [attr*=value], and `Prefix` corresponds to [attr|=value].
-}
type MatchValue
  = IsDefined
  | Exactly String
  | Includes String
  | StartsWith String
  | EndsWith String
  | Contains String
  | Prefix String


{-| A rule set consists of one or more selectors that define a set of elements
(and/or pseudo-elements) and one or more style declarations that apply to those
elements.
-}
type alias RuleSet =
  { selectors : List Selector
  , declarations : List (String, String)
  }


{-| A stylesheet consists of one or more rule sets and, optionally: (1) a list
of external style resources to import; (2) a list of inline CSS code snippets
to prepend above the stylesheet's rule statements. By default, a stylesheet
applies globally to the HTML document, but it also contains a `scoped` attribute
that can be set to `True` to take advantage of CSS scoping in HTML 5
(currently only implemented in the Firefox browser).
-}
type alias Stylesheet =
  { imports : List String
  , prepends : List String
  , rules : List RuleSet
  , scoped : Bool
  }


{-| Initialize a new rule set
-}
newRuleSet : RuleSet
newRuleSet =
  RuleSet [] []


{-| Add a list of selectors to a rule set, *replacing* any existing selectors
-}
withSelectors : List Selector -> RuleSet -> RuleSet
withSelectors selectorList ruleSet =
  { ruleSet
  | selectors =
      selectorList
  }


{-| Add a new selector to a rule set, *retaining* any existing selectors
-}
addSelector : Selector -> RuleSet -> RuleSet
addSelector newSelector ruleSet =
  { ruleSet
  | selectors =
      ruleSet.selectors ++ [ newSelector ]
  }


{-| Add a list of style declarations to a rule set, *replacing* any existing
declarations
-}
withDeclarations : List (String, String) -> RuleSet -> RuleSet
withDeclarations declarationList ruleSet =
  { ruleSet
  | declarations =
      declarationList
  }


{-| Add a new style declaration to a rule set, *retaining* any existing
declarations
-}
addDeclaration : (String, String) -> RuleSet -> RuleSet
addDeclaration newDeclaration ruleSet =
  { ruleSet
  | declarations =
      ruleSet.declarations ++ [ newDeclaration ]
  }


{-| Initialize a new stylesheet
-}
newStylesheet : Stylesheet
newStylesheet =
  Stylesheet [] [] [] False


{-| Add a list of imports to a stylesheet, *replacing* any existing imports
-}
withImports : List String -> Stylesheet -> Stylesheet
withImports importList stylesheet =
  { stylesheet
  | imports =
      importList
  }


{-| Add a new import to a stylesheet, *retaining* any existing imports
-}
addImport : String -> Stylesheet -> Stylesheet
addImport newImport stylesheet =
  { stylesheet
  | imports =
      stylesheet.imports ++ [ newImport ]
  }


{-| Insert one or more strings of CSS code into a stylesheet after its imports
and before its rule statements; this constructor will *replace* any existing
prepends
-}
withPrepends : List String -> Stylesheet -> Stylesheet
withPrepends prependList stylesheet =
  { stylesheet
  | prepends =
      prependList
  }


{-| Add a string of CSS code into a stylesheet after its imports (and after
any existing prepends) and before its rule statements; this constructor will
*retain* any existing prepends
-}
addPrepend : String -> Stylesheet -> Stylesheet
addPrepend prepend stylesheet =
  { stylesheet
  | prepends =
      stylesheet.prepends ++ [ prepend ]
  }


{-| Add a list of rule sets to a stylesheet, *replacing* any existing rule sets
-}
withRules : List RuleSet -> Stylesheet -> Stylesheet
withRules ruleList stylesheet =
  { stylesheet
  | rules =
      ruleList
  }


{-| Add a new rule set to a stylesheet, *retaining* any existing rule sets
-}
addRuleSet : RuleSet -> Stylesheet -> Stylesheet
addRuleSet newRuleSet stylesheet =
  { stylesheet
  | rules =
      stylesheet.rules ++ [ newRuleSet ]
  }


{-| Set the stylesheet's `scoped` attribute to `True`. In an HTML 5 compliant
browser, the stylesheet will only be applied to the element on which
`embedStylesheet` is called and all of that element's children. As of September
2016 this scoping feature is only embedded in the Firefox browser.
-}
scoped : Stylesheet -> Stylesheet
scoped stylesheet =
  { stylesheet
  | scoped =
      True
  }


{-| Using `<style>` tags, embed the stylesheet above the root node of an HTML
tree as its sibling, inserting a new `<div>` as the common parent
-}
embedStylesheet : Stylesheet -> HtmlTree msg -> HtmlTree msg
embedStylesheet stylesheet rootNode =
  let
    importDirectives =
      stylesheet.imports
        .|> (\a -> "@import url(" ++ a ++ ");")
        |> String.concat

    prependedCss =
      stylesheet.prepends
        |> String.concat

    ruleStatements =
      stylesheet.rules
        .|> encodeRuleSet
        |> String.concat

    styleNode =
      importDirectives ++ prependedCss ++ ruleStatements
        |> textWrapper "style"
        |> withAttributes
          [ ("scoped", stylesheet.scoped |> toString) ]

  in
    [ styleNode, rootNode ]
      |> container "div"


{-| Encode a rule set to CSS
-}
encodeRuleSet : RuleSet -> String
encodeRuleSet ruleSet =
  let
    selectors =
      ruleSet.selectors
        .|> encodeSelector
        |> String.join ","

    descriptors =
      ruleSet.declarations
        .|> ( \(k, v) -> k ++ ":" ++ v ++ ";" )
        |> String.concat

  in
    selectors ++ "{" ++ descriptors ++ "}"


{-| Encode a selector to CSS
-}
encodeSelector : Selector -> String
encodeSelector selector =
  case selector of
    Any ->
      "*"

    Tag tagName ->
      tagName

    Id idString ->
      "#" ++ idString

    Class className ->
      "." ++ className

    Attribute (s, name, expr) ->
      encodeSelector s
        |++ "[" ++ name
        |++ encodeMatchExpr expr
        |++ "]"

    Descendant (s1, s2) ->
      encodeSelector s1
        |++ " "
        |++ encodeSelector s2

    Child (s1, s2) ->
      encodeSelector s1
        |++ " > "
        |++ encodeSelector s2

    Sibling (s1, s2) ->
      encodeSelector s1
        |++ " ~ "
        |++ encodeSelector s2

    Adjacent (s1, s2) ->
      encodeSelector s1
        |++ " + "
        |++ encodeSelector s2

    PseudoClass (s, pseudoList) ->
      [ encodeSelector s ] ++ pseudoList
        |> String.join ":"

    PseudoElement (s, pseudo) ->
      encodeSelector s
        |++ "::"
        |++ pseudo

    At (keyword, expr) ->
      "@" ++ keyword ++ " " ++ expr

    CssCode expr ->
      expr


{-| Encode a matching expression to CSS
-}
encodeMatchExpr : MatchValue -> String
encodeMatchExpr matchValue =
  case matchValue of
    IsDefined ->
      ""

    Exactly value ->
      "=" ++ "\"" ++ value ++ "\""

    Includes value ->
      "~=" ++ "\"" ++ value ++ "\""

    StartsWith value ->
      "^=" ++ "\"" ++ value ++ "\""

    EndsWith value ->
      "$=" ++ "\"" ++ value ++ "\""

    Contains value ->
      "*=" ++ "\"" ++ value ++ "\""

    Prefix value ->
      "|=" ++ "\"" ++ value ++ "\""


{-| Constructor function to create a descendent selector; intended to be
used semantically as an infix function

    Tag "p" `descendantOf` Tag "article"
-}
descendantOf : Selector -> Selector -> Selector
descendantOf s2 s1 =
  Descendant (s1, s2)


{-| Constructor function to create a child selector; intended to be used
semantically as an infix function

    Tag "li" `childOf` Tag "ul"
-}
childOf : Selector -> Selector -> Selector
childOf s2 s1 =
  Child (s1, s2)


{-| Constructor function to create a sibling selector; intended to be used
semantically as an infix function

    Tag "ul" `siblingOf` Tag "p"
-}
siblingOf : Selector -> Selector -> Selector
siblingOf s2 s1 =
  Sibling (s1, s2)


{-| Constructor function to create an adjacent selector; intended to be used
semantically as an infix function

    Tag "ul" `adjacentTo` Tag "p"
-}
adjacentTo : Selector -> Selector -> Selector
adjacentTo s2 s1 =
  Adjacent (s1, s2)


{-| Constructor function to create a pseudo class selector; intended to be used
semantically as an infix function

    Tag "li" `pseudoClass` "hover"
-}
pseudoClass : Selector -> String -> Selector
pseudoClass s pseudo =
  PseudoClass (s, [ pseudo ])


{-| Constructor function to create a pseudo element selector; intended to be
used semantically as an infix function

    Tag "li" `pseudoElement` "after"
-}
pseudoElement : Selector -> String -> Selector
pseudoElement s pseudo =
  PseudoElement (s, pseudo)


{-| Represents a family of fonts that may have multiple variants and character
subsets. Used to construct import directives for Google Fonts.
-}
type alias FontFamily =
  { name : String
  , variants : List String
  , subsets : List String
  }


{-| Initialize a new font family; the string argument provides the font name
-}
newFontFamily : String -> FontFamily
newFontFamily name =
  FontFamily name [] []


{-| Add a list of variants to a font family, *replacing* any existing variants
-}
withVariants : List String -> FontFamily -> FontFamily
withVariants variantList family =
  { family
  | variants =
      variantList
  }


{-| Add a new variant to a font family, *retaining* any existing variants
-}
addVariant : String -> FontFamily -> FontFamily
addVariant variant family =
  { family
  | variants =
      family.variants ++ [ variant ]
  }


{-| Add a list of character subsets (e.g., "cyrillic") to a font family,
*replacing* any existing subsets
-}
withSubsets : List String -> FontFamily -> FontFamily
withSubsets subsetList family =
  { family
  | subsets =
      subsetList
  }


{-| Add a new character subset to a font family, *retaining* any existing
subsets
-}
addSubset : String -> FontFamily -> FontFamily
addSubset subset family =
  { family
  | subsets =
      family.subsets ++ [ subset ]
  }


{-| Given a list of `FontFamily` records and a stylesheet, add an import
directive containing an API query that will retreive the specified
families/variants from Google Fonts
-}
importGoogleFonts : List FontFamily -> Stylesheet -> Stylesheet
importGoogleFonts fontFamilies stylesheet =
  let
    encodeName name =
      name
        |> String.words
        |> String.join "+"

    encodeVariants variants =
      if variants |> List.isEmpty then
        ""
      else
        ":"
          |++ (variants |> String.join ",")

    encodeSubsets subsets =
      if subsets |> List.isEmpty then
        ""
      else
        "&subset="
          |++ (subsets |> String.join ",")

    encodeFamily family =
      family.name
        |> encodeName
        |++ (family.variants |> encodeVariants)
        |++ (family.subsets |> encodeSubsets)

    queryValue =
      fontFamilies
        .|> encodeFamily
        |> String.join "|"

  in
    stylesheet
      |> addImport ("https://fonts.googleapis.com/css?family=" ++ queryValue)