joicomponents
Version:
Design patterns to build native web components
93 lines (80 loc) • 2.88 kB
JavaScript
/**
* Acknowledgments
*
* Many thanks to Jan Miksovsky and the Elix project for input and inspiration.
*/
import {flattenNodes} from "./flattenNodes.js";
const slotchangeListener = Symbol("slotchangeListener");
const triggerAllSlotchangeCB = Symbol("triggerSlotchangeCallback");
const triggerSlotchangeCB = Symbol("triggerSlotchangeCB");
const slots = Symbol("slots");
const assigneds = Symbol("assigneds");
function arrayEquals(a, b) {
return a && b && a.length === b.length && a.every((v, i) => v === b[i]);
}
function slotMap(slotList) {
const dict = {};
for (let i = slotList.length - 1; i >= 0; i--) {
let slot = slotList[i];
dict[slot.name] = slot;
}
return Object.values(dict);
}
export function StaticSlotchangeMixin(Base) {
return class StaticSlotchangeMixin extends Base {
constructor() {
super();
this[slotchangeListener] = (e) => this[triggerSlotchangeCB](e.currentTarget);
this[slots] = [];
this[assigneds] = new WeakMap();
}
connectedCallback() {
if (super.connectedCallback) super.connectedCallback();
this.addSlotListeners();
}
disconnectedCallback() {
if (super.disconnectedCallback) super.disconnectedCallback();
this.removeSlotListeners();
}
//todo new updateSlotListeners is untested
updateSlotListeners() {
const newSlots = slotMap(this.shadowRoot.querySelectorAll("slot"));
const oldSlots = this[slots];
for (let newSlot of newSlots) {
if (!oldSlots.indexOf(newSlot))
newSlot.addEventListener("slotchange", this[slotchangeListener]);
}
for (let oldSlot of oldSlots) {
if (!newSlots.indexOf(oldSlot))
oldSlot.removeEventListener("slotchange", this[slotchangeListener]);
}
this[slots] = newSlots;
this[triggerAllSlotchangeCB]();
}
addSlotListeners() {
this[slots] = slotMap(this.shadowRoot.querySelectorAll("slot"));
for (let slot of this[slots])
slot.addEventListener("slotchange", this[slotchangeListener]);
this[triggerAllSlotchangeCB]();
//todo Should I wait for other microtasks before triggering the slotchangeUpdate??
// Promise.resolve().then(() => this[triggerAllSlotchangeCB]());
}
removeSlotListeners() {
for (let slot of this[slots])
slot.removeEventListener("slotchange", this[slotchangeListener]);
this[slots] = [];
}
[triggerAllSlotchangeCB]() {
for (let slot of this[slots])
this[triggerSlotchangeCB](slot);
}
[triggerSlotchangeCB](slot) {
let newAssigned = slot.assignedNodes({flatten: true});
let oldAssigned = this[assigneds].get(slot);
if (arrayEquals(oldAssigned, newAssigned))
return;
this[assigneds].set(slot, newAssigned);
this.slotchangedCallback(slot.name, newAssigned, oldAssigned);
}
}
}