Skip to content

[css-values] Add a way to set longhands to the corresponding expansion of a shorthand value #8055

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
Loirooriol opened this issue Nov 9, 2022 · 6 comments

Comments

@Loirooriol
Copy link
Contributor

Loirooriol commented Nov 9, 2022

Rationale

There are some ways to set longhands to values that can't be represented at specified-value time, so they just serialize as empty string. In particular:

This doesn't seem ideal, since we lose round-tripping (assigning empty string in CSSOM will just remove the declaration) and it's hard to tell what the value is. All these cases have in common that the value is set from some shorthand, so cssText can help serializing such shorthand, but this produces poor results when some longhand has been modified afterwards (see #2515):

document.body.style.cssText = "margin: var(--m); margin-top: 1px";
document.body.style.marginLeft; // "" :( and the following don't help:
document.body.style.cssText; // "margin-right: ; margin-bottom: ; margin-left: ; margin-top: 1px;"
document.body.style.margin; // ""

Proposal

So I had an idea to address all of this: introduce from-shorthand( <custom-ident> ':' <whole-value>):

  • from-shorthand() is a <whole-value>.
  • It's invalid if the <custom-ident> doesn't match the name of some shorthand ASCII case-insensitively.
  • When used on a longhand, it's invalid if the referred shorthand is not a shorthand of the longhand.
    When used on a shorthand, it's invalid if the referred shorthand is not a "super-shorthand" (i.e. it's invalid if there is a property which is a longhand of the current shorthand but is not a longhand of the referred shorthand).
  • It's invalid if the <whole-value> is an invalid value for the referred shorthand.
  • Custom properties don't have shorthands, so when used on an unregistered prop it should just be tokenized instead of resolving there.
  • At computed-value time, it basically resolves to the value that the current property would get if the referred shorthand had been set to the <whole-value>.
  • I'm using a : since it looks clearer, like a declaration. Function arguments typically use , but that could get confusing in case the <whole-value> has commas. (Edited: initially suggested ;)

Then:

document.body.style.margin = "var(--m)";
document.body.style.marginTop; // "from-shorthand(margin: var(--m))"
document.body.style.margin = "toggle(1px, 2px)";
document.body.style.marginTop; // "from-shorthand(margin: toggle(1px, 2px))"
document.body.style.font = "small-caption";
document.body.style.fontFamily; // "from-shorthand(font: small-caption)"
document.body.style.webkitBackgroundClip = "text";
document.body.style.backgroundClip; // "from-shorthand(-webkit-background-clip: text)" in L3
document.body.style.webkitBorderImage = "100% / 1px";
document.body.style.borderImageWidth; // "from-shorthand(-webkit-border-image: 100% / 1px)" in WebKit
                                      // (the 100% is discarded and may be something else)
document.body.style.cssText = "margin: var(--m); margin-top: 1px";
document.body.style.marginLeft; // "from-shorthand(margin: var(--m))"
document.body.style.cssText; // "margin-right: from-shorthand(margin: var(--m)); margin-bottom: from-shorthand(margin: var(--m)); margin-left: from-shorthand(margin: var(--m)); margin-top: 1px;"
document.body.style.margin; // ""

I guess there may be compat issues in the var() case, but hopefully people are not relying on getting an empty string.

@tabatkins
Copy link
Member

Hm, this definitely solves the round-trip (setting a longhand to itself) and cssText issues, but still doesn't let you reserialize the shorthand when one of the longhands is modified.

If we specified which longhand was being extracted from the shorthand, rather than it being contextual from the property it's used in, we'd get around that, but it would open us up to allowing less-sensical things to be specified, like font: from-shorthand(font font-family, ...) from-shorthand(font family, ...);. I suppose keeping it contextual and thus only valid in longhands lets us keep the resolved value abstract and not have to worry about how it combines with other things.


Nit: I wouldn't use ; for this. It's not necessary for disambiguation, and I want to keep ; as only being used where absolutely necessary. I think , is fine, but if we really think it could be confusing, using : instead would be much better imo: margin-left: from-shorthand(margin: var(--x));.

@Loirooriol
Copy link
Contributor Author

but still doesn't let you reserialize the shorthand when one of the longhands is modified.

Yes, but it's not strange that a shorthand may not be able to represent all possible values of the longhands, and it's not such a big deal, since cssText can serialize longhands if shorthands don't work, and iterating a CSSStyleDeclaration goes through the longhands. So the important thing is being able to serialize the longhands.

Being able to specify which longhand to extract would offer more flexibility but it still doesn't guarantee that all shorthands will be serializable, and it opens the can of worms of using values from one property on another, which is difficult to define since some browsers normalize values at parse time by adding/removing/reordering components, which may affect whether it's still valid in the other property. Even in the same property it seems tricky if from-shorthand() can be mixed with other things, that's why I think it should be the only component and for the same property.

using : instead would be much better imo

Yes I thought about : after filing the issue, may be the most clear option. Comma seems potentially confusing in e.g. transition-property: from-shorthand(transition, margin, padding) if you are not familiar with the function.

@Loirooriol
Copy link
Contributor Author

I have edited the top post to use :. Also, if in the future we can avoid problems when assigning one expanded longhand to another longhand, the syntax could be extended to from-shorthand(shorthand: value; longhand).

@Loirooriol
Copy link
Contributor Author

Something that I missed is that we usually turn longhands into shorthands, so this should be allowed in shorthands as long as the referenced shorthand covers all the longhands (it's a supershorthand).

So for example, in Backgrounds L3,

background-position: from-shorthand(background: 10px 20px red);

would behave as

background-position: 10px 20px;

And in L4 it becomes a shorthand so it would expand to

background-position-x: from-shorthand(background: 10px 20px red);
background-position-y: from-shorthand(background: 10px 20px red);

and behave as

background-position-x: 10px;
background-position-y: 20px;

@tabatkins
Copy link
Member

From @andruud in #11055:

Pending-substitution values must be serialized as the empty string, if an API allows them to be observed.

https://drafts.csswg.org/css-variables/#substitution-in-shorthands

Authors do trip over this behavior sometimes (https://issues.chromium.org/issues/40804066), and it's not ideal for cases where round-tripping via text is crucial, like serializing to MHTML.

Can we somehow expose the pending-substitution value? E.g.:

border-width: shorthand(border, 1px var(--x) black);

@tabatkins
Copy link
Member

Yeah, so far I avoided exposing this just for lack of use-case, but if the lack is confusing authors, we should def expose it. I haven't given a lot of thought to what syntax to use; we've got a few here in the thread.

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

2 participants