fable-compiler / Fable.Lit

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

Accessing children in custom element #32

Closed JordanMarr closed 2 years ago

JordanMarr commented 2 years ago

I know is more of a discussion than an issue, but for lack of a discussion forum I will post it here: Is it possible to access an array of children elements in my custom element?

AngelMunoz commented 2 years ago

you can access them in the standard way by using .children

[<LitElement("my-element">]
let MyElement() =
  let host, _ = LitElement.init(cfg -> cfg.useShadowDom <- false)

  host.children //
  html $""" ... content ... """
// for shadow dom you need to use shadowRoot
[<LitElement("my-shadow-element">]
let MyElement() =
  let host, _ = LitElement.init()

  host.shadowRoot.children // children are inside the shadow DOM
  html $""" ... content ... """

Is there any particular case you have in mind?

I know is more of a discussion than an issue

it would be nice indeed if the discussions were enabled in this repo

AngelMunoz commented 2 years ago

oh looks like the types don't have the children property but in the browser works

JordanMarr commented 2 years ago

Yes, the discussion forum feature would be great in this repo!

So I was just messing around to see what kind of stuff I can automate with custom elements. I wanted to recreate the FluentUI React Stack control that lets you pass in an array of ReactElement's and it will do some grid stuff to it. I ended up doing it with a HookComponent:

[<HookComponent>]
let HorizontalStack(children: (int * TemplateResult) list) = 
    let columns = 
        children
        |> List.map (fun (colSize, child) -> 
            html $"""
            <div class="ms-Grid-col ms-md{colSize}">
                {child}
            </div>
            """
        )

    html $"""
        <div class="ms-Grid" dir="ltr">
            <div class="ms-Grid-row">
                {columns |> Lit.ofList}
            </div>
        </div>
    """

Usage:

Ctrls.HorizontalStack [ 
        3, html $"""
            <fluent-icon icon-name="Car"></fluent-icon>
            """
        9, html $"""
            <div>
                <fluent-button @click={Ev(fun _ -> dispatch ForgePrev)} appearance="accent">Back</fluent-button>
                <div style="display: inline-block; vertical-align: -webkit-baseline-middle">{model.ForgePageIdx + 1}</div>
                <fluent-button @click={Ev(fun _ -> dispatch ForgeNext)} appearance="accent">Next</fluent-button>
            </div>

            <fluent-data-grid style="max-height: 30em; overflow-y: auto;" .rowsData={model.ForgeProjects}>
            </fluent-data-grid>
            """
    ]

However, I really want to have something more like this:

<fluent-stack horizontal gap="10px" sizes="3,9">
    <div>Col A</div>
    <div>Col B</div>
</fluent-stack>
AngelMunoz commented 2 years ago

This is very tricky to do with CustomElements, the most straight approach would be to use Shadow DOM + Slots since that's the primary use case for it

[<LitElement("shadow-card">]
let MyElement() =
  LitElement.init() |> ignore

  html
    $"""
    <article class="card">
      <!-- Any other structures, styles and logic -->
      <header class="card-header">
        <slot name=header></slot>
      </header>
      <slot></slot>
    </article>
    """

with slots in this case we can do content projection for the header content and the rest of the elements, you would use it just like you want it

<shadow-card>
  <!-- Both of these go inside the header slot,
       they don't overwrite each other -->
  <h1 slot="header">Title</h1>
  <p slot="header">Sub title</p>
  <!-- the rest go to the unnamed slot -->
  <section>The rest of the content</section>
  <aside>Any other content </aside>
</shadow-card>

but as you may be aware, that requires a little bit more of planning in regards how to consume external styles or define your own for that component

JordanMarr commented 2 years ago

Welp... tricky is right. There are so many rules and quirks that it makes hook components look a lot more attractive.

alfonsogarciacaro commented 2 years ago

I enabled the discussions for the repo :)

Yes, besides the templates and other Lit concepts, as @AngelMunoz said, you do thinks the "web components way" which in this case it's probably slots.

The idea of adding the HookComponent was to have the benefit of Lit templating with the advantages of F# function composition, so passing the children as an argument looks right to me :)