Class: CSS::Cascade

Inherits:
Object
  • Object
show all
Defined in:
lib/css/cascade.rb

Overview

Resolves the cascade for a Stylesheet against a single element. Returns ‘Hash<String, Declaration>` keyed by property name with the winning declaration after applying:

- `@media` filtering (against a `MediaQueries::Context`)
- selector matching (`Selectors::Matcher`)
- cascade sort: `!important` > origin / inline > specificity > source order

The Stylesheet is compiled once on construction (selectors are pre-parsed, specificities pre-computed, ‘@media` chains are evaluated against the supplied context up-front, and rules are indexed by the rightmost compound’s strongest anchor — id > class > tag > universal). ‘resolve(element)` then visits only the rules whose anchor could match the element.

Cascade layers, ‘@scope` proximity, and Shadow DOM encapsulation are not modeled — `@layer`, `@supports`, `@container`, `@scope`, and `@starting-style` blocks are descended into unconditionally.

Defined Under Namespace

Classes: AnchorKey, Index, Match, RuleEntry

Constant Summary collapse

TRANSPARENT_AT_RULES =
%w[supports layer scope starting-style container].freeze

Instance Method Summary collapse

Constructor Details

#initialize(stylesheet, context: MediaQueries::Context.default) ⇒ Cascade

Returns a new instance of Cascade.



27
28
29
30
31
# File 'lib/css/cascade.rb', line 27

def initialize(stylesheet, context: MediaQueries::Context.default)
  @context = context
  @entries = compile(stylesheet)
  @index   = build_index(@entries)
end

Instance Method Details

#resolve(element, inline_style: nil, state: nil, cache: nil) ⇒ Object

Returns Hash<String, Declaration> of winning declarations.

‘state:` opts into stateful-pseudo matching — see `Selectors::Matcher#matches?` for the shape. Defaults to the stateless behavior (`:hover`, `:focus`, etc. never match).

‘cache:` lets callers share a per-element context cache across many resolves. The default `{}` is local to one call. Pass a persistent Hash when the DOM is stable across many resolves — Context (tag, id, classes) computation runs once per element instead of per resolve. The caller is responsible for clearing/replacing the cache on DOM mutation.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/css/cascade.rb', line 45

def resolve(element, inline_style: nil, state: nil, cache: nil)
  cache     ||= {}
  candidates  = collect_candidate_indexes(element, cache)
  order       = 0
  matches     = []

  candidates.each do |idx|
    entry = @entries[idx]
    spec  = best_matching_specificity(element, entry.selector_pairs, cache, state)

    next if spec.nil?

    entry.declarations.each do |decl|
      order += 1
      matches << Match.new(declaration: decl, specificity: spec, inline: false, order: order)
    end
  end

  if inline_style
    inline_declarations(inline_style).each do |decl|
      order += 1
      matches << Match.new(declaration: decl, specificity: Selectors::Specificity::ZERO, inline: true, order: order)
    end
  end

  pick_winners(matches)
end