fable-compiler / Fable.Lit

Write Fable Elmish apps with Lit
https://fable.io/Fable.Lit/
MIT License
91 stars 13 forks source link

Add renderRoot in host (LitElement) #22

Closed evilz closed 2 years ago

evilz commented 2 years ago

Hello,

I think we should had renderRoot in type LitElement.

I try to do something like this

let input () : HTMLInputElement =
        host?renderRoot?querySelector ("#newitem")

Maybe I miss something with host.el ??? when I change useShadowDom to false this is working.

What do you think ?

AngelMunoz commented 2 years ago

I can confirm that Lit supports this by default in this sample repl

but can you share a more complete example of what you were trying to do? a simple component definition perhaps

evilz commented 2 years ago

Yes here a port of the tuturial of Lit.dev

module Lit.TodoMVC.App

open Lit
open Browser.Types
open Browser
open Fable.Core.JsInterop

type ToDoItem =
    { Text: string
      mutable Completed: bool }

let styles =
    [ css
          $"""
        .completed {{
        text-decoration-line: line-through;
        color: #777;
                }}""" ]

[<LitElement("todo-list")>]
let ToDoList () =

    // This call is obligatory to initialize the web component
    let host, props =
        LitElement.init
            (fun init ->
                init.styles <- styles
                init.useShadowDom
                init.props <-
                    {| listItems =
                           Prop.Of(
                               [ { Text = "Start Lit tutorial"
                                   Completed = true }
                                 { Text = "Make to-do list"
                                   Completed = false } ]
                           )
                       hideCompleted = Prop.Of(false) |})

    let input () : HTMLInputElement =
        host?renderRoot?querySelector ("#newitem")

    let addToDo (event: Event) =
        //let input = event.target
        //props.firstname.Value <- input.Value
        let input = input ()

        props.listItems.Value <-
            { Text = input.Value
              Completed = false }
            :: props.listItems.Value

        input.value <- ""
        host.requestUpdate ()

    let toggleCompleted (item: ToDoItem) =
        item.Completed <- not item.Completed
        host.requestUpdate ()

    let getItemTemplate item =
        html
            $"""<li class={if item.Completed then
                               "completed"
                           else
                               ""}
                           @click={fun _ -> toggleCompleted (item)}>{item.Text}</li>"""

    let setHideCompleted (e: Event) =
        props.hideCompleted.Value <- (e.target :?> HTMLInputElement).``checked``

    let items =
        if props.hideCompleted.Value then
            props.listItems.Value
            |> List.filter (fun item -> not item.Completed)
        else
            props.listItems.Value

    let caughtUpMessage =
        html
            $"""
            <p>
            You're all caught up!
            </p>
            """

    let todos =
        html
            $"""<ul>
            {items
             |> Lit.mapUnique (fun item -> item.Text) getItemTemplate}
            <!-- TODO: Render list items. -->
         </ul>
          """

    let todosOrMessage =
        if items.Length > 0 then
            todos
        else
            caughtUpMessage

    html
        $"""
          <h2>To Do</h2>
          {todosOrMessage}
          <input id="newitem" aria-label="New item">
          <button @click={addToDo}>Add</button>
          <br>
          <label>
            <input type="checkbox"
              @change={setHideCompleted}
              ?checked={props.hideCompleted}>
            Hide completed
          </label>
        """
<!doctype html>
<html lang="en">

<head>
    <title>Lit.TodoMVC</title>
    <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="shortcut icon" href="fable.ico" />

    <!-- Used to hide custom components until they are defined and prevent a flash of unstyled content -->
    <style>
        *:not(:defined) { display:none }
     </style>
     <style>
        body {
          font-family: 'Open Sans', sans-serif;
          font-size: 1.5em;
          padding-left: 0.5em;
        }
      </style>
</head>

<body>
    <script type="module" src="./build/client/App.js"></script>
    <todo-list />
</body>

</html>
alfonsogarciacaro commented 2 years ago

Thanks for pointing this @evilz! You're definitely right. I was using .el as a "fake" property to access the HTMLElement because it was not easy to make LitElement inherit from it (right now, HTMLElement is defined as a interface in Fable.Browser.Dom), but renderRoot is a much better choice because it matches Lit documentation and it also gives you directly the shadowRoot when present. I've changed it :+1: