Class: Sass::Selector::Pseudo
- Inherits:
-
Simple
- Object
- Simple
- Sass::Selector::Pseudo
- Defined in:
- .ruby-sass/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
- ACTUALLY_ELEMENTS =
Some pseudo-class-syntax selectors are actually considered pseudo-elements and must be treated differently. This is a list of such selectors.
%w(after before first-line first-letter).to_set
Instance Attribute Summary (collapse)
-
#arg ⇒ String?
readonly
The argument to the selector, or `nil` if no argument was given.
-
#name ⇒ String
readonly
The name of the selector.
-
#selector ⇒ CommaSequence
readonly
The selector argument, or `nil` if no selector exists.
-
#syntactic_type ⇒ Symbol
readonly
Like #type, but returns the type of selector this looks like, rather than the type it is semantically.
Attributes inherited from Simple
Instance Method Summary (collapse)
-
#initialize(syntactic_type, name, arg, selector) ⇒ Pseudo
constructor
A new instance of Pseudo.
-
#invisible? ⇒ Boolean
Whether or not this selector should be hidden due to containing a placeholder.
-
#normalized_name ⇒ String
Like #name, but without any vendor prefix.
- #specificity ⇒ Object
-
#superselector?(their_sseq, parents = []) ⇒ Boolean
Returns whether or not this selector matches all elements that the given selector matches (as well as possibly more).
- #to_s(opts = {}) ⇒ Object
-
#type ⇒ Symbol
The type of the selector.
-
#unify(sels) ⇒ Object
Returns `nil` if this is a pseudoelement selector and `sels` contains a pseudoelement selector different than this one.
- #unique? ⇒ Boolean
-
#with_selector(new_selector) ⇒ Array<Simple>
Returns a copy of this with #selector set to #new_selector.
Methods inherited from Simple
Constructor Details
#initialize(syntactic_type, name, arg, selector) ⇒ Pseudo
Returns a new instance of Pseudo
45 46 47 48 49 50 |
# File '.ruby-sass/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 |
Constructor Details
#initialize(syntactic_type, name, arg, selector) ⇒ Pseudo
Returns a new instance of Pseudo
45 46 47 48 49 50 |
# File '.ruby-sass/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
#arg ⇒ String? (readonly)
The argument to the selector, or `nil` if no argument was given.
31 32 33 |
# File '.ruby-sass/lib/sass/selector/pseudo.rb', line 31 def arg @arg end |
#name ⇒ String (readonly)
The name of the selector.
25 26 27 |
# File '.ruby-sass/lib/sass/selector/pseudo.rb', line 25 def name @name end |
#selector ⇒ CommaSequence (readonly)
39 40 41 |
# File '.ruby-sass/lib/sass/selector/pseudo.rb', line 39 def selector @selector end |
#syntactic_type ⇒ Symbol (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.
20 21 22 |
# File '.ruby-sass/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.
58 59 60 61 62 |
# File '.ruby-sass/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_name ⇒ String
Like #name, but without any vendor prefix.
124 125 126 |
# File '.ruby-sass/lib/sass/selector/pseudo.rb', line 124 def normalized_name @normalized_name ||= name.gsub(/^-[a-zA-Z0-9]+-/, '') end |
#specificity ⇒ Object
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 '.ruby-sass/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).
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 '.ruby-sass/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 = {}) ⇒ Object
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File '.ruby-sass/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 << @arg.strip if @arg res << " " if @arg && @selector res << @selector.to_s(opts) if @selector res << ")" end res end |
#type ⇒ Symbol
The type of the selector. `:class` if this is a pseudoclass selector, `:element` if it's a pseudoelement.
117 118 119 |
# File '.ruby-sass/lib/sass/selector/pseudo.rb', line 117 def type ACTUALLY_ELEMENTS.include?(normalized_name) ? :element : syntactic_type end |
#unify(sels) ⇒ Object
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 '.ruby-sass/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
52 53 54 |
# File '.ruby-sass/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.
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 '.ruby-sass/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 |