Class: Sass::Selector::Pseudo

Inherits:
Simple
  • Object
show all
Defined in:
lib/sass/selector/pseudo.rb

Overview

A pseudoclass (e.g. :visited) or pseudoelement (e.g. ::first-line) selector. It can have arguments (e.g. :nth-child(2n+1)) which can contain selectors (e.g. :nth-child(2n+1 of .foo)).

Constant Summary collapse

ACTUALLY_ELEMENTS =

Some pseudo-class-syntax selectors are actually considered pseudo-elements and must be treated differently. This is a list of such selectors.

Returns:

  • (Set<String>)
%w(after before first-line first-letter).to_set

Instance Attribute Summary collapse

Attributes inherited from Simple

#filename, #line

Instance Method Summary collapse

Methods inherited from Simple

#eql?, #equality_key, #hash, #inspect, #unify_namespaces

Constructor Details

#initialize(syntactic_type, name, arg, selector) ⇒ Pseudo

Returns a new instance of Pseudo.

Parameters:



45
46
47
48
49
50
# File 'lib/sass/selector/pseudo.rb', line 45

def initialize(syntactic_type, name, arg, selector)
  @syntactic_type = syntactic_type
  @name = name
  @arg = arg
  @selector = selector
end

Instance Attribute Details

#argString? (readonly)

The argument to the selector, or nil if no argument was given.

Returns:

  • (String, nil)


31
32
33
# File 'lib/sass/selector/pseudo.rb', line 31

def arg
  @arg
end

#nameString (readonly)

The name of the selector.

Returns:

  • (String)


25
26
27
# File 'lib/sass/selector/pseudo.rb', line 25

def name
  @name
end

#selectorCommaSequence (readonly)

The selector argument, or nil if no selector exists.

If this and #arg are both set, #arg is considered a non-selector prefix.

Returns:



39
40
41
# File 'lib/sass/selector/pseudo.rb', line 39

def selector
  @selector
end

#syntactic_typeSymbol (readonly)

Like #type, but returns the type of selector this looks like, rather than the type it is semantically. This only differs from type for selectors in ACTUALLY_ELEMENTS.

Returns:

  • (Symbol)


20
21
22
# File 'lib/sass/selector/pseudo.rb', line 20

def syntactic_type
  @syntactic_type
end

Instance Method Details

#invisible?Boolean

Whether or not this selector should be hidden due to containing a placeholder.

Returns:

  • (Boolean)


58
59
60
61
62
# File 'lib/sass/selector/pseudo.rb', line 58

def invisible?
  # :not() is a special caseā€”if you eliminate all the placeholders from
  # it, it should match anything.
  name != 'not' && @selector && @selector.members.all? {|s| s.invisible?}
end

#normalized_nameString

Like #name, but without any vendor prefix.

Returns:

  • (String)


124
125
126
# File 'lib/sass/selector/pseudo.rb', line 124

def normalized_name
  @normalized_name ||= name.gsub(/^-[a-zA-Z0-9]+-/, '')
end

#specificity



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/sass/selector/pseudo.rb', line 247

def specificity
  return 1 if type == :element
  return SPECIFICITY_BASE unless selector
  @specificity ||=
    if normalized_name == 'not'
      min = 0
      max = 0
      selector.members.each do |seq|
        spec = seq.specificity
        if spec.is_a?(Range)
          min = Sass::Util.max(spec.begin, min)
          max = Sass::Util.max(spec.end, max)
        else
          min = Sass::Util.max(spec, min)
          max = Sass::Util.max(spec, max)
        end
      end
      min == max ? max : (min..max)
    else
      min = 0
      max = 0
      selector.members.each do |seq|
        spec = seq.specificity
        if spec.is_a?(Range)
          min = Sass::Util.min(spec.begin, min)
          max = Sass::Util.max(spec.end, max)
        else
          min = Sass::Util.min(spec, min)
          max = Sass::Util.max(spec, max)
        end
      end
      min == max ? max : (min..max)
    end
end

#superselector?(their_sseq, parents = []) ⇒ Boolean

Returns whether or not this selector matches all elements that the given selector matches (as well as possibly more).

Examples:

(.foo).superselector?(.foo.bar) #=> true
(.foo).superselector?(.bar) #=> false

Parameters:

Returns:

  • (Boolean)


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/sass/selector/pseudo.rb', line 166

def superselector?(their_sseq, parents = [])
  case normalized_name
  when 'matches', 'any'
    # :matches can be a superselector of another selector in one of two
    # ways. Either its constituent selectors can be a superset of those of
    # another :matches in the other selector, or any of its constituent
    # selectors can individually be a superselector of the other selector.
    (their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel|
      next false unless their_sel.is_a?(Pseudo)
      next false unless their_sel.name == name
      selector.superselector?(their_sel.selector)
    end || selector.members.any? do |our_seq|
      their_seq = Sequence.new(parents + [their_sseq])
      our_seq.superselector?(their_seq)
    end
  when 'has', 'host', 'host-context', 'slotted'
    # Like :matches, :has (et al) can be a superselector of another
    # selector if its constituent selectors are a superset of those of
    # another :has in the other selector. However, the :matches other case
    # doesn't work, because :has refers to nested elements.
    (their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel|
      next false unless their_sel.is_a?(Pseudo)
      next false unless their_sel.name == name
      selector.superselector?(their_sel.selector)
    end
  when 'not'
    selector.members.all? do |our_seq|
      their_sseq.members.any? do |their_sel|
        if their_sel.is_a?(Element) || their_sel.is_a?(Id)
          # `:not(a)` is a superselector of `h1` and `:not(#foo)` is a
          # superselector of `#bar`.
          our_sseq = our_seq.members.last
          next false unless our_sseq.is_a?(SimpleSequence)
          our_sseq.members.any? do |our_sel|
            our_sel.class == their_sel.class && our_sel != their_sel
          end
        else
          next false unless their_sel.is_a?(Pseudo)
          next false unless their_sel.name == name
          # :not(X) is a superselector of :not(Y) exactly when Y is a
          # superselector of X.
          their_sel.selector.superselector?(CommaSequence.new([our_seq]))
        end
      end
    end
  when 'current'
    (their_sseq.selector_pseudo_classes['current'] || []).any? do |their_current|
      next false if their_current.name != name
      # Explicitly don't check for nested superselector relationships
      # here. :current(.foo) isn't always a superselector of
      # :current(.foo.bar), since it matches the *innermost* ancestor of
      # the current element that matches the selector. For example:
      #
      #     <div class="foo bar">
      #       <p class="foo">
      #         <span>current element</span>
      #       </p>
      #     </div>
      #
      # Here :current(.foo) would match the p element and *not* the div
      # element, whereas :current(.foo.bar) would match the div and not
      # the p.
      selector == their_current.selector
    end
  when 'nth-child', 'nth-last-child'
    their_sseq.members.any? do |their_sel|
      # This misses a few edge cases. For example, `:nth-child(n of X)`
      # is a superselector of `X`, and `:nth-child(2n of X)` is a
      # superselector of `:nth-child(4n of X)`. These seem rare enough
      # not to be worth worrying about, though.
      next false unless their_sel.is_a?(Pseudo)
      next false unless their_sel.name == name
      next false unless their_sel.arg == arg
      selector.superselector?(their_sel.selector)
    end
  else
    throw "[BUG] Unknown selector pseudo class #{name}"
  end
end

#to_s(opts = {})

See Also:

  • Selector#to_s


129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/sass/selector/pseudo.rb', line 129

def to_s(opts = {})
  # :not() is a special case, because :not(<nothing>) should match
  # everything.
  return '' if name == 'not' && @selector && @selector.members.all? {|m| m.invisible?}

  res = (syntactic_type == :class ? ":" : "::") + @name
  if @arg || @selector
    res << "("
    res << Sass::Util.strip_except_escapes(@arg) if @arg
    res << " " if @arg && @selector
    res << @selector.to_s(opts) if @selector
    res << ")"
  end
  res
end

#typeSymbol

The type of the selector. :class if this is a pseudoclass selector, :element if it's a pseudoelement.

Returns:

  • (Symbol)


117
118
119
# File 'lib/sass/selector/pseudo.rb', line 117

def type
  ACTUALLY_ELEMENTS.include?(normalized_name) ? :element : syntactic_type
end

#unify(sels)

Returns nil if this is a pseudoelement selector and sels contains a pseudoelement selector different than this one.



149
150
151
152
153
154
155
# File 'lib/sass/selector/pseudo.rb', line 149

def unify(sels)
  return if type == :element && sels.any? do |sel|
    sel.is_a?(Pseudo) && sel.type == :element &&
      (sel.name != name || sel.arg != arg || sel.selector != selector)
  end
  super
end

#unique?Boolean

Returns:

  • (Boolean)


52
53
54
# File 'lib/sass/selector/pseudo.rb', line 52

def unique?
  type == :class && normalized_name == 'root'
end

#with_selector(new_selector) ⇒ Array<Simple>

Returns a copy of this with #selector set to #new_selector.

Parameters:

Returns:



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/sass/selector/pseudo.rb', line 68

def with_selector(new_selector)
  result = Pseudo.new(syntactic_type, name, arg,
    CommaSequence.new(new_selector.members.map do |seq|
      next seq unless seq.members.length == 1
      sseq = seq.members.first
      next seq unless sseq.is_a?(SimpleSequence) && sseq.members.length == 1
      sel = sseq.members.first
      next seq unless sel.is_a?(Pseudo) && sel.selector

      case normalized_name
      when 'not'
        # In theory, if there's a nested :not its contents should be
        # unified with the return value. For example, if :not(.foo)
        # extends .bar, :not(.bar) should become .foo:not(.bar). However,
        # this is a narrow edge case and supporting it properly would make
        # this code and the code calling it a lot more complicated, so
        # it's not supported for now.
        next [] unless sel.normalized_name == 'matches'
        sel.selector.members
      when 'matches', 'any', 'current', 'nth-child', 'nth-last-child'
        # As above, we could theoretically support :not within :matches, but
        # doing so would require this method and its callers to handle much
        # more complex cases that likely aren't worth the pain.
        next [] unless sel.name == name && sel.arg == arg
        sel.selector.members
      when 'has', 'host', 'host-context', 'slotted'
        # We can't expand nested selectors here, because each layer adds an
        # additional layer of semantics. For example, `:has(:has(img))`
        # doesn't match `<div><img></div>` but `:has(img)` does.
        sel
      else
        []
      end
    end.flatten))

  # Older browsers support :not but only with a single complex selector.
  # In order to support those browsers, we break up the contents of a :not
  # unless it originally contained a selector list.
  return [result] unless normalized_name == 'not'
  return [result] if selector.members.length > 1
  result.selector.members.map do |seq|
    Pseudo.new(syntactic_type, name, arg, CommaSequence.new([seq]))
  end
end