joicomponents
Version:
Design patterns to build native web components
92 lines (83 loc) • 2.6 kB
JavaScript
/**
* Acknowledgments
*
* Many thanks to Jan Miksovsky and the Elix project for input and inspiration.
*/
//pure function to find the last in toRun, that !hasRun
function findLastNotChecked(toRun, hasRun) {
for (let i = toRun.length - 1; i >= 0; i--) {
let el = toRun[i];
if (hasRun.indexOf(el) < 0)
return el;
}
return null;
}
//Ques for batched tasks
let startedQue = [];
let completed = [];
let isStarted = false;
//First, block flushing of the que until DCL, and on DCL, open the que and try to flush it
let dcl = document.readyState === "complete" || document.readyState === "loaded";
dcl || window.addEventListener("DOMContentLoaded", function () {
dcl = true;
flushQue();
});
//process for flushing que
function flushQue() {
//step 1: check that dcl is ready.
if (!dcl) return;
//step 2: all elements started has been processed? reset and end
const fnel = findLastNotChecked(startedQue, completed);
if (!fnel) {
startedQue = [];
completed = [];
return;
}
//step 3: run function, add the element to the completed list, and run again with TCO
fnel[0](fnel[1]);
completed.push(fnel);
flushQue();
}
function batchedConstructorCallback(fn, el) {
startedQue.push([fn, el]);
if (!isStarted) {
isStarted = true;
Promise.resolve().then(() => {
Promise.resolve().then(() => { //DoublePrtPostSlotchangeTrick
flushQue();
isStarted = false;
});
});
}
}
/**
* findYourOwnSlot and avoid SlotchangeEavesdropping
*/
function triggerSlotchangeCallback(e, el) {
const path = e.composedPath();
for (let i = 0; i < path.length; i++) {
const node = path[i];
if (node.tagName !== "SLOT") //[*] no SlotchangeEavesdropping
return; //[*] no SlotchangeEavesdropping
if (node.getRootNode() === el.shadowRoot) {
el.slotchangeCallback(node, i, e);
return;
}
}
}
const initFn = function (el) {
const slots = el.shadowRoot.querySelectorAll("slot");
for (let i = 0; i < slots.length; i++)
el.slotchangeCallback(slots[i], 0, undefined);
if (slots.length === 0) //adds a slotchangeCallback(undefined) if none is available
el.slotchangeCallback(undefined, 0, undefined); //todo move Slottables into its own class
el.shadowRoot.addEventListener("slotchange", e => triggerSlotchangeCallback(e, el));
};
export function SlotchangeMixin(Base) {
return class SlotchangeMixin extends Base {
constructor() {
super();
batchedConstructorCallback(initFn, this);
}
}
}