I am creating a dropdown menu web component that will be used by consumers like:
<custom-menu>
<custom-menu-anchor>
<button>Toggle Menu</button>
</custom-menu-anchor>
<custom-menu-item>Fish</custom-menu-item>
<custom-menu-item>
<custom-icon name="chicken"/>
<span>Chicken</span>
</custom-menu-item>
</custom-menu>
Here, the slot items like <custom-menu-items>
need to be absolutely positioned.
To do this, we need to
- Create an overlay
- Remove slot elements from web component
- Attach them to the overlay element.
- Provide correct positioning
To setup a perfect overlay, I need to create an overlay/surface
element and remove the custom-menu-item
children and append them all to the overlay element.
To achieve this, I attempted something like below in connectedCallback
lifecycle method:
const slot = this.shadowRoot.querySelector('slot');
const surface = document.createElement('div');
const nodes = slot.assignedNodes();
surface.append(...nodes);
document.body.appendChild(surface);
Problems with this approach:
- I noticed that removing the
assignedNodes
from slot messes up many things. - Many things do not work when I attempt to move slot's lightDOM
slotchange
doesn't work once the elements are moved.- Further, web component could be used in any framework, It could be
lit-html
,Vue
or even plainJavaScript
. I noticed that this starts breaking abstractions of the consumer libraries after moving these DOM elements.
This need applies to any absolutely positioned/offset components like notification, dialog, dropdown, Snackbar and the above approach clearly struggles and is certainly not a cleaner way to do things.
How can we do this in a more effective manner avoiding all the mentioned side effects ?