Skip to content

[css-values-5] if() conditions with calc() comparisons #11104

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

Open
fantasai opened this issue Oct 28, 2024 · 7 comments
Open

[css-values-5] if() conditions with calc() comparisons #11104

fantasai opened this issue Oct 28, 2024 · 7 comments

Comments

@fantasai
Copy link
Collaborator

Several of the examples in #5009 were comparing <length> values, but this was not included in #10064

Do we want this functionality, and if so what are the use cases and what should be the syntax?

Currently specced conditionals are:

  supports( [ <supports-condition> | <ident> : <declaration-value> ] ) |
  media( <media-query> ) |
  style( <style-query> )
@tabatkins
Copy link
Member

Currently all the conditions in if() are functions; I propose we use the "just parentheses" syntax for this. Namely:

( <calc-sum> <mf-comparison> <calc-sum> )

This way you can write, say, flex-flow: if( (100vw > 200px): row ; else : column; );

(or, spread over multiple lines:

flex-flow: if(
    (100vw > 200px): row;
    else: column;
);

)

@fantasai fantasai added the Agenda+ Later Lower-priority items that are flagged for CSSWG discussion label Oct 28, 2024
@Loirooriol
Copy link
Contributor

if() is defined to resolve at computed-value time. But this needs layout:

width: if( (100% > 200px): max-content; else : stretch; );

So I guess it needs to be invalid? But it would be cool to have an if() as a syntax sugar for arithmetic conditionals, e.g.

width: if( (100% > 200px): 1em; else : 1lh; );
/* behaves like this: */
width: calc(max(0, sign(100% - 200px)) * 1em + (1 - max(0, sign(100% - 200px))) * 1lh)

@tabatkins
Copy link
Member

Yeah, I think you'd need to use a CQ condition to test something similar to that; we need to be able to resolve the condition at computed-value time. I suppose we'd just say that the condition is always false if it uses values that can't be resolved at computed-value time.

I was hoping that if() would subsume the need for a conditional math function, but I guess it doesn't. :/ Gonna be a little hard to explain when exactly calc-if() is needed, unfortunately.


An additional request: the other thing brought up as a common conditional need is just comparing a value to an ident, so you can set a custom property on a component like --style: button and it'll do different things.

I think we can just slot that into the parenthesized syntax, with an <ident> [ '=' | '!' '=' ] <ident> form. It overlaps grammatically with the calc-sum version when you compare calc keywords with each other, but it would resolve the same under either interpretation, so that's fine.

@LeaVerou
Copy link
Member

Why do we need the parens at all? What kind of ambiguity exists if we can simply use bare comparisons? Also, at first, we should probably expand the syntax of style() to allow for comparison operators in addition to :.

@fantasai fantasai added Agenda+ F2F and removed Agenda+ Later Lower-priority items that are flagged for CSSWG discussion labels Oct 29, 2024
@tabatkins
Copy link
Member

tabatkins commented Nov 1, 2024

Why do we need the parens at all? What kind of ambiguity exists if we can simply use bare comparisons?

You need parens to work with the <boolean[]> syntax - the base grammar of a boolean expression must be either parenthesized or functions, in order to match with the <general-enclosed> term that catches future-compat.

We could, in theory, let you omit the parens if you were just doing a single comparison on its own. But then you'd need to add them if you did and/or/not. I'm moderately against the consistency break. Every existing construct that does comparisons uses parens, like @media (width < 600px) {...}

Also, at first, we should probably expand the syntax of style() to allow for comparison operators in addition to :.

That's something for Containment to define; if() just pulls that syntax in. (But yes, we should allow it.)

@LeaVerou
Copy link
Member

Just to add, this would make it possible to use if() with relative colors.

Currently, there is no way to do something like oklch(from var(--color) if(l < .65, var(--dark-math), var(--light-math))).

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-values-5] if() conditions with calc() comparisons, and agreed to the following:

  • RESOLVED: Add numerical comparisons wrapped in bare parens, use 3-value logic for incomparable comparisons
The full IRC log of that discussion <fantasai> TabAtkins: if() function lets you specify several types of test. MQ, CQ, supports Q
<fantasai> TabAtkins: style Q
<fantasai> TabAtkins: Would be useful to be able to do generic numeric comparisons, so e.g. could do var and number
<fantasai> TabAtkins: You can currently do that by style query, but would be nice ot be able to inline the value
<fantasai> TabAtkins: so suggest to allow simple numeric comparisons. Comparison operator in the middle. Just like MQ.
<fantasai> TabAtkins: question about how to spell it
<miriam> we haven't resolved on range comparisons for style queries, it's on the agenda
<fantasai> TabAtkins: Suggest to use naked parens
<lea> q+
<TabAtkins> flex-flow: if( (100vw > 200px): row ; else : column; );
<fantasai> TabAtkins: could come up with a name for this test, make it a function if ppl object to bare parens / have a good function name
<astearns> ack lea
<oriol> q+
<fantasai> lea: We control the grammar.. couldn't we just put inline?
<fantasai> TabAtkins: it's ambiguous because we allow boolean operators
<fantasai> lea: If you don't use boolean operators, could we drop parens?
<fantasai> TabAtkins: You do need brackets even then, to handle future expansion.
<fantasai> TabAtkins: if you put anything unknown, e.g. new unit, you don't want to invalidate the entire declaration
<fantasai> TabAtkins: you want to catch it by the general-enclosed production
<fantasai> TabAtkins: That way "x or y" if x is new syntax, it's false, and y is true, test passes
<fantasai> lea: Could we define [missed]
<fantasai> TabAtkins: not in general case, because you don't know the bounds of an arbitrary new syntax
<fantasai> TabAtkins: we're ok with defining ourselves into matched brackets, but without explicit boundary it imposes too much grammatical ambiguity
<fantasai> lea: any cases where it wouldn't be ambiguous?
<fantasai> lea: e.g. dimension cmp dimension. Or replace one of those with a var. These are the most common cases.
<fantasai> lea: I would argue that over 90% of use cases will fall into that category
<fantasai> TabAtkins: I don't want to try and burden us with the fact that all future CSS value design in all contexts has to live under this restriction due to if()
<fantasai> lea: I'm suggesting to have the parens for most cases, but carve out a subset of cases that don't require it
<fantasai> TabAtkins: that still runs us into future ambiguity problems. E.g. can't use ':' because that's delimeter
<fantasai> astearns: not necessarily. lea's saying that we can possibly drop parens for a subset of thing, but require them in other situations
<fantasai> astearns: so that would require parens
<astearns> ack oriol
<fantasai> TabAtkins: Would need to learn weirdness of our parsing limitations. Try to avoid that when possible
<dbaron> (I think it might also require 2-pass parsing.)
<fantasai> oriol: Some values are resolved at used value, not computed value, e.g. length vs percentage.
<fantasai> oriol: that would always resolve as false
<TabAtkins> width: if( (100% > 200px): max-content; else : stretch; );
<fantasai> TabAtkins: in this example, the percent depends on available space which is not known until layout time
<fantasai> TabAtkins: but needs to replace for computed value
<fantasai> TabAtkins: at the moment, I think only thing we can do is treat this comparison as false, because can't resolve at computed value time
<fantasai> TabAtkins: alternative would be to also define a calc() conditional thing, and have it resolve into that calc() expression
<fantasai> TabAtkins: but that would be a lot of extra complexity for this case
<lea> q+
<fantasai> TabAtkins: I think this is a not-great situation no matter what we do
<fantasai> TabAtkins: so current idea is to just call it false
<astearns> ack lea
<fantasai> lea: Right now there are ways to use min()/max()/clamp() for this
<fantasai> lea: seems weird if doesn't have same machinery
<fantasai> TabAtkins: yes, but we're mixing 2 different classes of functions. This is arbitrary substitution function, can do anything, but has to be fully resolved before inheritane.
<fantasai> TabAtkins: we cannot inherit an unresolved substitution function
<fantasai> TabAtkins: e.g. var() has to be resolved by computed value time. This is the same thing.
<lea> q?
<TabAtkins> fantasai: instead of making those always false, can we state them as always invalid? same effect but then CSS devtools can flag them as wrong?
<lea> q+ or possibly have an unknown state
<lea> q+
<TabAtkins> fantasai: then authors won't be as confused
<fantasai> TabAtkins: sure, we can have devtools flag that as a problem
<astearns> ack lea
<fantasai> lea: If we must have that sort of thing, it doesn't seem right to evaluate as false. What if we had an unknown state?
<fantasai> TabAtkins: We do have 3-value algebra in MQ, so ...
<fantasai> lea: So would want to style it differently.
<kizu> `catch: `
<fantasai> TabAtkins: you don't want the test to be false, but not-test to be true, 3-value logic we have handles that
<fantasai> TabAtkins: so yes, makes sense
<fantasai> TabAtkins: So treat these as unknown, yes
<fantasai> fantasai: so like I said, invalid / uknown
<fantasai> astearns: So should we resolve to adopt the wrapped-in-parens version of this with 3-value logic?
<fantasai> PROPOSED: Add numerical comparisons wrapped in bare parens, use 3-value logic for incomparable comparisons
<fantasai> astearns: People can open separate issues for refinements
<fantasai> astearns: e.g. for dropping parens in some cases, or evaluating different timing
<fantasai> astearns: any more comments or concerns?
<fantasai> RESOLVED: Add numerical comparisons wrapped in bare parens, use 3-value logic for incomparable comparisons
<dbaron> (I'm also a bit hesitant about the bare parens syntax...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Tuesday Afternoon
Status: Friday afternoon
Development

No branches or pull requests

5 participants