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

Add info about supported tags to props/attrs #96

Open
armanbilge opened this issue Feb 3, 2023 · 6 comments
Open

Add info about supported tags to props/attrs #96

armanbilge opened this issue Feb 3, 2023 · 6 comments

Comments

@armanbilge
Copy link
Contributor

armanbilge commented Feb 3, 2023

For example, the current data for the label attribute is:

ReflectedHtmlAttrDef(
scalaName = "labelAttr",
domAttrName = "label",
domPropName = "label",
scalaValueType = "String",
domPropValueType = "String",
attrCodec = "StringAsIs",
propCodec = "StringAsIs",
commentLines = List(
"For `optgroup` elements, specifies the name of the group of options, which the browser can",
"use when labeling the options in the user interface.",
),
docUrls = Nil,
),

At least according to this page, it is supported only for <track>, <option>, <optgroup>, <menu>, and <menuitem> tags.

https://www.geeksforgeeks.org/html-label-attribute/

In fact, I'm confused—is the label for <track> and <option> really the same?

In any case, Calico would be able to take advantage of this information.


The reason I'm thinking about this now, is because I am looking at web components, and they often seem to be defining their own custom attributes that may be "clashing" with other HTML attributes.

For example, I'm stuck on label specifically because of its use in Material Button.
https://github.com/material-components/material-web/blob/e15c4b86d584cfda5dc850cb697bc9b9552e9536/docs/quick-start.md#usage

Basically we seem to have a "namespacing" issue, where "attributes" should be "namespaced" within their element, because they may have different implementations and types across elements (or not even be available).

@raquo
Copy link
Owner

raquo commented Feb 4, 2023

Before you go down this road, keep in mind that an attribute's applicability is not just based on the element type, it can require all kinds of complex conditions that are very hard to express in types, e.g. see the example here: https://github.com/raquo/scala-dom-types#reasonably-precise-types

Also, I'm not sure how knowing which element types an attribute applies to will help you de-conflict two label attributes.


In fact, I'm confused—is the label for and really the same?

It has the same name and expects the same value type (string), they serve approximately the same purpose, that's about as "same" as it gets in the soup that is DOM. The native JS call to set the label attribute is the same in both cases.


If this is just for web components, then you can define special attribute types specific to that web component, without complicating the types of regular HTML attributes.

In Laminar the canonical approach is to scope the web component's attributes to the web component itself, and access them as properties, e.g. like so:

Button(
      _.id := "myButton",
      _.label <-- actionVar.signal,
      ...
)

Which is just syntax sugar for:

Button(
      Button.id := "myButton",
      Button.label <-- actionVar.signal,
      ...
)

(from https://laminar.dev/examples/web-components, but Antoine's Laminar SAP UI5 Bindings takes the same approach)

This syntax allows using both specific webcomponent attrs as shown above, as well as regular html properties (e.g. (_ => idAttr := "someId")) that you might not want to redundantly define on every web component.

@armanbilge
Copy link
Contributor Author

armanbilge commented Feb 4, 2023

Thanks!


Before you go down this road, keep in mind that an attribute's applicability is not just based on the element type, it can require all kinds of complex conditions that are very hard to express in types

Sure :) I did re-read that "reasonably precise" bit before opening this issue. FWIW I'm not on any particular mission to make the API more typed, I think what we have is good. It just happens that the way Calico is currently working, we can do this "for free". And also, the web component thing put this issues of clashes onto my mind.


If this is just for web components, then you can define special attribute types specific to that web component, without complicating the types of regular HTML attributes.

Yep, I studied the approach in the UI5 bindings. I'm not sure if this technique will work with how we are currently handling "modifiers" in Calico. And/or I didn't like it for other reasons 😂

@raquo
Copy link
Owner

raquo commented Feb 4, 2023

Also just for the record an older issue about this is #13 but that was waaay before we had generators.

Now that we have generators, different UI libraries can pick and choose how much of the available data they want to use, so in principle I don't mind if someone added this data to SDT.

@armanbilge
Copy link
Contributor Author

Also just for the record an older issue about this is #13 but that was waaay before we had generators.

Ah, my bad, I couldn't tell from the title :)


Also, I'm not sure how knowing which element types an attribute applies to will help you de-conflict two label attributes.

Good question. Here's a prototype of how I plan to do this in Calico. In particular, check out the fizzBuzzAttr which is a Boolean for fooTag and an Int for barTag.

//> using lib "org.typelevel::shapeless3-deriving:3.3.0"
//> using option "-Ykind-projector:underscores"
import shapeless3.deriving.K0

trait Modifier[E, M]

trait SetAttribute[A, V]

class Tag[E]:
  def apply[M <: Tuple](modifiers: M)(using
      K0.ProductInstances[Modifier[E, _], M]
  ): E = ???

class Attribute[A]:
  def :=[V](value: V): SetAttribute[A, V] = ???

// common to both
def sharedAttr: Attribute["shared"] = ???

// only on foo
def fooAttr: Attribute["foo"] = ???

// only on bar
def barAttr: Attribute["bar"] = ???

// on foo and bar, but with different types
def fizzBuzzAttr: Attribute["fizzbuzz"] = ???

trait Element
given sharedForElement[E <: Element]: Modifier[E, SetAttribute["shared", String]] = ???

trait FooElement extends Element
given fooForFooElement[E <: FooElement]: Modifier[E, SetAttribute["foo", String]] = ???
given fizzBuzzForFooElement[E <: FooElement]: Modifier[E, SetAttribute["fizzbuzz", Boolean]] = ???

trait BarElement extends Element
given barForBarElement[E <: BarElement]: Modifier[E, SetAttribute["bar", String]] = ???
given fizzBuzzForBarlement[E <: BarElement]: Modifier[E, SetAttribute["fizzbuzz", Int]] = ???


def fooTag: Tag[FooElement] = ???
def barTag: Tag[BarElement] = ???

def demo =
  fooTag(
    sharedAttr := "hello",
    fooAttr := "foo",
    fizzBuzzAttr := true
  )

  barTag(
    sharedAttr := "hello",
    barAttr := "bar",
    fizzBuzzAttr := 42
  )

@raquo
Copy link
Owner

raquo commented Feb 4, 2023

Ah, I see. I figured it'd be something with implicits. Interesting pattern.

Note: I don't think that there's ever a case in the DOM where the same attribute / property has a different type based the element type. Like, the label attribute will always be string, for all elements that it's defined for. The only part I'm not sure about are such incompatibilities between HTML and SVG attribute names.

@armanbilge
Copy link
Contributor Author

Ah, I see. I figured it'd be something with implicits. Interesting pattern.

Part of my motivation for this pattern was to avoid relying on implicit conversions, which seem to be discouraged in Scala 3.


Note: I don't think that there's ever a case in the DOM where the same attribute / property has a different type based the element type.

That may be true, but IIUC that seems down to luck or "good practice" rather than a hard rule.

The only part I'm not sure about are such incompatibilities between HTML and SVG attribute names.

Exactly, and web components creates new potential for such incompatibilities. As you point out, attributes for web components could be handled differently. But one of the big ideas of web components in HTML is that they can be used like vanilla elements, so I'm interested to see if we can preserve that experience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants