Reputation: 155822
I have a custom element that shows a list, where each item is also a component.
One team builds the library components - they don't know what each record should look like. A different team uses them in lots of different places (one might be a list to be carousels of product photos, another might need each record to be a link, inputs or action menus) - they know how to render each item but not the repeater.
The library components might be doing a lot of work - say a virtual scroller that's paginating data based in visibility observers.
Something like (pseudo code):
<my-list .ids=[1,2,3]>
#shadow-root (open)
<ul>
<li><my-item id=1></my-item></li>
<li><my-item id=2></my-item></li>
<li><my-item id=3></my-item></li>
</ul>
</my-list>
I want to add something inside the nested element, using light-DOM and a <slot>
. I realise I can do this explicitly with named slots, something like...
<my-list .ids=[1,2,3]>
<button slot="item1" @click=${this.remove(1)}>remove</button>
<button slot="item2" @click=${this.remove(1)}>remove</button>
<button slot="item3" @click=${this.remove(1)}0>remove</button>
</my-list>
Rendering to
<my-list>
#shadow-root (open)
<ul>
<li><my-item id=1>
<slot name="item1">
<button @click=${this.remove(1)}>remove</button>
</slot>
</my-item></li>...
But I don't know how many items I will have in my list and everything needs looping twice.
Ideally I want to do something like:
<my-list .ids=[large array]>
<button @click=${e=>this.remove(e.target.someProperty)}>remove</button>
</my-list>
And have that button appear in every <my-item>
's <slot>
content.
But that will fill one default slot, it won't repeat.
In <my-list>
I can listen for the slotchange
event and when it fires check (e.target as HTMLSlotElement).assignedElements()
and copy the contents to each repeated child slot, but this seems to get very nasty with events and properties.
For instance, an implementation might look like this:
<my-list
.getRowData=${() => /* set the data property here */}>
<item-template .data=${ignored}>
This control is actually a template that will be cloned for each row
</item-template>
<header-row slot="header">
As this is a regular slot, it just works as expected
</header-row>
</my-list>
Users have to implement complex data functions and understand how some slotted content is picked up as templates while others work as expected.
The other practice would be to pass lots of extra information to <my-list>
so that when it's repeating the shadow DOM elements it can render the <slot>
content for each one, but that basically serialises down (through properties) what should be rendering information.
An example of this one might be:
<my-list
.renderRow=${() =>
// output the content to populate
// for instance if we use Lit
html`
<span>row text</span>
<input @change=${e => this.expectComponentOrParent?}>`
}
@change=${does this handle the events from inputs?}>
</my-list>
Whether the events work depends on whether the component is populating the calling code's light DOM... it probably shouldn't, but if it populates it's own shadow DOM the uncomposed event won't reach the calling code.
It's not that these can't be solved, it's that every solution involves some pretty painful and extremely easy to get confused/wrong compromises.
Is there a way to have light DOM repeated in <slot>
content in the shadow DOM?
If not, are there any emergent best practices on how to do this kind of repeated content?
Upvotes: 1
Views: 119