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-typed-om] Provide a way to resolve a CSSUnitValue and CSSMath* to a pixel value #798

Open
surma opened this issue Aug 23, 2018 · 9 comments

Comments

@surma
Copy link
Member

surma commented Aug 23, 2018

Especially in conjunction with css-layout-api, it seems desirable to be able to resolve lengths with percentages in them.

const marginLeft = child.styleMap.get('margin-left'); // "calc(10px + 2%)"
const marginLeftResolved = CSS.resolveLength(marginLeft, {percentResolutionSize: 250});

... or something. A similar use case might be column-gap etc.

@tabatkins
Copy link
Member

If you have the percent resolution size, you can just do this yourself, no? Just go thru and replace all the %s with an appropriate pixel value. I suppose this is mostly a convenience method?

I'd also like to be able to resolve em and such, which wants to know and about the element and the property. Like, without that info, we can resolve em the way MQs work; with an element, we can properly resolve against 'font-size'; with an element and a property, we can correctly handle em in font-size vs in other properties.

We already have to to() method for converting a value into a specific unit; we can give it some additional powers with an options bag giving resolution details:

partial interface CSSNumericValue {
    CSSUnitValue to(USVString unit, CSSUnitResolvingOptions options);
};

dictionary CSSUnitResolvingOptions {
  CSSUnitValue? hundredPercent = null;
  Element? element = null;
  USVString? property = null;
};

And it'll succeed if it can use the provided information to resolve everything to your desired type.

@tabatkins
Copy link
Member

tabatkins commented Aug 23, 2018

I'm using to() here, rather than the method shape you suggested, because we need to resolve it into some specific unit. We don't have a type that represents a nebulous "length, in some unspecified unit" type, which is the only thing we could reasonably return from such a conversion function.

That said! I think we might want to allow something that, for example, keeps %s as they are while resolving the rest into a length as much as possible. This is useful for cases like background-position, where the % doesn't resolve into a length in a straightforward way. I think we can handle all the use-cases for this by just adding this options bag to toSum() as well; if a % doesn't trivially resolve into a length, then it can't be used in a px / % expression anyway, so requiring that it resolve into a sum should be fine.

In other words, calling len.toSum("px", "percent", {element:el}) should be able to always succeed, and will always return a CSSMathSum where the first element is a CSS.px() and the second is a CSS.percent().

@tabatkins
Copy link
Member

Oh, hm, slight wrinkle in getting it to invoke the MQ-ish behavior. Can't just rely on not passing an element, because our current behavior for not passing the options bag at all (which has to be treated identically) is to throw when you can't convert, rather than relying on global information.

However, if I remove the default values from the dictionary listed up above, then I can test for presence/absence of the key, and use an explicit {element: null} as a signal to resolve based on global info like MQs do. Leaving it out (or explicitly passing {element:undefined}) will leave us with the current behavior, where conversion just fails.

@surma
Copy link
Member Author

surma commented Aug 23, 2018

I like the idea with to(). I was thinking of ems as well, actually, just forgot to put it in the OP 🤦‍♂️

I think your suggestion with {element: null} to distinguish the behaviors is fine.

Ship it? :D

@css-meeting-bot
Copy link
Member

The Houdini Task Force just discussed Resolve values to a px with external information.

The full IRC log of that discussion <TabAtkins> Topic: Resolve values to a px with external information
<TabAtkins> https://github.com//issues/798
<TabAtkins> github: https://github.com//issues/798
<heycam> TabAtkins: if all of the units you have in a math value are compatible with pixels e.g. then you can always resolve them to a single px
<heycam> ... if you have combinatons of values that aren't directly resolvable, there's no way to resolve them
<heycam> ... since the custom layout API works mostly in px values, it would be useful to give people more direct control over how they can simplify them down to px
<TabAtkins> https://github.com//issues/798#issuecomment-415555133
<heycam> ... so we add an options bag to the to and toSum functions
<heycam> ... giving extra information for resolving the values
<heycam> ... in particular, the element being resolved on (for ems etc.), the property it's on (so font-size can properly resolve to parent element), and what a % should resolve to
<heycam> ... going with this explicit % resolution thing to avoid some problems with some properties resolving %s against different sizes
<heycam> ... this makes it more explicit with cases like the layout API, where you have an available space, and you can just pass that in rather than finding a property to pretend it's being resolved as
<heycam> ... I think that lets us resolve all the units
<heycam> ... in particular, a comment further down suggests that we don't have a default value, so if you don't pass an element, resolve like MQs do, with no context
<heycam> ... right now, if you call to() without this information and it needs it, it throws
<heycam> emilio: the layout API doesn't have access to any element, right?
<heycam> TabAtkins: yes
<heycam> emilio: can we instead pass what the function depends on explicitly? what's the expected thing if you pass an element to there, I assume you have to do style updates etc.?
<heycam> ... what happens if that element's not in the flat tree etc.?
<heycam> ... would be nice to be more explicit about which data you're depending on
<heycam> ... then people can use the no context thing without having magic
<heycam> TabAtkins: there's a lot of information. which font's being used. it's possible to enumerate this
<heycam> emilio: instead of getting a font and doing font matching, you may as well .... I guess there's no way to get ex height without script
<heycam> TabAtkins: a possibility is to allow you to pass an element, but also enumerate all the things that the various relative units can depend on
<heycam> ... and if you don't pass a unit, that's the info you have to work with
<heycam> ... and if you use a unit but no way to determine what that is, continue to throw
<heycam> emilio: if you need to download fonts to make this work....
<heycam> TabAtkins: that's what we have to do
<heycam> ... normally you wait for a computed value, which triggers all that
<heycam> emilio: that only happens if you actually lay out the element
<heycam> TabAtkins: and the assumption here is you are doing something layouty but not with an element
<heycam> emilio: let's say you call this function from a layout worklet with a given font
<heycam> ... does the layout trigger the font load somewhere?
<heycam> TabAtkins: good question
<heycam> ... possibly
<heycam> emilio: does it do it sync? probably not
<heycam> ... you can't make it sync
<heycam> TabAtkins: I think then we should remove the element resolver, figure out what relative units we can easily resolve on, e.g. asking for font-size, other font relative ones are a bit hard for now
<heycam> ... and then if you try to use an ex unit to px, we just throw
<heycam> ... then we can remove the property too
<heycam> fremy: if you still want the behavior to pass an element, you can have another element, and ti returns the properties you would fill in
<heycam> TabAtkins: one of those dictionaries
<heycam> dbaron: I'm not convinced you canremove the property
<heycam> ... if you're doing it from the element, you still need to know font-size / line-height behave differently for em and lh units
<heycam> TabAtkins: I'm suggesting that just like the 100% line, where you explicitly indicate what the resolves to, you just supply what them resolves to
<heycam> ... and then also supply a function that can extract that from an element automatically
<heycam> fremy: CSSNumericValue -- is that calc or more stuff?
<heycam> TabAtkins: everything
<heycam> fremy: can just create a 1em and call to
<heycam> emilio: layout API functions could need 1vmax, you need to expose viewport size to those functions
<heycam> ... not sure if you want to?
<dbaron> The function to extract from an element automatically would need a property.
<heycam> iank_: no access to viewport info
<heycam> iank_: it's not necessary
<heycam> ... the side effect of exposiong that info is that when you resize that viewport, you'd need to call every layout function
<heycam> ... since it's a side input
<heycam> TabAtkins: this would be an implicity dependency
<heycam> ... unless we made it explicit somehow
<heycam> fremy: we could add a function in the worklet, before calling it viewport sizes are 0, and only after you can use it...
<heycam> TabAtkins: we have a case for later registrationt of dependencies
<heycam> iank_: probably you want to do this in the layout registration dictionry
<heycam> ... you don't know which global scope you're doing to run it in
<heycam> fremy: the other option is to create custom properties that would be 1vmin, etc., type them to be lengths, then you'll get the value
<heycam> ... it's a big handwavy but it does work
<heycam> iank_: if this becomes a common pattern, in layout options, say that resolve vw/vh against the real thing
<heycam> ... but part of the precedent for resolving to 0 is what happens when you set a font-size in an offscreen canvas context, it resolves to 0
<heycam> TabAtkins: that's different from how MQs work
<heycam> iank_: not saying that's good or bad but that's the precedent
<heycam> TabAtkins: I think that's all the feedback I need; I will come back with a more mature proposal in the future

@astearns astearns removed the Agenda+ label Nov 13, 2018
@MadeByMike
Copy link

I'd love to be able to define a custom resolution. For example resolve a font-size relative to the width of a container. Or resolve a text color relative to the background. I know this stuff can be done after the initial render but it would be nice if houdini provided some way to do this within render pipeline.

@Rolf-B
Copy link

Rolf-B commented Dec 28, 2022

It's more complicated than that. Assume something like transform:translate(10px,20px) translate(30%,200%) rotate(20deg) translate(-30%,-200%) - this is from an attempt to make a card game where I show a hand of cards, then I want to drag one card away from it's position. Now I need the transformation matrix. I can to that with window.getComputedStyle(card), but there I get a string notation of the matrix and must parse it. With card.computedStyleMap().transform, I get a CSSTransformValue, but I cannot call toMatrix on it because of the % values. Converting percentages by myself means that I need to run through all transform components, check for units that are incompatible with px, find their context, convert them according to context, et cetera. From window.getComputedStyle, I get this for free, only in string format.
So, yes, it's convenience. A big, whopping, VERY useful convenience.

@tabatkins
Copy link
Member

Ooh, that would be a little bit more complicated, since you want the resolution method on CSSTransformValue as well as just CSSNumericValue. That's doable, tho, and presumably in the same way.

@tabatkins
Copy link
Member

Draft proposal: Add a .resolveUnits() method to CSSNumericValue, CSSTransformValue, and CSSTransformComponent. (Maybe CSSColorValue as well?) Defined as:

partial interface CSSNumericValue {
    CSSUnitValue resolveUnits(CSSUnitResolvingOptions options);
};
/* etc for the other interfaces */

dictionary CSSUnitResolvingOptions {
  CSSUnitValue? hundredPercent = null;
  Element? element = null;
  USVString? property = null;
};
  • If hundredPercent is specified, all percentages resolve against that.
  • If element is specified, all non-percent relative units resolve against that element, in a generic fashion. (So 'em' uses the 'font-size' on the element, rather than the special behavior that 'em' has in 'font-size' itself.)
  • If element and property is specified, all relative units resolve against that element in the context of that property. Need to define this generically in some way.
  • For the transform types (and other specific types), element (and property, if needed) will resolve %s as appropriate, too, if you're not overriding it with hundredPercent. So you can el.styleMap.get('transform').resolveUnits(el) and get it fully resolved correctly, with the x and y values using the correct length to resolve %s automatically.

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

6 participants