p_css 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +57 -2
- data/lib/css/cascade.rb +6 -6
- data/lib/css/code_points.rb +1 -1
- data/lib/css/media_queries/evaluator.rb +3 -3
- data/lib/css/nesting.rb +3 -3
- data/lib/css/parser.rb +1 -1
- data/lib/css/selectors/matcher.rb +6 -6
- data/lib/css/selectors/serializer.rb +2 -2
- data/lib/css/selectors/specificity.rb +2 -2
- data/lib/css/serializer.rb +5 -5
- data/lib/css/token.rb +1 -1
- data/lib/css/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c46687ed2138f367e3d83b51065e7faaeea99190ba035f47f0a9ad9ae7fabcbb
|
|
4
|
+
data.tar.gz: 773fb3861bb5f5c18bf5027327592477349747d7dde550d93b640add292c5b4b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 161f1b01423d7389446bbc32bc2df68812f35423c2e8c4ab8b94493a02e2b7e1b032c2b94c6c60c1e0b11411117326ad68dda66d2ebe1c88a6ab0a13fe1aa30a
|
|
7
|
+
data.tar.gz: 71e99867725196832e2598d4b3e0ffb8e40f5aef62b36ce95fd2785a3eacf114113253206db2731764450d8e6fde5f3940bbef01b74286429e58da26d0fa6e73
|
data/README.md
CHANGED
|
@@ -185,8 +185,9 @@ CSS.matches?(active, 'ul > li:not(:first-child)') # => true
|
|
|
185
185
|
```
|
|
186
186
|
|
|
187
187
|
Stateful pseudo-classes (`:hover`, `:focus`, `:visited`, validity API states,
|
|
188
|
-
etc.)
|
|
189
|
-
|
|
188
|
+
etc.) return `false` by default — there's no UA in the loop. Pass a `state:`
|
|
189
|
+
Hash to opt in; see [Stateful pseudo-classes](#stateful-pseudo-classes)
|
|
190
|
+
below. `:has()` is not yet implemented (its argument is kept as opaque
|
|
190
191
|
component values).
|
|
191
192
|
|
|
192
193
|
### Nesting de-sugar
|
|
@@ -270,6 +271,60 @@ source order. Cascade layers, `@scope` proximity, and Shadow DOM
|
|
|
270
271
|
encapsulation are not modeled — `@layer` / `@supports` / `@scope` /
|
|
271
272
|
`@container` / `@starting-style` blocks are descended into unconditionally.
|
|
272
273
|
|
|
274
|
+
### Stateful pseudo-classes
|
|
275
|
+
|
|
276
|
+
`:hover`, `:focus`, `:focus-within`, `:focus-visible`, `:active`, `:visited`,
|
|
277
|
+
and `:target` return `false` from the matcher by default. Pass a `state:`
|
|
278
|
+
Hash to override:
|
|
279
|
+
|
|
280
|
+
```ruby
|
|
281
|
+
state = {
|
|
282
|
+
hover: Set[hovered_element], # match these and their ancestors
|
|
283
|
+
focus: Set[focused_element], # match only this element
|
|
284
|
+
'focus-within' => Set[el], # propagates to ancestors
|
|
285
|
+
active: true # match every element
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
CSS.matches?(element, ':hover', state: state)
|
|
289
|
+
cascade.resolve(element, state: state)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Values:
|
|
293
|
+
|
|
294
|
+
- `Set` or `Array` of elements — matches those elements (and, for
|
|
295
|
+
`:hover`, `:active`, `:focus-within`, their ancestors per Selectors §10)
|
|
296
|
+
- `true` — matches every element
|
|
297
|
+
- falsy / missing — default behavior; never matches
|
|
298
|
+
|
|
299
|
+
Symbol and String keys are both accepted. Hyphenated names (`focus-within`,
|
|
300
|
+
`focus-visible`) read more naturally as String keys.
|
|
301
|
+
|
|
302
|
+
#### Limits of stateful matching
|
|
303
|
+
|
|
304
|
+
The API gives you the primitives but not a policy. Two patterns are
|
|
305
|
+
inherently hard:
|
|
306
|
+
|
|
307
|
+
- **`hover: true` over-reveals.** Every `:hover`-gated rule matches every
|
|
308
|
+
element, so multiple dropdowns / popovers / menus all become "visible"
|
|
309
|
+
simultaneously. Useful for "is this element *potentially* visible
|
|
310
|
+
somehow?" but not for unique-match queries.
|
|
311
|
+
|
|
312
|
+
- **Peer-row reveal patterns are unsolvable without mouse position.**
|
|
313
|
+
Stylesheets like `.row:hover .icon-copy { display: block }` reveal one
|
|
314
|
+
icon per row when its row is hovered. Per-candidate evaluation (giving
|
|
315
|
+
each candidate its own ancestor chain in the hover Set) doesn't break
|
|
316
|
+
the symmetry — every candidate sees its own `.row` ancestor as hovered
|
|
317
|
+
and reports itself visible. Real browsers disambiguate via the actual
|
|
318
|
+
mouse position; a headless analyzer can't reproduce that without the
|
|
319
|
+
test explicitly recording which element it treats as hovered (e.g. via
|
|
320
|
+
Capybara's `element.hover`).
|
|
321
|
+
|
|
322
|
+
The recommendation for tools layered on top of p CSS: track explicit hover
|
|
323
|
+
actions and pass the corresponding Set; for queries that depend on
|
|
324
|
+
hover-based uniqueness without an explicit hover, treat them as fragile
|
|
325
|
+
and disambiguate by `text:` / `id:` / data attributes instead of relying
|
|
326
|
+
on stateful CSS.
|
|
327
|
+
|
|
273
328
|
### `urange`
|
|
274
329
|
|
|
275
330
|
```ruby
|
data/lib/css/cascade.rb
CHANGED
|
@@ -89,11 +89,11 @@ module CSS
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def register_qualified_rule(rule, media_chain, out)
|
|
92
|
-
return unless media_chain.all? { MediaQueries::Evaluator.evaluate(
|
|
92
|
+
return unless media_chain.all? { MediaQueries::Evaluator.evaluate(_1, @context) }
|
|
93
93
|
|
|
94
94
|
sl = Selectors::Parser.parse_selector_list(rule.prelude)
|
|
95
|
-
pairs = sl.selectors.map { [
|
|
96
|
-
decls = rule.block.items.select {
|
|
95
|
+
pairs = sl.selectors.map { [_1, Selectors::SpecificityCalculator.calculate(_1)] }
|
|
96
|
+
decls = rule.block.items.select { _1.is_a?(Nodes::Declaration) }
|
|
97
97
|
|
|
98
98
|
out << RuleEntry.new(selector_pairs: pairs, declarations: decls)
|
|
99
99
|
rescue ParseError
|
|
@@ -259,9 +259,9 @@ module CSS
|
|
|
259
259
|
|
|
260
260
|
def inline_declarations(style)
|
|
261
261
|
case style
|
|
262
|
-
when String then CSS.parse_block_contents(style).items.select {
|
|
263
|
-
when Nodes::Block then style.items.select {
|
|
264
|
-
when Array then style.select {
|
|
262
|
+
when String then CSS.parse_block_contents(style).items.select { _1.is_a?(Nodes::Declaration) }
|
|
263
|
+
when Nodes::Block then style.items.select { _1.is_a?(Nodes::Declaration) }
|
|
264
|
+
when Array then style.select { _1.is_a?(Nodes::Declaration) }
|
|
265
265
|
else
|
|
266
266
|
raise ArgumentError, "cannot derive inline declarations from #{style.class}"
|
|
267
267
|
end
|
data/lib/css/code_points.rb
CHANGED
|
@@ -38,7 +38,7 @@ module CSS
|
|
|
38
38
|
PREFIX_OP = {min: :ge, max: :le}.freeze
|
|
39
39
|
|
|
40
40
|
def evaluate(query_list, context)
|
|
41
|
-
query_list.queries.any? { evaluate_query(
|
|
41
|
+
query_list.queries.any? { evaluate_query(_1, context) }
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
private
|
|
@@ -65,8 +65,8 @@ module CSS
|
|
|
65
65
|
def evaluate_condition(node, context)
|
|
66
66
|
case node
|
|
67
67
|
when MediaNot then !evaluate_condition(node.operand, context)
|
|
68
|
-
when MediaAnd then node.operands.all? { evaluate_condition(
|
|
69
|
-
when MediaOr then node.operands.any? { evaluate_condition(
|
|
68
|
+
when MediaAnd then node.operands.all? { evaluate_condition(_1, context) }
|
|
69
|
+
when MediaOr then node.operands.any? { evaluate_condition(_1, context) }
|
|
70
70
|
when MediaFeature then evaluate_feature(node, context)
|
|
71
71
|
when GeneralEnclosed then false
|
|
72
72
|
else false
|
data/lib/css/nesting.rb
CHANGED
|
@@ -12,7 +12,7 @@ module CSS
|
|
|
12
12
|
extend self
|
|
13
13
|
|
|
14
14
|
def desugar(stylesheet)
|
|
15
|
-
Nodes::Stylesheet.new(rules: stylesheet.rules.flat_map { desugar_top_level(
|
|
15
|
+
Nodes::Stylesheet.new(rules: stylesheet.rules.flat_map { desugar_top_level(_1) })
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
private
|
|
@@ -135,7 +135,7 @@ module CSS
|
|
|
135
135
|
|
|
136
136
|
def contains_nesting?(complex_selector)
|
|
137
137
|
complex_selector.compounds.any? {|c|
|
|
138
|
-
c.components.any? {
|
|
138
|
+
c.components.any? { _1.is_a?(Selectors::NestingSelector) }
|
|
139
139
|
}
|
|
140
140
|
end
|
|
141
141
|
|
|
@@ -159,7 +159,7 @@ module CSS
|
|
|
159
159
|
replacement = Selectors::PseudoClass.new(name: 'is', argument: parent_list)
|
|
160
160
|
|
|
161
161
|
Selectors::ComplexSelector.new(
|
|
162
|
-
compounds: own_complex.compounds.map { swap_components_in_compound(
|
|
162
|
+
compounds: own_complex.compounds.map { swap_components_in_compound(_1, replacement) },
|
|
163
163
|
combinators: own_complex.combinators
|
|
164
164
|
)
|
|
165
165
|
end
|
data/lib/css/parser.rb
CHANGED
|
@@ -50,7 +50,7 @@ module CSS
|
|
|
50
50
|
|
|
51
51
|
case sel
|
|
52
52
|
when SelectorList
|
|
53
|
-
sel.selectors.any? { match_complex(element,
|
|
53
|
+
sel.selectors.any? { match_complex(element, _1, cache, state) }
|
|
54
54
|
when ComplexSelector
|
|
55
55
|
match_complex(element, sel, cache, state)
|
|
56
56
|
when CompoundSelector
|
|
@@ -99,7 +99,7 @@ module CSS
|
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
def match_compound(element, compound, cache, state)
|
|
102
|
-
compound.components.all? { match_simple(element,
|
|
102
|
+
compound.components.all? { match_simple(element, _1, cache, state) }
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def match_simple(element, simple, cache, state)
|
|
@@ -281,10 +281,10 @@ module CSS
|
|
|
281
281
|
return nil if p.nil?
|
|
282
282
|
|
|
283
283
|
siblings = element_children(p)
|
|
284
|
-
siblings = siblings.select { tag(
|
|
284
|
+
siblings = siblings.select { tag(_1).casecmp?(tag(element)) } if of_type
|
|
285
285
|
siblings = siblings.reverse if from_end
|
|
286
286
|
|
|
287
|
-
idx = siblings.index { same_node?(
|
|
287
|
+
idx = siblings.index { same_node?(_1, element) }
|
|
288
288
|
idx && idx + 1
|
|
289
289
|
end
|
|
290
290
|
|
|
@@ -316,7 +316,7 @@ module CSS
|
|
|
316
316
|
end
|
|
317
317
|
|
|
318
318
|
def inside_first_legend?(element, fieldset)
|
|
319
|
-
first_legend = element_children(fieldset).find { tag(
|
|
319
|
+
first_legend = element_children(fieldset).find { tag(_1) == 'legend' }
|
|
320
320
|
|
|
321
321
|
return false if first_legend.nil?
|
|
322
322
|
|
|
@@ -427,7 +427,7 @@ module CSS
|
|
|
427
427
|
def ident_argument(argument)
|
|
428
428
|
return nil unless argument.is_a?(Array)
|
|
429
429
|
|
|
430
|
-
token = argument.find {
|
|
430
|
+
token = argument.find { _1.is_a?(Token) && (_1.type == :ident || _1.type == :string) }
|
|
431
431
|
token&.value
|
|
432
432
|
end
|
|
433
433
|
|
|
@@ -21,9 +21,9 @@ module CSS
|
|
|
21
21
|
|
|
22
22
|
def serialize(node)
|
|
23
23
|
case node
|
|
24
|
-
when SelectorList then node.selectors.map { serialize(
|
|
24
|
+
when SelectorList then node.selectors.map { serialize(_1) }.join(', ')
|
|
25
25
|
when ComplexSelector then serialize_complex(node)
|
|
26
|
-
when CompoundSelector then node.components.map { serialize(
|
|
26
|
+
when CompoundSelector then node.components.map { serialize(_1) }.join
|
|
27
27
|
when TypeSelector then Escape.ident(node.name)
|
|
28
28
|
when UniversalSelector then '*'
|
|
29
29
|
when NestingSelector then '&'
|
|
@@ -41,7 +41,7 @@ module CSS
|
|
|
41
41
|
|
|
42
42
|
def calculate(node)
|
|
43
43
|
case node
|
|
44
|
-
when SelectorList then node.selectors.map { calculate(
|
|
44
|
+
when SelectorList then node.selectors.map { calculate(_1) }.max || Specificity::ZERO
|
|
45
45
|
when ComplexSelector then sum(node.compounds)
|
|
46
46
|
when CompoundSelector then sum(node.components)
|
|
47
47
|
when IdSelector then Specificity.new(a: 1, b: 0, c: 0)
|
|
@@ -59,7 +59,7 @@ module CSS
|
|
|
59
59
|
private
|
|
60
60
|
|
|
61
61
|
def sum(items)
|
|
62
|
-
items.map { calculate(
|
|
62
|
+
items.map { calculate(_1) }.reduce(Specificity::ZERO, :+)
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def specificity_of_pseudo_class(node)
|
data/lib/css/serializer.rb
CHANGED
|
@@ -21,7 +21,7 @@ module CSS
|
|
|
21
21
|
when Nodes::SimpleBlock then serialize_simple_block(node)
|
|
22
22
|
when Token then serialize_token(node)
|
|
23
23
|
when Selectors::Node then Selectors::Serializer.serialize(node)
|
|
24
|
-
when Array then node.map { serialize(
|
|
24
|
+
when Array then node.map { serialize(_1) }.join
|
|
25
25
|
else
|
|
26
26
|
raise ArgumentError, "cannot serialize #{node.class}"
|
|
27
27
|
end
|
|
@@ -30,7 +30,7 @@ module CSS
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
32
|
def serialize_stylesheet(ss)
|
|
33
|
-
ss.rules.map { serialize(
|
|
33
|
+
ss.rules.map { serialize(_1) }.join("\n")
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def serialize_at_rule(rule)
|
|
@@ -49,7 +49,7 @@ module CSS
|
|
|
49
49
|
def serialize_block(block)
|
|
50
50
|
return '{}' if block.items.empty?
|
|
51
51
|
|
|
52
|
-
inner = block.items.map { serialize(
|
|
52
|
+
inner = block.items.map { serialize(_1) }.join("\n")
|
|
53
53
|
"{\n#{indent(inner)}\n}"
|
|
54
54
|
end
|
|
55
55
|
|
|
@@ -159,8 +159,8 @@ module CSS
|
|
|
159
159
|
def serialize_string(s) = Escape.string(s)
|
|
160
160
|
|
|
161
161
|
def indent(str)
|
|
162
|
-
str.lines.map { "#{INDENT}#{
|
|
163
|
-
|
|
162
|
+
str.lines.map { "#{INDENT}#{_1}" }.join.then {
|
|
163
|
+
_1.end_with?("\n") ? _1.chomp : _1
|
|
164
164
|
}
|
|
165
165
|
end
|
|
166
166
|
end
|
data/lib/css/token.rb
CHANGED
|
@@ -93,7 +93,7 @@ module CSS
|
|
|
93
93
|
private
|
|
94
94
|
|
|
95
95
|
def compute_position
|
|
96
|
-
idx = @newlines.bsearch_index {
|
|
96
|
+
idx = @newlines.bsearch_index { _1 >= @start_offset } || @newlines.size
|
|
97
97
|
prev_nl = idx.zero? ? -1 : @newlines[idx - 1]
|
|
98
98
|
|
|
99
99
|
Position.new(
|
data/lib/css/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: p_css
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keita Urashima
|
|
@@ -66,7 +66,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
66
66
|
requirements:
|
|
67
67
|
- - ">="
|
|
68
68
|
- !ruby/object:Gem::Version
|
|
69
|
-
version: '3.
|
|
69
|
+
version: '3.3'
|
|
70
70
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
72
|
- - ">="
|