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.
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
@type rule() :: {Drafter.Style.Selector.t() | [Drafter.Style.Selector.t()], Drafter.Style.t()}
@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.
@type t() :: %Drafter.Style.Stylesheet{rules: [rule()]}
Functions
@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}
@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
@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})
%{}
@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}
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}
@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}