Skip to content
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-values] Type conversion functions #6408

Open
SebastianZ opened this issue Jun 24, 2021 · 18 comments
Open

[css-values] Type conversion functions #6408

SebastianZ opened this issue Jun 24, 2021 · 18 comments

Comments

@SebastianZ
Copy link
Contributor

In #542 there was already some discussion about whether type conversion for strings should be im- or explicit. And it seems the conclusion was to be explicit about that.

I'd like to go a step further and start the discussion whether other type conversion functions should be introduced, too.

One use case I had today was that I wanted to use a counter within a calculation, i.e. something like calc(counter(x) * 3). Though that currently doesn't work because counter(x) doesn't return a numeric value.

This could be approached in two ways:

  1. One conversion function that is able to convert any type into any other type. This would require several parameters for the value to convert, the type to convert to and an optional unit.

    Examples:

    grid-row-start: calc(convert(counter(x), "<numeric>") * 3);
    height: convert(counter(x), "<length>", "em");
    content: convert(5, "<string>");
    transform: rotate(convert("45", "<angle>", "deg"));
  2. One conversion function for each type.

    Examples:

    grid-row-start: calc(number(counter(x)) * 3);
    height: length(counter(x), "em");
    content: string(5);
    transform: rotate(angle("45", "deg"));

Sebastian

@Loirooriol
Copy link
Contributor

See #1026. Rather than converting the internal counter value into a string, and then parsing it as a number, something like counter-value() would make more sense to me. Anyways, upsuper raises some concerns regarding parallelization.

@SebastianZ
Copy link
Contributor Author

Thank you for pointing me at #1026, @Loirooriol!

For my current use case, a counter-value() function would suffice, too.

Though I still believe general type conversion functions would have some value and would make the conversion explicit.
@upsuper's point about the phase at which conversion should happen is important, of course. If they are meant to work everywhere, I guess they need to do the conversion at used value time.

Note that general conversion may also be used for functions returning a number like the discussed random() function or all the mathematical expressions. And other type conversions like from angles to numbers or the other way round also may have their use cases.

Sebastian

@LeaVerou
Copy link
Member

LeaVerou commented Oct 9, 2021

The problem with a general conversion function is that often these conversions are impossible, so the function needs to either give up and return NaN or do weird stuff (like JS' parseInt() and parseFloat()).

Since most use cases of conversion to number revolve around counters, I think we should add counter-value() or a similar counter-based syntax (what about an extra argument in counter()?) and see if any other use cases emerge that are not addressed by this. These syntaxes would also allow us to get a number even when the counter has a non-numerical style, e.g. upper-roman.

Note that the conversion to strings is an entirely different issue that is discussed in #542.

@fantasai
Copy link
Collaborator

@SebastianZ Seems to me we should close this out in favor of #1026 and #542, does that work for you or is there something left here that we should keep it open for?

@SebastianZ
Copy link
Contributor Author

SebastianZ commented Nov 1, 2021

#1026 and #542 cover the current use cases of converting counter values to numbers and any kind of value to strings.

Note that the main point of this issue is to discuss whether it would be better to provide a unified and explicit way for type conversion instead of having different mechanisms. And the idea of making all type conversions explicit actually rose from the discussion in #542, as I initially mentioned.

The problem with a general conversion function is that often these conversions are impossible, so the function needs to either give up and return NaN or do weird stuff (like JS' parseInt() and parseFloat()).
...
Note that the conversion to strings is an entirely different issue that is discussed in #542.

You're right. Though I don't see this as a problem but an advantage of an explicit function. And I include string conversion in this, as that is probably the most common use case for type conversions.

Of course a general mechanism as I mentioned it in my initial post has the downside that it's much longer to write than implicit conversion. Also in relation to @LeaVerou's point, it might not be obvious to authors if and how specific conversions actually work.
Though again, I believe a general explicit way of converting different values into each other is better than doing it implicitly or having different solutions for different cases.

Sebastian

@LeaVerou
Copy link
Member

LeaVerou commented Nov 1, 2021

I think there's one case where implicit conversion makes sense, and I just opened an issue on it: #6786

Though again, I believe a general explicit way of converting different values into each other is better than [...] having different solutions for different cases.

We generally don't add things without enough use cases, just for completeness' sake. If going from and to any type has enough use cases, sure. But so far it seems that use cases concentrate in a couple of conversions.

@SebastianZ
Copy link
Contributor Author

We generally don't add things without enough use cases, just for completeness' sake. If going from and to any type has enough use cases, sure. But so far it seems that use cases concentrate in a couple of conversions.

A general function could also be defined to just cover the current use cases for now and be extended in the future.

Sebastian

@fantasai fantasai removed the css-values-4 Current Work label Jun 9, 2022
@Crissov
Copy link
Contributor

Crissov commented Mar 3, 2024

In a comment on #10001, I suggested that it would be useful for authors to be able to parse an attribute value, class in particular, as a space-separated list and randomly access its entries. Would this be in scope of this issue?

With explicit type conversion functions, the current specification of attr() could be simplified a lot because its second parameter <attributes-type> would not be necessary anymore. (Possibly #4482 is the relevant issue, or #3702.)

@SebastianZ
Copy link
Contributor Author

In a comment on #10001, I suggested that it would be useful for authors to be able to parse an attribute value, class in particular, as a space-separated list and randomly access its entries. Would this be in scope of this issue?

Well, a space-separated list is is a more complex type than e.g. <string> or <number>. It might be added at some point, though for now I'd like to focus on the simple types.

Sebastian

@SebastianZ
Copy link
Contributor Author

SebastianZ commented Aug 29, 2024

To make progress on this, here's a concrete proposal:

Let's introduce a convert() function that converts one value into another value.
It's syntax should look like this:

convert() = convert( <declaration-value>, <conversion-type>, <conversion-unit>? )

where <conversion-type> must be a syntax string and <conversion-unit> a unit string.

Which syntax strings are allowed as <conversion-type> needs to be discussed. Though I suggest to be able to convert any supported syntax component name.
The current use cases would then look like this:

Definition for converting to <string>:

  • From <integer>: The integer will be converted including a possible negative sign, e.g. 5"5", -5"-5".
  • From <number>: The number will be converted including the decimal dot, e.g. 3.1415"3.1415".
  • From <dimension>, <percentage>, or <flex>: The dimension, percentage, or flexible value will be converted including the value plus a possible negative sign and the unit, e.g. 2em"2em", 1.2s"1.2s", -90deg"-90deg", 25%"25%", 2fr"2fr".
  • From <string>: No change happens, e.g. "foo""foo".

Definition for converting to <number>:

  • From <integer>: The integer will be converted directly, e.g. 55, -5-5.
  • From <number>: No change happens, e.g. 3.14153.1415.
  • From <dimension>, <percentage>, or <flex>: The dimension, percentage, or flexible value will be converted by taking its value and stripping away the unit, e.g. 2em2, 1.2s1.2, -90deg-90, 25%25, 2fr2. Any mathematical expression is also allowed, e.g. calc(5em / 2)2.5.
  • From <string>: The string is parsed as a component value and tried to be consumed as a number of type "number". In case the string consists of a numeric value, it is converted, e.g. "2"2, "3.1415"3.1415, "-5"-5. Otherwise, the conversion fails and results in an invalid value.

Definition for converting to <integer>:

  • From <integer>: No change happens, e.g. 55.
  • From <number>: The number will be rounded to the nearest integer, e.g. 3.14153, 2.71823.
  • From <dimension>, <percentage>, or <flex>: The dimension, percentage, or flexible value will be converted by taking its value and stripping away the unit, then rounding it to the nearest integer, e.g. 2em2, 1.2s1, -90deg-90, 25%25, 2fr2. Any mathematical expression is also allowed, e.g. calc(5em / 2)3.
  • From <string>: The string is parsed as a component value and tried to be consumed as a number of type "number". In case the string consists of a number value, it is converted and rounded to the nearest integer, e.g. "2"2, "3.1415"3, "2.7182"3, "-5.1"-5. Otherwise, the conversion fails and results in an invalid value.

Sebastian

@Loirooriol
Copy link
Contributor

Is this addressing any usecase not covered by #1026 and #542? Because I would prefer to address these with a specific solution for them.

Also it's not clear if converting string to number will accept scientific notation, leading/surrounding whitespace, strings like "calc(1 + 3)", etc.

Also not a fan of just dropping units. Converting 2em into 2 is better achieved with calc(2em / 1em).

@SebastianZ
Copy link
Contributor Author

Is this addressing any usecase not covered by #1026 and #542? Because I would prefer to address these with a specific solution for them.

Yes, explicit type conversion functions may also cover type conversion in attr() and possibly even complex types, as noted earlier by @Crissov.

Also it's not clear if converting string to number will accept scientific notation, leading/surrounding whitespace, strings like "calc(1 + 3)", etc.

Any string that can be interpreted as a valid <number> value. That at least includes scientific notation. For math functions like calc(), I tend to say yes, to surrounding whitespace I'd say no. Though that needs to be discussed.

Also not a fan of just dropping units. Converting 2em into 2 is better achieved with calc(2em / 1em).

So how would you convert a string "2em" into a number? Should it just fail?

Sebastian

@Loirooriol
Copy link
Contributor

type conversion functions may also cover type conversion in attr()

If we had multiple functions like that, it could make sense to do it in a generic way. But if attr() is the only case, it's probably simpler to just keep the 2nd parameter.

Any string that can be interpreted as a valid <number> value [...] to surrounding whitespace I'd say no

Well, if it's parsing as <number>, then I would expect to use https://www.w3.org/TR/css-syntax-3/#parse-a-component-value and ignore whitespace tokens.

So how would you convert a string "2em" into a number? Should it just fail?

Yes, it should fail like in https://drafts.csswg.org/css-values-5/#valdef-attr-number
If you want to get a number, you should convert it into a length, and then use calc() to divide by 1em or whatever.

@Crissov
Copy link
Contributor

Crissov commented Sep 2, 2024

Isn’t there also a proposal somewhere for getting query parameters or fragment identifiers from the stylesheet URL into property value space? That, env(), var(), string(), content() and counter() seem like likely candidates besides attr(). (Some of those may be bad ideas.)

@SebastianZ
Copy link
Contributor Author

@LeaVerou mentioned in a CSS Day talk from 2022 some other use cases for explicit type conversion. She used counters to display a percentage on a bar chart. Here's a screenshot of that talk (@LeaVerou Hope that's fine to post it here!):

Using counter for displaying percentages in content

With explicit type conversion, you could shorten this example to

<div style="--p: 49%">F</div>
.bar-chart > div {
  height: var(--p);
}

.bar-chart > div::before {
  content: convert(var(--p), <string>);
}

And it would look much less hacky.

type conversion functions may also cover type conversion in attr()

If we had multiple functions like that, it could make sense to do it in a generic way. But if attr() is the only case, it's probably simpler to just keep the 2nd parameter.

Explicit type conversion is non-exclusive to implicit conversions. So, an attr() with a second parameter could coexist. The use cases for explicit conversions are much broader, though.

Any string that can be interpreted as a valid <number> value [...] to surrounding whitespace I'd say no

Well, if it's parsing as <number>, then I would expect to use https://www.w3.org/TR/css-syntax-3/#parse-a-component-value and ignore whitespace tokens.

Fine for me.

So how would you convert a string "2em" into a number? Should it just fail?

Yes, it should fail like in https://drafts.csswg.org/css-values-5/#valdef-attr-number If you want to get a number, you should convert it into a length, and then use calc() to divide by 1em or whatever.

Fair enough. Instead of using calc() you could also convert to a length first and then to a number, i.e. convert(convert("2em", <length>), <number>).


Isn’t there also a proposal somewhere for getting query parameters or fragment identifiers from the stylesheet URL into property value space?

I'm not sure there is one for extracting information from a URL. Maybe you are referring to linked parameters to pass parameters to a resource?

If there was some way to extract information from a URL, explicit type conversion could be used to interpret those parameters, though.

Sebastian

@LeaVerou
Copy link
Member

LeaVerou commented Sep 2, 2024

@SebastianZ In general, the need to print out a numeric value as text comes up all the time. E.g. most recently in printing out page numbers that were set via a Paged.js-like script (and not via counters). But also for debugging — until there is a better way.

@Crissov this is a completely orthogonal issue. Please search and open a new issue if it doesn't exist.

@SebastianZ
Copy link
Contributor Author

I've updated my suggestion to include the use case for converting to <integer>, incorporated some feedback and added a few more details.

Also not a fan of just dropping units. Converting 2em into 2 is better achieved with calc(2em / 1em).

This is debatable. As an author, I'd expect 2em to be convertible to a number or integer, making the conversion resilient. Though I don't have a strong opinion about this.

Sebastian

@Loirooriol
Copy link
Contributor

You can't directly convert lengths to numbers. If 1em = 16px, it doesn't make sense for 1em and 16px to be converted to different things, and for 1em and 1px to be converted to the same number since one length is larger than the other. And what number does calc(1px + 1em) get converted into? It just doesn't make sense.

See Why does hypot() allow dimensions (values with units), but pow() and sqrt() only work on numbers? in the spec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants