[csswg-drafts] [css-values] Proposal: `round()` to a finite scale (#11067)

kizu has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-values] Proposal: `round()` to a finite scale ==
## Background

This is a proposal that I briefly discussed with @fantasai at CSWG F2F in A Coruña this year. The problem it tries to solve came up when I was writing [my latest article](https://kizu.dev/tree-counting-and-random/), so I finally found time to write it.

One of the motivations for this proposal is a problem that was, for example, mentioned by @matthiasott in [his talk at CSS Day 2024](https://youtu.be/su6WA0kUUJE?feature=shared&t=2371) (a bit after 39:31), where he argues that using container query units for fluid type, while possible, leads to too many font-sizes on the page, making it not possible to create a harmonic type scale.

But what if we could augment the existing `round()` to help with this and other similar cases?

## Proposal

The current syntax of `round()` is  `round(<rounding-strategy>?, A, B?)` where `A` and `B` are calculations that must share a type and resolve to any can resolve to any `<number>`, `<dimension>`, or `<percentage>`.

My proposal is to change it to `round(<rounding-strategy>?, A, B*)` — use [`*`](https://www.w3.org/TR/css-values-4/#mult-zero-plus) instead of [`?`](https://www.w3.org/TR/css-values-4/#mult-opt) for the last argument.

When only one value is provided as `B`, `round()` will work the same as now.

When _multiple_ values are provided to `B`, the values would be treated as a _scale_ of the only values that the `A` should be rounded to.

These values must be the only possible outcomes of the `round()` function: this is **not** rounding the value to either of the values in the regular sense, but using the provided values as a finite _scale_.

If we were to think of a single B, it would represent an _infinite linear scale_, for example the default `1` argument there represents an infinite `1 2 3 4 N` scale, but if we'd want to round to a _finite_ scale, for example, to find _the closest prime number_ to a certain list of them, we could do

```CSS
round(var(--foo), 1 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101);
```

This way, if we'd provide `--foo: 10`, it will round to the closest prime, resulting in `11`. And if we will provide something that is equally close to two values, like `--foo: 6`, then the `<rounding-strategy>?` will come into play: we could choose how exactly we'd want to handle cases like it — round things to the closest, or ceil/floor it.

Implementation-wise, this should not be too complex: something like a binary search of the first argument with the provided list could be good enough.

## Other use-cases

Aside from the typographic scale and choosing the closest prime number, I remember stumbling upon many different use cases.

One prominent one: regular spacing scale. Various design systems can have a scale like `2px 4px 8px 12px 16px 20px 24px 32px 48px` for spacing, and similar to the Matthias's case with fluid typography, it would be great to evaluate some container query length units to the closest value from such a scale.

There were other cases I encountered, but at the moment of writing this proposal I don't remember them: will update if I'll stumble upon them later, and if anyone had them as well — drop them in the comments, I'll update the proposal with them as well.

## Current Workaround

Today, the simplest way we can attempt to approach this with the current CSS is by using a rather complicated complex conditional calculation which I described in my article. For the above list of primes (with some lower ones removed), it looks like this:

```CSS
    --limit: 102;
    --closest-prime: calc(
      var(--limit)
      -
      max(
        min(1, 11 - var(--x)) * (var(--limit) - 11),
        min(1, 13 - var(--x)) * (var(--limit) - 13),
        min(1, 17 - var(--x)) * (var(--limit) - 17),
        min(1, 19 - var(--x)) * (var(--limit) - 19),
        min(1, 23 - var(--x)) * (var(--limit) - 23),
        min(1, 29 - var(--x)) * (var(--limit) - 29),
        min(1, 31 - var(--x)) * (var(--limit) - 31),
        min(1, 37 - var(--x)) * (var(--limit) - 37),
        min(1, 41 - var(--x)) * (var(--limit) - 41),
        min(1, 43 - var(--x)) * (var(--limit) - 43),
        min(1, 47 - var(--x)) * (var(--limit) - 47),
        min(1, 53 - var(--x)) * (var(--limit) - 53),
        min(1, 59 - var(--x)) * (var(--limit) - 59),
        min(1, 61 - var(--x)) * (var(--limit) - 61),
        min(1, 67 - var(--x)) * (var(--limit) - 67),
        min(1, 71 - var(--x)) * (var(--limit) - 71),
        min(1, 73 - var(--x)) * (var(--limit) - 73),
        min(1, 79 - var(--x)) * (var(--limit) - 79),
        min(1, 83 - var(--x)) * (var(--limit) - 83),
        min(1, 89 - var(--x)) * (var(--limit) - 89),
        min(1, 97 - var(--x)) * (var(--limit) - 97),
        min(1, 101 - var(--x)) * (var(--limit) - 101)
      )
    );
```

This works! But this is not something that is easy to write or maintain by hand.

## Alternatives Considered

Initially, I was thinking about either introducing a new function, or looking if we could somehow do this with `clamp()`. For some reason, I was not thinking about `round()` as a possible alternative, as I was stuck with it rounding to an _infinite_ scale. But `clamp()` works very differently from `round()`, where it _keeps_ the original value if it fits into the provided range. But we're really _rounding_ it to a scale, and it was @fantasai that proposed to think about just augmenting `round()`.

## Out of Scope

I think there is something about being able to specify an alternative, non-linear _infinite_ list, maybe in a form of an equation similar to the one in the nth-child, but maybe a bit more complex.

It would be great to be able to round something to a scale like 2 4 8 16 32 64 etc, or be able to specify a fallback if the value falls outside of the chosen _finite_ scale.

In the future, I think these would be nice to have, but I'd propose to have separate issues discussing them and bikeshedding their syntax. A finite scale is a common enough case, and seems to be simple enough to implement, that I'd want us to first focus on it.

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/11067 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Tuesday, 22 October 2024 08:01:11 UTC