lwd icon indicating copy to clipboard operation
lwd copied to clipboard

Brr-lwd: reactive styles

Open panglesd opened this issue 3 months ago • 4 comments

It is sometimes useful to have reactive styles. In particular for "continuous" properties such as width and top/left, as one cannot rely on a continuum infinite number of classes.

This PR adds to elements constructors a st argument with a collection of styles ((Brr.El.Style.prop * Jstr.v) col) which are applied to the element. Example of use:

  let block =
    let top =
      let$ top = Lwd.get top in
      let top = Jstr.append (Jstr.of_float top) (Jstr.v "px") in
      (El.Style.top, top)
    in
    let st = [
        `R top;
        `P (El.Style.width, Jstr.v "50px");
      ]
    in
    Elwd.div ~st []

It also adds an example in the example folder, as well as changelog entries (for #53 and this one).

panglesd avatar Sep 24 '25 13:09 panglesd

I'd like to also add reactive properties...

This would be useful when implementing reactive UI elements: for instance, setting the checked attribute of a checkbox (c.setAttribute("checked", "true")) does not update the checkbox state, but setting the checked property (c.checked = true) does update the state. @voodoos has complained to me about this issue too :)

I can do that in this PR or another, or not do it at all, which do you prefer? Another option is to replace attributes setting with property setting, this is more of a breaking change but it's likely that we prefer setting the property most of the time (?).

panglesd avatar Sep 24 '25 19:09 panglesd

update the checkbox state For reference, my current workaround for that is to sample the Lwd.var that holds the state to set the checked property:

https://github.com/voodoos/brr-lwd-ui/blob/4d3101122c21dfd2c3d22419d79c2a322bad01c6/lib/forms/field_checkboxes.ml#L81-L87

voodoos avatar Sep 24 '25 19:09 voodoos

Thanks. This is indeed something that should be improved. It reminds me a bit of the special-casing: https://github.com/let-def/lwd/blob/0fdf2e8cb823005f0bcadf4e5ff9d3aa1333d43c/lib/tyxml-lwd/tyxml_lwd.ml#L194-L205

It's a bit annoying that the same surface notion translates to different setters but we have to live with that.

I am not familiar enough with the difference between attributes and properties and the web customs to know how this should be exposed. Ideally, API-wise I think the library should just model the syntax (e.g. do the runtime state corresponds to a <foo checked=true> or to a <foo checked=false>) and maintain this state synchronized at runtime, hiding from the end-user whether this has to go through an attribute or a property. Is this short-sighted?

Styles have their own syntax so it is less-surprising to treat them differently.

let-def avatar Sep 24 '25 22:09 let-def

I am not familiar enough with the difference between attributes and properties and the web customs to know how this should be exposed.

Here are some information I think are relevant:

HTML

I think attributes are "syntactic" and defined in the HTML syntax. Elements of the DOM access the corresponding attributes via getAttribute. As a syntactic notion, the associated value to a (js string) key is always a (Javascript) string. On the other hand, by properties I refer to properties of DOM objects. The associated value to a (js string) key is a js value whose type depend on the property.

From tests and what I understand, all standard attributes have a corresponding property:

  • Attributes like checked have a el.checked property
  • Attributes like aria-label have a el.ariaLabel property (that is, property names are camelCase while attributes are snake-case)
  • Attributes like data-foo-bar have a el.dataset.fooBar (that is, custom attributes (those starting with data- are camelCased and inside dataset)

Non-standard attributes do not have a corresponding attribute:

  • The DOM obj corresponding to <div nonstdattr="yo"></div> will have el.nonstdattr be undefined.

Conversely, some properties like scrollTop do not have a corresponding attribute.

SVG

(SVG is not really yet supported in brr-lwd because we cannot decide the namespace because we cannot do it in brr. However, I have a patch for brr and brr-lwd that I use in slipshow to allow this, so being nice to svg is very relevant to me!)

Many attributes don't have a corresponding property. So for instance if el is the DOM element representing <path d="..."> then el.d is undefined...


Ideally, API-wise I think the library should just model the syntax (e.g. do the runtime state corresponds to a or to a ) and maintain this state synchronized at runtime, hiding from the end-user whether this has to go through an attribute or a property. Is this short-sighted?

I think this would be ideal indeed! However, I find it not so easy to make it work nicely in all contexts:

  • In svg, not all attributes have associated properties, so we would need to check if the property exist, and if not fallback to setting the attribute.
  • The property and attribute name differ, but in a predictable way so it's possible to convert them.
  • It's strange to label as attribute (if we do not change the argument name) things like scrollTop which are not attributes
  • We would need anyway to change the type of attribute collections from Brr.At.t Brr_lwd.Elwd.col to (Jstr.t * Jv.t) Brr_lwd.Elwd.col since we cannot always use strings as values: setting el.checked="false" will check the checkbox, since "false" is true ^^
  • Non standard atributes will not be supported. Who uses them anyway? Me?

So in conclusion it's possible to have a single merged at+prop, which is nice, but with some work, some breaking changes and some drawbacks (non-standard attributes and maybe future situation I did not anticipate).

Keeping attributes and properties separate (in ~at and ~prop) is less satisfying, gives a bit more work on the user of the library (distinguish which they want to use and bigger API). But it's future-proof and likely a good first step before deprecating ~at and merging attributes and properties inside ~prop.


Sorry for the wall of text!

panglesd avatar Sep 25 '25 09:09 panglesd