w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.35k stars 641 forks source link

Can we reinstate `<number-percentage>` and friends? #9955

Open LeaVerou opened 4 months ago

LeaVerou commented 4 months ago

This was prompted by this Chrome bug report: https://issues.chromium.org/issues/41483822

The author was trying to do things like:

background-color: hsl(from blue calc(h + 30deg) calc(s + 25%) calc(l + 5%));

background-color: lch(from blue calc(l + 5) calc(c + .1) calc(h + 20deg)); 

background-color: oklch(from blue calc(l + 0.5) calc(c + 0.1) calc(h + 20deg)); 

and found they were not working in Chrome (whereas their <number> counterparts don't work in Safari, that implements an older version.

As I wrote in the issue…

This is actually a Safari bug, Chrome is correct here.

For background, we had to decide what type these keywords would return, since even though components accept multiple types, they had to return a specific type when used like this, they couldn't return e.g. an angle sometimes and a number other times. For simplicity, the rule we converged on was "return a number if the component accepts one". And then we made all colors accept numbers to make it even simpler: just return numbers, always 😀

However, it does present a clear ergonomics issue. Also, going from numbers to percentages in certain color formats is not just a matter of removing the unit, or even dividing by 100, but can be a lot more complex (e.g. in device independent colors, percentages provide a reference range that roughly corresponds to a cylinder encompassing the P3 gamut).

I think we used to have a <number-percentage> type at some point and we removed it (@tabatkins do you remember why?). I wonder if we could reinstate it? This seems like the perfect use case. We'd also need a <number-angle> for hues, but that is needed far less.

tabatkins commented 4 months ago

Yes, I removed <number-percentage> because it's unsound as a type. You can multiply anything by a number and keep its type stable; multiplying by any typed value changes the type (and eventually needs a division to get us back to a useful type).

Percentages are potentially resolveable to anything. Currently, with the restriction against combining percentage and number in calculations, I can immediately infer the type of an expression containing percentages, even if I have no idea what the percentage resolves to yet. If they were combinable, tho, then I couldn't.

Now, granted, previously every instance that could have combined numbers and percentages was using them in a trivial way - the numeric range was either 0-1 or 0-100, so whether you spelled something .25 or 25% was just a stylistic choice. The presence of non-trivial reference ranges in the newer color syntaxes makes this a little unfortunate.

LeaVerou commented 4 months ago

Yes, I removed <number-percentage> because it's unsound as a type. You can multiply anything by a number and keep its type stable; multiplying by any typed value changes the type (and eventually needs a division to get us back to a useful type).

But once you know how to convert between the two, why not handle it seamlessly? E.g. h + 20deg + 30 does make sense, just like 20% + 1em + 10% makes sense in <length-percentage>.

Percentages are potentially resolveable to anything. Currently, with the restriction against combining percentage and number in calculations, I can immediately infer the type of an expression containing percentages, even if I have no idea what the percentage resolves to yet. If they were combinable, tho, then I couldn't.

Why is this a problem? (FWIW I’m not suggesting we expose these to authors via @property, only to use them internally for RCS)

Now, granted, previously every instance that could have combined numbers and percentages was using them in a trivial way - the numeric range was either 0-1 or 0-100, so whether you spelled something .25 or 25% was just a stylistic choice. The presence of non-trivial reference ranges in the newer color syntaxes makes this a little unfortunate.

Yup.

xiaochengh commented 4 months ago

The type arithmetic doesn't allow <number-percentage>, so we will never allow a calc tree that adds a number to a percentage.

On the other hand, the usage in the examples looks totally legit, because there's a clear context that the percentages should be resolved into numbers.

So can we solve it at parsing time? We introduce a treat-percentage-as-number mode for parsing certain <number> values (like a color channel), so that percentages are immediately converted into numbers at parsing time, and we never really create a <number-percentage> value. This also follows the idea of calc simplification that calc expressions are eagerly simplified at the earliest possible time.

LeaVerou commented 4 months ago

@xiaochengh I think that would be fine. I’m assuming both of these would work, right?

background: oklch(from var(--color) calc(l + 20%) c h);
--offset: 20%;
background: oklch(from var(--color) calc(l + var(--offset)) c h);
xiaochengh commented 4 months ago

Yes, both should work.

cdoublev commented 4 months ago

Fwiw, in #9395:

RESOLVED: % that are resolved against numbers do that at parse time

The issue was also about resolving <percentage> to <angle> at parse time (as noted in the title of the issue but not in the initial comment though). So opacity: calc(50% + 0.5) or background-color: hsl(from blue calc(h + 30deg) calc(s + 25%) calc(l + 5%)) would be valid. edit: it does not fix h + 30deg, which would require to determine the type of h as <angle> | <number>, I think.

That said, I am not fond of resolving values at parse time, whether it is naked or inside a math function. Would you also allow font-weight: calc(bold + 100), since bold could be resolved to <number> at parse time?