JetBrains / kotlin-wrappers

Kotlin wrappers for popular JavaScript libraries
Apache License 2.0
1.34k stars 164 forks source link

[Web Components] use fun instead of val for CustomElementCallbacks #2222

Closed joostbaas closed 7 months ago

joostbaas commented 7 months ago

Now that kotlin can compile to ES6 classes it is almost possible to write a web component in simple and pure kotlin. One issue I ran into is that when I extend web.html.HTMLElement the custom element callbacks are called with the wrong context in js, causing unexpected things like "typeError: Illegal invocation" when trying to do things like this.attachShadow(). This is because the callbacks are defined as fields with a function type (so lambda's), and not as a function.

Solution changing kotlin-browser/src/jsMain/generated/web/components/CustomElementCallbacks.kt to

external interface CustomElementCallbacks {
    fun connectedCallback()
    fun disconnectedCallback()
    fun adoptedCallback()
    fun attributeChangedCallback(name: String, oldValue: Any?, newValue: Any?)
}

works (tested locally).

If necessary I can create a minimal application to demonstrate the problem and solution, just let me know!

turansky commented 7 months ago

If necessary I can create a minimal application to demonstrate the problem and solution, just let me know!

Yes, it will be helpful. Gist or isolated GitHub project is preferred.

joostbaas commented 7 months ago

https://github.com/joostbaas/kotlin-webcomponent-issue-demo

turansky commented 7 months ago

Will it work without getter?

override val disconnectedCallback = {
    console.log("$WHO_AM_I disconnected")
}
joostbaas commented 7 months ago

I tried (with the connectedCallback), and then it does not get called at all it seems.

turansky commented 7 months ago

JFYI:

  1. patchObservedAttributes is redundant - component can patch yourself
  2. observedAttributes - we have related interface CustomElementCompanion with declared property
turansky commented 7 months ago

@Leonya Could you please release these changes?

joostbaas commented 7 months ago

The callbacks with fun declaration work great in pre710! Thanks for the quick fix!

turansky commented 7 months ago

@joostbaas Did you tried CustomElementData?

class MyComponent: HTMLElement {
    companion object: CustomElementData(
        name = HtmlTagName("my-component"),
        clazz = MyComponent::class,
    )
}

customElements.define(MyComponent)
joostbaas commented 7 months ago

I did, but I did not get it, I didn't realize you could do customElements.define(MyComponent), I thought you had to do the more verbose customElements.define(MyComponent.Companion). I don't like so much that this way you have to define the tagname in the class, that should be done in main() like in the normal define call. I would prefer an API like I had: customElements.define("component-name", MyComponent::class), though I like the idea to patch the observedAttributes immediately in the init(). A @JsStatic annotation like proposed here would be the nicest solution I think.

turansky commented 7 months ago

I don't like so much that this way you have to define the tagname in the class

That's how compiler plugin should work :) In common case registration in main isn't expected. Current implementation with CustomElementData - first step to automatic registration.

joostbaas commented 7 months ago

For a JavaScript API Wrapper I expect an API that is very close to the original. There is loads of documentation and tutorials on how to create webcomponents in JS, and none of them use anything like what you created. At least it is discoverable because it is an extension function so my IDE will suggest it when I am looking for it, but then it is not documented how to actually use it, and I had trouble figuring that out by myself. But that's ok, I will just write my own wrapper function for this ;)

turansky commented 7 months ago

JFYI - Lit