src/bindings/slot.js
import { cleanNode, insertBefore, removeChild } from '@riotjs/util/dom.js'
import { PARENT_KEY_SYMBOL } from '@riotjs/util/constants.js'
import { evaluateAttributeExpressions } from '@riotjs/util/misc.js'
import template from '../template.js'
function extendParentScope(attributes, scope, parentScope) {
if (!attributes || !attributes.length) return parentScope
const expressions = attributes.map((attr) => ({
...attr,
value: attr.evaluate(scope),
}))
return Object.assign(
Object.create(parentScope || null),
evaluateAttributeExpressions(expressions),
)
}
// this function is only meant to fix an edge case
// https://github.com/riot/riot/issues/2842
const getRealParent = (scope, parentScope) =>
scope[PARENT_KEY_SYMBOL] || parentScope
export const SlotBinding = {
// dynamic binding properties
// node: null,
// name: null,
attributes: [],
// template: null,
getTemplateScope(scope, parentScope) {
return extendParentScope(this.attributes, scope, parentScope)
},
// API methods
mount(scope, parentScope) {
const templateData = scope.slots
? scope.slots.find(({ id }) => id === this.name)
: false
const { parentNode } = this.node
const realParent = getRealParent(scope, parentScope)
this.template =
templateData &&
template(templateData.html, templateData.bindings).createDOM(parentNode)
if (this.template) {
cleanNode(this.node)
this.template.mount(
this.node,
this.getTemplateScope(scope, realParent),
realParent,
)
this.template.children = Array.from(this.node.childNodes)
}
moveSlotInnerContent(this.node)
removeChild(this.node)
return this
},
update(scope, parentScope) {
if (this.template) {
const realParent = getRealParent(scope, parentScope)
this.template.update(this.getTemplateScope(scope, realParent), realParent)
}
return this
},
unmount(scope, parentScope, mustRemoveRoot) {
if (this.template) {
this.template.unmount(
this.getTemplateScope(scope, parentScope),
null,
mustRemoveRoot,
)
}
return this
},
}
/**
* Move the inner content of the slots outside of them
* @param {HTMLElement} slot - slot node
* @returns {undefined} it's a void method ¯\_(ツ)_/¯
*/
function moveSlotInnerContent(slot) {
const child = slot && slot.firstChild
if (!child) return
insertBefore(child, slot)
moveSlotInnerContent(slot)
}
/**
* Create a single slot binding
* @param {HTMLElement} node - slot node
* @param {string} name - slot id
* @param {AttributeExpressionData[]} attributes - slot attributes
* @returns {Object} Slot binding object
*/
export default function createSlot(node, { name, attributes }) {
return {
...SlotBinding,
attributes,
node,
name,
}
}