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

Nested light DOM slots don't render correctly #3843

Closed
jmsjtu opened this issue Nov 1, 2023 · 0 comments · Fixed by #3883
Closed

Nested light DOM slots don't render correctly #3843

jmsjtu opened this issue Nov 1, 2023 · 0 comments · Fixed by #3883

Comments

@jmsjtu
Copy link
Member

jmsjtu commented Nov 1, 2023

Description

Light DOM slots are not rendering correctly when there are nested slots, repro:

<template>
    <x-light-child>
        <div slot="namedSlot">Named Slot Content</div>
        <div>Unnamed Slot Content</div>
    </x-light-child>
</template>

// parent
<template lwc:render-mode="light">
  <h2>Slotted content in Light DOM (note missing named slot)</h2>
  <x-leaf>
    <slot name="namedSlot"></slot>
  </x-leaf>
  <x-leaf>
    <slot></slot>
  </x-leaf>
</template>

// x-light-child
<template>
  <slot></slot>
</template>

// x-leaf

In the above example, the slotted content "Named Slot Content" should render in the default slot for x-leaf but does not.

This is happening because for light DOM slots, the children are directly returned rather than creating a slot element like in synthetic shadow.

Root Cause

Each component that contains a slot will look up the slot's name in a slot map called slotset that determines what's slotted into the component.

This mapping is determined at runtime by traversing through the vnodes and their children.

When a new component containing a slot element is generated, it will look at the slot mapping from its parent to determine both the slot name and the content of what gets slotted.

Since light DOM slots directly return the content rather than the slot element, when there are nested slots, the mapping past the first level is missing and causes the component to render incorrectly.

Details

In the above example, the two div elements are mapped to the slot mapping :

<template>
    <x-light-child>
        <div slot="namedSlot">Named Slot Content</div>
        <div>Unnamed Slot Content</div>
    </x-light-child>
</template>

The above will generate a slot map that looks something like this:

{
    "nameSlot":  <div slot="namedSlot">Named Slot Content</div>, // This is for the named slot
    "": <div>Unnamed Slot Content</div> // This is for the default slot
}

// slotset

This map is used to determine what will go in the slotted content inside x-light-child (value of slotset):

<template lwc:render-mode="light">
  <h2>Slotted content in Light DOM (note missing named slot)</h2>
  <x-leaf>
    <slot name="namedSlot"></slot>
  </x-leaf>
...
</template>

// x-light-child

Based on the slotset mapping, the children of x-leaf will be replaced with the vnode equivalent of <div slot="namedSlot">Named Slot Content</div>

Once the children have been allocated, they will be used to determine the slot mapping of x-leaf:

{
    "nameSlot":  <div slot="namedSlot">Named Slot Content</div>,
}

When we finally get to x-leaf, since there is no default slot ("" entry in the slotset), the content of x-leaf is not rendered:

<template>
  <slot></slot>
</template>

// x-leaf

The slot element needs an empty string entry to retrieve the children.

To demonstrate this, if we add name="namedSlot" the component will render correctly.

<template>
  <slot name="namedSlot"></slot>
</template>

The extra slot element acts as a buffer to help set the default slot mapping.

I pushed up a branch to demonstrate a potential fix for the existing issue, although this change causes the karma tests to fail.

The branch also has the example described here to test with.

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