Drafter.Style.Stylesheet (drafter v0.3.2)

Copy Markdown View Source

An ordered list of {selectors, style} rules, and the cascade that resolves them.

A rule pairs a list of Drafter.Style.Selector structs with a Drafter.Style map. compute_style/2 keeps the rules whose selectors match, sorts them by ascending specificity, and merges them left to right, so the most specific rule wins. Rules of equal specificity keep insertion order, so the one added last wins.

A rule matches when any of its selectors matches — the list is a selector group, as written "a, b" in CSS.

iex> alias Drafter.Style.Stylesheet
iex> sheet = Stylesheet.new(%{"button" => %{color: :red}, "button:focus" => %{bold: true}})
iex> Stylesheet.compute_style(sheet, %{widget_type: :button})
%{color: :red}
iex> Stylesheet.compute_style(sheet, %{widget_type: :button, focused: true})
%{bold: true, color: :red}

Summary

Types

Anything new/1, add_rule/3 and add_rules/2 accept where a selector is expected.

t()

Functions

The stylesheet with one more rule appended.

The stylesheet with every rule in rules appended, in order.

The style context resolves to under this stylesheet.

The style a named sub-region of a widget resolves to.

A stylesheet holding the first's rules followed by the second's.

A stylesheet holding rules.

Types

rule()

selector_source()

@type selector_source() ::
  String.t()
  | atom()
  | Drafter.Style.Selector.t()
  | [Drafter.Style.Selector.t()]

Anything new/1, add_rule/3 and add_rules/2 accept where a selector is expected.

A string or atom is handed to Drafter.Style.Selector.parse/1; a Selector struct is wrapped in a one-element list; a list is taken as already parsed.

t()

@type t() :: %Drafter.Style.Stylesheet{rules: [rule()]}

Functions

add_rule(stylesheet, selector, style)

@spec add_rule(t(), selector_source(), map()) :: t()

The stylesheet with one more rule appended.

Appending, rather than prepending, is what makes a later rule win a specificity tie. style is filtered through Drafter.Style.new/1.

Examples

iex> alias Drafter.Style.Stylesheet
iex> sheet =
...>   Stylesheet.new()
...>   |> Stylesheet.add_rule("label", %{color: :red})
...>   |> Stylesheet.add_rule("label", %{color: :blue})
iex> Stylesheet.compute_style(sheet, %{widget_type: :label})
%{color: :blue}

add_rules(stylesheet, rules)

@spec add_rules(t(), map() | [{selector_source(), map()}]) :: t()

The stylesheet with every rule in rules appended, in order.

rules is a map or a list of {selector, style} pairs. Map order is undefined.

Examples

iex> alias Drafter.Style.Stylesheet
iex> Stylesheet.new()
...> |> Stylesheet.add_rules([{"label", %{color: :red}}, {"button", %{bold: true}}])
...> |> Map.fetch!(:rules)
...> |> length()
2

compute_style(stylesheet, context)

@spec compute_style(t(), Drafter.Style.Selector.context()) :: Drafter.Style.t()

The style context resolves to under this stylesheet.

Matching rules are merged in ascending specificity order, so a more specific rule overrides a less specific one key by key. Returns %{} when nothing matches. See Drafter.Style.Selector.matches?/2 for what a context may hold.

Examples

iex> alias Drafter.Style.Stylesheet
iex> sheet = Stylesheet.new([{"button", %{color: :red}}, {"button.primary", %{color: :blue}}])
iex> Stylesheet.compute_style(sheet, %{widget_type: :button, classes: [:primary]})
%{color: :blue}
iex> Stylesheet.compute_style(sheet, %{widget_type: :label})
%{}

compute_style_for_part(stylesheet, context, part)

@spec compute_style_for_part(t(), Drafter.Style.Selector.context(), atom()) ::
  Drafter.Style.t()

The style a named sub-region of a widget resolves to.

Equivalent to compute_style/2 on context with :part set to part, overwriting any :part already there. Rules naming no part still match, so the widget's own rules apply underneath the part's.

Examples

iex> alias Drafter.Style.Stylesheet
iex> sheet =
...>   Stylesheet.new([
...>     {"text_input", %{color: :red}},
...>     {"text_input::border", %{bold: true}}
...>   ])
iex> Stylesheet.compute_style_for_part(sheet, %{widget_type: :text_input}, :border)
%{bold: true, color: :red}

merge(s1, s2)

@spec merge(t(), t()) :: t()

A stylesheet holding the first's rules followed by the second's.

Nothing is deduplicated, and the second sheet's rules win ties because they come later.

Examples

iex> alias Drafter.Style.Stylesheet
iex> base = Stylesheet.new([{"label", %{color: :red}}])
iex> override = Stylesheet.new([{"label", %{color: :blue}}])
iex> Stylesheet.merge(base, override) |> Stylesheet.compute_style(%{widget_type: :label})
%{color: :blue}

new(rules \\ [])

@spec new(map() | [{selector_source(), map()}]) :: t()

A stylesheet holding rules.

rules is a map or a list of {selector, style} pairs, [] by default. Each selector is parsed as selector_source/0 and each style is filtered through Drafter.Style.new/1, which drops keys that are not style properties. A list entry that is not a two-element tuple is stored unchanged and will fail later in compute_style/2.

Map order is undefined, so pass a list when rules of equal specificity must resolve in a known order.

Examples

iex> Drafter.Style.Stylesheet.new()
%Drafter.Style.Stylesheet{rules: []}

iex> sheet = Drafter.Style.Stylesheet.new([{"label", %{color: :red, nonsense: 1}}])
iex> [{_selectors, style}] = sheet.rules
iex> style
%{color: :red}