-
Notifications
You must be signed in to change notification settings - Fork 661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[css-syntax] Make it easier to express ranges for <An+B> selectors #4140
Comments
While that allows a range, it doesn't work with values other than I'm not opposed to that, tho; you can always write two pseudo-classes, one setting up the skip and the other setting up the range. If we were to do this, using the MQ syntax, with |
For other values we could also consider something like
But |
My main use case was to make it easier to express the first or last n elements, but if we want to allow values other than
This comma-separated syntax is rather confusing to me, because
It makes it easier if you want to your selector to apply exactly C times, though it doesn't allow to restrict the selection to index C. The question here is what's the more common use case. Sebastian |
But as you said, one of the non-trivial aspects of
No, |
Ok, so let's make up some examples with your and my syntax for clarification: break down by exampleMatch the first three elements
Match every even element up to the eighth
|
Match every even element up to the eighth / match every even element up to four |
|
---|---|
current syntax | :nth-child(even):nth-child(-n+8) |
first proposed syntax | :nth-child(even <= 8) |
second proposed syntax | :nth-child(2n, n <= 4) |
filter syntax | :nth-child(2n, first 4) |
Match every third element between the sixth and the fifteenth
/ match every third element up to four starting at the sixth
Match every third element between the sixth and the fifteenth / match every third element up to four starting at the sixth |
|
---|---|
current syntax | :nth-child(3n+6):nth-child(-n+15) |
first proposed syntax | :nth-child(6 <= 3n <= 15) :nth-child(3n):nth-child(6 <= n <= 15) |
second proposed syntax | :nth-child(3n+6, n < 4) :nth-child(3n, 2 <= n <= 5) :nth-child(3n):nth-child(6 <= n <= 15) |
filter syntax | :nth-child(3n, skip 1, first 4) |
Match every third element starting with the second one and ending at the third last
Match every third element starting with the second one and ending at the third last | |
---|---|
current syntax | :nth-child(3n+2):nth-last-child(n+3) |
first proposed syntax | :nth-child(2 <= 3n+2 <= -3) :nth-child(3n+2):nth-child(2 <= n <= -3) :nth-child(2 <= 3n+2 <= last-3) (using last keyword) |
second proposed syntax | - |
filter syntax | :nth-child(3n, not first 2, not last 2) |
full table
matching | current syntax | first proposed syntax | second proposed syntax | filter syntax |
---|---|---|---|---|
Match the first three elements | :nth-child(-n+3) |
:nth-child(n <= 3) |
:nth-child(n <= 3) |
:nth-child(first 3) |
Match every even element up to the eighth / match every even element up to four | :nth-child(even):nth-child(-n+8) |
:nth-child(even <= 8) |
:nth-child(2n, n <= 4) |
:nth-child(2n, first 4) |
Match every third element between the sixth and the fifteenth / match every third element up to four starting at the sixth | :nth-child(3n+6):nth-child(-n+15) |
:nth-child(6 <= 3n <= 15) :nth-child(3n):nth-child(6 <= n <= 15) |
:nth-child(3n+6, n < 4) :nth-child(3n, 2 <= n <= 5) :nth-child(3n):nth-child(6 <= n <= 15) |
:nth-child(3n, skip 1, first 4) |
Match every third element starting with the second one and ending at the third last | :nth-child(3n+2):nth-last-child(n+3) |
:nth-child(2 <= 3n+2 <= -3) :nth-child(3n+2):nth-child(2 <= n <= -3) :nth-child(2 <= 3n+2 <= last-3) (using last keyword) |
- | :nth-child(3n, not first 2, not last 2) |
--> |
The last example additionally introduces negative values to count from the last element. One approach is to just use negative numbers (like for Grid Lines). Or, because of the mathematical incorrectness, there could be an additional last
keyword, which looks quite readable, IMHO.
Sebastian
I really don't like the syntax that puts the condition on the N; the fact that N goes over 0-Inf is already kinda confusing. I strongly prefer mixing the conditions into the An+B condition, so you filter the outputted element indexes. For dealing with counting from the end, I think we should just allow negatives. |
The variable n is determined by the pseudo-class for existing applications, but in some other scenarios, e. g.
|
What do you mean by this? I don't think :heading() leans either way on whether it wants to clamp N (input) or result. (:heading() doesn't really have a reason to want the |
I meant that the number of heading levels (and thus the number of possible values of n) is usually limited globally by specifications of markup languages, whereas n in |
@SebastianZ In your example table your proposal seems to fit much better to the description. But that's because all the cases are expressed in the same way, the one which works better with your proposal. But as @Crissov says the proposals represent different use cases, or maybe different ways of thinking. Then @tabatkins "the fact that N goes over 0-Inf is already kinda confusing". Precisely, if we put a condition on On the other hand counting from the end seems interesting and I agree that it seems more suited for constraints on |
I think giving them control over the N values would be even worse; the Anyway, stated more plainly:
|
Right, those are two different ways of thinking. My approach focuses on the indexes of the elements while your one focuses on the number of elements to be styled.
I believe the fact that n is in the range of 0 to infinity is not the issue here. People knowing other languages already know this from array indexes. The hard part of the current syntax is that n isn't the actual index and you have to calculate the index using it. Sebastian |
My third example seemed to be wrong. I've corrected it now. And I have also added descriptions that fit @Loirooriol's syntax. As this issue is related to the An+B microsyntax, I've now also added [css-syntax] to the subject. Sebastian |
After some great discussion, this feature request unfortunately fell asleep. So let's wake it up again! Just a quick summary of the two proposals we had so far:
I think the use cases are clearly expressed and the two approaches as well. So maybe we can discuss that on the F2F. Sebastian |
I have updated the table since my proposal can also accept min and max values, they just refer to |
@Loirooriol Thank you for updating the table! I've also updated it now to show how counting from the last element could be added. Sebastian |
BTW, I think omitting |
Negative numbers counting from the end of a sequence is a pretty common idiom in a number of programming languages, including JS (
Note that you can already write this today, with
I think using
(I'm also fine with just using a negative number on its own, but if we go with the keyword I prefer this syntax.) |
Perhaps it would be clearer to have a simpler syntax in its own selector, like: The |
My proposal would be to add a list of filters, instead of ranges: We could optionally add a |
The CSS Working Group just discussed
The full IRC log of that discussion<dbaron> SebastianZ: I previously suggested a new syntax for expressing An+B selectors in :nth-child() etc.<dbaron> SebastianZ: We were discussing this, we had 2 proposals. They seem orthogonal; we could have both. But I think one I suggested initially is targeting the most common use cases. <TabAtkins> q+ <dbaron> SebastianZ: Tab was also mentioning some things. <dbaron> SebastianZ: reusing the range syntax from Media Queries, with one addition: adding a "last" keyword to count from the last element instead of the first. <dbaron> TabAtkins: The problem we're trying to solve is that right now a selector targeting a range of elements (e.g., 3rd through 7th), it's possible to write using two :nth-child()s, but it's not obvious how, and tricky. <dbaron> TabAtkins: So the first thing we should agree on is whether to agree on developing a syntax to enable expressing ranges more easily. <dbaron> TabAtkins: And if so we have some suggestions to go over and possibly decide between. <dbaron> TabAtkins: I think it's a reasonably common case (e.g., "select the first 3 elements") <Rossen_> ack TabAtkins <SebastianZ> See also https://github.com//issues/4140#issuecomment-517857973 <dbaron> TabAtkins: you have to write :nth-child(3-n) or :nth-child(-n+3). I'd prefer n <= 3, like you'd write elsewhere. <dbaron> TabAtkins: do we think this is worth adding to selectors? <dbaron> florian: yes <miriam> +1 <oriol> +1 <dbaron> fantasai: not sold on the syntax, but seems reasonable to come up with something <fremy> I have written code that would have benefited from this, so sympathetic <dbaron> +1 to working on the problem <dbaron> RESOLVED: Work on the problem of expressing ranges of children in selectors in a more intuitive way. <emilio> q+ <dbaron> TabAtkins: a few suggestions for syntax. <dbaron> TabAtkins: oriol suggested a different approach from what TabAtkins and SebastianZ are thinking about. <dbaron> TabAtkins: If you're expressing a range, do you want a range on the "n' variable or a range on the output? <dbaron> TabAtkins: You want a range on the output rather than the input. <oriol> q+ <dbaron> TabAtkins: If your range was 2n and your range is 1-5, are you selecting 2,4,6,8,10 or 2,4 ? <fantasai> TabAtkins: In this case you want 2,4 <fremy> can you type this, @tabatkins <TabAtkins> 2n <= 5, for example, will return 2,4 <TabAtkins> if a "<= 5" condition can return 6,8,10 I think that's super weird <astearns> range-limited output? <dbaron> TabAtkins: this ties in to what syntax we're using <dbaron> TabAtkins: I think if you see 2n <= 5, you expect all the values of 2n that result in a value <= 5. <fremy> q? <fremy> q+ <dbaron> emilio: Before anything else, can we agree on making it part of :nth-child() rather than inventing new pseudo-classes. I think it makes sense as part of :nth-child(). <Rossen_> ack emilio <dbaron> TabAtkins: I think basically building this into An+B is good. <Rossen_> ack oriol <myles> q+ <dbaron> oriol: I prefer my proposal -- when I started ??? with the An+B syntax, I first assumed that "n" would be strictly positive, then I thought all integers -- there's an implicit condition that "n" is nonnegative. I think this implicit condition may be confusing. I was thinking my proposal adding an explicit condition to n could make things more clear. <fremy> (I'm in the queue for a different topic, so I would not want to interrupt the decision on ruling out new pseudo-classes) <fremy> q- later <dbaron> oriol: That said, it may be true that the other proposal maybe covers more frquent cases in a shorter way. <dbaron> oriol: You can convert from the other proposal to mine by adding another :nth-child() pseudo-class. I'm not opposed to the other one. <dbaron> oriol: I think this is a good point covers ???. <Rossen_> q? <dbaron> s/covers/that my proposal covers/ <TabAtkins> my actual proposed syntax kinda splits the difference tho, I suppose: ranges must be simple, just a number and n. So `:nth-child(n <= 5)` only, not `2n <= 5`. If you want to both, `:nth-child(n <= 5):nth-child(2n)` or `:nth-child(n <= 5 of 2n)` <dbaron> myles: I think we've been reinventing list comprehensions that are in many proper prorgramming languages. We should take something that already exists. <dbaron> TabAtkins: This is reinventing the range operators that some operators... different syntax space from list comprehensions. <dbaron> myles: I'm not saying to dump python's list comprehensions in CSS -- but I'm saying for input vs. output do what the programming languages do. <dbaron> TabAtkins: I disagree with that on principle -- it's more like a range than a list comprehension. <Rossen_> ack myles <dbaron> TabAtkins: the syntax affects whether your ranges are on the input on the output. <dbaron> TabAtkins: Different languages make map-then-filter or filter-then-map easier, and I don't think there's a clear reason to prefer one based on precedent. <SebastianZ> q+ <dbaron> TabAtkins: my preferred syntax proposal makes this moot. Rather than full An+B combined with ranges is that when we do ranges, you're limited to N on one side, a number on one side , and a sign in the middle. IF you want An+b stuff as well, you can do the same ?? as if you're combining 2 conditions together. Not ??? An ???. I proposed we only allow n <= 5. You can decide whether you allow the first 5 children that are also <Rossen_> q? <dbaron> ... even or the first five even children, depneding on how you write it. <dbaron> hober: If I understand, I think I agree. <TabAtkins> `:nth-child(n <= 5):nth-child(2n)` or `:nth-child(n <= 5 of 2n)` <dbaron> TabAtkins: We can rely on existing CSS facilities. <ntim_> q+ <ntim_> +1 to fremy <dbaron> fremy: I think we're not going to have consensus today. But I want say I agree with following other languages. I think we shuold have multiple filters, applied in order author wrote them. I find it hard to understand "2n+3 < 5", no idea what it selects. I think we ned something more clear. <dbaron> fremy: :nth-child(2n) is for the even things, and then say first three. <dbaron> fremy: I think this reads better than the mathematic formula. <dbaron> fremy: I think 2n+1<5 is too difficult to understand. <ntim_> I agree 2n+5 < 5 is hard to understand <Rossen_> ack SebastianZ <Rossen_> ack fremy <dbaron> SebastianZ: I want to note first proposed syntax also involves "last" keyword as said in beginning, which allows counting from the end. From 3 rd element to 3rd-last element, style everything. Wouldn't be psosible with second proposed syntax. <dbaron> TabAtkins: We could have negative indexes mean what they do in most programming languages. <dbaron> fantasai: but we don't do that now <fantasai> Like, I really love negative indexes representing from the end. But we don't do that now, and so I think it would be confusing to have it work fo rome things and not others <plinss> we do use it in grid <dbaron> SebastianZ: With the proposal of the "last" keyword we can also express ranges to an nth-last child. Tab said probably possible with second proposed syntax, but I don't think possible, but I disagree, since we're there focusing on the "n" which means the number of elements. <TabAtkins> once we drain the queue, i propose we take this back to the issue for syntax discussion <Rossen_> q? <dbaron> oriol: with my proposal "n" is just the ? in the expression <astearns> s/?/entity <dbaron> oriol: Going from the index of the last child .. <dbaron> oriol: if we want to express conditions to the end, constrain on the output seems more suited <fantasai> plinss, good point :) but not in :nth-child(), and in fact some of the :nth-child() patterns rely on us not processing negative indexes :/ <dbaron> ntim_: +1 to fremy about 2n+1 < 5 being hard to understand. <dbaron> ntim_: hope we don't end up with that <Rossen_> ack ntim_ <Rossen_> ack fantasai <fremy> Can I suggest maybe to do like we did before in such cases, make a list of use cases, and then write how each proposal writes that range? <dbaron> fantasai: I was going to suggest table discussion until friday. Tomorrow we'll have time for people to workshop ideas. We should do that tomorrow. <dbaron> TabAtkins: This is going too long, we should go back to the issue. <myles> ScribeNick: myles <dbaron> SUMMARY: Will discuss syntax further in issue. |
We did a breakout at the WG meeting, and participants united behind the following proposal:
So the examples in the table could be written as:
|
In addendum to tab’s reply above: Using negative numbers to count back from the end in such an expression also came up (e.g. |
Hi @tabatkins, I was not in the breakout, but added the filter proposal to the tables. In my opinion it reads way more naturally than the ranges, so I would like to see it discussed at least, especially since I proposed it before the breakout :) I would not object to a consensus from the group of course, but I don't know how many people made it to the breakout in the end, and if they saw my comment. I just did mini tables based on the consensus proposal: Examples (comparison)
Concrete proposalBasically, the proposal is as follows: Optionally, we can add operator ReasoningI think I have never needed to express a range as "from 3rd to 5th" in my CSS life. I have however many times wanted to exclude the edges of a range. I think a proposal that allows to say "not first 1" is strictly more intuitive and readable than "n < 1" as the latter requires to remember whether CSS child indexes are 0-indexed or not, and also, it combines poorly with "even/odd" which is frequent, too. Overall, the proposal is also more powerful, because it can easily be extended to more complex filters over time without having to add yet another syntax. For example, if we want to allow subsequent |
Just to clarify, this meant to target the range between the second and the third last element and of them just every third element. Sebastian |
If that's the case, then it's just |
The syntax in Tab's comment should also include the To summarize the desugarings of the breakout proposal into the existing syntax: :nth-child(n >= k of S) = :nth-child(n + k of S)
:nth-child(n > k of S) = :nth-child(n + (k+1) of S)
:nth-child(n <= k of S) = :nth-child(-n + k of S)
:nth-child(n < k of S) = :nth-child(-n + (k-1) of S)
:nth-child(k1 <= n <= k2 of S) = :nth-child(n + k1 of S):nth-child(-n + k2 of S)
:nth-child(k1 < n <= k2 of S) = :nth-child(n + (k1+1) of S):nth-child(-n + k2 of S)
:nth-child(k1 <= n < k2 of S) = :nth-child(n + k1 of S):nth-child(-n + (k2-1) of S)
:nth-child(k1 < n < k2 of S) = :nth-child(n + (k1+1) of S):nth-child(-n + (k2-1) of S) Sebastian's proposal can be desugared into this proposal as such (analogous for :nth-child(k1 <= An+B <= k2 of S) = :nth-child(k1 <= n <= k2 of S):nth-child(An+B of S)
:nth-child(k1 <= An+B <= last k2 of S) = :nth-child(k1 <= n of S):nth-last-child(n <= k2 of S):nth-child(An+B of S) And for my proposal it's less straightforward in general, if I didn't make a mistake, If A*k1+B >= 1,
:nth-child(An+B, k1 <= n <= k2, of S) = :nth-child(n <= (k2-k1+1) of :nth-child(An + (A*k1+B) of S))
Otherwise,
:nth-child(An+B, k1 <= n <= k2, of S) = :nth-child(n <= (k2+ceil(B/A)) of :nth-child(An + (A*k1+B) of S)) but as Tab said, the typical case of just wanting to limit the number of selected elements is simply :nth-child(n <= k of :nth-child(An + B of S)) |
To express the selection of the first or last n elements the An+B syntax is somewhat hard to understand. It requires the author to calculate the different resulting index values and requires them to know that negative indexes are ignored.
Therefore, I suggest to introduce a simpler syntax that covers those cases. My suggestions:
to n
orn <= x
Positive side effect is that these syntaxes would allow to extend them to specify ranges, i.e.
m to n
orx <= n <= y
. With that there wouldn't be the need anymore to define the ranges using both the:nth-child()
and:nth-last-child()
pseudo-classes resp.:nth-of-type()
and:nth-last-of-type()
.The latter also aligns with the new range syntax introduced for media queries.
Sebastian
The text was updated successfully, but these errors were encountered: