liveview-native / liveview-client-swiftui

MIT License
349 stars 29 forks source link

Support `style` attribute #1345

Open bcardarella opened 1 month ago

bcardarella commented 1 month ago

I think we should pull the trigger on this as a blocker for v0.3

My changes to the UtilityStyles gave people the ability to write class names that are effectively syntax parity with the actual modifiers. This is looking odd now as it appears to be rules written as class names.

I'd like to move this format for the UtilityStyles over to a style attribute:

<Text style={[
"font(.title)",
~S(foobar(a: "SomeString"))
]}>Hello, world!</Text>

I'm going to make the changes in the Elixir code. The above template should output as:

<Text style="font(.title); foobar(a: &quot;SomeString&quot;);">Hello, world!</Text>

The HTML entities should be decoded first then the string is split on ; instead of whitespace.

The stylesheet will be identical. The key value will be the pre-decoded version and the value will be post-decoded so the above would result in the following stylesheet:

%{
  "font(.title)" => [{:font, [], [{:., [], [nil, :title]}]],
  "foobar(a: &quot;SomeString&quot;)" => [{:foobar, [], [a: "SomeString"]}]
}

This would require the client to collect class names from both class and style attr. Similar to HTML the modifiers from style have a higher precdence than the ones from class.

As an example, the folllowing HTML:

<style>
.blue {
  color: blue
}
</style>

<div class="blue" style="color: red">Hello, world!</div>

this will result in a red color as style will be applied after the styles from class. However modifier application precedence is done we should ensure that this behavior is similar.

Edge cases

Any trailing ; should be collapsed:

styles = "a; b; c;"
assert parse_styles(styles) == ["a", "b", "c"]

styles = "a;b;c"
assert parse_styles(styles) == ["a", "b", "c"]

styles = "a;b;c;;;;;;;;;;"
assert parse_styles(styles) == ["a", "b", "c"]
bcardarella commented 1 month ago

@BrooklinJazz take note ☝️ this affects the tutorial if you're using this style of class name. You will be able to just change class to style though to quickly fix once this is available

bcardarella commented 1 month ago

As far as implementation goes for the Elixir side, I will likely remove the changes I made to the UtilityStyle module and rely on the existing rules parser.

What I'm not sure about is if we should add support for ; within the rules parser or not. I can easily just split and treat each individually when compiling the template. While adding ; to the parser does align it more closely to CSS I question what we benefit from that other than just ceremonial.

bcardarella commented 1 month ago

As far as why "style" over "modifiers"

  1. "style" is more concise
  2. "style" can be platform agnostic, so we can use a similar approach in WinUI3 or Jetpack without having to worry about platform-relevant attribute names
  3. it aligns with our naming for our stylesheets
bcardarella commented 1 month ago

The other benefit is I believe this will put people on the better path towards wanting a styling framework in the future. Supporting inline-styles along with the escape hatch of supporting class names for styling with multiple modifiers is familiar for those coming from web and I think this will result in less WTFs.

BrooklinJazz commented 1 month ago

As far as implementation goes for the Elixir side, I will likely remove the changes I made to the UtilityStyle module and rely on the existing rules parser.

What I'm not sure about is if we should add support for ; within the rules parser or not. I can easily just split and treat each individually when compiling the template. While adding ; to the parser does align it more closely to CSS I question what we benefit from that other than just ceremonial.

What about supporting .?

.modifier()
.otherModifier()

That way writing modifiers is nearly 1-1. It probably won't be as easy as a split on colon but should be do-able.

It could also mean we can solve the whitespace problem. It'd be nice to be able to do stuff like this:

.modifier(param: 10, param: 10)
bcardarella commented 1 month ago

I've debated that, it more of less turns it into a pure parser at that point. My only reason, as you surmised, for not doing so is that splitting between the rules requires more than a simple split and you have to start tokenizing the string to find where the outer-most .s are for splitting

bcardarella commented 1 month ago

Part 1/2 https://github.com/liveview-native/live_view_native/pull/179

NduatiK commented 1 month ago

Why do we bother with HTML encoding? (Why is " turned into &quot;?)

bcardarella commented 1 month ago

@NduatiK on native devices we're using html parsers and they don't like seeing " even when escaped

bcardarella commented 1 month ago

FWIW I am not a fan of having to use &quot; but I don't have another solution at the moment

NduatiK commented 1 month ago

That's a good point, I forgot about the HTML bits