joicomponents
Version:
Design patterns to build native web components
79 lines (73 loc) • 2.69 kB
JavaScript
/**
* Acknowledgments
*
* Many thanks to Jan Miksovsky and the Elix project for input and inspiration.
*/
const init = Symbol("init");
const processSlotchangeEvent = Symbol("processSlotchangeEvent");
const microTaskRegister = Symbol("microTaskRegister");
function onlyOncePerMicroTaskCycle(register, key) {
if (register.has(key)){
console.log("abourt");
return false;
}
register.add(key);
Promise.resolve().then(() => {
register.delete(key);
});
return true;
}
export function ShadowSlotchangeMixinArnold(Base) {
return class ShadowSlotchangeMixinArnold extends Base {
constructor() {
super();
this[microTaskRegister] = new WeakSet();
requestAnimationFrame(() => this[init]());
}
[init]() {
this.shadowRoot.addEventListener("slotchange", e => this[processSlotchangeEvent](e));
const slots = this.shadowRoot.querySelectorAll("slot");
if (!slots)
return;
for (let i = 0; i < slots.length; i++)
this.slotCallback(slots[i]);
}
/**
* There is a bug in either the spec or Chrome in how slotchange events are processed that require handling:
* Sometimes you get multiple `slotchange` events for the same change of assignedNodes, sometimes you don't.
*
* When the slotchange event is triggered, it might also trigger from with the .target property
* being a chained <slot> and not the slot that is located within the shadowRoot of the element in which you
* listen for slotchange events.
*
* This problem means the following. When you get a slotchange event in,
* you need to ensure/find the slot that is from this shadowRoot.
* When you find this slot, you need to:
* 1. register this slot as processed,
* 2. process the slotchange event viewed from that slot perspective, and
* 3. after the microtask que has finished, mark the slot as ready to be processed again.
*
* @param e the slotchange event
*/
[processSlotchangeEvent](e) {
// debugger;
//removes the GrandpaError slot
let path = e.composedPath();
let activeSlot;
for (let node of path) {
if (node.tagName === "SLOT")
activeSlot = node;
else
break;
}
if (activeSlot.getRootNode() !== this.shadowRoot){
// console.log("REMOVE", path);
return;
}
// const slot = e.composedPath().find(n => n.tagName === "SLOT" && n.getRootNode() === this.shadowRoot);
//todo check if I need this after initialization
//if (onlyOncePerMicroTaskCycle(this[microTaskRegister], activeSlot))
this.slotCallback(activeSlot);
}
}
}