jelhan/ember-style-modifier

Support type-coercion to strings

Closed this issue · 4 comments

I got this well known error:

Error: Assertion Failed: Your given value for property '--left' is 0 (number). 
Accepted types are string and undefined. Please change accordingly.

Which is quite explicit in the code, but I am curious why. If there's one thing I can count on JavaScript for, it's turning things into strings.

By not allowing type-coercion, I now have to do this casting in my code, which means introducing a helper for the sole purpose of .toString.

Here's my actual code to illustrate how quickly doing this in JS isn't really an option:

<div
  {{style
    --left=(scale-fn-compute xScale this.activeDatum.startTime)
    --top=(scale-fn-compute yScale this.activeDatum.count)
  }}
>

Which will become this for now

<div
  {{style
    --left=(str (scale-fn-compute xScale this.activeDatum.startTime))
    --top=(str (scale-fn-compute yScale this.activeDatum.count))
  }}
>

In most cases the string representation of a primitive or complex object will not be a valid value in CSS. E.g. for numbers CSS nearly always requires a unit as well. It should be margin: 1px, margin: 10% or margin: 1rem. Not margin: 1. I fear that type-coercion would result in bugs in many cases due to that.

I expect that type-coercion often happens on combining a calculated number with the unit it is in. Something like this:

<div
  {{style
    width=(concat this.optimalWidth 'px')
  }}
/>

Your example is confusing to me. I don't think --left is a valid CSS property. I guess it should be left. But in that case your value seems to miss a unit.

I absolutely agree with you that aside from 0 numbers in CSS when applied to properties need to accompanied by units.

Which leads to your confusion and my request:

I don't think --left is a valid CSS property.

Here --left is not a CSS property and is not meant to be left. It is a custom property (aka variable) which is allowed to be unitless since it needs to be invoked to be applied to a property.

The pattern I'm implementing works like this:

  1. Do as much styling as you can in CSS
.my-tooltip {
    pointer-events: none;
    position: absolute;
    top: 0;
    left: 0;

    border-radius: 4px;
    background: #f7f8fa;
    padding: 1rem;
}
  1. Use JS/HBS helpers to compute the dynamic bits
{{! from above example}}
<div
  {{style
    --left=(str (scale-fn-compute xScale this.activeDatum.startTime))
    --top=(str (scale-fn-compute yScale this.activeDatum.count))
  }}
>
  1. Use CSS variables as a message passing mechanism to ultimately transform the tooltip position
.my-tooltip {
    transform: translate(
      calc(-50% + 1px * var(--left, 0)),
      calc(-100% + 1px * var(--top, 0) - 1.5rem)
    );
}

Of course we could add px to the variable values, but we can also cast them to pixels in the CSS calc function. And if casting is deferred for CSS to deal with, then casting to strings in HBS feels especially superfluous.

Hopefully this use case made sense! I know it's a tad fringe, but my bet is that this pattern will become more mainstream.

Using {{style}} modifier to set CSS custom properties, is a very interesting use case. I must admit that I wasn't aware it's even possible. I like the readability that approach provides.

Nevertheless I'm not convinced that applying an unit late in CSS has much benefits. var(--left, 5px) is easier to read than 1px * var(--left, 5) in my opinion. Having an explicit unit may also help to understand what the CSS custom property is about.

Overall I'm not convinced that the better developer ergonomics for your very specific case outweigh the trade-offs. Most importantly the likelihood of errors due to missing units.

This is not only a question of the debug assertion. We will face the same question when adding types for this modifier.

I'm going to close this issue. If I have missed any important argument, please feel free to comment anyways.