lion/packages/core/src/SlotMixin.js

104 lines
3.4 KiB
JavaScript

/* eslint-disable class-methods-use-this */
import { dedupeMixin } from '@open-wc/dedupe-mixin';
import { render } from 'lit';
import { isTemplateResult } from 'lit/directive-helpers.js';
/**
* @typedef {import('../types/SlotMixinTypes').SlotMixin} SlotMixin
* @typedef {import('../types/SlotMixinTypes').SlotsMap} SlotsMap
* @typedef {import('../index').LitElement} LitElement
*/
/**
* @type {SlotMixin}
* @param {import('@open-wc/dedupe-mixin').Constructor<LitElement>} superclass
*/
const SlotMixinImplementation = superclass =>
// @ts-ignore https://github.com/microsoft/TypeScript/issues/36821#issuecomment-588375051
class SlotMixin extends superclass {
/**
* @return {SlotsMap}
*/
get slots() {
return {};
}
constructor() {
super();
/** @private */
this.__privateSlots = new Set(null);
}
connectedCallback() {
super.connectedCallback();
this._connectSlotMixin();
}
/**
* @private
* @param {import('@lion/core').TemplateResult} template
*/
__renderAsNodes(template) {
// @ts-expect-error wait for browser support
const supportsScopedRegistry = !!ShadowRoot.prototype.createElement;
const registryRoot = supportsScopedRegistry ? this.shadowRoot : document;
// @ts-expect-error wait for browser support
const tempRenderTarget = registryRoot.createElement('div');
// Providing all options breaks Safari; keep host and creationScope
const { creationScope, host } = this.renderOptions;
render(template, tempRenderTarget, { creationScope, host });
return Array.from(tempRenderTarget.childNodes);
}
/**
* @protected
*/
_connectSlotMixin() {
if (!this.__isConnectedSlotMixin) {
Object.keys(this.slots).forEach(slotName => {
const hasSlottableFromUser =
slotName === ''
? // for default slot (''), we can't use el.slot because polyfill for IE11
// will do .querySelector('[slot=]') which produces a fatal error
// therefore we check if there's children that do not have a slot attr
Array.from(this.children).find(el => !el.hasAttribute('slot'))
: Array.from(this.children).find(el => el.slot === slotName);
if (!hasSlottableFromUser) {
const slotContent = this.slots[slotName]();
/** @type {Node[]} */
let nodes = [];
if (isTemplateResult(slotContent)) {
nodes = this.__renderAsNodes(slotContent);
} else if (!Array.isArray(slotContent)) {
nodes = [/** @type {Node} */ (slotContent)];
}
nodes.forEach(node => {
if (!(node instanceof Node)) {
return;
}
if (node instanceof Element && slotName !== '') {
node.setAttribute('slot', slotName);
}
this.appendChild(node);
this.__privateSlots.add(slotName);
});
}
});
this.__isConnectedSlotMixin = true;
}
}
/**
* @param {string} slotName Name of the slot
* @return {boolean} true if given slot name been created by SlotMixin
* @protected
*/
_isPrivateSlot(slotName) {
return this.__privateSlots.has(slotName);
}
};
export const SlotMixin = dedupeMixin(SlotMixinImplementation);